Commit aeee2791 by David Ormsbee

Merge remote-tracking branch 'rocky/master' into feature/dave/forum_fixes

Conflicts:
	lms/lib/comment_client/utils.py
	lms/templates/discussion/mustache/_content.mustache
parents 2e39131b 952c9d56
...@@ -392,6 +392,7 @@ def instructor_dashboard(request, course_id): ...@@ -392,6 +392,7 @@ def instructor_dashboard(request, course_id):
# For now, just a static page # For now, just a static page
context = {'course': course, context = {'course': course,
'staff_access': True,} 'staff_access': True,}
return render_to_response('courseware/instructor_dashboard.html', context) return render_to_response('courseware/instructor_dashboard.html', context)
@ensure_csrf_cookie @ensure_csrf_cookie
......
...@@ -51,7 +51,8 @@ def ajax_content_response(request, course_id, content, template_name): ...@@ -51,7 +51,8 @@ def ajax_content_response(request, course_id, content, template_name):
'content': content, 'content': content,
} }
html = render_to_string(template_name, context) html = render_to_string(template_name, context)
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user) user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
return JsonResponse({ return JsonResponse({
'html': html, 'html': html,
'content': content, 'content': content,
......
...@@ -11,11 +11,11 @@ from courseware.courses import get_course_with_access ...@@ -11,11 +11,11 @@ from courseware.courses import get_course_with_access
from courseware.access import has_access from courseware.access import has_access
from urllib import urlencode from urllib import urlencode
from operator import methodcaller
from django_comment_client.permissions import check_permissions_by_view from django_comment_client.permissions import check_permissions_by_view
from django_comment_client.utils import merge_dict, extract, strip_none from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank
import json import json
import dateutil
import django_comment_client.utils as utils import django_comment_client.utils as utils
import comment_client as cc import comment_client as cc
...@@ -64,22 +64,26 @@ def render_discussion(request, course_id, threads, *args, **kwargs): ...@@ -64,22 +64,26 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])), 'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])),
}[discussion_type]() }[discussion_type]()
annotated_content_infos = map(lambda x: utils.get_annotated_content_infos(course_id, x, request.user), threads) user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = reduce(merge_dict, annotated_content_infos, {})
def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
context = { context = {
'threads': threads, 'threads': threads,
'discussion_id': discussion_id, 'discussion_id': discussion_id,
'user_id': user_id, 'user_id': user_id,
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
'course_id': course_id, 'course_id': course_id,
'request': request, 'request': request,
'performed_search': _should_perform_search(request), 'performed_search': _should_perform_search(request),
'pages_nearby_delta': PAGES_NEARBY_DELTA, 'pages_nearby_delta': PAGES_NEARBY_DELTA,
'discussion_type': discussion_type, 'discussion_type': discussion_type,
'base_url': base_url, 'base_url': base_url,
'query_params': strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text'])), 'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
'annotated_content_info': json.dumps(annotated_content_info), 'annotated_content_info': json.dumps(annotated_content_info),
'discussion_data': json.dumps({ discussion_id: threads }),
} }
context = dict(context.items() + query_params.items()) context = dict(context.items() + query_params.items())
return render_to_string(template, context) return render_to_string(template, context)
...@@ -121,7 +125,11 @@ def inline_discussion(request, course_id, discussion_id): ...@@ -121,7 +125,11 @@ def inline_discussion(request, course_id, discussion_id):
threads, query_params = get_threads(request, course_id, discussion_id) threads, query_params = get_threads(request, course_id, discussion_id)
html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \ html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
query_params=query_params) query_params=query_params)
return utils.HtmlResponse(html)
return utils.JsonResponse({
'html': html,
'discussionData': threads,
})
def render_search_bar(request, course_id, discussion_id=None, text=''): def render_search_bar(request, course_id, discussion_id=None, text=''):
if not discussion_id: if not discussion_id:
...@@ -138,19 +146,21 @@ def forum_form_discussion(request, course_id): ...@@ -138,19 +146,21 @@ def forum_form_discussion(request, course_id):
threads, query_params = get_threads(request, course_id) threads, query_params = get_threads(request, course_id)
content = render_forum_discussion(request, course_id, threads, discussion_id=_general_discussion_id(course_id), query_params=query_params) content = render_forum_discussion(request, course_id, threads, discussion_id=_general_discussion_id(course_id), query_params=query_params)
recent_active_threads = cc.search_recent_active_threads(
course_id,
recursive=False,
query_params={'follower_id': request.user.id},
)
trending_tags = cc.search_trending_tags(
course_id,
)
if request.is_ajax(): if request.is_ajax():
return utils.HtmlResponse(content) return utils.JsonResponse({
'html': content,
'discussionData': threads,
})
else: else:
recent_active_threads = cc.search_recent_active_threads(
course_id,
recursive=False,
query_params={'follower_id': request.user.id},
)
trending_tags = cc.search_trending_tags(
course_id,
)
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'course': course, 'course': course,
...@@ -164,17 +174,19 @@ def forum_form_discussion(request, course_id): ...@@ -164,17 +174,19 @@ def forum_form_discussion(request, course_id):
def render_single_thread(request, discussion_id, course_id, thread_id): def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve(recursive=True) thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict()
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread.to_dict(), user=request.user) annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread, user=request.user, user_info=user_info)
context = { context = {
'discussion_id': discussion_id, 'discussion_id': discussion_id,
'thread': thread, 'thread': thread,
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
'annotated_content_info': json.dumps(annotated_content_info), 'annotated_content_info': json.dumps(annotated_content_info),
'course_id': course_id, 'course_id': course_id,
'request': request, 'request': request,
'discussion_data': json.dumps({ discussion_id: [thread] }),
} }
return render_to_string('discussion/_single_thread.html', context) return render_to_string('discussion/_single_thread.html', context)
...@@ -182,13 +194,15 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -182,13 +194,15 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax(): if request.is_ajax():
user_info = cc.User.from_django_user(request.user).to_dict()
thread = cc.Thread.find(thread_id).retrieve(recursive=True) thread = cc.Thread.find(thread_id).retrieve(recursive=True)
annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user) annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info)
context = {'thread': thread.to_dict(), 'course_id': course_id} context = {'thread': thread.to_dict(), 'course_id': course_id}
html = render_to_string('discussion/_ajax_single_thread.html', context) html = render_to_string('discussion/_ajax_single_thread.html', context)
return utils.JsonResponse({ return utils.JsonResponse({
'html': html, 'html': html,
'content': thread.to_dict(),
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
}) })
...@@ -200,7 +214,6 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -200,7 +214,6 @@ def single_thread(request, course_id, discussion_id, thread_id):
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'init': '', 'init': '',
'content': render_single_thread(request, discussion_id, course_id, thread_id), 'content': render_single_thread(request, discussion_id, course_id, thread_id),
'accordion': render_accordion(request, course, discussion_id),
'course': course, 'course': course,
'course_id': course.id, 'course_id': course.id,
} }
......
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
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 django.core.urlresolvers import reverse
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,12 +22,32 @@ def show_if(text, condition): ...@@ -18,12 +22,32 @@ 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' / 'mustache'
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], 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', ''),
'displayed_body': content.get('highlighted_body') or content.get('body', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''),
'raw_tags': ','.join(content.get('tags', [])), 'raw_tags': ','.join(content.get('tags', [])),
} }
print content_info
if content['type'] == 'thread':
content_info['permalink'] = reverse('django_comment_client.forum.views.single_thread',
args=[content['course_id'], content['commentable_id'], content['id']])
else:
content_info['permalink'] = reverse('django_comment_client.forum.views.single_thread',
args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
context = { context = {
'content': merge_dict(content, content_info), 'content': merge_dict(content, content_info),
content['type']: True, content['type']: True,
...@@ -31,4 +55,4 @@ def render_content(content, additional_context={}): ...@@ -31,4 +55,4 @@ def render_content(content, additional_context={}):
context = merge_dict(context, additional_context) context = merge_dict(context, additional_context)
partial_mustache_helpers = {k: partial(v, content) for k, v in mustache_helpers.items()} partial_mustache_helpers = {k: partial(v, content) for k, v in mustache_helpers.items()}
context = merge_dict(context, partial_mustache_helpers) context = merge_dict(context, partial_mustache_helpers)
return render_mustache('discussion/_content.mustache', context) return render_mustache('discussion/mustache/_content.mustache', context)
...@@ -6,9 +6,9 @@ import inspect ...@@ -6,9 +6,9 @@ import inspect
def pluralize(content, text): def pluralize(content, text):
num, word = text.split(' ') num, word = text.split(' ')
if int(num or '0') >= 2: if int(num or '0') >= 2:
return num + ' ' + word + 's' return word + 's'
else: else:
return num + ' ' + word return word
def url_for_user(content, user_id): def url_for_user(content, user_id):
return urlresolvers.reverse('django_comment_client.forum.views.user_profile', args=[content['course_id'], user_id]) return urlresolvers.reverse('django_comment_client.forum.views.user_profile', args=[content['course_id'], user_id])
......
...@@ -18,6 +18,7 @@ class PermissionsTestCase(TestCase): ...@@ -18,6 +18,7 @@ class PermissionsTestCase(TestCase):
return ''.join(random.choice(chars) for x in range(length)) return ''.join(random.choice(chars) for x in range(length))
def setUp(self): def setUp(self):
self.course_id = "MITx/6.002x/2012_Fall" self.course_id = "MITx/6.002x/2012_Fall"
self.moderator_role = Role.objects.get_or_create(name="Moderator", course_id=self.course_id)[0] self.moderator_role = Role.objects.get_or_create(name="Moderator", course_id=self.course_id)[0]
......
...@@ -160,23 +160,32 @@ class QueryCountDebugMiddleware(object): ...@@ -160,23 +160,32 @@ class QueryCountDebugMiddleware(object):
logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time)) logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time))
return response return response
def get_annotated_content_info(course_id, content, user): def get_annotated_content_info(course_id, content, user, user_info):
voted = ''
if content['id'] in user_info['upvoted_ids']:
voted = 'up'
elif content['id'] in user_info['downvoted_ids']:
voted = 'down'
return { return {
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"), 'voted': voted,
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"), 'subscribed': content['id'] in user_info['subscribed_thread_ids'],
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False, 'ability': {
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"), 'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False, 'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"), 'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
},
} }
def get_annotated_content_infos(course_id, thread, user): def get_annotated_content_infos(course_id, thread, user, user_info):
infos = {} infos = {}
def _annotate(content): def annotate(content):
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user) infos[str(content['id'])] = get_annotated_content_info(course_id, content, user, user_info)
for child in content.get('children', []): for child in content.get('children', []):
_annotate(child) annotate(child)
_annotate(thread) annotate(thread)
return infos return infos
def render_mustache(template_name, dictionary, *args, **kwargs): def render_mustache(template_name, dictionary, *args, **kwargs):
......
from utils import * from utils import *
from thread import Thread
import models import models
import settings import settings
...@@ -10,7 +11,7 @@ class Comment(models.Model): ...@@ -10,7 +11,7 @@ class Comment(models.Model):
'endorsed', 'parent_id', 'thread_id', 'endorsed', 'parent_id', 'thread_id',
'username', 'votes', 'user_id', 'closed', 'username', 'votes', 'user_id', 'closed',
'created_at', 'updated_at', 'depth', 'created_at', 'updated_at', 'depth',
'at_position_list', 'type', 'at_position_list', 'type', 'commentable_id',
] ]
updatable_fields = [ updatable_fields = [
...@@ -23,6 +24,10 @@ class Comment(models.Model): ...@@ -23,6 +24,10 @@ class Comment(models.Model):
base_url = "{prefix}/comments".format(prefix=settings.PREFIX) base_url = "{prefix}/comments".format(prefix=settings.PREFIX)
type = 'comment' type = 'comment'
@property
def thread(self):
return Thread(id=self.thread_id, type='thread')
@classmethod @classmethod
def url_for_comments(cls, params={}): def url_for_comments(cls, params={}):
if params.get('thread_id'): if params.get('thread_id'):
......
...@@ -43,6 +43,9 @@ class Model(object): ...@@ -43,6 +43,9 @@ class Model(object):
raise KeyError("Field {0} does not exist".format(key)) raise KeyError("Field {0} does not exist".format(key))
self.attributes.__setitem__(key, value) self.attributes.__setitem__(key, value)
def items(self, *args, **kwargs):
return self.attributes.items(*args, **kwargs)
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
return self.attributes.get(*args, **kwargs) return self.attributes.get(*args, **kwargs)
......
...@@ -11,6 +11,7 @@ class Thread(models.Model): ...@@ -11,6 +11,7 @@ class Thread(models.Model):
'commentable_id', 'username', 'user_id', 'commentable_id', 'username', 'user_id',
'created_at', 'updated_at', 'comments_count', 'created_at', 'updated_at', 'comments_count',
'at_position_list', 'children', 'type', 'at_position_list', 'children', 'type',
'highlighted_title', 'highlighted_body',
] ]
updatable_fields = [ updatable_fields = [
......
...@@ -18,7 +18,7 @@ def extract(dic, keys): ...@@ -18,7 +18,7 @@ def extract(dic, keys):
def merge_dict(dic1, dic2): def merge_dict(dic1, dic2):
return dict(dic1.items() + dic2.items()) return dict(dic1.items() + dic2.items())
def perform_request(method, url, data_or_params=None, *args, **kwargs): def perform_request(method, url, data_or_params=None, *args, **kwargs):
if data_or_params is None: if data_or_params is None:
data_or_params = {} data_or_params = {}
......
if not @Discussion? class @DiscussionModuleView extends Backbone.View
@Discussion = {} events:
"click .discussion-show": "toggleDiscussion"
Discussion = @Discussion toggleDiscussion: (event) ->
if @showed
@Discussion = $.extend @Discussion, @$("section.discussion").hide()
initializeDiscussionModule: (elem) -> $(event.target).html("Show Discussion")
$discussionModule = $(elem) @showed = false
$local = Discussion.generateLocal($discussionModule) else
handleShowDiscussion = (elem) -> if @retrieved
$elem = $(elem) @$("section.discussion").show()
if not $local("section.discussion").length $(event.target).html("Hide Discussion")
@showed = true
else
$elem = $(event.target)
discussion_id = $elem.attr("discussion_id") discussion_id = $elem.attr("discussion_id")
url = Discussion.urlFor 'retrieve_discussion', discussion_id url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id
Discussion.safeAjax Discussion.safeAjax
$elem: $elem $elem: $elem
url: url url: url
type: "GET" type: "GET"
success: (data, textStatus, xhr) -> dataType: 'json'
$discussionModule.append(data) success: (response, textStatus) =>
discussion = $local("section.discussion") @$el.append(response.html)
Discussion.initializeDiscussion(discussion) $discussion = @$el.find("section.discussion")
Discussion.bindDiscussionEvents(discussion) $(event.target).html("Hide Discussion")
$elem.html("Hide Discussion") discussion = new Discussion()
$elem.unbind('click').click -> discussion.reset(response.discussionData, {silent: false})
handleHideDiscussion(this) view = new DiscussionView(el: $discussion[0], model: discussion)
dataType: 'html' DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
else @retrieved = true
$local("section.discussion").show() @showed = true
$elem.html("Hide Discussion")
$elem.unbind('click').click ->
handleHideDiscussion(this)
handleHideDiscussion = (elem) ->
$local("section.discussion").hide()
$elem = $(elem)
$elem.html("Show Discussion")
$elem.unbind('click').click ->
handleShowDiscussion(this)
$local(".discussion-show").click ->
handleShowDiscussion(this)
$ -> $ ->
toggle = -> window.$$contents = {}
$('.course-wrapper').toggleClass('closed') window.$$discussions = {}
Discussion = window.Discussion
if $('#accordion').length
active = $('#accordion ul:has(li.active)').index('#accordion ul')
$('#accordion').bind('accordionchange', @log).accordion
active: if active >= 0 then active else 1
header: 'h3'
autoHeight: false
$('#open_close_accordion a').click toggle
$('#accordion').show()
$(".discussion-module").each (index, elem) -> $(".discussion-module").each (index, elem) ->
Discussion.initializeDiscussionModule(elem) view = new DiscussionModuleView(el: elem)
$("section.discussion").each (index, discussion) -> $("section.discussion").each (index, elem) ->
Discussion.initializeDiscussion(discussion) discussionData = DiscussionUtil.getDiscussionData($(elem).attr("_id"))
Discussion.bindDiscussionEvents(discussion) discussion = new Discussion()
discussion.reset(discussionData, {silent: false})
view = new DiscussionView(el: elem, model: discussion)
Discussion.initializeUserProfile($(".discussion-sidebar>.user-profile")) DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
@Discussion = $.extend @Discussion,
newPostTemplate: """
<form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
<ul class="new-post-form-errors discussion-errors"></ul>
<input type="text" class="new-post-title title-input" placeholder="Title" />
<div class="new-post-similar-posts-wrapper" style="display: none">
Similar Posts:
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
<div class="new-post-similar-posts"></div>
</div>
<div class="new-post-body reply-body"></div>
<input class="new-post-tags" placeholder="Tags" />
<div class="post-options">
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-${discussion_id}">
<label for="discussion-post-anonymously-${discussion_id}">post anonymously</label>
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-${discussion_id}" checked="">
<label for="discussion-auto-watch-${discussion_id}">follow this thread</label>
</div>
<div class="new-post-control post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
"""
replyTemplate: """
<form class="discussion-reply-new">
<ul class="discussion-errors"></ul>
<div class="reply-body"></div>
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-{{id}}" />
<label for="discussion-post-anonymously-{{id}}">post anonymously</label>
{{#showWatchCheckbox}}
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-{{id}}" checked />
<label for="discussion-auto-watch-{{id}}">follow this thread</label>
{{/showWatchCheckbox}}
<br />
<div class = "reply-post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
"""
editThreadTemplate: """
<form class="discussion-content-edit discussion-thread-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<input type="text" class="thread-title-edit title-input" placeholder="Title" value="{{title}}"/>
<div class="thread-body-edit body-input">{{body}}</div>
<input class="thread-tags-edit" placeholder="Tags" value="{{tags}}" />
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
"""
editCommentTemplate: """
<form class="discussion-content-edit discussion-comment-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<div class="comment-body-edit body-input">{{body}}</div>
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
"""
if not @Discussion? class @DiscussionUtil
@Discussion = {}
Discussion = @Discussion @wmdEditors: {}
wmdEditors = {} @getTemplate: (id) ->
$("script##{id}").html()
@Discussion = $.extend @Discussion, @getDiscussionData: (id) ->
return $$discussion_data[id]
generateLocal: (elem) -> @addContent: (id, content) -> window.$$contents[id] = content
(selector) -> $(elem).find(selector)
generateDiscussionLink: (cls, txt, handler) -> @getContent: (id) -> window.$$contents[id]
@addDiscussion: (id, discussion) -> window.$$discussions[id] = discussion
@getDiscussion: (id) -> window.$$discussions[id]
@bulkUpdateContentInfo: (infos) ->
for id, info of infos
@getContent(id).updateInfo(info)
@generateDiscussionLink: (cls, txt, handler) ->
$("<a>").addClass("discussion-link") $("<a>").addClass("discussion-link")
.attr("href", "javascript:void(0)") .attr("href", "javascript:void(0)")
.addClass(cls).html(txt) .addClass(cls).html(txt)
.click -> handler(this) .click -> handler(this)
urlFor: (name, param, param1, param2) -> @urlFor: (name, param, param1, param2) ->
{ {
follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow" follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow"
unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow" unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow"
...@@ -48,7 +58,7 @@ wmdEditors = {} ...@@ -48,7 +58,7 @@ wmdEditors = {}
permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}" permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}"
}[name] }[name]
safeAjax: (params) -> @safeAjax: (params) ->
$elem = params.$elem $elem = params.$elem
if $elem.attr("disabled") if $elem.attr("disabled")
return return
...@@ -56,17 +66,31 @@ wmdEditors = {} ...@@ -56,17 +66,31 @@ wmdEditors = {}
$.ajax(params).always -> $.ajax(params).always ->
$elem.removeAttr("disabled") $elem.removeAttr("disabled")
handleAnchorAndReload: (response) -> @get: ($elem, url, data, success) ->
#window.location = window.location.pathname + "#" + response['id'] @safeAjax
window.location.reload() $elem: $elem
url: url
bindLocalEvents: ($local, eventsHandler) -> type: "GET"
dataType: "json"
data: data
success: success
@post: ($elem, url, data, success) ->
@safeAjax
$elem: $elem
url: url
type: "POST"
dataType: "json"
data: data
success: success
@bindLocalEvents: ($local, eventsHandler) ->
for eventSelector, handler of eventsHandler for eventSelector, handler of eventsHandler
[event, selector] = eventSelector.split(' ') [event, selector] = eventSelector.split(' ')
$local(selector).unbind(event)[event] handler $local(selector).unbind(event)[event] handler
tagsInputOptions: -> @tagsInputOptions: ->
autocomplete_url: Discussion.urlFor('tags_autocomplete') autocomplete_url: @urlFor('tags_autocomplete')
autocomplete: autocomplete:
remoteDataType: 'json' remoteDataType: 'json'
interactive: true interactive: true
...@@ -75,23 +99,7 @@ wmdEditors = {} ...@@ -75,23 +99,7 @@ wmdEditors = {}
defaultText: "Tag your post: press enter after each tag" defaultText: "Tag your post: press enter after each tag"
removeWithBackspace: true removeWithBackspace: true
isSubscribed: (id, type) -> @formErrorHandler: (errorsField) ->
$$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) -> (xhr, textStatus, error) ->
response = JSON.parse(xhr.responseText) response = JSON.parse(xhr.responseText)
if response.errors? and response.errors.length > 0 if response.errors? and response.errors.length > 0
...@@ -99,13 +107,13 @@ wmdEditors = {} ...@@ -99,13 +107,13 @@ wmdEditors = {}
for error in response.errors for error in response.errors
errorsField.append($("<li>").addClass("new-post-form-error").html(error)) errorsField.append($("<li>").addClass("new-post-form-error").html(error))
clearFormErrors: (errorsField) -> @clearFormErrors: (errorsField) ->
errorsField.empty() errorsField.empty()
postMathJaxProcessor: (text) -> @postMathJaxProcessor: (text) ->
RE_INLINEMATH = /^\$([^\$]*)\$/g RE_INLINEMATH = /^\$([^\$]*)\$/g
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g
Discussion.processEachMathAndCode text, (s, type) -> @processEachMathAndCode text, (s, type) ->
if type == 'display' if type == 'display'
s.replace RE_DISPLAYMATH, ($0, $1) -> s.replace RE_DISPLAYMATH, ($0, $1) ->
"\\[" + $1 + "\\]" "\\[" + $1 + "\\]"
...@@ -115,61 +123,43 @@ wmdEditors = {} ...@@ -115,61 +123,43 @@ wmdEditors = {}
else else
s s
makeWmdEditor: ($content, $local, cls_identifier) -> @makeWmdEditor: ($content, $local, cls_identifier) ->
elem = $local(".#{cls_identifier}") elem = $local(".#{cls_identifier}")
id = $content.attr("_id") id = $content.attr("_id")
appended_id = "-#{cls_identifier}-#{id}" appended_id = "-#{cls_identifier}-#{id}"
imageUploadUrl = Discussion.urlFor('upload') imageUploadUrl = @urlFor('upload')
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor _processor = (_this) ->
wmdEditors["#{cls_identifier}-#{id}"] = editor (text) -> _this.postMathJaxProcessor(text)
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, _processor(@)
@wmdEditors["#{cls_identifier}-#{id}"] = editor
editor editor
getWmdEditor: ($content, $local, cls_identifier) -> @getWmdEditor: ($content, $local, cls_identifier) ->
id = $content.attr("_id") id = $content.attr("_id")
wmdEditors["#{cls_identifier}-#{id}"] @wmdEditors["#{cls_identifier}-#{id}"]
getWmdInput: ($content, $local, cls_identifier) -> @getWmdInput: ($content, $local, cls_identifier) ->
id = $content.attr("_id") id = $content.attr("_id")
$local("#wmd-input-#{cls_identifier}-#{id}") $local("#wmd-input-#{cls_identifier}-#{id}")
getWmdContent: ($content, $local, cls_identifier) -> @getWmdContent: ($content, $local, cls_identifier) ->
Discussion.getWmdInput($content, $local, cls_identifier).val() @getWmdInput($content, $local, cls_identifier).val()
setWmdContent: ($content, $local, cls_identifier, text) -> @setWmdContent: ($content, $local, cls_identifier, text) ->
Discussion.getWmdInput($content, $local, cls_identifier).val(text) @getWmdInput($content, $local, cls_identifier).val(text)
Discussion.getWmdEditor($content, $local, cls_identifier).refreshPreview() @getWmdEditor($content, $local, cls_identifier).refreshPreview()
getContentInfo: (id, attr) -> @subscriptionLink: (type, id) ->
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
subscriptionLink: (type, id) ->
followLink = -> followLink = ->
Discussion.generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow) @generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow)
unfollowLink = -> unfollowLink = ->
Discussion.generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow) @generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow)
handleFollow = (elem) -> handleFollow = (elem) ->
Discussion.safeAjax @safeAjax
$elem: $(elem) $elem: $(elem)
url: Discussion.urlFor("follow_#{type}", id) url: @urlFor("follow_#{type}", id)
type: "POST" type: "POST"
success: (response, textStatus) -> success: (response, textStatus) ->
if textStatus == "success" if textStatus == "success"
...@@ -177,21 +167,21 @@ wmdEditors = {} ...@@ -177,21 +167,21 @@ wmdEditors = {}
dataType: 'json' dataType: 'json'
handleUnfollow = (elem) -> handleUnfollow = (elem) ->
Discussion.safeAjax @safeAjax
$elem: $(elem) $elem: $(elem)
url: Discussion.urlFor("unfollow_#{type}", id) url: @urlFor("unfollow_#{type}", id)
type: "POST" type: "POST"
success: (response, textStatus) -> success: (response, textStatus) ->
if textStatus == "success" if textStatus == "success"
$(elem).replaceWith followLink() $(elem).replaceWith followLink()
dataType: 'json' dataType: 'json'
if Discussion.isSubscribed(id, type) if @isSubscribed(id, type)
unfollowLink() unfollowLink()
else else
followLink() followLink()
processEachMathAndCode: (text, processor) -> @processEachMathAndCode: (text, processor) ->
codeArchive = [] codeArchive = []
...@@ -242,3 +232,18 @@ wmdEditors = {} ...@@ -242,3 +232,18 @@ wmdEditors = {}
text = $div.html() text = $div.html()
text 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
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
initializeFollowDiscussion = (discussion) ->
$discussion = $(discussion)
id = $following.attr("_id")
$local = Discussion.generateLocal()
$discussion.children(".discussion-non-content")
.find(".discussion-title-wrapper")
.append(Discussion.subscriptionLink('discussion', id))
@Discussion = $.extend @Discussion,
initializeDiscussion: (discussion) ->
$discussion = $(discussion)
$discussion.find(".thread").each (index, thread) ->
Discussion.initializeContent(thread)
Discussion.bindContentEvents(thread)
$discussion.find(".comment").each (index, comment) ->
Discussion.initializeContent(comment)
Discussion.bindContentEvents(comment)
#initializeFollowDiscussion(discussion) TODO move this somewhere else
bindDiscussionEvents: (discussion) ->
$discussion = $(discussion)
$discussionNonContent = $discussion.children(".discussion-non-content")
$local = Discussion.generateLocal($discussion.children(".discussion-local"))
id = $discussion.attr("_id")
handleSubmitNewPost = (elem) ->
title = $local(".new-post-title").val()
body = Discussion.getWmdContent $discussion, $local, "new-post-body"
tags = $local(".new-post-tags").val()
url = Discussion.urlFor('create_thread', id)
Discussion.safeAjax
$elem: $(elem)
url: url
type: "POST"
dataType: 'json'
data:
title: title
body: body
tags: tags
error: Discussion.formErrorHandler($local(".new-post-form-errors"))
success: (response, textStatus) ->
Discussion.clearFormErrors($local(".new-post-form-errors"))
$thread = $(response.html)
$discussion.children(".threads").prepend($thread)
$local(".new-post-title").val("")
Discussion.setWmdContent $discussion, $local, "new-post-body", ""
$local(".new-post-tags").val("")
if $discussion.hasClass("inline-discussion")
$local(".new-post-form").addClass("collapsed")
else if $discussion.hasClass("forum-discussion")
$local(".new-post-form").hide()
handleCancelNewPost = (elem) ->
if $discussion.hasClass("inline-discussion")
$local(".new-post-form").addClass("collapsed")
else if $discussion.hasClass("forum-discussion")
$local(".new-post-form").hide()
handleSimilarPost = (elem) ->
$title = $local(".new-post-title")
$wrapper = $local(".new-post-similar-posts-wrapper")
$similarPosts = $local(".new-post-similar-posts")
prevText = $title.attr("prev-text")
text = $title.val()
if text == prevText
if $local(".similar-post").length
$wrapper.show()
else if $.trim(text).length
Discussion.safeAjax
$elem: $(elem)
url: Discussion.urlFor 'search_similar_threads', id
type: "GET"
dateType: 'json'
data:
text: $local(".new-post-title").val()
success: (response, textStatus) ->
$similarPosts.empty()
console.log response
if $.type(response) == "array" and response.length
$wrapper.show()
for thread in response
#singleThreadUrl = Discussion.urlFor 'retrieve_single_thread
$similarPost = $("<a>").addClass("similar-post")
.html(thread["title"])
.attr("href", "javascript:void(0)") #TODO
.appendTo($similarPosts)
else
$wrapper.hide()
else
$wrapper.hide()
$title.attr("prev-text", text)
initializeNewPost = ->
view = { discussion_id: id }
$discussionNonContent = $discussion.children(".discussion-non-content")
if not $local(".wmd-panel").length
$discussionNonContent.append Mustache.render Discussion.newPostTemplate, view
$newPostBody = $local(".new-post-body")
Discussion.makeWmdEditor $discussion, $local, "new-post-body"
$input = Discussion.getWmdInput($discussion, $local, "new-post-body")
$input.attr("placeholder", "post a new topic...")
if $discussion.hasClass("inline-discussion")
$input.bind 'focus', (e) ->
$local(".new-post-form").removeClass('collapsed')
else if $discussion.hasClass("forum-discussion")
$local(".new-post-form").removeClass('collapsed')
$local(".new-post-tags").tagsInput Discussion.tagsInputOptions()
$local(".new-post-title").blur ->
handleSimilarPost(this)
$local(".hide-similar-posts").click ->
$local(".new-post-similar-posts-wrapper").hide()
$local(".discussion-submit-post").click ->
handleSubmitNewPost(this)
$local(".discussion-cancel-post").click ->
handleCancelNewPost(this)
$local(".new-post-form").show()
handleAjaxReloadDiscussion = (elem, url) ->
if not url then return
$elem = $(elem)
$discussion = $elem.parents("section.discussion")
Discussion.safeAjax
$elem: $elem
url: url
type: "GET"
dataType: 'html'
success: (data, textStatus) ->
$data = $(data)
$parent = $discussion.parent()
$discussion.replaceWith($data)
$discussion = $parent.children(".discussion")
Discussion.initializeDiscussion($discussion)
Discussion.bindDiscussionEvents($discussion)
handleAjaxSearch = (elem) ->
$elem = $(elem)
url = URI($elem.attr("action")).addSearch({text: $local(".search-input").val()})
handleAjaxReloadDiscussion($elem, url)
handleAjaxSort = (elem) ->
$elem = $(elem)
url = $elem.attr("sort-url")
handleAjaxReloadDiscussion($elem, url)
handleAjaxPage = (elem) ->
$elem = $(elem)
url = $elem.attr("page-url")
handleAjaxReloadDiscussion($elem, url)
if $discussion.hasClass("inline-discussion")
initializeNewPost()
if $discussion.hasClass("forum-discussion")
$discussionSidebar = $(".discussion-sidebar")
if $discussionSidebar.length
$sidebarLocal = Discussion.generateLocal($discussionSidebar)
Discussion.bindLocalEvents $sidebarLocal,
"click .sidebar-new-post-button": (event) ->
initializeNewPost()
Discussion.bindLocalEvents $local,
"submit .search-wrapper>.discussion-search-form": (event) ->
event.preventDefault()
handleAjaxSearch(this)
"click .discussion-search-link": ->
handleAjaxSearch($local(".search-wrapper>.discussion-search-form"))
"click .discussion-sort-link": ->
handleAjaxSort(this)
$discussion.children(".discussion-paginator").find(".discussion-page-link").unbind('click').click ->
handleAjaxPage(this)
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
@Discussion = $.extend @Discussion,
initializeDiscussionModule: (elem) ->
$discussionModule = $(elem)
$local = Discussion.generateLocal($discussionModule)
handleShowDiscussion = (elem) ->
$elem = $(elem)
if not $local("section.discussion").length
discussion_id = $elem.attr("discussion_id")
url = Discussion.urlFor 'retrieve_discussion', discussion_id
Discussion.safeAjax
$elem: $elem
url: url
type: "GET"
success: (data, textStatus, xhr) ->
$discussionModule.append(data)
discussion = $local("section.discussion")
Discussion.initializeDiscussion(discussion)
Discussion.bindDiscussionEvents(discussion)
$elem.html("Hide Discussion")
$elem.unbind('click').click ->
handleHideDiscussion(this)
dataType: 'html'
else
$local("section.discussion").show()
$elem.html("Hide Discussion")
$elem.unbind('click').click ->
handleHideDiscussion(this)
handleHideDiscussion = (elem) ->
$local("section.discussion").hide()
$elem = $(elem)
$elem.html("Show Discussion")
$elem.unbind('click').click ->
handleShowDiscussion(this)
$local(".discussion-show").click ->
handleShowDiscussion(this)
$ ->
#toggle = ->
# $('.course-wrapper').toggleClass('closed')
#Discussion = window.Discussion
#if $('#accordion').length
# active = $('#accordion ul:has(li.active)').index('#accordion ul')
# $('#accordion').bind('accordionchange', @log).accordion
# active: if active >= 0 then active else 1
# header: 'h3'
# autoHeight: false
# $('#open_close_accordion a').click toggle
# $('#accordion').show()
#$(".discussion-module").each (index, elem) ->
# Discussion.initializeDiscussionModule(elem)
#$("section.discussion").each (index, discussion) ->
# Discussion.initializeDiscussion(discussion)
# Discussion.bindDiscussionEvents(discussion)
#Discussion.initializeUserProfile($(".discussion-sidebar>.user-profile"))
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
@Discussion = $.extend @Discussion,
newPostTemplate: """
<form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
<ul class="new-post-form-errors discussion-errors"></ul>
<input type="text" class="new-post-title title-input" placeholder="Title" />
<div class="new-post-similar-posts-wrapper" style="display: none">
Similar Posts:
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
<div class="new-post-similar-posts"></div>
</div>
<div class="new-post-body reply-body"></div>
<input class="new-post-tags" placeholder="Tags" />
<div class="post-options">
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-${discussion_id}">
<label for="discussion-post-anonymously-${discussion_id}">post anonymously</label>
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-${discussion_id}" checked="">
<label for="discussion-auto-watch-${discussion_id}">follow this thread</label>
</div>
<div class="new-post-control post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
"""
replyTemplate: """
<form class="discussion-reply-new">
<ul class="discussion-errors"></ul>
<div class="reply-body"></div>
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-{{id}}" />
<label for="discussion-post-anonymously-{{id}}">post anonymously</label>
{{#showWatchCheckbox}}
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-{{id}}" checked />
<label for="discussion-auto-watch-{{id}}">follow this thread</label>
{{/showWatchCheckbox}}
<br />
<div class = "reply-post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
"""
editThreadTemplate: """
<form class="discussion-content-edit discussion-thread-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<input type="text" class="thread-title-edit title-input" placeholder="Title" value="{{title}}"/>
<div class="thread-body-edit body-input">{{body}}</div>
<input class="thread-tags-edit" placeholder="Tags" value="{{tags}}" />
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
"""
editCommentTemplate: """
<form class="discussion-content-edit discussion-comment-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<div class="comment-body-edit body-input">{{body}}</div>
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
"""
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
@Discussion = $.extend @Discussion,
initializeUserProfile: ($userProfile) ->
$local = Discussion.generateLocal $userProfile
handleUpdateModeratorStatus = (elem, isModerator) ->
confirmValue = confirm("Are you sure?")
if not confirmValue then return
url = Discussion.urlFor('update_moderator_status', $$profiled_user_id)
Discussion.safeAjax
$elem: $(elem)
url: url
type: "POST"
dataType: 'json'
data:
is_moderator: isModerator
error: (response, textStatus, e) ->
console.log e
success: (response, textStatus) ->
parent = $userProfile.parent()
$userProfile.replaceWith(response.html)
Discussion.initializeUserProfile parent.children(".user-profile")
Discussion.bindLocalEvents $local,
"click .sidebar-revoke-moderator-button": (event) ->
handleUpdateModeratorStatus(this, false)
"click .sidebar-promote-moderator-button": (event) ->
handleUpdateModeratorStatus(this, true)
initializeUserActiveDiscussion: ($discussion) ->
if not @Discussion?
@Discussion = {}
Discussion = @Discussion
wmdEditors = {}
@Discussion = $.extend @Discussion,
generateLocal: (elem) ->
(selector) -> $(elem).find(selector)
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: Discussion.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
Discussion.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 = Discussion.urlFor('upload')
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.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) ->
Discussion.getWmdInput($content, $local, cls_identifier).val()
setWmdContent: ($content, $local, cls_identifier, text) ->
Discussion.getWmdInput($content, $local, cls_identifier).val(text)
Discussion.getWmdEditor($content, $local, cls_identifier).refreshPreview()
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
subscriptionLink: (type, id) ->
followLink = ->
Discussion.generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow)
unfollowLink = ->
Discussion.generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow)
handleFollow = (elem) ->
Discussion.safeAjax
$elem: $(elem)
url: Discussion.urlFor("follow_#{type}", id)
type: "POST"
success: (response, textStatus) ->
if textStatus == "success"
$(elem).replaceWith unfollowLink()
dataType: 'json'
handleUnfollow = (elem) ->
Discussion.safeAjax
$elem: $(elem)
url: Discussion.urlFor("unfollow_#{type}", id)
type: "POST"
success: (response, textStatus) ->
if textStatus == "success"
$(elem).replaceWith followLink()
dataType: 'json'
if Discussion.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
...@@ -350,7 +350,7 @@ $tag-text-color: #5b614f; ...@@ -350,7 +350,7 @@ $tag-text-color: #5b614f;
margin-right: 1em; margin-right: 1em;
} }
.comment-count { .show-comments-wrapper {
display: inline; display: inline;
margin-right: 20px; margin-right: 20px;
} }
......
...@@ -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">
...@@ -20,10 +21,10 @@ ...@@ -20,10 +21,10 @@
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script> ## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script>
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script> ## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script>
<%static:js group='courseware'/> <%static:js group='courseware'/>
<%include file="../discussion/_js_dependencies.html" /> <%include file="../discussion/_js_body_dependencies.html" />
<%include file="/mathjax_include.html" />
<!-- TODO: http://docs.jquery.com/Plugins/Validation --> <!-- TODO: http://docs.jquery.com/Plugins/Validation -->
<script type="text/javascript"> <script type="text/javascript">
......
...@@ -5,11 +5,9 @@ ...@@ -5,11 +5,9 @@
</%def> </%def>
<%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']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}">
${render_content(content)} ${render_content(content)}
% if content.get('children') is not None: ${render_comments(content.get('children', []))}
${render_comments(content['children'])}
% endif
</div> </div>
</%def> </%def>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<section class="discussion forum-discussion" _id="${discussion_id}"> <section class="discussion forum-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local"> <div class="discussion-non-content local">
<div class="search-wrapper"> <div class="search-wrapper">
<%include file="_search_bar.html" /> <%include file="_search_bar.html" />
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<section class="discussion inline-discussion" _id="${discussion_id}"> <section class="discussion inline-discussion" _id="${discussion_id}">
<div class="discussion-non-content discussion-local"></div> <div class="discussion-non-content local"></div>
<div class="threads"> <div class="threads">
% for thread in threads: % for thread in threads:
......
<%! from django_comment_client.helpers import include_mustache_templates %>
<%include file="/mathjax_include.html" />
${include_mustache_templates()}
<%! from django.template.defaultfilters import escapejs %> <%! from django.template.defaultfilters import escapejs %>
<script type="text/javascript"> <script type="text/javascript">
var $$user_info = JSON.parse("${user_info | escapejs}");
var $$course_id = "${course_id | escapejs}"; var $$course_id = "${course_id | escapejs}";
if (typeof $$annotated_content_info === undefined || $$annotated_content_info === null) { if (typeof $$annotated_content_info === undefined) {
var $$annotated_content_info = {}; var $$annotated_content_info = {};
} }
$$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escapejs}")); $$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escapejs}"));
if (typeof $$discussion_data === undefined) {
var $$discussion_data = {};
}
$$discussion_data = $.extend($$discussion_data, JSON.parse("${discussion_data | escapejs}"));
</script> </script>
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [
["\\(","\\)"],
],
displayMath: [
["\\[","\\]"],
]
}
});
</script>
## This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
## It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of MathJax extension libraries
<script type="text/javascript" src="/static/js/vendor/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"></script>
<!---<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js"> </script>-->
<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>
...@@ -27,5 +10,8 @@ ...@@ -27,5 +10,8 @@
<script type="text/javascript" src="${static.url('js/jquery.tagsinput.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/mustache.js')}"></script>
<script type="text/javascript" src="${static.url('js/URI.min.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.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">
<%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">
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
% endfor % endfor
</%def> </%def>
<div class="discussion-${discussion_type}-paginator discussion-paginator"> <div class="discussion-${discussion_type}-paginator discussion-paginator local">
<div class="prev-page"> <div class="prev-page">
% if page > 1: % if page > 1:
${link_to_page(page - 1, "&lt; Previous page")} ${link_to_page(page - 1, "&lt; Previous page")}
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
<section class="discussion" _id="${discussion_id}"> <section class="discussion" _id="${discussion_id}">
<a class="discussion-title" href="javascript:void(0)">Discussion</a> <a class="discussion-title" href="javascript:void(0)">Discussion</a>
${renderer.render_content_with_comments(thread)} <div class="threads">
${renderer.render_content_with_comments(thread)}
</div>
</section> </section>
<%include file="_js_data.html" /> <%include file="_js_data.html" />
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a> <a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a>
</%def> </%def>
<div class="discussion-sort discussion-local"> <div class="discussion-sort local">
<span class="discussion-label">Sort by:</span> <span class="discussion-label">Sort by:</span>
${link_to_sort('activity', 'top')} ${link_to_sort('activity', 'top')}
......
...@@ -5,13 +5,14 @@ ...@@ -5,13 +5,14 @@
<%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'" />
<section class="container"> <section class="container">
<div class="course-wrapper"> <div class="course-wrapper">
......
<div class="discussion-content"> <div class="discussion-content local">
<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">
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
{{/thread}} {{/thread}}
</ul> </ul>
{{#thread}} {{#thread}}
<a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{{content.displayed_title}}}</a> <a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{content.displayed_title}}</a>
<div class="thread-raw-title" style="display: none">{{{content.title}}}</div> <div class="thread-raw-title" style="display: none">{{{content.title}}}</div>
{{/thread}} {{/thread}}
<div class="discussion-content-view"> <div class="discussion-content-view">
<a name="{{content.id}}" style="width: 0; height: 0; padding: 0; border: none;"></a> <a name="{{content.id}}" style="width: 0; height: 0; padding: 0; border: none;"></a>
<div class="content-body {{content.type}}-body" id="content-body-{{content.id}}">{{{content.displayed_body}}}</div> <div class="content-body {{content.type}}-body" id="content-body-{{content.id}}">{{content.displayed_body}}</div>
<div class="content-raw-body {{content.type}}-raw-body" style="display: none">{{{content.body}}}</div> <div class="content-raw-body {{content.type}}-raw-body" style="display: none">{{{content.body}}}</div>
{{#thread}} {{#thread}}
<div class="thread-tags"> <div class="thread-tags">
...@@ -40,22 +40,22 @@ ...@@ -40,22 +40,22 @@
{{content.username}} {{content.username}}
{{/content.anonymous}} {{/content.anonymous}}
</div> </div>
<div class="comment-count"> <div class="show-comments-wrapper">
{{#thread}} {{#thread}}
{{#partial_comments}} {{#partial_comments}}
<a href="javascript:void(0)" class="discussion-show-comments first-time">Show all comments ({{content.comments_count}} total)</a> <a href="javascript:void(0)" class="discussion-show-comments first-time">Show all comments (<span class="comments-count">{{content.comments_count}}</span> total)</a>
{{/partial_comments}} {{/partial_comments}}
{{^partial_comments}} {{^partial_comments}}
<a href="javascript:void(0)" class="discussion-show-comments">Show {{##pluralize}}{{content.comments_count}} comment{{/pluralize}}</a> <a href="javascript:void(0)" class="discussion-show-comments">Show <span class="comments-count">{{content.comments_count}}</span> {{##pluralize}}{{content.comments_count}} comment{{/pluralize}}</a>
{{/partial_comments}} {{/partial_comments}}
{{/thread}} {{/thread}}
</div> </div>
<ul class="discussion-actions"> <ul class="discussion-actions">
<li><a class="discussion-link discussion-reply discussion-reply-{{content.type}}" href="javascript:void(0)">Reply</a></li> <li><a class="discussion-link discussion-reply discussion-reply-{{content.type}}" href="javascript:void(0)">Reply</a></li>
<li><div class="follow-wrapper"></div></li> {{#thread}}
{{! disabling perma-link because it appears to broken <li><div class="follow-wrapper"><a class="discussion-link discussion-follow-thread" href="javascript:void(0)">Follow</a></div></li>
<li><a class="discussion-link discussion-permanent-link" href="javascript:void(0)">Permanent Link</a></li> {{/thread}}
}} <li><a class="discussion-link discussion-permanent-link" href="{{content.permalink}}">Permanent Link</a></li>
</ul> </ul>
</div> </div>
</div> </div>
......
<form class="discussion-content-edit discussion-comment-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<div class="comment-body-edit body-input">{{body}}</div>
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
<form class="discussion-content-edit discussion-thread-edit" _id="{{id}}">
<ul class="discussion-errors discussion-update-errors"></ul>
<input type="text" class="thread-title-edit title-input" placeholder="Title" value="{{title}}"/>
<div class="thread-body-edit body-input">{{body}}</div>
<input class="thread-tags-edit" placeholder="Tags" value="{{tags}}" />
<div class = "edit-post-control">
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
</div>
</form>
<form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
<ul class="new-post-form-errors discussion-errors"></ul>
<input type="text" class="new-post-title title-input" placeholder="Title" />
<div class="new-post-similar-posts-wrapper" style="display: none">
Similar Posts:
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
<div class="new-post-similar-posts"></div>
</div>
<div class="new-post-body reply-body"></div>
<input class="new-post-tags" placeholder="Tags" />
<div class="post-options">
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-${discussion_id}">
<label for="discussion-post-anonymously-${discussion_id}">post anonymously</label>
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-${discussion_id}" checked="">
<label for="discussion-auto-watch-${discussion_id}">follow this thread</label>
</div>
<div class="new-post-control post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
<form class="discussion-reply-new">
<ul class="discussion-errors"></ul>
<div class="reply-body"></div>
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-{{id}}" />
<label for="discussion-post-anonymously-{{id}}">post anonymously</label>
{{#showWatchCheckbox}}
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-{{id}}" checked />
<label for="discussion-auto-watch-{{id}}">follow this thread</label>
{{/showWatchCheckbox}}
<br />
<div class="reply-post-control">
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
</div>
</form>
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