Commit aabb116a by Rocky Duan

continue working on backbone & got vote migrated and working

parent 12d430f0
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template.defaultfilters import escapejs
from django.conf import settings
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from utils import *
from mustache_helpers import mustache_helpers from mustache_helpers import mustache_helpers
from functools import partial from functools import partial
from utils import *
import pystache_custom as pystache import pystache_custom as pystache
import urllib import urllib
import os
def pluralize(singular_term, count): def pluralize(singular_term, count):
if int(count) >= 2: if int(count) >= 2:
...@@ -18,6 +22,19 @@ def show_if(text, condition): ...@@ -18,6 +22,19 @@ def show_if(text, condition):
else: else:
return '' return ''
# TODO there should be a better way to handle this
def include_mustache_templates():
mustache_dir = settings.PROJECT_ROOT / 'templates' / 'discussion'
valid_file_name = lambda file_name: file_name.endswith('.mustache')
read_file = lambda file_name: (file_name, open(mustache_dir / file_name, "r").read())
strip_file_name = lambda x: (x[0].rpartition('.')[0], x[1])
wrap_in_tag = lambda x: "<script type='text/template' id='{0}'>{1}</script>".format(x[0], escapejs(x[1]))
file_contents = map(read_file, filter(valid_file_name, os.listdir(mustache_dir)))
return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents)))
def render_content(content, additional_context={}): def render_content(content, additional_context={}):
content_info = { content_info = {
'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_title': content.get('highlighted_title') or content.get('title', ''),
......
$ -> class @Content extends Backbone.Model
class Content extends Backbone.Model
class Thread extends Content template: -> DiscussionUtil.getTemplate('_content')
window.Content = Content actions:
window.Thread = Thread editable: '.admin-edit'
can_reply: '.discussion-reply'
can_endorse: '.admin-endorse'
can_delete: '.admin-delete'
can_openclose: '.admin-openclose'
isUpvoted: ->
DiscussionUtil.isUpvoted @id
isDownvoted: ->
DiscussionUtil.isDownvoted @id
can: (action) ->
DiscussionUtil.getContentInfo @id, action
class @ContentView extends Backbone.View
$: (selector) ->
@$local.find(selector)
showSingleThread: (event) ->
unvote: (event) ->
url = DiscussionUtil.urlFor("undo_vote_for_#{@model.get('type')}", @model.id)
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
dataType: "json"
success: (response, textStatus) =>
if textStatus == "success"
@$(".discussion-vote").removeClass("voted")
@$(".discussion-votes-point").html response.votes.point
vote: (event) ->
$elem = $(event.target)
if $elem.hasClass("voted")
@unvote(event)
else
value = $elem.attr("value")
url = Discussion.urlFor("#{value}vote_#{@model.get('type')}", @model.id)
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
dataType: "json"
success: (response, textStatus) =>
if textStatus == "success"
@$(".discussion-vote").removeClass("voted")
@$(".discussion-vote-#{value}").addClass("voted")
@$(".discussion-votes-point").html response.votes.point
hideSingleThread: ->
reply: ->
cancelReply: ->
endorse: ->
close: ->
edit: ->
delete: ->
events:
"click .thread-title": "showSingleThread"
"click .discussion-show-comments": "showSingleThread"
"click .discussion-hide-comments": "hideSingleThread"
"click .discussion-reply-thread": "reply"
"click .discussion-reply-comment": "reply"
"click .discussion-cancel-reply": "cancelReply"
"click .discussion-vote-up": "vote"
"click .discussion-vote-down": "vote"
"click .admin-endorse": "endorse"
"click .admin-openclose": "close"
"click .admin-edit": "edit"
"click .admin-delete": "delete"
initLocal: ->
@$local = @$el.children(".local")
initFollowThread: ->
$el.children(".discussion-content")
.find(".follow-wrapper")
.append(DiscussionUtil.subscriptionLink('thread', id))
initVote: ->
if @model.isUpvoted()
@$(".discussion-vote-up").addClass("voted")
else if @model.isDownvoted()
@$(".discussion-vote-down").addClass("voted")
initBody: ->
$contentBody = @$(".content-body")
$contentBody.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight $contentBody.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
initActions: ->
for action, elemSelector of @model.actions
if not @model.can action
@$(elemSelector).remove()
initTimeago: ->
@$("span.timeago").timeago()
initialize: ->
@model.view = @
@initLocal()
@initVote()
@initTimeago()
@initBody()
@initActions()
class @Thread extends @Content
class @ThreadView extends @ContentView
class @Comment extends @Content
class @Comments extends Backbone.Collection
model: Comment
$ -> class @Discussion extends Backbone.Collection
model: Thread
class Discussion extends Backbone.Collection initialize: ->
model: Thread this.bind "add", (item) =>
initialize: -> item.collection = this
this.bind "add", (item) =>
item.collection = this find: (id) ->
_.first @where(id: id)
class DiscussionModuleView extends Backbone.View
class @DiscussionModuleView extends Backbone.View
class DiscussionView extends Backbone.View
class @DiscussionView extends Backbone.View
$: (selector) ->
@$local.find(selector) $: (selector) ->
@$local.find(selector)
initialize: ->
@$local = @$el.children(".local") initLocal: ->
@$local = @$el.children(".local")
events:
"submit .search-wrapper>.discussion-search-form": "search" initialize: ->
"click .discussion-search-link": "search" @initLocal()
"click .discussion-sort-link": "sort" @model.view = @
"click .discussion-paginator>.discussion-page-link": "page" @$el.children(".threads").children(".thread").each (index, elem) =>
threadView = new ThreadView el: elem, model: @model.find $(elem).attr("_id")
$(".discussion-module").each (index, elem) ->
view = new DiscussionModuleView(el: elem) search: ->
$("section.discussion").each (index, elem) -> sort: ->
discussionData = DiscussionUtil.getDiscussionData(elem)
discussion = new Discussion(discussionData) page: ->
view = new DiscussionView(el: elem, model: discussion)
events:
"submit .search-wrapper>.discussion-search-form": "search"
"click .discussion-search-link": "search"
"click .discussion-sort-link": "sort"
"click .discussion-paginator>.discussion-page-link": "page"
$ ->
$(".discussion-module").each (index, elem) ->
view = new DiscussionModuleView(el: elem)
$("section.discussion").each (index, elem) ->
discussionData = DiscussionUtil.getDiscussionData(elem)
discussion = new Discussion(discussionData)
view = new DiscussionView(el: elem, model: discussion)
class @DiscussionUtil class @DiscussionUtil
@wmdEditors: {}
@getTemplate: (id) ->
$("script##{id}").html()
@getDiscussionData: (id) -> @getDiscussionData: (id) ->
if id instanceof $ if id instanceof $
id = id.attr("_id") id = id.attr("_id")
else if typeof id == "object" else if typeof id == "object"
id = $(id).attr("_id") id = $(id).attr("_id")
return $$discussion_data[id] return $$discussion_data[id]
@getContentInfo: (id, attr) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
(window.$$annotated_content_info[id] || {})[attr]
@setContentInfo: (id, attr, value) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info[id] ||= {}
window.$$annotated_content_info[id][attr] = value
@extendContentInfo: (id, newInfo) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info[id] = newInfo
@bulkExtendContentInfo: (newInfos) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info = $.extend window.$$annotated_content_info, newInfos
@generateDiscussionLink: (cls, txt, handler) ->
$("<a>").addClass("discussion-link")
.attr("href", "javascript:void(0)")
.addClass(cls).html(txt)
.click -> handler(this)
@urlFor: (name, param, param1, param2) ->
{
follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow"
unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow"
create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create"
search_similar_threads : "/courses/#{$$course_id}/discussion/#{param}/threads/search_similar"
update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update"
create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply"
delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete"
upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote"
downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote"
undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote"
follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow"
unfollow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unfollow"
update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update"
endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse"
create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply"
delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete"
upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote"
downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote"
undo_vote_for_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/unvote"
upload : "/courses/#{$$course_id}/discussion/upload"
search : "/courses/#{$$course_id}/discussion/forum/search"
tags_autocomplete : "/courses/#{$$course_id}/discussion/threads/tags/autocomplete"
retrieve_discussion : "/courses/#{$$course_id}/discussion/forum/#{param}/inline"
retrieve_single_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}"
update_moderator_status : "/courses/#{$$course_id}/discussion/users/#{param}/update_moderator_status"
openclose_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/close"
permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}"
permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}"
}[name]
@safeAjax: (params) ->
$elem = params.$elem
if $elem.attr("disabled")
return
$elem.attr("disabled", "disabled")
$.ajax(params).always ->
$elem.removeAttr("disabled")
@handleAnchorAndReload: (response) ->
#window.location = window.location.pathname + "#" + response['id']
window.location.reload()
@bindLocalEvents: ($local, eventsHandler) ->
for eventSelector, handler of eventsHandler
[event, selector] = eventSelector.split(' ')
$local(selector).unbind(event)[event] handler
@tagsInputOptions: ->
autocomplete_url: @urlFor('tags_autocomplete')
autocomplete:
remoteDataType: 'json'
interactive: true
height: '30px'
width: '100%'
defaultText: "Tag your post: press enter after each tag"
removeWithBackspace: true
@isSubscribed: (id, type) ->
$$user_info? and (
if type == "thread"
id in $$user_info.subscribed_thread_ids
else if type == "commentable" or type == "discussion"
id in $$user_info.subscribed_commentable_ids
else
id in $$user_info.subscribed_user_ids
)
@isUpvoted: (id) ->
$$user_info? and (id in $$user_info.upvoted_ids)
@isDownvoted: (id) ->
$$user_info? and (id in $$user_info.downvoted_ids)
@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))
@clearFormErrors: (errorsField) ->
errorsField.empty()
@postMathJaxProcessor: (text) ->
RE_INLINEMATH = /^\$([^\$]*)\$/g
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g
@processEachMathAndCode text, (s, type) ->
if type == 'display'
s.replace RE_DISPLAYMATH, ($0, $1) ->
"\\[" + $1 + "\\]"
else if type == 'inline'
s.replace RE_INLINEMATH, ($0, $1) ->
"\\(" + $1 + "\\)"
else
s
@makeWmdEditor: ($content, $local, cls_identifier) ->
elem = $local(".#{cls_identifier}")
id = $content.attr("_id")
appended_id = "-#{cls_identifier}-#{id}"
imageUploadUrl = @urlFor('upload')
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, @postMathJaxProcessor
@wmdEditors["#{cls_identifier}-#{id}"] = editor
editor
@getWmdEditor: ($content, $local, cls_identifier) ->
id = $content.attr("_id")
@wmdEditors["#{cls_identifier}-#{id}"]
@getWmdInput: ($content, $local, cls_identifier) ->
id = $content.attr("_id")
$local("#wmd-input-#{cls_identifier}-#{id}")
@getWmdContent: ($content, $local, cls_identifier) ->
@getWmdInput($content, $local, cls_identifier).val()
@setWmdContent: ($content, $local, cls_identifier, text) ->
@getWmdInput($content, $local, cls_identifier).val(text)
@getWmdEditor($content, $local, cls_identifier).refreshPreview()
@subscriptionLink: (type, id) ->
followLink = ->
@generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow)
unfollowLink = ->
@generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow)
handleFollow = (elem) ->
@safeAjax
$elem: $(elem)
url: @urlFor("follow_#{type}", id)
type: "POST"
success: (response, textStatus) ->
if textStatus == "success"
$(elem).replaceWith unfollowLink()
dataType: 'json'
handleUnfollow = (elem) ->
@safeAjax
$elem: $(elem)
url: @urlFor("unfollow_#{type}", id)
type: "POST"
success: (response, textStatus) ->
if textStatus == "success"
$(elem).replaceWith followLink()
dataType: 'json'
if @isSubscribed(id, type)
unfollowLink()
else
followLink()
@processEachMathAndCode: (text, processor) ->
codeArchive = []
RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m
RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m
ESCAPED_DOLLAR = '@@ESCAPED_D@@'
ESCAPED_BACKSLASH = '@@ESCAPED_B@@'
processedText = ""
$div = $("<div>").html(text)
$div.find("code").each (index, code) ->
codeArchive.push $(code).html()
$(code).html(codeArchive.length - 1)
text = $div.html()
text = text.replace /\\\$/g, ESCAPED_DOLLAR
while true
if RE_INLINEMATH.test(text)
text = text.replace RE_INLINEMATH, ($0, $1, $2, $3) ->
processedText += $1 + processor("$" + $2 + "$", 'inline')
$3
else if RE_DISPLAYMATH.test(text)
text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) ->
processedText += $1 + processor("$$" + $2 + "$$", 'display')
$3
else
processedText += text
break
text = processedText
text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$')
text = text.replace /\\\\\\\\/g, ESCAPED_BACKSLASH
text = text.replace /\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, ($0, $1, $2) ->
processor("\\begin{#{$1}}" + $2 + "\\end{#{$1}}")
text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\')
$div = $("<div>").html(text)
cnt = 0
$div.find("code").each (index, code) ->
$(code).html(processor(codeArchive[cnt], 'code'))
cnt += 1
text = $div.html()
text
@unescapeHighlightTag: (text) ->
text.replace(/\&lt\;highlight\&gt\;/g, "<span class='search-highlight'>")
.replace(/\&lt\;\/highlight\&gt\;/g, "</span>")
@stripHighlight: (text) ->
text.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "")
.replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "")
@stripLatexHighlight: (text) ->
@processEachMathAndCode text, @stripHighlight
@markdownWithHighlight: (text) ->
converter = Markdown.getMathCompatibleConverter()
@unescapeHighlightTag @stripLatexHighlight converter.makeHtml text
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
<%include file="discussion/_js_head_dependencies.html" />
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
...@@ -21,7 +22,7 @@ ...@@ -21,7 +22,7 @@
<%static:js group='courseware'/> <%static:js group='courseware'/>
<%include file="discussion/_js_dependencies.html" /> <%include file="discussion/_js_body_dependencies.html" />
<!-- TODO: http://docs.jquery.com/Plugins/Validation --> <!-- TODO: http://docs.jquery.com/Plugins/Validation -->
......
<div class="discussion-content"> <div class="discussion-content">
<div class="discussion-content-wrapper"> <div class="discussion-content-wrapper">
<div class="discussion-votes"> <div class="discussion-votes">
<a class="discussion-vote discussion-vote-up" href="javascript:void(0)">&#9650;</a> <a class="discussion-vote discussion-vote-up" href="javascript:void(0)" value="up">&#9650;</a>
<div class="discussion-votes-point">{{content.votes.point}}</div> <div class="discussion-votes-point">{{content.votes.point}}</div>
<a class="discussion-vote discussion-vote-down" href="javascript:void(0)">&#9660;</a> <a class="discussion-vote discussion-vote-down" href="javascript:void(0)" value="down">&#9660;</a>
</div> </div>
<div class="discussion-right-wrapper"> <div class="discussion-right-wrapper">
<ul class="admin-actions"> <ul class="admin-actions">
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<%def name="render_content_with_comments(content)"> <%def name="render_content_with_comments(content)">
<div class="${content['type']}" _id="${content['id']}" _discussion_id="${content.get('commentable_id')}" _author_id="${helpers.show_if(content['user_id'], content.get('anonymous'))}"> <div class="${content['type']}" _id="${content['id']}" _discussion_id="${content.get('commentable_id')}" _author_id="${helpers.show_if(content['user_id'], content.get('anonymous'))}">
${render_content(content)} <div class="local">${render_content(content)}</div>
% if content.get('children') is not None: % if content.get('children') is not None:
${render_comments(content['children'])} ${render_comments(content['children'])}
% endif % endif
......
<%! from django_comment_client.helpers import include_mustache_templates %>
<%include file="/mathjax_include.html" />
${include_mustache_templates()}
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%include file="/mathjax_include.html" />
<script type="text/javascript" src="${static.url('js/split.js')}"></script> <script type="text/javascript" src="${static.url('js/split.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.ajaxfileupload.js')}"></script> <script type="text/javascript" src="${static.url('js/jquery.ajaxfileupload.js')}"></script>
<script type="text/javascript" src="${static.url('js/Markdown.Converter.js')}"></script> <script type="text/javascript" src="${static.url('js/Markdown.Converter.js')}"></script>
...@@ -17,4 +15,3 @@ ...@@ -17,4 +15,3 @@
<link href="${static.url('css/vendor/jquery.tagsinput.css')}" rel="stylesheet" type="text/css"> <link href="${static.url('css/vendor/jquery.tagsinput.css')}" rel="stylesheet" type="text/css">
<link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css"> <link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css">
<script type="text/template" id="content-template" src="${static.url('templates/_content.mustache')}"></script>
<%namespace name='static' file='../static_content.html'/>
<script type="text/javascript" src="${static.url('js/split.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.ajaxfileupload.js')}"></script>
<script type="text/javascript" src="${static.url('js/Markdown.Converter.js')}"></script>
<script type="text/javascript" src="${static.url('js/Markdown.Sanitizer.js')}"></script>
<script type="text/javascript" src="${static.url('js/Markdown.Editor.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.autocomplete.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.timeago.js')}"></script>
<script type="text/javascript" src="${static.url('js/jquery.tagsinput.js')}"></script>
<script type="text/javascript" src="${static.url('js/mustache.js')}"></script>
<script type="text/javascript" src="${static.url('js/URI.min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<link href="${static.url('css/vendor/jquery.tagsinput.css')}" rel="stylesheet" type="text/css">
<link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css">
...@@ -5,10 +5,11 @@ ...@@ -5,10 +5,11 @@
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
<%include file="_js_head_dependencies.html" />
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<%include file="_js_dependencies.html" /> <%include file="_js_body_dependencies.html" />
</%block> </%block>
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" /> <%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
......
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