Merge branch 'master' into kimth/remember-caption
@@ -382,10 +382,10 @@ class LoncapaProblem(object):
|
||||
original_path = sys.path
|
||||
|
||||
for script in scripts:
|
||||
|
||||
sys.path = original_path + self._extract_system_path(script)
|
||||
|
||||
stype = script.get('type')
|
||||
|
||||
if stype:
|
||||
if 'javascript' in stype:
|
||||
continue # skip javascript
|
||||
|
||||
@@ -326,8 +326,16 @@ def textline_dynamath(element, value, status, render_template, msg=''):
|
||||
count = int(eid.split('_')[-2]) - 1 # HACK
|
||||
size = element.get('size')
|
||||
hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden
|
||||
|
||||
# Preprocessor to insert between raw input and Mathjax
|
||||
preprocessor = {'class_name': element.get('preprocessorClassName',''),
|
||||
'script_src': element.get('preprocessorSrc','')}
|
||||
if '' in preprocessor.values():
|
||||
preprocessor = None
|
||||
|
||||
context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size,
|
||||
'msg': msg, 'hidden': hidden,
|
||||
'preprocessor': preprocessor,
|
||||
}
|
||||
html = render_template("textinput_dynamath.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
###
|
||||
### version of textline.html which does dynammic math
|
||||
### version of textline.html which does dynamic math
|
||||
###
|
||||
<section class="text-input-dynamath">
|
||||
<section class="text-input-dynamath capa_inputtype" id="inputtype_${id}">
|
||||
|
||||
% if preprocessor is not None:
|
||||
<div class="text-input-dynamath_data" data-preprocessor="${preprocessor['class_name']}"/>
|
||||
<div class="script_placeholder" data-src="${preprocessor['script_src']}"/>
|
||||
% endif
|
||||
|
||||
% if state == 'unsubmitted':
|
||||
<div class="unanswered" id="status_${id}">
|
||||
% elif state == 'correct':
|
||||
@@ -15,27 +21,26 @@
|
||||
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
|
||||
% endif
|
||||
|
||||
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}"
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
/>
|
||||
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}"
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
/>
|
||||
<p class="status">
|
||||
% if state == 'unsubmitted':
|
||||
unanswered
|
||||
% elif state == 'correct':
|
||||
correct
|
||||
% elif state == 'incorrect':
|
||||
incorrect
|
||||
% elif state == 'incomplete':
|
||||
incomplete
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<p class="status">
|
||||
% if state == 'unsubmitted':
|
||||
unanswered
|
||||
% elif state == 'correct':
|
||||
correct
|
||||
% elif state == 'incorrect':
|
||||
incorrect
|
||||
% elif state == 'incomplete':
|
||||
incomplete
|
||||
% endif
|
||||
</p>
|
||||
<p id="answer_${id}" class="answer"></p>
|
||||
|
||||
<p id="answer_${id}" class="answer"></p>
|
||||
|
||||
<div id="display_${id}" class="equation">`{::}`</div>
|
||||
<div id="display_${id}" class="equation">`{::}`</div>
|
||||
|
||||
</div>
|
||||
<textarea style="display:none" id="input_${id}_dynamath" name="input_${id}_dynamath"> </textarea>
|
||||
|
||||
@@ -11,7 +11,9 @@ class @Problem
|
||||
$(selector, @el)
|
||||
|
||||
bind: =>
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@el.find('.problem > div').each (index, element) =>
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
|
||||
|
||||
window.update_schematics()
|
||||
|
||||
problem_prefix = @element_id.replace(/problem_/,'')
|
||||
@@ -23,7 +25,11 @@ class @Problem
|
||||
@$('section.action input.reset').click @reset
|
||||
@$('section.action input.show').click @show
|
||||
@$('section.action input.save').click @save
|
||||
@$('input.math').keyup(@refreshMath).each(@refreshMath)
|
||||
|
||||
# Dynamath
|
||||
@$('input.math').keyup(@refreshMath)
|
||||
@$('input.math').each (index, element) =>
|
||||
MathJax.Hub.Queue [@refreshMath, null, element]
|
||||
|
||||
updateProgress: (response) =>
|
||||
if response.progress_changed
|
||||
@@ -262,7 +268,9 @@ class @Problem
|
||||
showMethod = @inputtypeShowAnswerMethods[cls]
|
||||
showMethod(inputtype, display, answers) if showMethod?
|
||||
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@el.find('.problem > div').each (index, element) =>
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
|
||||
|
||||
@$('.show').val 'Hide Answer'
|
||||
@el.addClass 'showed'
|
||||
@updateProgress response
|
||||
@@ -296,12 +304,21 @@ class @Problem
|
||||
|
||||
refreshMath: (event, element) =>
|
||||
element = event.target unless element
|
||||
target = "display_#{element.id.replace(/^input_/, '')}"
|
||||
elid = element.id.replace(/^input_/,'')
|
||||
target = "display_" + elid
|
||||
|
||||
# MathJax preprocessor is loaded by 'setupInputTypes'
|
||||
preprocessor_tag = "inputtype_" + elid
|
||||
mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag]
|
||||
|
||||
if jax = MathJax.Hub.getAllJax(target)[0]
|
||||
MathJax.Hub.Queue ['Text', jax, $(element).val()],
|
||||
[@updateMathML, jax, element]
|
||||
eqn = $(element).val()
|
||||
if mathjax_preprocessor
|
||||
eqn = mathjax_preprocessor(eqn)
|
||||
MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element])
|
||||
|
||||
return # Explicit return for CoffeeScript
|
||||
|
||||
updateMathML: (jax, element) =>
|
||||
try
|
||||
$("##{element.id}_dynamath").val(jax.root.toMathML '')
|
||||
@@ -317,6 +334,22 @@ class @Problem
|
||||
@answers = @inputs.serialize()
|
||||
|
||||
inputtypeSetupMethods:
|
||||
|
||||
'text-input-dynamath': (element) =>
|
||||
###
|
||||
Return: function (eqn) -> eqn that preprocesses the user formula input before
|
||||
it is fed into MathJax. Return 'false' if no preprocessor specified
|
||||
###
|
||||
data = $(element).find('.text-input-dynamath_data')
|
||||
|
||||
preprocessorClassName = data.data('preprocessor')
|
||||
preprocessorClass = window[preprocessorClassName]
|
||||
if not preprocessorClass?
|
||||
return false
|
||||
else
|
||||
preprocessor = new preprocessorClass()
|
||||
return preprocessor.fn
|
||||
|
||||
javascriptinput: (element) =>
|
||||
|
||||
data = $(element).find(".javascriptinput_data")
|
||||
|
||||
BIN
common/static/images/spinner-on-grey.gif
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -9,7 +9,7 @@ If you haven't done so already:
|
||||
brew install mongodb
|
||||
|
||||
Make sure that you have mongodb running. You can simply open a new terminal tab and type:
|
||||
|
||||
|
||||
mongod
|
||||
|
||||
## Installing elasticsearch
|
||||
@@ -72,9 +72,9 @@ For convenience, add the following environment variables to the terminal (assumi
|
||||
export DJANGO_SETTINGS_MODULE=lms.envs.dev
|
||||
export PYTHONPATH=.
|
||||
|
||||
Now initialzie roles and permissions:
|
||||
Now initialzie roles and permissions, providing a course id eg.:
|
||||
|
||||
django-admin.py seed_permissions_roles
|
||||
django-admin.py seed_permissions_roles "MITx/6.002x/2012_Fall"
|
||||
|
||||
To assign yourself as a moderator, use the following command (assuming your username is "test", and the course id is "MITx/6.002x/2012_Fall"):
|
||||
|
||||
|
||||
@@ -195,7 +195,11 @@ def get_course_syllabus_section(course, section_key):
|
||||
|
||||
if section_key in ['syllabus', 'guest_syllabus']:
|
||||
try:
|
||||
with course.system.resources_fs.open(path("syllabus") / section_key + ".html") as htmlFile:
|
||||
fs = course.system.resources_fs
|
||||
# first look for a run-specific version
|
||||
dirs = [path("syllabus") / course.url_name, path("syllabus")]
|
||||
filepath = find_file(fs, dirs, section_key + ".html")
|
||||
with fs.open(filepath) as htmlFile:
|
||||
return replace_urls(htmlFile.read().decode('utf-8'),
|
||||
course.metadata['data_dir'])
|
||||
except ResourceNotFoundError:
|
||||
|
||||
@@ -22,7 +22,7 @@ from django.contrib.auth.models import User
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
from django_comment_client.utils import JsonResponse, JsonError, extract
|
||||
from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context
|
||||
|
||||
from django_comment_client.permissions import check_permissions_by_view
|
||||
from django_comment_client.models import Role
|
||||
@@ -38,11 +38,10 @@ def permitted(fn):
|
||||
else:
|
||||
content = None
|
||||
return content
|
||||
|
||||
if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
|
||||
return fn(request, *args, **kwargs)
|
||||
else:
|
||||
return JsonError("unauthorized")
|
||||
return JsonError("unauthorized", status=401)
|
||||
return wrapper
|
||||
|
||||
def ajax_content_response(request, course_id, content, template_name):
|
||||
@@ -63,22 +62,39 @@ def ajax_content_response(request, course_id, content, template_name):
|
||||
@login_required
|
||||
@permitted
|
||||
def create_thread(request, course_id, commentable_id):
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
post = request.POST
|
||||
|
||||
if course.metadata.get("allow_anonymous", True):
|
||||
anonymous = post.get('anonymous', 'false').lower() == 'true'
|
||||
else:
|
||||
anonymous = False
|
||||
|
||||
if course.metadata.get("allow_anonymous_to_peers", False):
|
||||
anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true'
|
||||
else:
|
||||
anonymous_to_peers = False
|
||||
|
||||
thread = cc.Thread(**extract(post, ['body', 'title', 'tags']))
|
||||
thread.update_attributes(**{
|
||||
'anonymous' : post.get('anonymous', 'false').lower() == 'true',
|
||||
'commentable_id' : commentable_id,
|
||||
'course_id' : course_id,
|
||||
'user_id' : request.user.id,
|
||||
'anonymous' : anonymous,
|
||||
'anonymous_to_peers' : anonymous_to_peers,
|
||||
'commentable_id' : commentable_id,
|
||||
'course_id' : course_id,
|
||||
'user_id' : request.user.id,
|
||||
})
|
||||
thread.save()
|
||||
if post.get('auto_subscribe', 'false').lower() == 'true':
|
||||
user = cc.User.from_django_user(request.user)
|
||||
user.follow(thread)
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
data = thread.to_dict()
|
||||
if courseware_context:
|
||||
data.update(courseware_context)
|
||||
if request.is_ajax():
|
||||
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html')
|
||||
return ajax_content_response(request, course_id, data, 'discussion/ajax_create_thread.html')
|
||||
else:
|
||||
return JsonResponse(utils.safe_content(thread.to_dict()))
|
||||
return JsonResponse(utils.safe_content(data))
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@@ -95,8 +111,21 @@ def update_thread(request, course_id, thread_id):
|
||||
def _create_comment(request, course_id, thread_id=None, parent_id=None):
|
||||
post = request.POST
|
||||
comment = cc.Comment(**extract(post, ['body']))
|
||||
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
if course.metadata.get("allow_anonymous", True):
|
||||
anonymous = post.get('anonymous', 'false').lower() == 'true'
|
||||
else:
|
||||
anonymous = False
|
||||
|
||||
if course.metadata.get("allow_anonymous_to_peers", False):
|
||||
anonymous_to_peers = post.get('anonymous_to_peers', 'false').lower() == 'true'
|
||||
else:
|
||||
anonymous_to_peers = False
|
||||
|
||||
comment.update_attributes(**{
|
||||
'anonymous' : post.get('anonymous', 'false').lower() == 'true',
|
||||
'anonymous' : anonymous,
|
||||
'anonymous_to_peers' : anonymous_to_peers,
|
||||
'user_id' : request.user.id,
|
||||
'course_id' : course_id,
|
||||
'thread_id' : thread_id,
|
||||
@@ -214,7 +243,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
|
||||
thread = cc.Thread.find(thread_id)
|
||||
user.unvote(thread)
|
||||
return JsonResponse(utils.safe_content(thread.to_dict()))
|
||||
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@@ -288,7 +317,7 @@ def update_moderator_status(request, course_id, user_id):
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
discussion_user = cc.User(id=user_id, course_id=course_id)
|
||||
context = {
|
||||
'course': course,
|
||||
'course': course,
|
||||
'course_id': course_id,
|
||||
'user': request.user,
|
||||
'django_user': user,
|
||||
@@ -327,7 +356,7 @@ def tags_autocomplete(request, course_id):
|
||||
@require_POST
|
||||
@login_required
|
||||
@csrf.csrf_exempt
|
||||
def upload(request, course_id):#ajax upload file to a question or answer
|
||||
def upload(request, course_id):#ajax upload file to a question or answer
|
||||
"""view that handles file upload via Ajax
|
||||
"""
|
||||
|
||||
@@ -337,7 +366,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
new_file_name = ''
|
||||
try:
|
||||
# TODO authorization
|
||||
#may raise exceptions.PermissionDenied
|
||||
#may raise exceptions.PermissionDenied
|
||||
#if request.user.is_anonymous():
|
||||
# msg = _('Sorry, anonymous users cannot upload files')
|
||||
# raise exceptions.PermissionDenied(msg)
|
||||
@@ -357,7 +386,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
new_file_name = str(
|
||||
time.time()
|
||||
).replace(
|
||||
'.',
|
||||
'.',
|
||||
str(random.randint(0,100000))
|
||||
) + file_extension
|
||||
|
||||
@@ -386,7 +415,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
parsed_url = urlparse.urlparse(file_url)
|
||||
file_url = urlparse.urlunparse(
|
||||
urlparse.ParseResult(
|
||||
parsed_url.scheme,
|
||||
parsed_url.scheme,
|
||||
parsed_url.netloc,
|
||||
parsed_url.path,
|
||||
'', '', ''
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.http import HttpResponse, Http404
|
||||
@@ -15,14 +18,15 @@ from operator import methodcaller
|
||||
from django_comment_client.permissions import check_permissions_by_view
|
||||
from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank, get_courseware_context
|
||||
|
||||
import json
|
||||
import django_comment_client.utils as utils
|
||||
import comment_client as cc
|
||||
import xml.sax.saxutils as saxutils
|
||||
|
||||
|
||||
THREADS_PER_PAGE = 8
|
||||
THREADS_PER_PAGE = 20
|
||||
INLINE_THREADS_PER_PAGE = 20
|
||||
PAGES_NEARBY_DELTA = 2
|
||||
|
||||
escapedict = {'"': '"'}
|
||||
log = logging.getLogger("edx.discussions")
|
||||
|
||||
def _general_discussion_id(course_id):
|
||||
return course_id.replace('/', '_').replace('.', '_')
|
||||
@@ -31,9 +35,8 @@ def _should_perform_search(request):
|
||||
return bool(request.GET.get('text', False) or \
|
||||
request.GET.get('tags', False))
|
||||
|
||||
|
||||
def render_accordion(request, course, discussion_id):
|
||||
|
||||
# TODO: Delete if obsolete
|
||||
discussion_info = utils.get_categorized_discussion_info(request, course)
|
||||
|
||||
context = {
|
||||
@@ -45,69 +48,7 @@ def render_accordion(request, course, discussion_id):
|
||||
|
||||
return render_to_string('discussion/_accordion.html', context)
|
||||
|
||||
def render_discussion(request, course_id, threads, *args, **kwargs):
|
||||
|
||||
discussion_id = kwargs.get('discussion_id')
|
||||
user_id = kwargs.get('user_id')
|
||||
discussion_type = kwargs.get('discussion_type', 'inline')
|
||||
query_params = kwargs.get('query_params', {})
|
||||
|
||||
template = {
|
||||
'inline': 'discussion/_inline.html',
|
||||
'forum': 'discussion/_forum.html',
|
||||
'user': 'discussion/_user_active_threads.html',
|
||||
}[discussion_type]
|
||||
|
||||
base_url = {
|
||||
'inline': (lambda: reverse('django_comment_client.forum.views.inline_discussion', args=[course_id, discussion_id])),
|
||||
'forum': (lambda: reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id])),
|
||||
'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])),
|
||||
}[discussion_type]()
|
||||
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
|
||||
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), {})
|
||||
|
||||
if discussion_type != 'inline':
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
|
||||
for thread in threads:
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
if courseware_context:
|
||||
thread['courseware_location'] = courseware_context['courseware_location']
|
||||
thread['courseware_title'] = courseware_context['courseware_title']
|
||||
|
||||
|
||||
context = {
|
||||
'threads': threads,
|
||||
'discussion_id': discussion_id,
|
||||
'user_id': user_id,
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
'performed_search': _should_perform_search(request),
|
||||
'pages_nearby_delta': PAGES_NEARBY_DELTA,
|
||||
'discussion_type': discussion_type,
|
||||
'base_url': base_url,
|
||||
'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
|
||||
'annotated_content_info': json.dumps(annotated_content_info),
|
||||
'discussion_data': json.dumps({ (discussion_id or user_id): map(utils.safe_content, threads) })
|
||||
}
|
||||
context = dict(context.items() + query_params.items())
|
||||
return render_to_string(template, context)
|
||||
|
||||
def render_inline_discussion(*args, **kwargs):
|
||||
return render_discussion(discussion_type='inline', *args, **kwargs)
|
||||
|
||||
def render_forum_discussion(*args, **kwargs):
|
||||
return render_discussion(discussion_type='forum', *args, **kwargs)
|
||||
|
||||
def render_user_discussion(*args, **kwargs):
|
||||
return render_discussion(discussion_type='user', *args, **kwargs)
|
||||
|
||||
def get_threads(request, course_id, discussion_id=None):
|
||||
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE):
|
||||
"""
|
||||
This may raise cc.utils.CommentClientError or
|
||||
cc.utils.CommentClientUnknownError if something goes wrong.
|
||||
@@ -115,7 +56,7 @@ def get_threads(request, course_id, discussion_id=None):
|
||||
|
||||
default_query_params = {
|
||||
'page': 1,
|
||||
'per_page': THREADS_PER_PAGE,
|
||||
'per_page': per_page,
|
||||
'sort_key': 'date',
|
||||
'sort_order': 'desc',
|
||||
'text': '',
|
||||
@@ -137,7 +78,7 @@ def get_threads(request, course_id, discussion_id=None):
|
||||
user.save()
|
||||
|
||||
query_params = merge_dict(default_query_params,
|
||||
strip_none(extract(request.GET, ['page', 'sort_key', 'sort_order', 'text', 'tags'])))
|
||||
strip_none(extract(request.GET, ['page', 'sort_key', 'sort_order', 'text', 'tags', 'commentable_ids'])))
|
||||
|
||||
threads, page, num_pages = cc.Thread.search(query_params)
|
||||
|
||||
@@ -146,132 +87,182 @@ def get_threads(request, course_id, discussion_id=None):
|
||||
|
||||
return threads, query_params
|
||||
|
||||
# discussion per page is fixed for now
|
||||
def inline_discussion(request, course_id, discussion_id):
|
||||
"""
|
||||
Renders JSON for DiscussionModules
|
||||
"""
|
||||
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
|
||||
try:
|
||||
threads, query_params = get_threads(request, course_id, discussion_id)
|
||||
threads, query_params = get_threads(request, course_id, discussion_id, per_page=INLINE_THREADS_PER_PAGE)
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
# TODO (vshnayder): since none of this code seems to be aware of the fact that
|
||||
# sometimes things go wrong, I suspect that the js client is also not
|
||||
# checking for errors on request. Check and fix as needed.
|
||||
raise Http404
|
||||
|
||||
html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
|
||||
query_params=query_params)
|
||||
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), {})
|
||||
|
||||
allow_anonymous = course.metadata.get("allow_anonymous", True)
|
||||
allow_anonymous_to_peers = course.metadata.get("allow_anonymous_to_peers", False)
|
||||
|
||||
return utils.JsonResponse({
|
||||
'html': html,
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
'user_info': user_info,
|
||||
'annotated_content_info': annotated_content_info,
|
||||
'page': query_params['page'],
|
||||
'num_pages': query_params['num_pages'],
|
||||
'roles': utils.get_role_ids(course_id),
|
||||
'allow_anonymous_to_peers': allow_anonymous_to_peers,
|
||||
'allow_anonymous': allow_anonymous,
|
||||
})
|
||||
|
||||
def render_search_bar(request, course_id, discussion_id=None, text=''):
|
||||
if not discussion_id:
|
||||
return ''
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'text': text,
|
||||
'course_id': course_id,
|
||||
}
|
||||
return render_to_string('discussion/_search_bar.html', context)
|
||||
|
||||
@login_required
|
||||
def forum_form_discussion(request, course_id):
|
||||
"""
|
||||
Renders the main Discussion page, potentially filtered by a search query
|
||||
"""
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
category_map = utils.get_discussion_category_map(course)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
if request.is_ajax():
|
||||
return utils.JsonResponse({
|
||||
'html': content,
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
})
|
||||
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 = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'content': content,
|
||||
'recent_active_threads': recent_active_threads,
|
||||
'trending_tags': trending_tags,
|
||||
'staff_access' : has_access(request.user, course, 'staff'),
|
||||
}
|
||||
# print "start rendering.."
|
||||
return render_to_response('discussion/index.html', context)
|
||||
unsafethreads, query_params = get_threads(request, course_id) # This might process a search query
|
||||
threads = [utils.safe_content(thread) for thread in unsafethreads]
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
raise Http404
|
||||
|
||||
|
||||
def render_single_thread(request, discussion_id, course_id, thread_id):
|
||||
|
||||
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, user=request.user, user_info=user_info)
|
||||
def infogetter(thread):
|
||||
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
|
||||
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'thread': thread,
|
||||
'annotated_content_info': json.dumps(annotated_content_info),
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
'discussion_data': json.dumps({ discussion_id: [utils.safe_content(thread)] }),
|
||||
}
|
||||
return render_to_string('discussion/_single_thread.html', context)
|
||||
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
|
||||
for thread in threads:
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
if courseware_context:
|
||||
thread.update(courseware_context)
|
||||
if request.is_ajax():
|
||||
return utils.JsonResponse({
|
||||
'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads'
|
||||
'annotated_content_info': annotated_content_info,
|
||||
'num_pages': query_params['num_pages'],
|
||||
'page': query_params['page'],
|
||||
})
|
||||
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 = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
#'recent_active_threads': recent_active_threads,
|
||||
#'trending_tags': trending_tags,
|
||||
'staff_access' : has_access(request.user, course, 'staff'),
|
||||
'threads': saxutils.escape(json.dumps(threads),escapedict),
|
||||
'thread_pages': query_params['num_pages'],
|
||||
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
|
||||
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict),
|
||||
'course_id': course.id,
|
||||
'category_map': category_map,
|
||||
'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
|
||||
}
|
||||
# print "start rendering.."
|
||||
return render_to_response('discussion/index.html', context)
|
||||
|
||||
@login_required
|
||||
def single_thread(request, course_id, discussion_id, thread_id):
|
||||
|
||||
try:
|
||||
if request.is_ajax():
|
||||
if request.is_ajax():
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
try:
|
||||
thread = cc.Thread.find(thread_id).retrieve(recursive=True)
|
||||
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}
|
||||
html = render_to_string('discussion/_ajax_single_thread.html', context)
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
raise Http404
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
|
||||
return utils.JsonResponse({
|
||||
'html': html,
|
||||
'content': utils.safe_content(thread.to_dict()),
|
||||
'annotated_content_info': annotated_content_info,
|
||||
})
|
||||
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}
|
||||
# TODO: Remove completely or switch back to server side rendering
|
||||
# html = render_to_string('discussion/_ajax_single_thread.html', context)
|
||||
content = utils.safe_content(thread.to_dict())
|
||||
if courseware_context:
|
||||
content.update(courseware_context)
|
||||
return utils.JsonResponse({
|
||||
#'html': html,
|
||||
'content': content,
|
||||
'annotated_content_info': annotated_content_info,
|
||||
})
|
||||
|
||||
else:
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
else:
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
category_map = utils.get_discussion_category_map(course)
|
||||
try:
|
||||
threads, query_params = get_threads(request, course_id)
|
||||
thread = cc.Thread.find(thread_id).retrieve(recursive=True)
|
||||
threads.append(thread.to_dict())
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
raise Http404
|
||||
|
||||
recent_active_threads = cc.search_recent_active_threads(
|
||||
course_id,
|
||||
recursive=False,
|
||||
query_params={'follower_id': request.user.id},
|
||||
)
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
|
||||
trending_tags = cc.search_trending_tags(
|
||||
course_id,
|
||||
)
|
||||
for thread in threads:
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
if courseware_context:
|
||||
thread.update(courseware_context)
|
||||
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'init': '',
|
||||
'content': render_single_thread(request, discussion_id, course_id, thread_id),
|
||||
'course': course,
|
||||
'recent_active_threads': recent_active_threads,
|
||||
'trending_tags': trending_tags,
|
||||
'course_id': course.id,
|
||||
}
|
||||
threads = [utils.safe_content(thread) for thread in threads]
|
||||
|
||||
return render_to_response('discussion/single_thread.html', context)
|
||||
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
|
||||
raise Http404
|
||||
#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,
|
||||
#)
|
||||
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
|
||||
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 = {
|
||||
'discussion_id': discussion_id,
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'init': '', #TODO: What is this?
|
||||
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
|
||||
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
|
||||
'course': course,
|
||||
#'recent_active_threads': recent_active_threads,
|
||||
#'trending_tags': trending_tags,
|
||||
'course_id': course.id, #TODO: Why pass both course and course.id to template?
|
||||
'thread_id': thread_id,
|
||||
'threads': saxutils.escape(json.dumps(threads), escapedict),
|
||||
'category_map': category_map,
|
||||
'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
|
||||
'thread_pages': query_params['num_pages'],
|
||||
}
|
||||
|
||||
return render_to_response('discussion/single_thread.html', context)
|
||||
|
||||
@login_required
|
||||
def user_profile(request, course_id, user_id):
|
||||
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
@@ -281,27 +272,33 @@ def user_profile(request, course_id, user_id):
|
||||
query_params = {
|
||||
'page': request.GET.get('page', 1),
|
||||
'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities
|
||||
}
|
||||
}
|
||||
|
||||
threads, page, num_pages = profiled_user.active_threads(query_params)
|
||||
|
||||
query_params['page'] = page
|
||||
query_params['num_pages'] = num_pages
|
||||
|
||||
content = render_user_discussion(request, course_id, threads, user_id=user_id, query_params=query_params)
|
||||
|
||||
if request.is_ajax():
|
||||
return utils.JsonResponse({
|
||||
'html': content,
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
})
|
||||
else:
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
|
||||
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 = {
|
||||
'course': course,
|
||||
'user': request.user,
|
||||
'django_user': User.objects.get(id=user_id),
|
||||
'profiled_user': profiled_user.to_dict(),
|
||||
'content': content,
|
||||
'threads': saxutils.escape(json.dumps(threads), escapedict),
|
||||
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
|
||||
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict),
|
||||
# 'content': content,
|
||||
}
|
||||
|
||||
return render_to_response('discussion/user_profile.html', context)
|
||||
|
||||
@@ -17,12 +17,6 @@ def pluralize(singular_term, count):
|
||||
return singular_term + 's'
|
||||
return singular_term
|
||||
|
||||
def show_if(text, condition):
|
||||
if condition:
|
||||
return text
|
||||
else:
|
||||
return ''
|
||||
|
||||
# TODO there should be a better way to handle this
|
||||
def include_mustache_templates():
|
||||
mustache_dir = settings.PROJECT_ROOT / 'templates' / 'discussion' / 'mustache'
|
||||
@@ -35,7 +29,7 @@ def include_mustache_templates():
|
||||
return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents)))
|
||||
|
||||
def render_content(content, additional_context={}):
|
||||
|
||||
|
||||
context = {
|
||||
'content': extend_content(content),
|
||||
content['type']: True,
|
||||
|
||||
@@ -7,8 +7,10 @@ class Command(BaseCommand):
|
||||
help = 'Seed default permisssions and roles'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 1:
|
||||
raise CommandError("The number of arguments does not match. ")
|
||||
if len(args) == 0:
|
||||
raise CommandError("Please provide a course id")
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
course_id = args[0]
|
||||
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
|
||||
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
|
||||
|
||||
@@ -5,12 +5,13 @@ from student.models import CourseEnrollment
|
||||
|
||||
import logging
|
||||
from util.cache import cache
|
||||
|
||||
from django.core import cache
|
||||
cache = cache.get_cache('default')
|
||||
|
||||
def cached_has_permission(user, permission, course_id=None):
|
||||
"""
|
||||
Call has_permission if it's not cached. A change in a user's role or
|
||||
a role's permissions will only become effective after CACHE_LIFESPAN seconds.
|
||||
a role's permissions will only become effective after CACHE_LIFESPAN seconds.
|
||||
"""
|
||||
CACHE_LIFESPAN = 60
|
||||
key = "permission_%d_%s_%s" % (user.id, str(course_id), permission)
|
||||
@@ -53,8 +54,8 @@ def check_conditions_permissions(user, permissions, course_id, **kwargs):
|
||||
"""
|
||||
Accepts a list of permissions and proceed if any of the permission is valid.
|
||||
Note that ["can_view", "can_edit"] will proceed if the user has either
|
||||
"can_view" or "can_edit" permission. To use AND operator in between, wrap them in
|
||||
a list.
|
||||
"can_view" or "can_edit" permission. To use AND operator in between, wrap them in
|
||||
a list.
|
||||
"""
|
||||
|
||||
def test(user, per, operator="or"):
|
||||
@@ -75,18 +76,18 @@ def check_conditions_permissions(user, permissions, course_id, **kwargs):
|
||||
VIEW_PERMISSIONS = {
|
||||
'update_thread' : ['edit_content', ['update_thread', 'is_open', 'is_author']],
|
||||
'create_comment' : [["create_comment", "is_open"]],
|
||||
'delete_thread' : ['delete_thread'],
|
||||
'delete_thread' : ['delete_thread', ['update_thread', 'is_author']],
|
||||
'update_comment' : ['edit_content', ['update_comment', 'is_open', 'is_author']],
|
||||
'endorse_comment' : ['endorse_comment'],
|
||||
'openclose_thread' : ['openclose_thread'],
|
||||
'create_sub_comment': [['create_sub_comment', 'is_open']],
|
||||
'delete_comment' : ['delete_comment'],
|
||||
'delete_comment' : ['delete_comment', ['update_comment', 'is_open', 'is_author']],
|
||||
'vote_for_comment' : [['vote', 'is_open']],
|
||||
'undo_vote_for_comment': [['unvote', 'is_open']],
|
||||
'vote_for_thread' : [['vote', 'is_open']],
|
||||
'undo_vote_for_thread': [['unvote', 'is_open']],
|
||||
'follow_thread' : ['follow_thread'],
|
||||
'follow_commentable': ['follow_commentable'],
|
||||
'follow_commentable': ['follow_commentable'],
|
||||
'follow_user' : ['follow_user'],
|
||||
'unfollow_thread' : ['unfollow_thread'],
|
||||
'unfollow_commentable': ['unfollow_commentable'],
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from collections import defaultdict
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from importlib import import_module
|
||||
|
||||
from courseware.models import StudentModuleCache
|
||||
from courseware.module_render import get_module
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
from django.http import HttpResponse
|
||||
from django.utils import simplejson
|
||||
from django.db import connection
|
||||
@@ -23,8 +26,7 @@ import pystache_custom as pystache
|
||||
|
||||
# TODO these should be cached via django's caching rather than in-memory globals
|
||||
_FULLMODULES = None
|
||||
_DISCUSSIONINFO = None
|
||||
|
||||
_DISCUSSIONINFO = defaultdict(dict)
|
||||
|
||||
def extract(dic, keys):
|
||||
return {k: dic.get(k) for k in keys}
|
||||
@@ -40,6 +42,14 @@ def strip_blank(dic):
|
||||
def merge_dict(dic1, dic2):
|
||||
return dict(dic1.items() + dic2.items())
|
||||
|
||||
def get_role_ids(course_id):
|
||||
roles = Role.objects.filter(course_id=course_id)
|
||||
staff = list(User.objects.filter(is_staff=True).values_list('id', flat=True))
|
||||
roles_with_ids = {'Staff': staff}
|
||||
for role in roles:
|
||||
roles_with_ids[role.name] = list(role.users.values_list('id', flat=True))
|
||||
return roles_with_ids
|
||||
|
||||
def get_full_modules():
|
||||
global _FULLMODULES
|
||||
if not _FULLMODULES:
|
||||
@@ -51,23 +61,60 @@ def get_discussion_id_map(course):
|
||||
return a dict of the form {category: modules}
|
||||
"""
|
||||
global _DISCUSSIONINFO
|
||||
if not _DISCUSSIONINFO:
|
||||
if not _DISCUSSIONINFO[course.id]:
|
||||
initialize_discussion_info(course)
|
||||
return _DISCUSSIONINFO['id_map']
|
||||
return _DISCUSSIONINFO[course.id]['id_map']
|
||||
|
||||
def get_discussion_title(request, course, discussion_id):
|
||||
def get_discussion_title(course, discussion_id):
|
||||
global _DISCUSSIONINFO
|
||||
if not _DISCUSSIONINFO:
|
||||
if not _DISCUSSIONINFO[course.id]:
|
||||
initialize_discussion_info(course)
|
||||
title = _DISCUSSIONINFO['id_map'].get(discussion_id, {}).get('title', '(no title)')
|
||||
title = _DISCUSSIONINFO[course.id]['id_map'].get(discussion_id, {}).get('title', '(no title)')
|
||||
return title
|
||||
|
||||
def get_discussion_category_map(course):
|
||||
|
||||
global _DISCUSSIONINFO
|
||||
if not _DISCUSSIONINFO:
|
||||
if not _DISCUSSIONINFO[course.id]:
|
||||
initialize_discussion_info(course)
|
||||
return _DISCUSSIONINFO['category_map']
|
||||
return filter_unstarted_categories(_DISCUSSIONINFO[course.id]['category_map'])
|
||||
|
||||
def filter_unstarted_categories(category_map):
|
||||
|
||||
now = time.gmtime()
|
||||
|
||||
result_map = {}
|
||||
|
||||
unfiltered_queue = [category_map]
|
||||
filtered_queue = [result_map]
|
||||
|
||||
while len(unfiltered_queue) > 0:
|
||||
|
||||
unfiltered_map = unfiltered_queue.pop()
|
||||
filtered_map = filtered_queue.pop()
|
||||
|
||||
filtered_map["children"] = []
|
||||
filtered_map["entries"] = {}
|
||||
filtered_map["subcategories"] = {}
|
||||
|
||||
for child in unfiltered_map["children"]:
|
||||
if child in unfiltered_map["entries"]:
|
||||
if unfiltered_map["entries"][child]["start_date"] <= now:
|
||||
filtered_map["children"].append(child)
|
||||
filtered_map["entries"][child] = {}
|
||||
for key in unfiltered_map["entries"][child]:
|
||||
if key != "start_date":
|
||||
filtered_map["entries"][child][key] = unfiltered_map["entries"][child][key]
|
||||
else:
|
||||
print "filtering %s" % child, unfiltered_map["entries"][child]["start_date"]
|
||||
else:
|
||||
if unfiltered_map["subcategories"][child]["start_date"] < now:
|
||||
filtered_map["children"].append(child)
|
||||
filtered_map["subcategories"][child] = {}
|
||||
unfiltered_queue.append(unfiltered_map["subcategories"][child])
|
||||
filtered_queue.append(filtered_map["subcategories"][child])
|
||||
|
||||
return result_map
|
||||
|
||||
def sort_map_entries(category_map):
|
||||
things = []
|
||||
@@ -78,11 +125,10 @@ def sort_map_entries(category_map):
|
||||
sort_map_entries(category_map["subcategories"][title])
|
||||
category_map["children"] = [x[0] for x in sorted(things, key=lambda x: x[1]["sort_key"])]
|
||||
|
||||
|
||||
def initialize_discussion_info(course):
|
||||
|
||||
global _DISCUSSIONINFO
|
||||
if _DISCUSSIONINFO:
|
||||
if _DISCUSSIONINFO[course.id]:
|
||||
return
|
||||
|
||||
course_id = course.id
|
||||
@@ -91,45 +137,68 @@ def initialize_discussion_info(course):
|
||||
all_modules = get_full_modules()[course_id]
|
||||
|
||||
discussion_id_map = {}
|
||||
|
||||
unexpanded_category_map = defaultdict(list)
|
||||
|
||||
for location, module in all_modules.items():
|
||||
if location.category == 'discussion':
|
||||
id = module.metadata['id']
|
||||
category = module.metadata['discussion_category']
|
||||
title = module.metadata['for']
|
||||
sort_key = module.metadata.get('sort_key', title)
|
||||
discussion_id_map[id] = {"location": location, "title": title}
|
||||
category = " / ".join([x.strip() for x in category.split("/")])
|
||||
last_category = category.split("/")[-1]
|
||||
discussion_id_map[id] = {"location": location, "title": last_category + " / " + title}
|
||||
unexpanded_category_map[category].append({"title": title, "id": id,
|
||||
"sort_key": sort_key})
|
||||
"sort_key": sort_key, "start_date": module.start})
|
||||
|
||||
category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
|
||||
category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
|
||||
for category_path, entries in unexpanded_category_map.items():
|
||||
node = category_map["subcategories"]
|
||||
path = [x.strip() for x in category_path.split("/")]
|
||||
|
||||
# Find the earliest start date for the entries in this category
|
||||
category_start_date = None
|
||||
for entry in entries:
|
||||
if category_start_date is None or entry["start_date"] < category_start_date:
|
||||
category_start_date = entry["start_date"]
|
||||
|
||||
for level in path[:-1]:
|
||||
if level not in node:
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
"sort_key": level}
|
||||
"sort_key": level,
|
||||
"start_date": category_start_date}
|
||||
else:
|
||||
if node[level]["start_date"] > category_start_date:
|
||||
node[level]["start_date"] = category_start_date
|
||||
node = node[level]["subcategories"]
|
||||
|
||||
level = path[-1]
|
||||
if level not in node:
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
"sort_key": level}
|
||||
for entry in entries:
|
||||
node[level]["entries"][entry["title"]] = {"id": entry["id"],
|
||||
"sort_key": entry["sort_key"]}
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
"sort_key": level,
|
||||
"start_date": category_start_date}
|
||||
else:
|
||||
if node[level]["start_date"] > category_start_date:
|
||||
node[level]["start_date"] = category_start_date
|
||||
|
||||
for entry in entries:
|
||||
node[level]["entries"][entry["title"]] = {"id": entry["id"],
|
||||
"sort_key": entry["sort_key"],
|
||||
"start_date": entry["start_date"]}
|
||||
|
||||
default_topics = {'General': course.location.html_id()}
|
||||
discussion_topics = course.metadata.get('discussion_topics', default_topics)
|
||||
for topic, entry in discussion_topics.items():
|
||||
category_map['entries'][topic] = {"id": entry["id"],
|
||||
"sort_key": entry.get("sort_key", topic),
|
||||
"start_date": time.gmtime()}
|
||||
sort_map_entries(category_map)
|
||||
|
||||
_DISCUSSIONINFO = {}
|
||||
|
||||
_DISCUSSIONINFO['id_map'] = discussion_id_map
|
||||
|
||||
_DISCUSSIONINFO['category_map'] = category_map
|
||||
_DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map
|
||||
_DISCUSSIONINFO[course.id]['category_map'] = category_map
|
||||
|
||||
class JsonResponse(HttpResponse):
|
||||
def __init__(self, data=None):
|
||||
@@ -138,14 +207,14 @@ class JsonResponse(HttpResponse):
|
||||
mimetype='application/json; charset=utf8')
|
||||
|
||||
class JsonError(HttpResponse):
|
||||
def __init__(self, error_messages=[]):
|
||||
def __init__(self, error_messages=[], status=400):
|
||||
if isinstance(error_messages, str):
|
||||
error_messages = [error_messages]
|
||||
content = simplejson.dumps({'errors': error_messages},
|
||||
indent=2,
|
||||
ensure_ascii=False)
|
||||
super(JsonError, self).__init__(content,
|
||||
mimetype='application/json; charset=utf8', status=400)
|
||||
mimetype='application/json; charset=utf8', status=status)
|
||||
|
||||
class HtmlResponse(HttpResponse):
|
||||
def __init__(self, html=''):
|
||||
@@ -228,8 +297,14 @@ def permalink(content):
|
||||
args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
|
||||
|
||||
def extend_content(content):
|
||||
user = User.objects.get(pk=content['user_id'])
|
||||
roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id']))
|
||||
roles = {}
|
||||
if content.get('user_id'):
|
||||
try:
|
||||
user = User.objects.get(pk=content['user_id'])
|
||||
roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id']))
|
||||
except user.DoesNotExist:
|
||||
logging.error('User ID {0} in comment content {1} but not in our DB.'.format(content.get('user_id'), content.get('id')))
|
||||
|
||||
content_info = {
|
||||
'displayed_title': content.get('highlighted_title') or content.get('title', ''),
|
||||
'displayed_body': content.get('highlighted_body') or content.get('body', ''),
|
||||
@@ -242,25 +317,33 @@ def extend_content(content):
|
||||
|
||||
def get_courseware_context(content, course):
|
||||
id_map = get_discussion_id_map(course)
|
||||
id = content['commentable_id']
|
||||
id = content['commentable_id']
|
||||
content_info = None
|
||||
if id in id_map:
|
||||
location = id_map[id]["location"].url()
|
||||
title = id_map[id]["title"]
|
||||
content_info = { "courseware_location": location, "courseware_title": title}
|
||||
(course_id, chapter, section, position) = path_to_location(modulestore(), course.id, location)
|
||||
url = reverse('courseware_position', kwargs={"course_id":course_id,
|
||||
"chapter":chapter,
|
||||
"section":section,
|
||||
"position":position})
|
||||
content_info = {"courseware_url": url, "courseware_title": title}
|
||||
return content_info
|
||||
|
||||
|
||||
def safe_content(content):
|
||||
fields = [
|
||||
'id', 'title', 'body', 'course_id', 'anonymous', 'endorsed',
|
||||
'parent_id', 'thread_id', 'votes', 'closed',
|
||||
'created_at', 'updated_at', 'depth', 'type',
|
||||
'commentable_id', 'comments_count', 'at_position_list',
|
||||
'children', 'highlighted_title', 'highlighted_body',
|
||||
'id', 'title', 'body', 'course_id', 'anonymous', 'anonymous_to_peers',
|
||||
'endorsed', 'parent_id', 'thread_id', 'votes', 'closed', 'created_at',
|
||||
'updated_at', 'depth', 'type', 'commentable_id', 'comments_count',
|
||||
'at_position_list', 'children', 'highlighted_title', 'highlighted_body',
|
||||
'courseware_title', 'courseware_url', 'tags'
|
||||
]
|
||||
|
||||
if content.get('anonymous') is False:
|
||||
if (content.get('anonymous') is False) and (content.get('anonymous_to_peers') is False):
|
||||
fields += ['username', 'user_id']
|
||||
|
||||
if 'children' in content:
|
||||
safe_children = [safe_content(child) for child in content['children']]
|
||||
content['children'] = safe_children
|
||||
|
||||
return strip_none(extract(content, fields))
|
||||
|
||||
@@ -70,6 +70,7 @@ SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
|
||||
|
||||
AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"]
|
||||
AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"]
|
||||
AWS_STORAGE_BUCKET_NAME = 'edxuploads'
|
||||
|
||||
DATABASES = AUTH_TOKENS['DATABASES']
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ main_vendor_js = [
|
||||
'js/vendor/swfobject/swfobject.js',
|
||||
]
|
||||
|
||||
discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/*.coffee'))
|
||||
discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/**/*.coffee'))
|
||||
|
||||
# Load javascript from all of the available xmodules, and
|
||||
# prep it for use in pipeline js
|
||||
|
||||
@@ -7,15 +7,14 @@ import settings
|
||||
class Comment(models.Model):
|
||||
|
||||
accessible_fields = [
|
||||
'id', 'body', 'anonymous', 'course_id',
|
||||
'endorsed', 'parent_id', 'thread_id',
|
||||
'username', 'votes', 'user_id', 'closed',
|
||||
'created_at', 'updated_at', 'depth',
|
||||
'at_position_list', 'type', 'commentable_id',
|
||||
'id', 'body', 'anonymous', 'anonymous_to_peers', 'course_id',
|
||||
'endorsed', 'parent_id', 'thread_id', 'username', 'votes', 'user_id',
|
||||
'closed', 'created_at', 'updated_at', 'depth', 'at_position_list',
|
||||
'type', 'commentable_id',
|
||||
]
|
||||
|
||||
updatable_fields = [
|
||||
'body', 'anonymous', 'course_id', 'closed',
|
||||
'body', 'anonymous', 'anonymous_to_peers', 'course_id', 'closed',
|
||||
'user_id', 'endorsed',
|
||||
]
|
||||
|
||||
|
||||
@@ -6,16 +6,14 @@ import settings
|
||||
class Thread(models.Model):
|
||||
|
||||
accessible_fields = [
|
||||
'id', 'title', 'body', 'anonymous',
|
||||
'course_id', 'closed', 'tags', 'votes',
|
||||
'commentable_id', 'username', 'user_id',
|
||||
'created_at', 'updated_at', 'comments_count',
|
||||
'at_position_list', 'children', 'type',
|
||||
'highlighted_title', 'highlighted_body',
|
||||
'id', 'title', 'body', 'anonymous', 'anonymous_to_peers', 'course_id',
|
||||
'closed', 'tags', 'votes', 'commentable_id', 'username', 'user_id',
|
||||
'created_at', 'updated_at', 'comments_count', 'at_position_list',
|
||||
'children', 'type', 'highlighted_title', 'highlighted_body', 'endorsed'
|
||||
]
|
||||
|
||||
updatable_fields = [
|
||||
'title', 'body', 'anonymous', 'course_id',
|
||||
'title', 'body', 'anonymous', 'anonymous_to_peers', 'course_id',
|
||||
'closed', 'tags', 'user_id', 'commentable_id',
|
||||
]
|
||||
|
||||
@@ -32,7 +30,7 @@ class Thread(models.Model):
|
||||
'course_id': query_params['course_id'],
|
||||
'recursive': False}
|
||||
params = merge_dict(default_params, strip_blank(strip_none(query_params)))
|
||||
if query_params.get('text') or query_params.get('tags'):
|
||||
if query_params.get('text') or query_params.get('tags') or query_params.get('commentable_ids'):
|
||||
url = cls.url(action='search')
|
||||
else:
|
||||
url = cls.url(action='get_all', params=extract(params, 'commentable_id'))
|
||||
@@ -40,7 +38,7 @@ class Thread(models.Model):
|
||||
del params['commentable_id']
|
||||
response = perform_request('get', url, params, *args, **kwargs)
|
||||
return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1)
|
||||
|
||||
|
||||
@classmethod
|
||||
def url_for_threads(cls, params={}):
|
||||
if params.get('commentable_id'):
|
||||
|
||||
@@ -8,7 +8,8 @@ class User(models.Model):
|
||||
accessible_fields = ['username', 'email', 'follower_ids', 'upvoted_ids', 'downvoted_ids',
|
||||
'id', 'external_id', 'subscribed_user_ids', 'children', 'course_id',
|
||||
'subscribed_thread_ids', 'subscribed_commentable_ids',
|
||||
'threads_count', 'comments_count', 'default_sort_key'
|
||||
'subscribed_course_ids', 'threads_count', 'comments_count',
|
||||
'default_sort_key'
|
||||
]
|
||||
|
||||
updatable_fields = ['username', 'external_id', 'email', 'default_sort_key']
|
||||
|
||||
@@ -28,9 +28,9 @@ def perform_request(method, url, data_or_params=None, *args, **kwargs):
|
||||
data_or_params['api_key'] = settings.API_KEY
|
||||
try:
|
||||
if method in ['post', 'put', 'patch']:
|
||||
response = requests.request(method, url, data=data_or_params)
|
||||
response = requests.request(method, url, data=data_or_params, timeout=5)
|
||||
else:
|
||||
response = requests.request(method, url, params=data_or_params)
|
||||
response = requests.request(method, url, params=data_or_params, timeout=5)
|
||||
except Exception as err:
|
||||
log.exception("Trying to call {method} on {url} with params {params}".format(
|
||||
method=method, url=url, params=data_or_params))
|
||||
|
||||
@@ -45,6 +45,7 @@ $ ->
|
||||
|
||||
removeMath: (text) ->
|
||||
|
||||
text = text || ""
|
||||
@math = []
|
||||
start = end = last = null
|
||||
braces = 0
|
||||
@@ -111,7 +112,7 @@ $ ->
|
||||
(text) -> _this.replaceMath(text)
|
||||
|
||||
if Markdown?
|
||||
|
||||
|
||||
Markdown.getMathCompatibleConverter = (postProcessor) ->
|
||||
postProcessor ||= ((text) -> text)
|
||||
converter = Markdown.getSanitizingConverter()
|
||||
@@ -123,11 +124,9 @@ $ ->
|
||||
|
||||
Markdown.makeWmdEditor = (elem, appended_id, imageUploadUrl, postProcessor) ->
|
||||
$elem = $(elem)
|
||||
|
||||
if not $elem.length
|
||||
console.log "warning: elem for makeWmdEditor doesn't exist"
|
||||
return
|
||||
|
||||
if not $elem.find(".wmd-panel").length
|
||||
initialText = $elem.html()
|
||||
$elem.empty()
|
||||
@@ -162,7 +161,7 @@ $ ->
|
||||
alert(e)
|
||||
if startUploadHandler
|
||||
$('#file-upload').unbind('change').change(startUploadHandler)
|
||||
|
||||
|
||||
imageUploadHandler = (elem, input) ->
|
||||
ajaxFileUpload(imageUploadUrl, input, imageUploadHandler)
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
if Backbone?
|
||||
class @Content extends Backbone.Model
|
||||
|
||||
@contents: {}
|
||||
@contentInfos: {}
|
||||
|
||||
template: -> DiscussionUtil.getTemplate('_content')
|
||||
|
||||
actions:
|
||||
@@ -9,19 +12,20 @@ if Backbone?
|
||||
can_endorse: '.admin-endorse'
|
||||
can_delete: '.admin-delete'
|
||||
can_openclose: '.admin-openclose'
|
||||
|
||||
|
||||
urlMappers: {}
|
||||
|
||||
urlFor: (name) ->
|
||||
@urlMappers[name].apply(@)
|
||||
|
||||
can: (action) ->
|
||||
DiscussionUtil.getContentInfo @id, action
|
||||
(@get('ability') || {})[action]
|
||||
|
||||
updateInfo: (info) ->
|
||||
@set('ability', info.ability)
|
||||
@set('voted', info.voted)
|
||||
@set('subscribed', info.subscribed)
|
||||
if info
|
||||
@set('ability', info.ability)
|
||||
@set('voted', info.voted)
|
||||
@set('subscribed', info.subscribed)
|
||||
|
||||
addComment: (comment, options) ->
|
||||
options ||= {}
|
||||
@@ -32,12 +36,14 @@ if Backbone?
|
||||
@get('children').push comment
|
||||
model = new Comment $.extend {}, comment, { thread: @get('thread') }
|
||||
@get('comments').add model
|
||||
@trigger "comment:add"
|
||||
model
|
||||
|
||||
removeComment: (comment) ->
|
||||
thread = @get('thread')
|
||||
comments_count = parseInt(thread.get('comments_count'))
|
||||
thread.set('comments_count', comments_count - 1 - comment.getCommentsCount())
|
||||
@trigger "comment:remove"
|
||||
|
||||
resetComments: (children) ->
|
||||
@set 'children', []
|
||||
@@ -46,364 +52,33 @@ if Backbone?
|
||||
@addComment comment, { silent: true }
|
||||
|
||||
initialize: ->
|
||||
DiscussionUtil.addContent @id, @
|
||||
Content.addContent @id, @
|
||||
if Content.getInfo(@id)
|
||||
@updateInfo(Content.getInfo(@id))
|
||||
@set 'user_url', DiscussionUtil.urlFor('user_profile', @get('user_id'))
|
||||
@resetComments(@get('children'))
|
||||
|
||||
|
||||
class @ContentView extends Backbone.View
|
||||
remove: ->
|
||||
|
||||
$: (selector) ->
|
||||
@$local.find(selector)
|
||||
|
||||
partial:
|
||||
endorsed: (endorsed) ->
|
||||
if endorsed
|
||||
@$el.addClass("endorsed")
|
||||
else
|
||||
@$el.removeClass("endorsed")
|
||||
|
||||
closed: (closed) -> # we should just re-render the whole thread, or update according to new abilities
|
||||
if closed
|
||||
@$el.addClass("closed")
|
||||
@$(".admin-openclose").text "Re-open Thread"
|
||||
else
|
||||
@$el.removeClass("closed")
|
||||
@$(".admin-openclose").text "Close Thread"
|
||||
|
||||
voted: (voted) ->
|
||||
@$(".discussion-vote-up").removeClass("voted") if voted != "up"
|
||||
@$(".discussion-vote-down").removeClass("voted") if voted != "down"
|
||||
@$(".discussion-vote-#{voted}").addClass("voted") if voted in ["up", "down"]
|
||||
|
||||
votes_point: (votes_point) ->
|
||||
@$(".discussion-votes-point").html(votes_point)
|
||||
|
||||
comments_count: (comments_count) ->
|
||||
@$(".comments-count").html(comments_count)
|
||||
|
||||
subscribed: (subscribed) ->
|
||||
if subscribed
|
||||
@$(".discussion-follow-thread").addClass("discussion-unfollow-thread").html("Unfollow")
|
||||
else
|
||||
@$(".discussion-follow-thread").removeClass("discussion-unfollow-thread").html("Follow")
|
||||
|
||||
ability: (ability) ->
|
||||
for action, elemSelector of @model.actions
|
||||
if not ability[action]
|
||||
@$(elemSelector).parent().hide()
|
||||
else
|
||||
@$(elemSelector).parent().show()
|
||||
|
||||
$discussionContent: ->
|
||||
@_discussionContent ||= @$el.children(".discussion-content")
|
||||
|
||||
$showComments: ->
|
||||
@_showComments ||= @$(".discussion-show-comments")
|
||||
|
||||
updateShowComments: ->
|
||||
if @showed
|
||||
@$showComments().html @$showComments().html().replace "Show", "Hide"
|
||||
if @get('type') == 'comment'
|
||||
@get('thread').removeComment(@)
|
||||
@get('thread').trigger "comment:remove", @
|
||||
else
|
||||
@$showComments().html @$showComments().html().replace "Hide", "Show"
|
||||
@trigger "thread:remove", @
|
||||
|
||||
retrieved: ->
|
||||
@$showComments().hasClass("retrieved")
|
||||
|
||||
hideSingleThread: (event) ->
|
||||
@$el.children(".comments").hide()
|
||||
@showed = false
|
||||
@updateShowComments()
|
||||
@addContent: (id, content) -> @contents[id] = content
|
||||
|
||||
showSingleThread: (event) ->
|
||||
if @retrieved()
|
||||
@$el.children(".comments").show()
|
||||
@showed = true
|
||||
@updateShowComments()
|
||||
else
|
||||
$elem = $.merge @$(".thread-title"), @$showComments()
|
||||
url = @model.urlFor('retrieve')
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: @$(".discussion-show-comments")
|
||||
type: "GET"
|
||||
url: url
|
||||
success: (response, textStatus) =>
|
||||
@showed = true
|
||||
@updateShowComments()
|
||||
@$showComments().addClass("retrieved")
|
||||
@$el.children(".comments").replaceWith response.html
|
||||
@model.resetComments response.content.children
|
||||
@initCommentViews()
|
||||
DiscussionUtil.bulkUpdateContentInfo response.annotated_content_info
|
||||
@getContent: (id) -> @contents[id]
|
||||
|
||||
toggleSingleThread: (event) ->
|
||||
if @showed
|
||||
@hideSingleThread(event)
|
||||
else
|
||||
@showSingleThread(event)
|
||||
|
||||
initCommentViews: ->
|
||||
@$el.children(".comments").children(".comment").each (index, elem) =>
|
||||
model = @model.get('comments').find $(elem).attr("_id")
|
||||
if not model.view
|
||||
commentView = new CommentView el: elem, model: model
|
||||
@getInfo: (id) ->
|
||||
@contentInfos[id]
|
||||
|
||||
reply: ->
|
||||
if @model.get('type') == 'thread'
|
||||
@showSingleThread()
|
||||
$replyView = @$(".discussion-reply-new")
|
||||
if $replyView.length
|
||||
$replyView.show()
|
||||
else
|
||||
view = {}
|
||||
view.id = @model.id
|
||||
view.showWatchCheckbox = not @model.get('thread').get('subscribed')
|
||||
html = Mustache.render DiscussionUtil.getTemplate('_reply'), view
|
||||
@$discussionContent().append html
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "reply-body"
|
||||
@$(".discussion-submit-post").click $.proxy(@submitReply, @)
|
||||
@$(".discussion-cancel-post").click $.proxy(@cancelReply, @)
|
||||
@$(".discussion-reply").hide()
|
||||
@$(".discussion-edit").hide()
|
||||
@loadContentInfos: (infos) ->
|
||||
for id, info of infos
|
||||
if @getContent(id)
|
||||
@getContent(id).updateInfo(info)
|
||||
$.extend @contentInfos, infos
|
||||
|
||||
submitReply: (event) ->
|
||||
url = @model.urlFor('reply')
|
||||
|
||||
body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "reply-body"
|
||||
|
||||
anonymous = false || @$(".discussion-post-anonymously").is(":checked")
|
||||
autowatch = false || @$(".discussion-auto-watch").is(":checked")
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
body: body
|
||||
anonymous: anonymous
|
||||
auto_subscribe: autowatch
|
||||
error: DiscussionUtil.formErrorHandler @$(".discussion-errors")
|
||||
success: (response, textStatus) =>
|
||||
DiscussionUtil.clearFormErrors @$(".discussion-errors")
|
||||
$comment = $(response.html)
|
||||
@$el.children(".comments").prepend $comment
|
||||
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "reply-body", ""
|
||||
comment = @model.addComment response.content
|
||||
commentView = new CommentView el: $comment[0], model: comment
|
||||
comment.updateInfo response.annotated_content_info
|
||||
if autowatch
|
||||
@model.get('thread').set('subscribed', true)
|
||||
@cancelReply()
|
||||
|
||||
cancelReply: ->
|
||||
$replyView = @$(".discussion-reply-new")
|
||||
if $replyView.length
|
||||
$replyView.hide()
|
||||
@$(".discussion-reply").show()
|
||||
@$(".discussion-edit").show()
|
||||
|
||||
unvote: (event) ->
|
||||
url = @model.urlFor('unvote')
|
||||
$elem = @$(".discussion-vote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('voted', '')
|
||||
@model.set('votes_point', response.votes.point)
|
||||
|
||||
vote: (event, value) ->
|
||||
url = @model.urlFor("#{value}vote")
|
||||
$elem = @$(".discussion-vote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('voted', value)
|
||||
@model.set('votes_point', response.votes.point)
|
||||
|
||||
toggleVote: (event) ->
|
||||
$elem = $(event.target)
|
||||
value = $elem.attr("value")
|
||||
if @model.get("voted") == value
|
||||
@unvote(event)
|
||||
else
|
||||
@vote(event, value)
|
||||
|
||||
toggleEndorse: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('endorse')
|
||||
endorsed = @model.get('endorsed')
|
||||
data = { endorsed: not endorsed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('endorsed', not endorsed)
|
||||
|
||||
toggleFollow: (event) ->
|
||||
$elem = $(event.target)
|
||||
subscribed = @model.get('subscribed')
|
||||
if subscribed
|
||||
url = @model.urlFor('unfollow')
|
||||
else
|
||||
url = @model.urlFor('follow')
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('subscribed', not subscribed)
|
||||
|
||||
toggleClosed: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('close')
|
||||
closed = @model.get('closed')
|
||||
data = { closed: not closed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
data: data
|
||||
success: (response, textStatus) =>
|
||||
@model.set('closed', not closed)
|
||||
@model.set('ability', response.ability)
|
||||
|
||||
edit: (event) ->
|
||||
@$(".discussion-content-wrapper").hide()
|
||||
$editView = @$(".discussion-content-edit")
|
||||
if $editView.length
|
||||
$editView.show()
|
||||
else
|
||||
view = {}
|
||||
view.id = @model.id
|
||||
if @model.get('type') == 'thread'
|
||||
view.title = @model.get('title')
|
||||
view.body = @model.get('body')
|
||||
view.tags = @model.get('tags')
|
||||
else
|
||||
view.body = @model.get('body')
|
||||
@$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "#{@model.get('type')}-body-edit"
|
||||
@$(".thread-tags-edit").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
@$(".discussion-submit-update").unbind("click").click $.proxy(@submitEdit, @)
|
||||
@$(".discussion-cancel-update").unbind("click").click $.proxy(@cancelEdit, @)
|
||||
|
||||
submitEdit: (event) ->
|
||||
|
||||
url = @model.urlFor('update')
|
||||
data = {}
|
||||
if @model.get('type') == 'thread'
|
||||
data.title = @$(".thread-title-edit").val()
|
||||
data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "thread-body-edit"
|
||||
data.tags = @$(".thread-tags-edit").val()
|
||||
else
|
||||
data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "comment-body-edit"
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data: data
|
||||
error: DiscussionUtil.formErrorHandler @$(".discussion-update-errors")
|
||||
success: (response, textStatus) =>
|
||||
DiscussionUtil.clearFormErrors @$(".discussion-update-errors")
|
||||
@$discussionContent().replaceWith(response.html)
|
||||
if @model.get('type') == 'thread'
|
||||
@model = new Thread response.content
|
||||
else
|
||||
@model = new Comment $.extend {}, response.content, { thread: @model.get('thread') }
|
||||
@reconstruct()
|
||||
@model.updateInfo response.annotated_content_info, { forceUpdate: true }
|
||||
|
||||
cancelEdit: (event) ->
|
||||
@$(".discussion-content-edit").hide()
|
||||
@$(".discussion-content-wrapper").show()
|
||||
|
||||
delete: (event) ->
|
||||
url = @model.urlFor('delete')
|
||||
if @model.get('type') == 'thread'
|
||||
c = confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
|
||||
else
|
||||
c = confirm "Are you sure to delete this comment? "
|
||||
if not c
|
||||
return
|
||||
$elem = $(event.target)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@$el.remove()
|
||||
if @model.get('type') == 'comment'
|
||||
@model.get('thread').removeComment(@model)
|
||||
|
||||
events:
|
||||
"click .discussion-follow-thread": "toggleFollow"
|
||||
"click .thread-title": "toggleSingleThread"
|
||||
"click .discussion-show-comments": "toggleSingleThread"
|
||||
"click .discussion-reply-thread": "reply"
|
||||
"click .discussion-reply-comment": "reply"
|
||||
"click .discussion-cancel-reply": "cancelReply"
|
||||
"click .discussion-vote-up": "toggleVote"
|
||||
"click .discussion-vote-down": "toggleVote"
|
||||
"click .admin-endorse": "toggleEndorse"
|
||||
"click .admin-openclose": "toggleClosed"
|
||||
"click .admin-edit": "edit"
|
||||
"click .admin-delete": "delete"
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".local")
|
||||
@$delegateElement = @$local
|
||||
|
||||
initTitle: ->
|
||||
$contentTitle = @$(".thread-title")
|
||||
if $contentTitle.length
|
||||
$contentTitle.html DiscussionUtil.unescapeHighlightTag DiscussionUtil.stripLatexHighlight $contentTitle.html()
|
||||
|
||||
initBody: ->
|
||||
$contentBody = @$(".content-body")
|
||||
$contentBody.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight $contentBody.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
|
||||
|
||||
initTimeago: ->
|
||||
@$("span.timeago").each (index, element) ->
|
||||
elem = $(element)
|
||||
elem.html("posted on #{$.timeago.parse(elem.html()).toLocaleString()}")
|
||||
@$("span.timeago").timeago()
|
||||
|
||||
renderPartial: ->
|
||||
for attr, value of @model.changedAttributes()
|
||||
if @partial[attr]
|
||||
@partial[attr].apply(@, [value])
|
||||
|
||||
initBindings: ->
|
||||
@model.view = @
|
||||
@model.bind('change', @renderPartial, @)
|
||||
|
||||
initialize: ->
|
||||
@initBindings()
|
||||
@initLocal()
|
||||
@initTimeago()
|
||||
@initTitle()
|
||||
@initBody()
|
||||
@initCommentViews()
|
||||
|
||||
reconstruct: ->
|
||||
@initBindings()
|
||||
@initLocal()
|
||||
@initTimeago()
|
||||
@initTitle()
|
||||
@initBody()
|
||||
@delegateEvents()
|
||||
|
||||
class @Thread extends @Content
|
||||
urlMappers:
|
||||
'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
|
||||
@@ -421,7 +96,38 @@ if Backbone?
|
||||
@set('thread', @)
|
||||
super()
|
||||
|
||||
class @ThreadView extends @ContentView
|
||||
comment: ->
|
||||
@set("comments_count", parseInt(@get("comments_count")) + 1)
|
||||
|
||||
follow: ->
|
||||
@set('subscribed', true)
|
||||
|
||||
unfollow: ->
|
||||
@set('subscribed', false)
|
||||
|
||||
vote: ->
|
||||
@get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) + 1
|
||||
@trigger "change", @
|
||||
|
||||
unvote: ->
|
||||
@get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) - 1
|
||||
@trigger "change", @
|
||||
|
||||
display_body: ->
|
||||
if @has("highlighted_body")
|
||||
String(@get("highlighted_body")).replace(/<highlight>/g, '<mark>').replace(/<\/highlight>/g, '</mark>')
|
||||
else
|
||||
@get("body")
|
||||
|
||||
display_title: ->
|
||||
if @has("highlighted_title")
|
||||
String(@get("highlighted_title")).replace(/<highlight>/g, '<mark>').replace(/<\/highlight>/g, '</mark>')
|
||||
else
|
||||
@get("title")
|
||||
|
||||
toJSON: ->
|
||||
json_attributes = _.clone(@attributes)
|
||||
_.extend(json_attributes, { title: @display_title(), body: @display_body() })
|
||||
|
||||
class @Comment extends @Content
|
||||
urlMappers:
|
||||
@@ -439,8 +145,6 @@ if Backbone?
|
||||
count += comment.getCommentsCount() + 1
|
||||
count
|
||||
|
||||
class @CommentView extends @ContentView
|
||||
|
||||
class @Comments extends Backbone.Collection
|
||||
|
||||
model: Comment
|
||||
|
||||
@@ -2,185 +2,67 @@ if Backbone?
|
||||
class @Discussion extends Backbone.Collection
|
||||
model: Thread
|
||||
|
||||
initialize: ->
|
||||
DiscussionUtil.addDiscussion @id, @
|
||||
initialize: (models, options={})->
|
||||
@pages = options['pages'] || 1
|
||||
@current_page = 1
|
||||
@bind "add", (item) =>
|
||||
item.discussion = @
|
||||
@comparator = @sortByDateRecentFirst
|
||||
@on "thread:remove", (thread) =>
|
||||
@remove(thread)
|
||||
|
||||
find: (id) ->
|
||||
_.first @where(id: id)
|
||||
|
||||
hasMorePages: ->
|
||||
@current_page < @pages
|
||||
|
||||
addThread: (thread, options) ->
|
||||
options ||= {}
|
||||
model = new Thread thread
|
||||
@add model
|
||||
model
|
||||
# TODO: Check for existing thread with same ID in a faster way
|
||||
if not @find(thread.id)
|
||||
options ||= {}
|
||||
model = new Thread thread
|
||||
@add model
|
||||
model
|
||||
|
||||
class @DiscussionView extends Backbone.View
|
||||
|
||||
$: (selector) ->
|
||||
@$local.find(selector)
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".local")
|
||||
@$delegateElement = @$local
|
||||
|
||||
initialize: ->
|
||||
@initLocal()
|
||||
@model.id = @$el.attr("_id")
|
||||
@model.view = @
|
||||
@$el.children(".threads").children(".thread").each (index, elem) =>
|
||||
threadView = new ThreadView el: elem, model: @model.find $(elem).attr("_id")
|
||||
if @$el.hasClass("forum-discussion")
|
||||
$(".discussion-sidebar").find(".sidebar-new-post-button")
|
||||
.unbind('click').click $.proxy @newPost, @
|
||||
else if @$el.hasClass("inline-discussion")
|
||||
@newPost()
|
||||
|
||||
reload: ($elem, url) ->
|
||||
if not url then return
|
||||
retrieveAnotherPage: (search_text="", commentable_ids="", sort_key="")->
|
||||
# TODO: I really feel that this belongs in DiscussionThreadListView
|
||||
@current_page += 1
|
||||
url = DiscussionUtil.urlFor 'threads'
|
||||
data = { page: @current_page }
|
||||
if search_text
|
||||
data['text'] = search_text
|
||||
if sort_key
|
||||
data['sort_key'] = sort_key
|
||||
if commentable_ids
|
||||
data['commentable_ids'] = commentable_ids
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: $elem
|
||||
loadingCallback: ->
|
||||
$(this).parent().append("<span class='discussion-loading'></span>")
|
||||
loadedCallback: ->
|
||||
$(this).parent().children(".discussion-loading").remove()
|
||||
$elem: @$el
|
||||
url: url
|
||||
type: "GET"
|
||||
success: (response, textStatus) =>
|
||||
$parent = @$el.parent()
|
||||
@$el.replaceWith(response.html)
|
||||
$discussion = $parent.find("section.discussion")
|
||||
@model.reset(response.discussion_data, { silent: false })
|
||||
view = new DiscussionView el: $discussion[0], model: @model
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
$("html, body").animate({ scrollTop: 0 }, 0)
|
||||
|
||||
loadSimilarPost: (event) ->
|
||||
console.log "loading similar"
|
||||
$title = @$(".new-post-title")
|
||||
$wrapper = @$(".new-post-similar-posts-wrapper")
|
||||
$similarPosts = @$(".new-post-similar-posts")
|
||||
prevText = $title.attr("prev-text")
|
||||
text = $title.val()
|
||||
if text == prevText
|
||||
if @$(".similar-post").length
|
||||
$wrapper.show()
|
||||
else if $.trim(text).length
|
||||
$elem = $(event.target)
|
||||
url = DiscussionUtil.urlFor 'search_similar_threads', @model.id
|
||||
data = { text: @$(".new-post-title").val() }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
dataType: 'json'
|
||||
success: (response, textStatus) =>
|
||||
$wrapper.html(response.html)
|
||||
if $wrapper.find(".similar-post").length
|
||||
$wrapper.show()
|
||||
$wrapper.find(".hide-similar-posts").click =>
|
||||
$wrapper.hide()
|
||||
else
|
||||
$wrapper.hide()
|
||||
$title.attr("prev-text", text)
|
||||
|
||||
|
||||
newPost: ->
|
||||
if not @$(".wmd-panel").length
|
||||
view = { discussion_id: @model.id }
|
||||
@$el.children(".discussion-non-content").append Mustache.render DiscussionUtil.getTemplate("_new_post"), view
|
||||
$newPostBody = @$(".new-post-body")
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||
|
||||
$input = DiscussionUtil.getWmdInput @$el, $.proxy(@$, @), "new-post-body"
|
||||
$input.attr("placeholder", "post a new topic...")
|
||||
if @$el.hasClass("inline-discussion")
|
||||
$input.bind 'focus', (e) =>
|
||||
@$(".new-post-form").removeClass('collapsed')
|
||||
else if @$el.hasClass("forum-discussion")
|
||||
@$(".new-post-form").removeClass('collapsed')
|
||||
|
||||
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
|
||||
@$(".new-post-title").blur $.proxy(@loadSimilarPost, @)
|
||||
|
||||
@$(".hide-similar-posts").click =>
|
||||
@$(".new-post-similar-posts-wrapper").hide()
|
||||
|
||||
@$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
|
||||
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
|
||||
|
||||
|
||||
@$el.children(".blank").hide()
|
||||
@$(".new-post-form").show()
|
||||
|
||||
submitNewPost: (event) ->
|
||||
title = @$(".new-post-title").val()
|
||||
body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "new-post-body"
|
||||
tags = @$(".new-post-tags").val()
|
||||
anonymous = false || @$(".discussion-post-anonymously").is(":checked")
|
||||
autowatch = false || @$(".discussion-auto-watch").is(":checked")
|
||||
url = DiscussionUtil.urlFor('create_thread', @model.id)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
data: data
|
||||
dataType: 'json'
|
||||
data:
|
||||
title: title
|
||||
body: body
|
||||
tags: tags
|
||||
anonymous: anonymous
|
||||
auto_subscribe: autowatch
|
||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
|
||||
$thread = $(response.html)
|
||||
@$el.children(".threads").prepend($thread)
|
||||
models = @models
|
||||
new_threads = [new Thread(data) for data in response.discussion_data][0]
|
||||
new_collection = _.union(models, new_threads)
|
||||
@reset new_collection
|
||||
|
||||
@$el.children(".blank").remove()
|
||||
sortByDate: (thread) ->
|
||||
thread.get("created_at")
|
||||
|
||||
@$(".new-post-similar-posts").empty()
|
||||
@$(".new-post-similar-posts-wrapper").hide()
|
||||
@$(".new-post-title").val("").attr("prev-text", "")
|
||||
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "new-post-body", ""
|
||||
@$(".new-post-tags").val("")
|
||||
@$(".new-post-tags").importTags("")
|
||||
sortByDateRecentFirst: (thread) ->
|
||||
-(new Date(thread.get("created_at")).getTime())
|
||||
#return String.fromCharCode.apply(String,
|
||||
# _.map(thread.get("created_at").split(""),
|
||||
# ((c) -> return 0xffff - c.charChodeAt()))
|
||||
#)
|
||||
|
||||
thread = @model.addThread response.content
|
||||
threadView = new ThreadView el: $thread[0], model: thread
|
||||
thread.updateInfo response.annotated_content_info
|
||||
@cancelNewPost()
|
||||
|
||||
sortByVotes: (thread1, thread2) ->
|
||||
thread1_count = parseInt(thread1.get("votes")['up_count'])
|
||||
thread2_count = parseInt(thread2.get("votes")['up_count'])
|
||||
thread2_count - thread1_count
|
||||
|
||||
cancelNewPost: (event) ->
|
||||
if @$el.hasClass("inline-discussion")
|
||||
@$(".new-post-form").addClass("collapsed")
|
||||
else if @$el.hasClass("forum-discussion")
|
||||
@$(".new-post-form").hide()
|
||||
@$el.children(".blank").show()
|
||||
|
||||
search: (event) ->
|
||||
event.preventDefault()
|
||||
$elem = $(event.target)
|
||||
url = URI($elem.attr("action")).addSearch({text: @$(".search-input").val()})
|
||||
@reload($elem, url)
|
||||
|
||||
sort: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = $elem.attr("sort-url")
|
||||
@reload($elem, url)
|
||||
|
||||
page: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = $elem.attr("page-url")
|
||||
@reload($elem, url)
|
||||
|
||||
events:
|
||||
"submit .search-wrapper>.discussion-search-form": "search"
|
||||
"click .discussion-search-link": "search"
|
||||
"click .discussion-sort-link": "sort"
|
||||
"click .discussion-page-link": "page"
|
||||
sortByComments: (thread1, thread2) ->
|
||||
thread1_count = parseInt(thread1.get("comments_count"))
|
||||
thread2_count = parseInt(thread2.get("comments_count"))
|
||||
thread2_count - thread1_count
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
if Backbone?
|
||||
class @DiscussionModuleView extends Backbone.View
|
||||
events:
|
||||
"click .discussion-show": "toggleDiscussion"
|
||||
toggleDiscussion: (event) ->
|
||||
if @showed
|
||||
@$("section.discussion").hide()
|
||||
$(event.target).html("Show Discussion")
|
||||
@showed = false
|
||||
else
|
||||
if @retrieved
|
||||
@$("section.discussion").show()
|
||||
$(event.target).html("Hide Discussion")
|
||||
@showed = true
|
||||
else
|
||||
$elem = $(event.target)
|
||||
discussion_id = $elem.attr("discussion_id")
|
||||
url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: $elem
|
||||
url: url
|
||||
type: "GET"
|
||||
dataType: 'json'
|
||||
success: (response, textStatus) =>
|
||||
@$el.append(response.html)
|
||||
$discussion = @$el.find("section.discussion")
|
||||
$(event.target).html("Hide Discussion")
|
||||
discussion = new Discussion()
|
||||
discussion.reset(response.discussion_data, {silent: false})
|
||||
view = new DiscussionView(el: $discussion[0], model: discussion)
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@retrieved = true
|
||||
@showed = true
|
||||
124
lms/static/coffee/src/discussion/discussion_module_view.coffee
Normal file
@@ -0,0 +1,124 @@
|
||||
if Backbone?
|
||||
class @DiscussionModuleView extends Backbone.View
|
||||
events:
|
||||
"click .discussion-show": "toggleDiscussion"
|
||||
"click .new-post-btn": "toggleNewPost"
|
||||
"click .new-post-cancel": "hideNewPost"
|
||||
"click .discussion-paginator a": "navigateToPage"
|
||||
|
||||
paginationTemplate: -> DiscussionUtil.getTemplate("_pagination")
|
||||
page_re: /\?discussion_page=(\d+)/
|
||||
initialize: ->
|
||||
@toggleDiscussionBtn = @$(".discussion-show")
|
||||
# Set the page if it was set in the URL. This is used to allow deep linking to pages
|
||||
match = @page_re.exec(window.location.href)
|
||||
if match
|
||||
@page = parseInt(match[1])
|
||||
else
|
||||
@page = 1
|
||||
|
||||
toggleNewPost: (event) ->
|
||||
event.preventDefault()
|
||||
if !@newPostForm
|
||||
@toggleDiscussion()
|
||||
@isWaitingOnNewPost = true;
|
||||
return
|
||||
if @showed
|
||||
@newPostForm.slideDown(300)
|
||||
else
|
||||
@newPostForm.show()
|
||||
@toggleDiscussionBtn.addClass('shown')
|
||||
@toggleDiscussionBtn.find('.button-text').html("Hide Discussion")
|
||||
@$("section.discussion").slideDown()
|
||||
@showed = true
|
||||
|
||||
hideNewPost: (event) ->
|
||||
event.preventDefault()
|
||||
@newPostForm.slideUp(300)
|
||||
|
||||
toggleDiscussion: (event) ->
|
||||
if @showed
|
||||
@$("section.discussion").slideUp()
|
||||
@toggleDiscussionBtn.removeClass('shown')
|
||||
@toggleDiscussionBtn.find('.button-text').html("Show Discussion")
|
||||
@showed = false
|
||||
else
|
||||
@toggleDiscussionBtn.addClass('shown')
|
||||
@toggleDiscussionBtn.find('.button-text').html("Hide Discussion")
|
||||
|
||||
if @retrieved
|
||||
@$("section.discussion").slideDown()
|
||||
@showed = true
|
||||
else
|
||||
$elem = @toggleDiscussionBtn
|
||||
@loadPage $elem
|
||||
|
||||
loadPage: ($elem)=>
|
||||
discussionId = @$el.data("discussion-id")
|
||||
url = DiscussionUtil.urlFor('retrieve_discussion', discussionId) + "?page=#{@page}"
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: $elem
|
||||
url: url
|
||||
type: "GET"
|
||||
dataType: 'json'
|
||||
success: (response, textStatus, jqXHR) => @renderDiscussion($elem, response, textStatus, discussionId)
|
||||
|
||||
renderDiscussion: ($elem, response, textStatus, discussionId) =>
|
||||
window.user = new DiscussionUser(response.user_info)
|
||||
Content.loadContentInfos(response.annotated_content_info)
|
||||
DiscussionUtil.loadRoles(response.roles)
|
||||
allow_anonymous = response.allow_anonymous
|
||||
allow_anonymous_to_peers = response.allow_anonymous_to_peers
|
||||
# $elem.html("Hide Discussion")
|
||||
@discussion = new Discussion()
|
||||
@discussion.reset(response.discussion_data, {silent: false})
|
||||
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId, 'allow_anonymous_to_peers': allow_anonymous_to_peers, 'allow_anonymous': allow_anonymous})
|
||||
if @$('section.discussion').length
|
||||
@$('section.discussion').replaceWith($discussion)
|
||||
else
|
||||
$(".discussion-module").append($discussion)
|
||||
@newPostForm = $('.new-post-article')
|
||||
@threadviews = @discussion.map (thread) ->
|
||||
new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
|
||||
_.each @threadviews, (dtv) -> dtv.render()
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@newPostView = new NewPostInlineView el: @$('.new-post-article'), collection: @discussion
|
||||
@discussion.on "add", @addThread
|
||||
@retrieved = true
|
||||
@showed = true
|
||||
@renderPagination(2, response.num_pages)
|
||||
if @isWaitingOnNewPost
|
||||
@newPostForm.show()
|
||||
|
||||
addThread: (thread, collection, options) =>
|
||||
# TODO: When doing pagination, this will need to repaginate. Perhaps just reload page 1?
|
||||
article = $("<article class='discussion-thread' id='thread_#{thread.id}'></article>")
|
||||
@$('section.discussion > .threads').prepend(article)
|
||||
threadView = new DiscussionThreadInlineView el: article, model: thread
|
||||
threadView.render()
|
||||
@threadviews.unshift threadView
|
||||
|
||||
renderPagination: (delta, numPages) =>
|
||||
minPage = Math.max(@page - delta, 1)
|
||||
maxPage = Math.min(@page + delta, numPages)
|
||||
pageUrl = (number) ->
|
||||
"?discussion_page=#{number}"
|
||||
params =
|
||||
page: @page
|
||||
lowPages: _.range(minPage, @page).map (n) -> {number: n, url: pageUrl(n)}
|
||||
highPages: _.range(@page+1, maxPage+1).map (n) -> {number: n, url: pageUrl(n)}
|
||||
previous: if @page-1 >= 1 then {url: pageUrl(@page-1), number: @page-1} else false
|
||||
next: if @page+1 <= numPages then {url: pageUrl(@page+1), number: @page+1} else false
|
||||
leftdots: minPage > 2
|
||||
rightdots: maxPage < numPages-1
|
||||
first: if minPage > 1 then {url: pageUrl(1)} else false
|
||||
last: if maxPage < numPages then {number: numPages, url: pageUrl(numPages)} else false
|
||||
thing = Mustache.render @paginationTemplate(), params
|
||||
@$('section.pagination').html(thing)
|
||||
|
||||
navigateToPage: (event) =>
|
||||
event.preventDefault()
|
||||
window.history.pushState({}, window.document.title, event.target.href)
|
||||
@page = $(event.target).data('page-number')
|
||||
@loadPage($(event.target))
|
||||
45
lms/static/coffee/src/discussion/discussion_router.coffee
Normal file
@@ -0,0 +1,45 @@
|
||||
if Backbone?
|
||||
class @DiscussionRouter extends Backbone.Router
|
||||
routes:
|
||||
"": "allThreads"
|
||||
":forum_name/threads/:thread_id" : "showThread"
|
||||
|
||||
initialize: (options) ->
|
||||
@discussion = options['discussion']
|
||||
@nav = new DiscussionThreadListView(collection: @discussion, el: $(".sidebar"))
|
||||
@nav.on "thread:selected", @navigateToThread
|
||||
@nav.on "thread:removed", @navigateToAllThreads
|
||||
@nav.on "threads:rendered", @setActiveThread
|
||||
@nav.render()
|
||||
|
||||
@newPostView = new NewPostView(el: $(".new-post-article"), collection: @discussion)
|
||||
@nav.on "thread:created", @navigateToThread
|
||||
|
||||
allThreads: ->
|
||||
@nav.updateSidebar()
|
||||
|
||||
setActiveThread: =>
|
||||
if @thread
|
||||
@nav.setActiveThread(@thread.get("id"))
|
||||
|
||||
showThread: (forum_name, thread_id) ->
|
||||
@thread = @discussion.get(thread_id)
|
||||
@setActiveThread()
|
||||
if(@main)
|
||||
@main.cleanup()
|
||||
@main.undelegateEvents()
|
||||
|
||||
@main = new DiscussionThreadView(el: $(".discussion-column"), model: @thread)
|
||||
@main.render()
|
||||
@main.on "thread:responses:rendered", =>
|
||||
@nav.updateSidebar()
|
||||
@main.on "tag:selected", (tag) =>
|
||||
search = "[#{tag}]"
|
||||
@nav.setAndSearchFor(search)
|
||||
|
||||
navigateToThread: (thread_id) =>
|
||||
thread = @discussion.get(thread_id)
|
||||
@navigate("#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true)
|
||||
|
||||
navigateToAllThreads: =>
|
||||
@navigate("", trigger: true)
|
||||
@@ -1,18 +1,29 @@
|
||||
$ ->
|
||||
|
||||
window.$$contents = {}
|
||||
window.$$discussions = {}
|
||||
|
||||
$("section.discussion").each (index, elem) ->
|
||||
discussionData = DiscussionUtil.getDiscussionData($(elem).attr("_id"))
|
||||
discussion = new Discussion()
|
||||
discussion.reset(discussionData, {silent: false})
|
||||
view = new DiscussionView(el: elem, model: discussion)
|
||||
|
||||
if window.$$annotated_content_info?
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
|
||||
$userProfile = $(".discussion-sidebar>.user-profile")
|
||||
if $userProfile.length
|
||||
console.log "initialize user profile"
|
||||
view = new DiscussionUserProfileView(el: $userProfile[0])
|
||||
if Backbone?
|
||||
DiscussionApp =
|
||||
start: (elem)->
|
||||
# TODO: Perhaps eliminate usage of global variables when possible
|
||||
DiscussionUtil.loadRolesFromContainer()
|
||||
element = $(elem)
|
||||
window.$$course_id = element.data("course-id")
|
||||
user_info = element.data("user-info")
|
||||
threads = element.data("threads")
|
||||
thread_pages = element.data("thread-pages")
|
||||
content_info = element.data("content-info")
|
||||
window.user = new DiscussionUser(user_info)
|
||||
Content.loadContentInfos(content_info)
|
||||
discussion = new Discussion(threads, pages: thread_pages)
|
||||
new DiscussionRouter({discussion: discussion})
|
||||
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"})
|
||||
DiscussionProfileApp =
|
||||
start: (elem) ->
|
||||
element = $(elem)
|
||||
window.$$course_id = element.data("course-id")
|
||||
threads = element.data("threads")
|
||||
user_info = element.data("user-info")
|
||||
window.user = new DiscussionUser(user_info)
|
||||
new DiscussionUserProfileView(el: element, collection: threads)
|
||||
$ ->
|
||||
$("section.discussion").each (index, elem) ->
|
||||
DiscussionApp.start(elem)
|
||||
$("section.discussion-user-threads").each (index, elem) ->
|
||||
DiscussionProfileApp.start(elem)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
if Backbone?
|
||||
class @DiscussionUser extends Backbone.Model
|
||||
following: (thread) ->
|
||||
_.include(@get('subscribed_thread_ids'), thread.id)
|
||||
|
||||
voted: (thread) ->
|
||||
_.include(@get('upvoted_ids'), thread.id)
|
||||
|
||||
vote: (thread) ->
|
||||
@get('upvoted_ids').push(thread.id)
|
||||
thread.vote()
|
||||
|
||||
unvote: (thread) ->
|
||||
@set('upvoted_ids', _.without(@get('upvoted_ids'), thread.id))
|
||||
thread.unvote()
|
||||
@@ -1,29 +1,30 @@
|
||||
class @DiscussionUserProfileView extends Backbone.View
|
||||
toggleModeratorStatus: (event) ->
|
||||
confirmValue = confirm("Are you sure?")
|
||||
if not confirmValue then return
|
||||
$elem = $(event.target)
|
||||
if $elem.hasClass("sidebar-promote-moderator-button")
|
||||
isModerator = true
|
||||
else if $elem.hasClass("sidebar-revoke-moderator-button")
|
||||
isModerator = false
|
||||
else
|
||||
console.error "unrecognized moderator status"
|
||||
return
|
||||
url = DiscussionUtil.urlFor('update_moderator_status', $$profiled_user_id)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
is_moderator: isModerator
|
||||
error: (response, textStatus, e) ->
|
||||
console.log e
|
||||
success: (response, textStatus) =>
|
||||
parent = @$el.parent()
|
||||
@$el.replaceWith(response.html)
|
||||
view = new DiscussionUserProfileView el: parent.children(".user-profile")
|
||||
if Backbone?
|
||||
class @DiscussionUserProfileView extends Backbone.View
|
||||
toggleModeratorStatus: (event) ->
|
||||
confirmValue = confirm("Are you sure?")
|
||||
if not confirmValue then return
|
||||
$elem = $(event.target)
|
||||
if $elem.hasClass("sidebar-promote-moderator-button")
|
||||
isModerator = true
|
||||
else if $elem.hasClass("sidebar-revoke-moderator-button")
|
||||
isModerator = false
|
||||
else
|
||||
console.error "unrecognized moderator status"
|
||||
return
|
||||
url = DiscussionUtil.urlFor('update_moderator_status', $$profiled_user_id)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
is_moderator: isModerator
|
||||
error: (response, textStatus, e) ->
|
||||
console.log e
|
||||
success: (response, textStatus) =>
|
||||
parent = @$el.parent()
|
||||
@$el.replaceWith(response.html)
|
||||
view = new DiscussionUserProfileView el: parent.children(".user-profile")
|
||||
|
||||
events:
|
||||
"click .sidebar-toggle-moderator-button": "toggleModeratorStatus"
|
||||
events:
|
||||
"click .sidebar-toggle-moderator-button": "toggleModeratorStatus"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
$ ->
|
||||
if !window.$$contents
|
||||
window.$$contents = {}
|
||||
$.fn.extend
|
||||
loading: ->
|
||||
@$_loading = $("<span class='discussion-loading'></span>")
|
||||
@$_loading = $("<div class='loading-animation'></div>")
|
||||
$(this).after(@$_loading)
|
||||
loaded: ->
|
||||
@$_loading.remove()
|
||||
@@ -13,27 +15,26 @@ class @DiscussionUtil
|
||||
@getTemplate: (id) ->
|
||||
$("script##{id}").html()
|
||||
|
||||
@getDiscussionData: (id) ->
|
||||
return $$discussion_data[id]
|
||||
@loadRoles: (roles)->
|
||||
@roleIds = roles
|
||||
|
||||
@addContent: (id, content) -> window.$$contents[id] = content
|
||||
@loadRolesFromContainer: ->
|
||||
@loadRoles($("#discussion-container").data("roles"))
|
||||
|
||||
@getContent: (id) -> window.$$contents[id]
|
||||
@isStaff: (user_id) ->
|
||||
staff = _.union(@roleIds['Staff'], @roleIds['Moderator'], @roleIds['Administrator'])
|
||||
_.include(staff, parseInt(user_id))
|
||||
|
||||
@addDiscussion: (id, discussion) -> window.$$discussions[id] = discussion
|
||||
|
||||
@getDiscussion: (id) -> window.$$discussions[id]
|
||||
|
||||
@bulkUpdateContentInfo: (infos) ->
|
||||
for id, info of infos
|
||||
@getContent(id).updateInfo(info)
|
||||
Content.getContent(id).updateInfo(info)
|
||||
|
||||
@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"
|
||||
@@ -64,26 +65,32 @@ class @DiscussionUtil
|
||||
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}"
|
||||
user_profile : "/courses/#{$$course_id}/discussion/forum/users/#{param}"
|
||||
threads : "/courses/#{$$course_id}/discussion/forum"
|
||||
}[name]
|
||||
|
||||
@safeAjax: (params) ->
|
||||
$elem = params.$elem
|
||||
if $elem.attr("disabled")
|
||||
if $elem and $elem.attr("disabled")
|
||||
return
|
||||
params["url"] = URI(params["url"]).addSearch ajax: 1
|
||||
params["beforeSend"] = ->
|
||||
$elem.attr("disabled", "disabled")
|
||||
if $elem
|
||||
$elem.attr("disabled", "disabled")
|
||||
if params["$loading"]
|
||||
if params["loadingCallback"]?
|
||||
params["loadingCallback"].apply(params["$loading"])
|
||||
else
|
||||
params["$loading"].loading()
|
||||
$.ajax(params).always ->
|
||||
$elem.removeAttr("disabled")
|
||||
request = $.ajax(params).always ->
|
||||
if $elem
|
||||
$elem.removeAttr("disabled")
|
||||
if params["$loading"]
|
||||
if params["loadedCallback"]?
|
||||
params["loadedCallback"].apply(params["$loading"])
|
||||
else
|
||||
params["$loading"].loaded()
|
||||
return request
|
||||
|
||||
@get: ($elem, url, data, success) ->
|
||||
@safeAjax
|
||||
@@ -108,6 +115,9 @@ class @DiscussionUtil
|
||||
[event, selector] = eventSelector.split(' ')
|
||||
$local(selector).unbind(event)[event] handler
|
||||
|
||||
@processTag: (text) ->
|
||||
text.toLowerCase()
|
||||
|
||||
@tagsInputOptions: ->
|
||||
autocomplete_url: @urlFor('tags_autocomplete')
|
||||
autocomplete:
|
||||
@@ -117,6 +127,7 @@ class @DiscussionUtil
|
||||
width: '100%'
|
||||
defaultText: "Tag your post: press enter after each tag"
|
||||
removeWithBackspace: true
|
||||
preprocessTag: @processTag
|
||||
|
||||
@formErrorHandler: (errorsField) ->
|
||||
(xhr, textStatus, error) ->
|
||||
@@ -124,11 +135,11 @@ class @DiscussionUtil
|
||||
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))
|
||||
errorsField.append($("<li>").addClass("new-post-form-error").html(error)).show()
|
||||
|
||||
@clearFormErrors: (errorsField) ->
|
||||
errorsField.empty()
|
||||
|
||||
|
||||
@postMathJaxProcessor: (text) ->
|
||||
RE_INLINEMATH = /^\$([^\$]*)\$/g
|
||||
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g
|
||||
@@ -144,21 +155,26 @@ class @DiscussionUtil
|
||||
|
||||
@makeWmdEditor: ($content, $local, cls_identifier) ->
|
||||
elem = $local(".#{cls_identifier}")
|
||||
id = $content.attr("_id")
|
||||
placeholder = elem.data('placeholder')
|
||||
id = elem.data("id")
|
||||
appended_id = "-#{cls_identifier}-#{id}"
|
||||
imageUploadUrl = @urlFor('upload')
|
||||
_processor = (_this) ->
|
||||
(text) -> _this.postMathJaxProcessor(text)
|
||||
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, _processor(@)
|
||||
@wmdEditors["#{cls_identifier}-#{id}"] = editor
|
||||
if placeholder?
|
||||
elem.find("#wmd-input#{appended_id}").attr('placeholder', placeholder)
|
||||
editor
|
||||
|
||||
@getWmdEditor: ($content, $local, cls_identifier) ->
|
||||
id = $content.attr("_id")
|
||||
elem = $local(".#{cls_identifier}")
|
||||
id = elem.data("id")
|
||||
@wmdEditors["#{cls_identifier}-#{id}"]
|
||||
|
||||
@getWmdInput: ($content, $local, cls_identifier) ->
|
||||
id = $content.attr("_id")
|
||||
elem = $local(".#{cls_identifier}")
|
||||
id = elem.data("id")
|
||||
$local("#wmd-input-#{cls_identifier}-#{id}")
|
||||
|
||||
@getWmdContent: ($content, $local, cls_identifier) ->
|
||||
@@ -199,9 +215,9 @@ class @DiscussionUtil
|
||||
unfollowLink()
|
||||
else
|
||||
followLink()
|
||||
|
||||
|
||||
@processEachMathAndCode: (text, processor) ->
|
||||
|
||||
|
||||
codeArchive = []
|
||||
|
||||
RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m
|
||||
@@ -264,5 +280,17 @@ class @DiscussionUtil
|
||||
@processEachMathAndCode text, @stripHighlight
|
||||
|
||||
@markdownWithHighlight: (text) ->
|
||||
text = text.replace(/^\>\;/gm, ">")
|
||||
converter = Markdown.getMathCompatibleConverter()
|
||||
@unescapeHighlightTag @stripLatexHighlight converter.makeHtml text
|
||||
text = @unescapeHighlightTag @stripLatexHighlight converter.makeHtml text
|
||||
return text.replace(/^>/gm,">")
|
||||
|
||||
@abbreviateString: (text, minLength) ->
|
||||
# Abbreviates a string to at least minLength characters, stopping at word boundaries
|
||||
if text.length<minLength
|
||||
return text
|
||||
else
|
||||
while minLength < text.length && text[minLength] != ' '
|
||||
minLength++
|
||||
return text.substr(0, minLength) + '...'
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
if Backbone?
|
||||
class @DiscussionContentView extends Backbone.View
|
||||
|
||||
attrRenderer:
|
||||
endorsed: (endorsed) ->
|
||||
if endorsed
|
||||
@$(".action-endorse").show().addClass("is-endorsed")
|
||||
else
|
||||
if @model.get('ability')?.can_endorse
|
||||
@$(".action-endorse").show()
|
||||
else
|
||||
@$(".action-endorse").hide()
|
||||
@$(".action-endorse").removeClass("is-endorsed")
|
||||
|
||||
closed: (closed) ->
|
||||
return if not @$(".action-openclose").length
|
||||
return if not @$(".post-status-closed").length
|
||||
if closed
|
||||
@$(".post-status-closed").show()
|
||||
@$(".action-openclose").html(@$(".action-openclose").html().replace("Close", "Open"))
|
||||
@$(".discussion-reply-new").hide()
|
||||
else
|
||||
@$(".post-status-closed").hide()
|
||||
@$(".action-openclose").html(@$(".action-openclose").html().replace("Open", "Close"))
|
||||
@$(".discussion-reply-new").show()
|
||||
|
||||
voted: (voted) ->
|
||||
|
||||
votes_point: (votes_point) ->
|
||||
|
||||
comments_count: (comments_count) ->
|
||||
|
||||
subscribed: (subscribed) ->
|
||||
if subscribed
|
||||
@$(".dogear").addClass("is-followed")
|
||||
else
|
||||
@$(".dogear").removeClass("is-followed")
|
||||
|
||||
ability: (ability) ->
|
||||
for action, selector of @abilityRenderer
|
||||
if not ability[action]
|
||||
selector.disable.apply(@)
|
||||
else
|
||||
selector.enable.apply(@)
|
||||
|
||||
abilityRenderer:
|
||||
editable:
|
||||
enable: -> @$(".action-edit").closest("li").show()
|
||||
disable: -> @$(".action-edit").closest("li").hide()
|
||||
can_delete:
|
||||
enable: -> @$(".action-delete").closest("li").show()
|
||||
disable: -> @$(".action-delete").closest("li").hide()
|
||||
can_endorse:
|
||||
enable: ->
|
||||
@$(".action-endorse").show().css("cursor", "auto")
|
||||
disable: ->
|
||||
@$(".action-endorse").css("cursor", "default")
|
||||
if not @model.get('endorsed')
|
||||
@$(".action-endorse").hide()
|
||||
else
|
||||
@$(".action-endorse").show()
|
||||
can_openclose:
|
||||
enable: -> @$(".action-openclose").closest("li").show()
|
||||
disable: -> @$(".action-openclose").closest("li").hide()
|
||||
|
||||
renderPartialAttrs: ->
|
||||
for attr, value of @model.changedAttributes()
|
||||
if @attrRenderer[attr]
|
||||
@attrRenderer[attr].apply(@, [value])
|
||||
|
||||
renderAttrs: ->
|
||||
for attr, value of @model.attributes
|
||||
if @attrRenderer[attr]
|
||||
@attrRenderer[attr].apply(@, [value])
|
||||
|
||||
$: (selector) ->
|
||||
@$local.find(selector)
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".local")
|
||||
if not @$local.length
|
||||
@$local = @$el
|
||||
@$delegateElement = @$local
|
||||
|
||||
makeWmdEditor: (cls_identifier) =>
|
||||
if not @$el.find(".wmd-panel").length
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), cls_identifier
|
||||
|
||||
getWmdEditor: (cls_identifier) =>
|
||||
DiscussionUtil.getWmdEditor @$el, $.proxy(@$, @), cls_identifier
|
||||
|
||||
getWmdContent: (cls_identifier) =>
|
||||
DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), cls_identifier
|
||||
|
||||
setWmdContent: (cls_identifier, text) =>
|
||||
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), cls_identifier, text
|
||||
|
||||
initialize: ->
|
||||
@initLocal()
|
||||
@model.bind('change', @renderPartialAttrs, @)
|
||||
@@ -0,0 +1,26 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadEditView extends Backbone.View
|
||||
|
||||
events:
|
||||
"click .post-update": "update"
|
||||
"click .post-cancel": "cancel_edit"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
|
||||
render: ->
|
||||
@template = _.template($("#thread-edit-template").html())
|
||||
@$el.html(@template(@model.toJSON()))
|
||||
@delegateEvents()
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-post-body"
|
||||
@$(".edit-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
@
|
||||
|
||||
update: (event) ->
|
||||
@trigger "thread:update", event
|
||||
|
||||
cancel_edit: (event) ->
|
||||
@trigger "thread:cancel_edit", event
|
||||
@@ -0,0 +1,358 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadListView extends Backbone.View
|
||||
events:
|
||||
"click .search": "showSearch"
|
||||
"click .browse": "toggleTopicDrop"
|
||||
"keydown .post-search-field": "performSearch"
|
||||
"click .sort-bar a": "sortThreads"
|
||||
"click .browse-topic-drop-menu": "filterTopic"
|
||||
"click .browse-topic-drop-search-input": "ignoreClick"
|
||||
"click .post-list .list-item a": "threadSelected"
|
||||
"click .post-list .more-pages a": "loadMorePages"
|
||||
|
||||
initialize: ->
|
||||
@displayedCollection = new Discussion(@collection.models, pages: @collection.pages)
|
||||
@collection.on "change", @reloadDisplayedCollection
|
||||
@sortBy = "date"
|
||||
@discussionIds=""
|
||||
@collection.on "reset", (discussion) =>
|
||||
board = $(".current-board").html()
|
||||
@displayedCollection.current_page = discussion.current_page
|
||||
@displayedCollection.pages = discussion.pages
|
||||
@displayedCollection.reset discussion.models
|
||||
# TODO: filter correctly
|
||||
# target = _.filter($("a.topic:contains('#{board}')"), (el) -> el.innerText == "General" || el.innerHTML == "General")
|
||||
# if target.length > 0
|
||||
# @filterTopic($.Event("filter", {'target': target[0]}))
|
||||
@collection.on "add", @addAndSelectThread
|
||||
@sidebar_padding = 10
|
||||
@sidebar_header_height = 87
|
||||
@boardName
|
||||
@template = _.template($("#thread-list-template").html())
|
||||
@current_search = ""
|
||||
|
||||
reloadDisplayedCollection: (thread) =>
|
||||
thread_id = thread.get('id')
|
||||
content = @renderThread(thread)
|
||||
current_el = @$("a[data-id=#{thread_id}]")
|
||||
active = current_el.hasClass("active")
|
||||
current_el.replaceWith(content)
|
||||
if active
|
||||
@setActiveThread(thread_id)
|
||||
|
||||
#TODO fix this entire chain of events
|
||||
addAndSelectThread: (thread) =>
|
||||
commentable_id = thread.get("commentable_id")
|
||||
commentable = @$(".board-name[data-discussion_id]").filter(-> $(this).data("discussion_id").id == commentable_id)
|
||||
@setTopicHack(commentable)
|
||||
@retrieveDiscussion commentable_id, =>
|
||||
@trigger "thread:created", thread.get('id')
|
||||
|
||||
updateSidebar: =>
|
||||
|
||||
scrollTop = $(window).scrollTop();
|
||||
windowHeight = $(window).height();
|
||||
|
||||
discussionBody = $(".discussion-article")
|
||||
discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top
|
||||
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight()
|
||||
|
||||
sidebar = $(".sidebar")
|
||||
if scrollTop > discussionsBodyTop - @sidebar_padding
|
||||
sidebar.addClass('fixed');
|
||||
sidebar.css('top', @sidebar_padding);
|
||||
else
|
||||
sidebar.removeClass('fixed');
|
||||
sidebar.css('top', '0');
|
||||
|
||||
sidebarWidth = .31 * $(".discussion-body").width();
|
||||
sidebar.css('width', sidebarWidth + 'px');
|
||||
|
||||
sidebarHeight = windowHeight - Math.max(discussionsBodyTop - scrollTop, @sidebar_padding)
|
||||
|
||||
topOffset = scrollTop + windowHeight
|
||||
discussionBottomOffset = discussionsBodyBottom + @sidebar_padding
|
||||
amount = Math.max(topOffset - discussionBottomOffset, 0)
|
||||
|
||||
sidebarHeight = sidebarHeight - @sidebar_padding - amount
|
||||
sidebarHeight = Math.min(sidebarHeight + 1, discussionBody.outerHeight())
|
||||
sidebar.css 'height', sidebarHeight
|
||||
|
||||
postListWrapper = @$('.post-list-wrapper')
|
||||
postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px')
|
||||
|
||||
|
||||
# Because we want the behavior that when the body is clicked the menu is
|
||||
# closed, we need to ignore clicks in the search field and stop propagation.
|
||||
# Without this, clicking the search field would also close the menu.
|
||||
ignoreClick: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
render: ->
|
||||
@timer = 0
|
||||
@$el.html(@template())
|
||||
|
||||
$(window).bind "scroll", @updateSidebar
|
||||
$(window).bind "resize", @updateSidebar
|
||||
|
||||
@displayedCollection.on "reset", @renderThreads
|
||||
@displayedCollection.on "thread:remove", @renderThreads
|
||||
@renderThreads()
|
||||
@
|
||||
|
||||
renderThreads: =>
|
||||
@$(".post-list").html("")
|
||||
rendered = $("<div></div>")
|
||||
for thread in @displayedCollection.models
|
||||
content = @renderThread(thread)
|
||||
rendered.append content
|
||||
content.wrap("<li class='list-item' data-id='\"#{thread.get('id')}\"' />")
|
||||
|
||||
@$(".post-list").html(rendered.html())
|
||||
@renderMorePages()
|
||||
@updateSidebar()
|
||||
@trigger "threads:rendered"
|
||||
|
||||
renderMorePages: ->
|
||||
if @displayedCollection.hasMorePages()
|
||||
@$(".post-list").append("<li class='more-pages'><a href='#'>Load more</a></li>")
|
||||
|
||||
loadMorePages: (event) ->
|
||||
event.preventDefault()
|
||||
@$(".more-pages").html('<div class="loading-animation"></div>')
|
||||
@$(".more-pages").addClass("loading")
|
||||
@collection.retrieveAnotherPage(@current_search, @discussionIds, @sortBy)
|
||||
|
||||
renderThread: (thread) =>
|
||||
content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
|
||||
if thread.get('subscribed')
|
||||
content.addClass("followed")
|
||||
if thread.get('endorsed')
|
||||
content.addClass("resolved")
|
||||
@highlight(content)
|
||||
|
||||
|
||||
highlight: (el) ->
|
||||
el.html(el.html().replace(/<mark>/g, "<mark>").replace(/<\/mark>/g, "</mark>"))
|
||||
|
||||
renderThreadListItem: (thread) =>
|
||||
view = new ThreadListItemView(model: thread)
|
||||
view.on "thread:selected", @threadSelected
|
||||
view.on "thread:removed", @threadRemoved
|
||||
view.render()
|
||||
@$(".post-list").append(view.el)
|
||||
|
||||
threadSelected: (e) =>
|
||||
thread_id = $(e.target).closest("a").data("id")
|
||||
@setActiveThread(thread_id)
|
||||
@trigger("thread:selected", thread_id)
|
||||
false
|
||||
|
||||
threadRemoved: (thread_id) =>
|
||||
@trigger("thread:removed", thread_id)
|
||||
|
||||
setActiveThread: (thread_id) ->
|
||||
@$(".post-list a[data-id!='#{thread_id}']").removeClass("active")
|
||||
@$(".post-list a[data-id='#{thread_id}']").addClass("active")
|
||||
|
||||
showSearch: ->
|
||||
@$(".browse").removeClass('is-dropped')
|
||||
@hideTopicDrop()
|
||||
|
||||
@$(".search").addClass('is-open')
|
||||
@$(".browse").removeClass('is-open')
|
||||
setTimeout (-> @$(".post-search-field").focus()), 200
|
||||
|
||||
toggleTopicDrop: (event) =>
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if @current_search != ""
|
||||
@clearSearch()
|
||||
@$(".search").removeClass('is-open')
|
||||
@$(".browse").addClass('is-open')
|
||||
@$(".browse").toggleClass('is-dropped')
|
||||
|
||||
if @$(".browse").hasClass('is-dropped')
|
||||
@$(".browse-topic-drop-menu-wrapper").show()
|
||||
$(".browse-topic-drop-search-input").focus()
|
||||
$("body").bind "click", @toggleTopicDrop
|
||||
$("body").bind "keydown", @setActiveItem
|
||||
else
|
||||
@hideTopicDrop()
|
||||
|
||||
hideTopicDrop: ->
|
||||
@$(".browse-topic-drop-menu-wrapper").hide()
|
||||
$("body").unbind "click", @toggleTopicDrop
|
||||
$("body").unbind "keydown", @setActiveItem
|
||||
|
||||
# TODO get rid of this asap
|
||||
setTopicHack: (boardNameContainer) ->
|
||||
item = $(boardNameContainer).closest('a')
|
||||
boardName = item.find(".board-name").html()
|
||||
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
|
||||
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
|
||||
@$(".current-board").html(@fitName(boardName))
|
||||
|
||||
setTopic: (event) ->
|
||||
item = $(event.target).closest('a')
|
||||
boardName = item.find(".board-name").html()
|
||||
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
|
||||
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
|
||||
@$(".current-board").html(@fitName(boardName))
|
||||
|
||||
setSelectedTopic: (name) ->
|
||||
@$(".current-board").html(@fitName(name))
|
||||
|
||||
getNameWidth: (name) ->
|
||||
test = $("<div>")
|
||||
test.css
|
||||
"font-size": @$(".current-board").css('font-size')
|
||||
opacity: 0
|
||||
position: 'absolute'
|
||||
left: -1000
|
||||
top: -1000
|
||||
$("body").append(test)
|
||||
test.html(name)
|
||||
width = test.width()
|
||||
test.remove()
|
||||
return width
|
||||
|
||||
fitName: (name) ->
|
||||
@maxNameWidth = (@$el.width() * .8) - 50
|
||||
width = @getNameWidth(name)
|
||||
if width < @maxNameWidth
|
||||
return name
|
||||
path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
|
||||
while path.length > 1
|
||||
path.shift()
|
||||
partialName = "…/" + path.join("/")
|
||||
if @getNameWidth(partialName) < @maxNameWidth
|
||||
return partialName
|
||||
rawName = path[0]
|
||||
name = "…/" + rawName
|
||||
while @getNameWidth(name) > @maxNameWidth
|
||||
rawName = rawName[0...rawName.length-1]
|
||||
name = "…/" + rawName + "…"
|
||||
return name
|
||||
|
||||
filterTopic: (event) ->
|
||||
if @current_search != ""
|
||||
@setTopic(event)
|
||||
@clearSearch @filterTopic, event
|
||||
else
|
||||
@setTopic(event) # just sets the title for the dropdown
|
||||
item = $(event.target).closest('li')
|
||||
if item.find("span.board-name").data("discussion_id") == "#all"
|
||||
@discussionIds = ""
|
||||
@clearSearch()
|
||||
else
|
||||
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
|
||||
@retrieveDiscussions(discussionIds)
|
||||
|
||||
retrieveDiscussion: (discussion_id, callback=null) ->
|
||||
url = DiscussionUtil.urlFor("retrieve_discussion", discussion_id)
|
||||
DiscussionUtil.safeAjax
|
||||
url: url
|
||||
type: "GET"
|
||||
success: (response, textStatus) =>
|
||||
@collection.current_page = response.page
|
||||
@collection.pages = response.num_pages
|
||||
@collection.reset(response.discussion_data)
|
||||
Content.loadContentInfos(response.content_info)
|
||||
@displayedCollection.reset(@collection.models)
|
||||
if callback?
|
||||
callback()
|
||||
|
||||
retrieveDiscussions: (discussion_ids) ->
|
||||
@discussionIds = discussion_ids.join(',')
|
||||
url = DiscussionUtil.urlFor("search")
|
||||
DiscussionUtil.safeAjax
|
||||
data: { 'commentable_ids': @discussionIds }
|
||||
url: url
|
||||
type: "GET"
|
||||
success: (response, textStatus) =>
|
||||
@collection.current_page = response.page
|
||||
@collection.pages = response.num_pages
|
||||
@collection.reset(response.discussion_data)
|
||||
Content.loadContentInfos(response.content_info)
|
||||
@displayedCollection.reset(@collection.models)
|
||||
|
||||
sortThreads: (event) ->
|
||||
@$(".sort-bar a").removeClass("active")
|
||||
$(event.target).addClass("active")
|
||||
@sortBy = $(event.target).data("sort")
|
||||
if @sortBy == "date"
|
||||
@displayedCollection.comparator = @displayedCollection.sortByDateRecentFirst
|
||||
else if @sortBy == "votes"
|
||||
@displayedCollection.comparator = @displayedCollection.sortByVotes
|
||||
else if @sortBy == "comments"
|
||||
@displayedCollection.comparator = @displayedCollection.sortByComments
|
||||
@displayedCollection.sort()
|
||||
|
||||
performSearch: (event) ->
|
||||
if event.which == 13
|
||||
event.preventDefault()
|
||||
text = @$(".post-search-field").val()
|
||||
@searchFor(text)
|
||||
|
||||
setAndSearchFor: (text) ->
|
||||
@showSearch()
|
||||
@$(".post-search-field").val(text)
|
||||
@searchFor(text)
|
||||
|
||||
searchFor: (text, callback, value) ->
|
||||
@current_search = text
|
||||
url = DiscussionUtil.urlFor("search")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".post-search-field")
|
||||
data: { text: text }
|
||||
url: url
|
||||
type: "GET"
|
||||
$loading: $
|
||||
loadingCallback: =>
|
||||
@$(".post-list").html('<li class="loading"><div class="loading-animation"></div></li>')
|
||||
loadedCallback: =>
|
||||
if callback
|
||||
callback.apply @, [value]
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
# TODO: Augment existing collection?
|
||||
@collection.reset(response.discussion_data)
|
||||
Content.loadContentInfos(response.content_info)
|
||||
@collection.current_page = response.page
|
||||
@collection.pages = response.num_pages
|
||||
# TODO: Perhaps reload user info so that votes can be updated.
|
||||
# In the future we might not load all of a user's votes at once
|
||||
# so this would probably be necessary anyway
|
||||
@displayedCollection.reset(@collection.models)
|
||||
|
||||
clearSearch: (callback, value) ->
|
||||
@$(".post-search-field").val("")
|
||||
@searchFor("", callback, value)
|
||||
|
||||
setActiveItem: (event) ->
|
||||
if event.which == 13
|
||||
$(".browse-topic-drop-menu-wrapper .focused").click()
|
||||
return
|
||||
if event.which != 40 && event.which != 38
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
items = $.makeArray($(".browse-topic-drop-menu-wrapper a").not(".hidden"))
|
||||
index = items.indexOf($('.browse-topic-drop-menu-wrapper .focused')[0])
|
||||
|
||||
if event.which == 40
|
||||
index = Math.min(index + 1, items.length - 1)
|
||||
if event.which == 38
|
||||
index = Math.max(index - 1, 0)
|
||||
|
||||
$(".browse-topic-drop-menu-wrapper .focused").removeClass("focused")
|
||||
$(items[index]).addClass("focused")
|
||||
|
||||
itemTop = $(items[index]).parent().offset().top
|
||||
scrollTop = $(".browse-topic-drop-menu").scrollTop()
|
||||
itemFromTop = $(".browse-topic-drop-menu").offset().top - itemTop
|
||||
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
|
||||
scrollTarget = Math.max(scrollTop - itemFromTop - $(".browse-topic-drop-menu").height() + $(items[index]).height(), scrollTarget)
|
||||
$(".browse-topic-drop-menu").scrollTop(scrollTarget)
|
||||
@@ -0,0 +1,145 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadProfileView extends DiscussionContentView
|
||||
expanded = false
|
||||
events:
|
||||
"click .discussion-vote": "toggleVote"
|
||||
"click .action-follow": "toggleFollowing"
|
||||
"click .expand-post": "expandPost"
|
||||
"click .collapse-post": "collapsePost"
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".discussion-article").children(".local")
|
||||
@$delegateElement = @$local
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
render: ->
|
||||
@template = DiscussionUtil.getTemplate("_profile_thread")
|
||||
if not @model.has('abbreviatedBody')
|
||||
@abbreviateBody()
|
||||
params = $.extend(@model.toJSON(),{expanded: @expanded, permalink: @model.urlFor('retrieve')})
|
||||
if not @model.get('anonymous')
|
||||
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url})
|
||||
@$el.html(Mustache.render(@template, params))
|
||||
@initLocal()
|
||||
@delegateEvents()
|
||||
@renderDogear()
|
||||
@renderVoted()
|
||||
@renderAttrs()
|
||||
@$("span.timeago").timeago()
|
||||
@convertMath()
|
||||
if @expanded
|
||||
@renderResponses()
|
||||
@
|
||||
|
||||
renderDogear: ->
|
||||
if window.user.following(@model)
|
||||
@$(".dogear").addClass("is-followed")
|
||||
|
||||
renderVoted: =>
|
||||
if window.user.voted(@model)
|
||||
@$("[data-role=discussion-vote]").addClass("is-cast")
|
||||
else
|
||||
@$("[data-role=discussion-vote]").removeClass("is-cast")
|
||||
|
||||
updateModelDetails: =>
|
||||
@renderVoted()
|
||||
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".post-body")
|
||||
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
|
||||
|
||||
renderResponses: ->
|
||||
DiscussionUtil.safeAjax
|
||||
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
|
||||
$loading: @$el
|
||||
success: (data, textStatus, xhr) =>
|
||||
@$el.find(".loading").remove()
|
||||
Content.loadContentInfos(data['annotated_content_info'])
|
||||
comments = new Comments(data['content']['children'])
|
||||
comments.each @renderResponse
|
||||
@trigger "thread:responses:rendered"
|
||||
|
||||
renderResponse: (response) =>
|
||||
response.set('thread', @model)
|
||||
view = new ThreadResponseView(model: response)
|
||||
view.on "comment:add", @addComment
|
||||
view.render()
|
||||
@$el.find(".responses").append(view.el)
|
||||
|
||||
addComment: =>
|
||||
@model.comment()
|
||||
|
||||
toggleVote: (event) ->
|
||||
event.preventDefault()
|
||||
if window.user.voted(@model)
|
||||
@unvote()
|
||||
else
|
||||
@vote()
|
||||
|
||||
toggleFollowing: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = null
|
||||
console.log "follow"
|
||||
if not @model.get('subscribed')
|
||||
@model.follow()
|
||||
url = @model.urlFor("follow")
|
||||
else
|
||||
@model.unfollow()
|
||||
url = @model.urlFor("unfollow")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
|
||||
vote: ->
|
||||
window.user.vote(@model)
|
||||
url = @model.urlFor("upvote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response)
|
||||
|
||||
unvote: ->
|
||||
window.user.unvote(@model)
|
||||
url = @model.urlFor("unvote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response)
|
||||
|
||||
edit: ->
|
||||
|
||||
abbreviateBody: ->
|
||||
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
|
||||
@model.set('abbreviatedBody', abbreviated)
|
||||
|
||||
expandPost: (event) ->
|
||||
@expanded = true
|
||||
@$el.addClass('expanded')
|
||||
@$el.find('.post-body').html(@model.get('body'))
|
||||
@convertMath()
|
||||
@$el.find('.expand-post').css('display', 'none')
|
||||
@$el.find('.collapse-post').css('display', 'block')
|
||||
@$el.find('.post-extended-content').show()
|
||||
if @$el.find('.loading').length
|
||||
@renderResponses()
|
||||
|
||||
collapsePost: (event) ->
|
||||
@expanded = false
|
||||
@$el.removeClass('expanded')
|
||||
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
|
||||
@convertMath()
|
||||
@$el.find('.collapse-post').css('display', 'none')
|
||||
@$el.find('.post-extended-content').hide()
|
||||
@$el.find('.expand-post').css('display', 'block')
|
||||
@@ -0,0 +1,139 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadShowView extends DiscussionContentView
|
||||
|
||||
events:
|
||||
"click .discussion-vote": "toggleVote"
|
||||
"click .action-follow": "toggleFollowing"
|
||||
"click .action-edit": "edit"
|
||||
"click .action-delete": "delete"
|
||||
"click .action-openclose": "toggleClosed"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
renderTemplate: ->
|
||||
@template = _.template($("#thread-show-template").html())
|
||||
@template(@model.toJSON())
|
||||
|
||||
render: ->
|
||||
@$el.html(@renderTemplate())
|
||||
@delegateEvents()
|
||||
@renderDogear()
|
||||
@renderVoted()
|
||||
@renderAttrs()
|
||||
@$("span.timeago").timeago()
|
||||
@convertMath()
|
||||
@highlight @$(".post-body")
|
||||
@highlight @$("h1,h3")
|
||||
@
|
||||
|
||||
renderDogear: ->
|
||||
if window.user.following(@model)
|
||||
@$(".dogear").addClass("is-followed")
|
||||
|
||||
renderVoted: =>
|
||||
if window.user.voted(@model)
|
||||
@$("[data-role=discussion-vote]").addClass("is-cast")
|
||||
else
|
||||
@$("[data-role=discussion-vote]").removeClass("is-cast")
|
||||
|
||||
updateModelDetails: =>
|
||||
@renderVoted()
|
||||
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".post-body")
|
||||
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
|
||||
|
||||
toggleVote: (event) ->
|
||||
event.preventDefault()
|
||||
if window.user.voted(@model)
|
||||
@unvote()
|
||||
else
|
||||
@vote()
|
||||
|
||||
toggleFollowing: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = null
|
||||
if not @model.get('subscribed')
|
||||
@model.follow()
|
||||
url = @model.urlFor("follow")
|
||||
else
|
||||
@model.unfollow()
|
||||
url = @model.urlFor("unfollow")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
|
||||
vote: ->
|
||||
window.user.vote(@model)
|
||||
url = @model.urlFor("upvote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response, {silent: true})
|
||||
|
||||
unvote: ->
|
||||
window.user.unvote(@model)
|
||||
url = @model.urlFor("unvote")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response, {silent: true})
|
||||
|
||||
edit: (event) ->
|
||||
@trigger "thread:edit", event
|
||||
|
||||
delete: (event) ->
|
||||
@trigger "thread:delete", event
|
||||
|
||||
toggleClosed: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('close')
|
||||
closed = @model.get('closed')
|
||||
data = { closed: not closed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('closed', not closed)
|
||||
@model.set('ability', response.ability)
|
||||
|
||||
toggleEndorse: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('endorse')
|
||||
endorsed = @model.get('endorsed')
|
||||
data = { endorsed: not endorsed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('endorsed', not endorsed)
|
||||
|
||||
highlight: (el) ->
|
||||
if el.html()
|
||||
el.html(el.html().replace(/<mark>/g, "<mark>").replace(/<\/mark>/g, "</mark>"))
|
||||
|
||||
class @DiscussionThreadInlineShowView extends DiscussionThreadShowView
|
||||
renderTemplate: ->
|
||||
@template = DiscussionUtil.getTemplate('_inline_thread_show')
|
||||
params = @model.toJSON()
|
||||
if @model.get('username')?
|
||||
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url})
|
||||
Mustache.render(@template, params)
|
||||
@@ -0,0 +1,215 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadView extends DiscussionContentView
|
||||
|
||||
events:
|
||||
"click .discussion-submit-post": "submitComment"
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#"click .thread-tag": "tagSelected"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@createShowView()
|
||||
|
||||
renderTemplate: ->
|
||||
@template = _.template($("#thread-template").html())
|
||||
@template(@model.toJSON())
|
||||
|
||||
render: ->
|
||||
@$el.html(@renderTemplate())
|
||||
@$el.find(".loading").hide()
|
||||
@delegateEvents()
|
||||
|
||||
@renderShowView()
|
||||
@renderAttrs()
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#@renderTags()
|
||||
|
||||
@$("span.timeago").timeago()
|
||||
@makeWmdEditor "reply-body"
|
||||
@renderResponses()
|
||||
@
|
||||
|
||||
cleanup: ->
|
||||
if @responsesRequest?
|
||||
@responsesRequest.abort()
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#renderTags: ->
|
||||
# # tags
|
||||
# for tag in @model.get("tags")
|
||||
# if !tags
|
||||
# tags = $('<div class="thread-tags">')
|
||||
# tags.append("<a href='#' class='thread-tag'>#{tag}</a>")
|
||||
# @$(".post-body").after(tags)
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w tags, removing them.
|
||||
#tagSelected: (e) ->
|
||||
# @trigger "tag:selected", $(e.target).html()
|
||||
|
||||
renderResponses: ->
|
||||
setTimeout(=>
|
||||
@$el.find(".loading").show()
|
||||
, 200)
|
||||
@responsesRequest = DiscussionUtil.safeAjax
|
||||
url: DiscussionUtil.urlFor('retrieve_single_thread', @model.get('commentable_id'), @model.id)
|
||||
success: (data, textStatus, xhr) =>
|
||||
@responsesRequest = null
|
||||
@$el.find(".loading").remove()
|
||||
Content.loadContentInfos(data['annotated_content_info'])
|
||||
comments = new Comments(data['content']['children'])
|
||||
comments.each @renderResponse
|
||||
@trigger "thread:responses:rendered"
|
||||
|
||||
renderResponse: (response) =>
|
||||
response.set('thread', @model)
|
||||
view = new ThreadResponseView(model: response)
|
||||
view.on "comment:add", @addComment
|
||||
view.on "comment:endorse", @endorseThread
|
||||
view.render()
|
||||
@$el.find(".responses").append(view.el)
|
||||
view.afterInsert()
|
||||
|
||||
addComment: =>
|
||||
@model.comment()
|
||||
|
||||
endorseThread: (endorsed) =>
|
||||
is_endorsed = @$el.find(".is-endorsed").length
|
||||
@model.set 'endorsed', is_endorsed
|
||||
|
||||
submitComment: (event) ->
|
||||
event.preventDefault()
|
||||
url = @model.urlFor('reply')
|
||||
body = @getWmdContent("reply-body")
|
||||
return if not body.trim().length
|
||||
@setWmdContent("reply-body", "")
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), votes: { up_count: 0 }, endorsed: false, user_id: window.user.get("id"))
|
||||
comment.set('thread', @model.get('thread'))
|
||||
@renderResponse(comment)
|
||||
@model.addComment()
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
body: body
|
||||
success: (data, textStatus) =>
|
||||
comment.updateInfo(data.annotated_content_info)
|
||||
comment.set(data.content)
|
||||
|
||||
edit: (event) =>
|
||||
@createEditView()
|
||||
@renderEditView()
|
||||
|
||||
update: (event) =>
|
||||
|
||||
newTitle = @editView.$(".edit-post-title").val()
|
||||
newBody = @editView.$(".edit-post-body textarea").val()
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#newTags = @editView.$(".edit-post-tags").val()
|
||||
|
||||
url = DiscussionUtil.urlFor('update_thread', @model.id)
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
async: false # TODO when the rest of the stuff below is made to work properly..
|
||||
data:
|
||||
title: newTitle
|
||||
body: newBody
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#tags: newTags
|
||||
|
||||
error: DiscussionUtil.formErrorHandler(@$(".edit-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
@editView.$(".edit-post-title").val("").attr("prev-text", "")
|
||||
@editView.$(".edit-post-body textarea").val("").attr("prev-text", "")
|
||||
@editView.$(".edit-post-tags").val("")
|
||||
@editView.$(".edit-post-tags").importTags("")
|
||||
@editView.$(".wmd-preview p").html("")
|
||||
|
||||
@model.set
|
||||
title: newTitle
|
||||
body: newBody
|
||||
tags: response.content.tags
|
||||
|
||||
@createShowView()
|
||||
@renderShowView()
|
||||
|
||||
# TODO tags
|
||||
# Until we decide what to do w/ tags, removing them.
|
||||
#@renderTags()
|
||||
|
||||
createEditView: () ->
|
||||
|
||||
if @showView?
|
||||
@showView.undelegateEvents()
|
||||
@showView.$el.empty()
|
||||
@showView = null
|
||||
|
||||
@editView = new DiscussionThreadEditView(model: @model)
|
||||
@editView.bind "thread:update", @update
|
||||
@editView.bind "thread:cancel_edit", @cancelEdit
|
||||
|
||||
renderSubView: (view) ->
|
||||
view.setElement(@$('.thread-content-wrapper'))
|
||||
view.render()
|
||||
view.delegateEvents()
|
||||
|
||||
renderEditView: () ->
|
||||
@renderSubView(@editView)
|
||||
|
||||
createShowView: () ->
|
||||
|
||||
if @editView?
|
||||
@editView.undelegateEvents()
|
||||
@editView.$el.empty()
|
||||
@editView = null
|
||||
|
||||
@showView = new DiscussionThreadShowView(model: @model)
|
||||
@showView.bind "thread:delete", @delete
|
||||
@showView.bind "thread:edit", @edit
|
||||
|
||||
renderShowView: () ->
|
||||
@renderSubView(@showView)
|
||||
|
||||
cancelEdit: (event) =>
|
||||
event.preventDefault()
|
||||
@createShowView()
|
||||
@renderShowView()
|
||||
|
||||
|
||||
delete: (event) =>
|
||||
url = @model.urlFor('delete')
|
||||
if not @model.can('can_delete')
|
||||
return
|
||||
if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
|
||||
return
|
||||
@model.remove()
|
||||
@showView.undelegateEvents()
|
||||
@undelegateEvents()
|
||||
@$el.empty()
|
||||
$elem = $(event.target)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@@ -0,0 +1,124 @@
|
||||
if Backbone?
|
||||
class @DiscussionThreadInlineView extends DiscussionThreadView
|
||||
expanded = false
|
||||
events:
|
||||
"click .discussion-submit-post": "submitComment"
|
||||
"click .expand-post": "expandPost"
|
||||
"click .collapse-post": "collapsePost"
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".discussion-article").children(".local")
|
||||
if not @$local.length
|
||||
@$local = @$el
|
||||
@$delegateElement = @$local
|
||||
|
||||
render: ->
|
||||
@template = DiscussionUtil.getTemplate("_inline_thread")
|
||||
|
||||
if not @model.has('abbreviatedBody')
|
||||
@abbreviateBody()
|
||||
params = @model.toJSON()
|
||||
@$el.html(Mustache.render(@template, params))
|
||||
#@createShowView()
|
||||
|
||||
@initLocal()
|
||||
@delegateEvents()
|
||||
@renderShowView()
|
||||
@renderAttrs()
|
||||
|
||||
# TODO tags commenting out til we decide what to do with tags
|
||||
#@renderTags()
|
||||
|
||||
@$("span.timeago").timeago()
|
||||
@$el.find('.post-extended-content').hide()
|
||||
if @expanded
|
||||
@makeWmdEditor "reply-body"
|
||||
@renderResponses()
|
||||
@
|
||||
createShowView: () ->
|
||||
|
||||
if @editView?
|
||||
@editView.undelegateEvents()
|
||||
@editView.$el.empty()
|
||||
@editView = null
|
||||
@showView = new DiscussionThreadInlineShowView(model: @model)
|
||||
@showView.bind "thread:delete", @delete
|
||||
@showView.bind "thread:edit", @edit
|
||||
|
||||
renderResponses: ->
|
||||
#TODO: threadview
|
||||
DiscussionUtil.safeAjax
|
||||
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
|
||||
$loading: @$el
|
||||
success: (data, textStatus, xhr) =>
|
||||
# @$el.find(".loading").remove()
|
||||
Content.loadContentInfos(data['annotated_content_info'])
|
||||
comments = new Comments(data['content']['children'])
|
||||
comments.each @renderResponse
|
||||
@trigger "thread:responses:rendered"
|
||||
@$('.loading').remove()
|
||||
|
||||
|
||||
toggleClosed: (event) ->
|
||||
#TODO: showview
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('close')
|
||||
closed = @model.get('closed')
|
||||
data = { closed: not closed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('closed', not closed)
|
||||
@model.set('ability', response.ability)
|
||||
|
||||
toggleEndorse: (event) ->
|
||||
#TODO: showview
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('endorse')
|
||||
endorsed = @model.get('endorsed')
|
||||
data = { endorsed: not endorsed }
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('endorsed', not endorsed)
|
||||
|
||||
abbreviateBody: ->
|
||||
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
|
||||
@model.set('abbreviatedBody', abbreviated)
|
||||
|
||||
expandPost: (event) =>
|
||||
@expanded = true
|
||||
@$el.addClass('expanded')
|
||||
@$el.find('.post-body').html(@model.get('body'))
|
||||
@showView.convertMath()
|
||||
@$el.find('.expand-post').css('display', 'none')
|
||||
@$el.find('.collapse-post').css('display', 'block')
|
||||
@$el.find('.post-extended-content').show()
|
||||
@makeWmdEditor "reply-body"
|
||||
@renderAttrs()
|
||||
if @$el.find('.loading').length
|
||||
@renderResponses()
|
||||
|
||||
collapsePost: (event) ->
|
||||
@expanded = false
|
||||
@$el.removeClass('expanded')
|
||||
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
|
||||
@showView.convertMath()
|
||||
@$el.find('.collapse-post').css('display', 'none')
|
||||
@$el.find('.post-extended-content').hide()
|
||||
@$el.find('.expand-post').css('display', 'block')
|
||||
|
||||
createEditView: () ->
|
||||
super()
|
||||
@editView.bind "thread:update", @expandPost
|
||||
@editView.bind "thread:update", @abbreviateBody
|
||||
@editView.bind "thread:cancel_edit", @expandPost
|
||||
@@ -0,0 +1,26 @@
|
||||
if Backbone?
|
||||
class @DiscussionUserProfileView extends Backbone.View
|
||||
# events:
|
||||
# "":""
|
||||
initialize: (options) ->
|
||||
@renderThreads @$el, @collection
|
||||
renderThreads: ($elem, threads) =>
|
||||
#Content.loadContentInfos(response.annotated_content_info)
|
||||
console.log threads
|
||||
@discussion = new Discussion()
|
||||
@discussion.reset(threads, {silent: false})
|
||||
$discussion = $(Mustache.render $("script#_user_profile").html(), {'threads':threads})
|
||||
console.log $discussion
|
||||
$elem.append($discussion)
|
||||
@threadviews = @discussion.map (thread) ->
|
||||
new DiscussionThreadProfileView el: @$("article#thread_#{thread.id}"), model: thread
|
||||
console.log @threadviews
|
||||
_.each @threadviews, (dtv) -> dtv.render()
|
||||
|
||||
addThread: (thread, collection, options) =>
|
||||
# TODO: When doing pagination, this will need to repaginate. Perhaps just reload page 1?
|
||||
article = $("<article class='discussion-thread' id='thread_#{thread.id}'></article>")
|
||||
@$('section.discussion > .threads').prepend(article)
|
||||
threadView = new DiscussionThreadInlineView el: article, model: thread
|
||||
threadView.render()
|
||||
@threadviews.unshift threadView
|
||||
@@ -0,0 +1,68 @@
|
||||
if Backbone?
|
||||
class @NewPostInlineView extends Backbone.View
|
||||
|
||||
initialize: () ->
|
||||
|
||||
@topicId = @$(".topic").first().data("discussion-id")
|
||||
|
||||
@maxNameWidth = 100
|
||||
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||
|
||||
# TODO tags: commenting out til we know what to do with them
|
||||
#@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
|
||||
events:
|
||||
"submit .new-post-form": "createPost"
|
||||
|
||||
# Because we want the behavior that when the body is clicked the menu is
|
||||
# closed, we need to ignore clicks in the search field and stop propagation.
|
||||
# Without this, clicking the search field would also close the menu.
|
||||
ignoreClick: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
createPost: (event) ->
|
||||
event.preventDefault()
|
||||
title = @$(".new-post-title").val()
|
||||
body = @$(".new-post-body").find(".wmd-input").val()
|
||||
|
||||
# TODO tags: commenting out til we know what to do with them
|
||||
#tags = @$(".new-post-tags").val()
|
||||
|
||||
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
||||
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
|
||||
follow = false || @$("input.discussion-follow").is(":checked")
|
||||
|
||||
url = DiscussionUtil.urlFor('create_thread', @topicId)
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
async: false # TODO when the rest of the stuff below is made to work properly..
|
||||
data:
|
||||
title: title
|
||||
body: body
|
||||
|
||||
# TODO tags: commenting out til we know what to do with them
|
||||
#tags: tags
|
||||
|
||||
anonymous: anonymous
|
||||
anonymous_to_peers: anonymous_to_peers
|
||||
auto_subscribe: follow
|
||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
thread = new Thread response['content']
|
||||
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
|
||||
@$el.hide()
|
||||
@$(".new-post-title").val("").attr("prev-text", "")
|
||||
@$(".new-post-body textarea").val("").attr("prev-text", "")
|
||||
|
||||
# TODO tags, commenting out til we know what to do with them
|
||||
#@$(".new-post-tags").val("")
|
||||
#@$(".new-post-tags").importTags("")
|
||||
|
||||
@collection.add thread
|
||||
179
lms/static/coffee/src/discussion/views/new_post_view.coffee
Normal file
@@ -0,0 +1,179 @@
|
||||
if Backbone?
|
||||
class @NewPostView extends Backbone.View
|
||||
|
||||
initialize: () ->
|
||||
@dropdownButton = @$(".topic_dropdown_button")
|
||||
@topicMenu = @$(".topic_menu_wrapper")
|
||||
|
||||
@menuOpen = @dropdownButton.hasClass('dropped')
|
||||
|
||||
@topicId = @$(".topic").first().data("discussion_id")
|
||||
@topicText = @getFullTopicName(@$(".topic").first())
|
||||
|
||||
@maxNameWidth = 100
|
||||
@setSelectedTopic()
|
||||
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
|
||||
events:
|
||||
"submit .new-post-form": "createPost"
|
||||
"click .topic_dropdown_button": "toggleTopicDropdown"
|
||||
"click .topic_menu_wrapper": "setTopic"
|
||||
"click .topic_menu_search": "ignoreClick"
|
||||
|
||||
# Because we want the behavior that when the body is clicked the menu is
|
||||
# closed, we need to ignore clicks in the search field and stop propagation.
|
||||
# Without this, clicking the search field would also close the menu.
|
||||
ignoreClick: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
toggleTopicDropdown: (event) ->
|
||||
event.stopPropagation()
|
||||
if @menuOpen
|
||||
@hideTopicDropdown()
|
||||
else
|
||||
@showTopicDropdown()
|
||||
|
||||
showTopicDropdown: () ->
|
||||
@menuOpen = true
|
||||
@dropdownButton.addClass('dropped')
|
||||
@topicMenu.show()
|
||||
$(".form-topic-drop-search-input").focus()
|
||||
|
||||
$("body").bind "keydown", @setActiveItem
|
||||
$("body").bind "click", @hideTopicDropdown
|
||||
|
||||
# Set here because 1) the window might get resized and things could
|
||||
# change and 2) can't set in initialize because the button is hidden
|
||||
@maxNameWidth = @dropdownButton.width() * 0.9
|
||||
|
||||
# Need a fat arrow because hideTopicDropdown is passed as a callback to bind
|
||||
hideTopicDropdown: () =>
|
||||
@menuOpen = false
|
||||
@dropdownButton.removeClass('dropped')
|
||||
@topicMenu.hide()
|
||||
|
||||
$("body").unbind "keydown", @setActiveItem
|
||||
$("body").unbind "click", @hideTopicDropdown
|
||||
|
||||
setTopic: (event) ->
|
||||
$target = $(event.target)
|
||||
if $target.data('discussion_id')
|
||||
@topicText = $target.html()
|
||||
@topicText = @getFullTopicName($target)
|
||||
@topicId = $target.data('discussion_id')
|
||||
@setSelectedTopic()
|
||||
|
||||
setSelectedTopic: ->
|
||||
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>')
|
||||
|
||||
getFullTopicName: (topicElement) ->
|
||||
name = topicElement.html()
|
||||
topicElement.parents('ul').not('.topic_menu').each ->
|
||||
name = $(this).siblings('a').html() + ' / ' + name
|
||||
return name
|
||||
|
||||
getNameWidth: (name) ->
|
||||
test = $("<div>")
|
||||
test.css
|
||||
"font-size": @dropdownButton.css('font-size')
|
||||
opacity: 0
|
||||
position: 'absolute'
|
||||
left: -1000
|
||||
top: -1000
|
||||
$("body").append(test)
|
||||
test.html(name)
|
||||
width = test.width()
|
||||
test.remove()
|
||||
return width
|
||||
|
||||
fitName: (name) ->
|
||||
width = @getNameWidth(name)
|
||||
if width < @maxNameWidth
|
||||
return name
|
||||
path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
|
||||
while path.length > 1
|
||||
path.shift()
|
||||
partialName = "... / " + path.join(" / ")
|
||||
if @getNameWidth(partialName) < @maxNameWidth
|
||||
return partialName
|
||||
|
||||
rawName = path[0]
|
||||
|
||||
name = "... / " + rawName
|
||||
|
||||
while @getNameWidth(name) > @maxNameWidth
|
||||
rawName = rawName[0...rawName.length-1]
|
||||
name = "... / " + rawName + " ..."
|
||||
|
||||
return name
|
||||
|
||||
|
||||
createPost: (event) ->
|
||||
event.preventDefault()
|
||||
title = @$(".new-post-title").val()
|
||||
body = @$(".new-post-body").find(".wmd-input").val()
|
||||
tags = @$(".new-post-tags").val()
|
||||
|
||||
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
||||
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
|
||||
follow = false || @$("input.discussion-follow").is(":checked")
|
||||
|
||||
$formTopicDropBtn.bind('click', showFormTopicDrop)
|
||||
$formTopicDropMenu.bind('click', setFormTopic)
|
||||
|
||||
url = DiscussionUtil.urlFor('create_thread', @topicId)
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
async: false # TODO when the rest of the stuff below is made to work properly..
|
||||
data:
|
||||
title: title
|
||||
body: body
|
||||
tags: tags
|
||||
anonymous: anonymous
|
||||
anonymous_to_peers: anonymous_to_peers
|
||||
auto_subscribe: follow
|
||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
thread = new Thread response['content']
|
||||
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
|
||||
@$el.hide()
|
||||
@$(".new-post-title").val("").attr("prev-text", "")
|
||||
@$(".new-post-body textarea").val("").attr("prev-text", "")
|
||||
@$(".new-post-tags").val("")
|
||||
@$(".new-post-tags").importTags("")
|
||||
@$(".wmd-preview p").html("")
|
||||
@collection.add thread
|
||||
|
||||
setActiveItem: (event) ->
|
||||
if event.which == 13
|
||||
$(".topic_menu_wrapper .focused").click()
|
||||
return
|
||||
if event.which != 40 && event.which != 38
|
||||
return
|
||||
event.preventDefault()
|
||||
|
||||
items = $.makeArray($(".topic_menu_wrapper a").not(".hidden"))
|
||||
index = items.indexOf($('.topic_menu_wrapper .focused')[0])
|
||||
|
||||
if event.which == 40
|
||||
index = Math.min(index + 1, items.length - 1)
|
||||
if event.which == 38
|
||||
index = Math.max(index - 1, 0)
|
||||
|
||||
$(".topic_menu_wrapper .focused").removeClass("focused")
|
||||
$(items[index]).addClass("focused")
|
||||
|
||||
itemTop = $(items[index]).parent().offset().top
|
||||
scrollTop = $(".topic_menu").scrollTop()
|
||||
itemFromTop = $(".topic_menu").offset().top - itemTop
|
||||
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
|
||||
scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget)
|
||||
$(".topic_menu").scrollTop(scrollTarget)
|
||||
@@ -0,0 +1,34 @@
|
||||
if Backbone?
|
||||
class @ResponseCommentShowView extends DiscussionContentView
|
||||
|
||||
tagName: "li"
|
||||
|
||||
render: ->
|
||||
@template = _.template($("#response-comment-show-template").html())
|
||||
params = @model.toJSON()
|
||||
|
||||
@$el.html(@template(params))
|
||||
@initLocal()
|
||||
@delegateEvents()
|
||||
@renderAttrs()
|
||||
@markAsStaff()
|
||||
@$el.find(".timeago").timeago()
|
||||
@convertMath()
|
||||
@addReplyLink()
|
||||
@
|
||||
|
||||
addReplyLink: () ->
|
||||
if @model.hasOwnProperty('parent')
|
||||
name = @model.parent.get('username') ? "anonymous"
|
||||
html = "<a href='#comment_#{@model.parent.id}'>@#{name}</a>: "
|
||||
p = @$('.response-body p:first')
|
||||
p.prepend(html)
|
||||
|
||||
convertMath: ->
|
||||
body = @$el.find(".response-body")
|
||||
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
|
||||
|
||||
markAsStaff: ->
|
||||
if DiscussionUtil.isStaff(@model.get("user_id"))
|
||||
@$el.find("a.profile-link").after('<span class="staff-label">staff</span>')
|
||||
@@ -0,0 +1,31 @@
|
||||
if Backbone?
|
||||
class @ResponseCommentView extends DiscussionContentView
|
||||
tagName: "li"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@createShowView()
|
||||
|
||||
render: ->
|
||||
@renderShowView()
|
||||
@
|
||||
|
||||
createShowView: () ->
|
||||
|
||||
if @editView?
|
||||
@editView.undelegateEvents()
|
||||
@editView.$el.empty()
|
||||
@editView = null
|
||||
|
||||
@showView = new ResponseCommentShowView(model: @model)
|
||||
|
||||
renderSubView: (view) ->
|
||||
view.setElement(@$el)
|
||||
view.render()
|
||||
view.delegateEvents()
|
||||
|
||||
renderShowView: () ->
|
||||
@renderSubView(@showView)
|
||||
@@ -0,0 +1,25 @@
|
||||
if Backbone?
|
||||
class @ThreadResponseEditView extends Backbone.View
|
||||
|
||||
events:
|
||||
"click .post-update": "update"
|
||||
"click .post-cancel": "cancel_edit"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
|
||||
render: ->
|
||||
@template = _.template($("#thread-response-edit-template").html())
|
||||
@$el.html(@template(@model.toJSON()))
|
||||
@delegateEvents()
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-post-body"
|
||||
@
|
||||
|
||||
update: (event) ->
|
||||
@trigger "response:update", event
|
||||
|
||||
cancel_edit: (event) ->
|
||||
@trigger "response:cancel_edit", event
|
||||
@@ -0,0 +1,91 @@
|
||||
if Backbone?
|
||||
class @ThreadResponseShowView extends DiscussionContentView
|
||||
events:
|
||||
"click .vote-btn": "toggleVote"
|
||||
"click .action-endorse": "toggleEndorse"
|
||||
"click .action-delete": "delete"
|
||||
"click .action-edit": "edit"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
renderTemplate: ->
|
||||
@template = _.template($("#thread-response-show-template").html())
|
||||
@template(@model.toJSON())
|
||||
|
||||
render: ->
|
||||
@$el.html(@renderTemplate())
|
||||
@delegateEvents()
|
||||
if window.user.voted(@model)
|
||||
@$(".vote-btn").addClass("is-cast")
|
||||
@renderAttrs()
|
||||
@$el.find(".posted-details").timeago()
|
||||
@convertMath()
|
||||
@markAsStaff()
|
||||
@
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".response-body")
|
||||
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
|
||||
|
||||
markAsStaff: ->
|
||||
if DiscussionUtil.isStaff(@model.get("user_id"))
|
||||
@$el.addClass("staff")
|
||||
@$el.prepend('<div class="staff-banner">staff</div>')
|
||||
|
||||
toggleVote: (event) ->
|
||||
event.preventDefault()
|
||||
@$(".vote-btn").toggleClass("is-cast")
|
||||
if @$(".vote-btn").hasClass("is-cast")
|
||||
@vote()
|
||||
else
|
||||
@unvote()
|
||||
|
||||
vote: ->
|
||||
url = @model.urlFor("upvote")
|
||||
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) + 1)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response)
|
||||
|
||||
unvote: ->
|
||||
url = @model.urlFor("unvote")
|
||||
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) - 1)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response)
|
||||
|
||||
edit: (event) ->
|
||||
@trigger "response:edit", event
|
||||
|
||||
delete: (event) ->
|
||||
@trigger "response:delete", event
|
||||
|
||||
toggleEndorse: (event) ->
|
||||
event.preventDefault()
|
||||
if not @model.can('can_endorse')
|
||||
return
|
||||
$elem = $(event.target)
|
||||
url = @model.urlFor('endorse')
|
||||
endorsed = @model.get('endorsed')
|
||||
data = { endorsed: not endorsed }
|
||||
@model.set('endorsed', not endorsed)
|
||||
@trigger "comment:endorse", not endorsed
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
@@ -0,0 +1,188 @@
|
||||
if Backbone?
|
||||
class @ThreadResponseView extends DiscussionContentView
|
||||
tagName: "li"
|
||||
|
||||
events:
|
||||
"click .discussion-submit-comment": "submitComment"
|
||||
"focus .wmd-input": "showEditorChrome"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
|
||||
initialize: ->
|
||||
@createShowView()
|
||||
|
||||
renderTemplate: ->
|
||||
@template = _.template($("#thread-response-template").html())
|
||||
|
||||
templateData = @model.toJSON()
|
||||
templateData.wmdId = @model.id ? (new Date()).getTime()
|
||||
@template(templateData)
|
||||
|
||||
render: ->
|
||||
@$el.html(@renderTemplate())
|
||||
@delegateEvents()
|
||||
|
||||
@renderShowView()
|
||||
@renderAttrs()
|
||||
|
||||
@renderComments()
|
||||
@
|
||||
|
||||
afterInsert: ->
|
||||
@makeWmdEditor "comment-body"
|
||||
@hideEditorChrome()
|
||||
|
||||
hideEditorChrome: ->
|
||||
@$('.wmd-button-row').hide()
|
||||
@$('.wmd-preview').hide()
|
||||
@$('.wmd-input').css({
|
||||
height: '35px',
|
||||
padding: '5px'
|
||||
})
|
||||
@$('.comment-post-control').hide()
|
||||
|
||||
showEditorChrome: ->
|
||||
@$('.wmd-button-row').show()
|
||||
@$('.wmd-preview').show()
|
||||
@$('.comment-post-control').show()
|
||||
@$('.wmd-input').css({
|
||||
height: '125px',
|
||||
padding: '10px'
|
||||
})
|
||||
|
||||
renderComments: ->
|
||||
comments = new Comments()
|
||||
comments.comparator = (comment) ->
|
||||
comment.get('created_at')
|
||||
collectComments = (comment) ->
|
||||
comments.add(comment)
|
||||
children = new Comments(comment.get('children'))
|
||||
children.each (child) ->
|
||||
child.parent = comment
|
||||
collectComments(child)
|
||||
@model.get('comments').each collectComments
|
||||
comments.each (comment) => @renderComment(comment, false, null)
|
||||
|
||||
renderComment: (comment) =>
|
||||
comment.set('thread', @model.get('thread'))
|
||||
view = new ResponseCommentView(model: comment)
|
||||
view.render()
|
||||
@$el.find(".comments .new-comment").before(view.el)
|
||||
view
|
||||
|
||||
submitComment: (event) ->
|
||||
event.preventDefault()
|
||||
url = @model.urlFor('reply')
|
||||
body = @getWmdContent("comment-body")
|
||||
return if not body.trim().length
|
||||
@setWmdContent("comment-body", "")
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), user_id: window.user.get("id"), id:"unsaved")
|
||||
view = @renderComment(comment)
|
||||
@hideEditorChrome()
|
||||
@trigger "comment:add", comment
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
body: body
|
||||
success: (response, textStatus) ->
|
||||
comment.set(response.content)
|
||||
view.render() # This is just to update the id for the most part, but might be useful in general
|
||||
|
||||
delete: (event) =>
|
||||
event.preventDefault()
|
||||
if not @model.can('can_delete')
|
||||
return
|
||||
if not confirm "Are you sure to delete this response? "
|
||||
return
|
||||
url = @model.urlFor('delete')
|
||||
@model.remove()
|
||||
@$el.remove()
|
||||
$elem = $(event.target)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
|
||||
createEditView: () ->
|
||||
if @showView?
|
||||
@showView.undelegateEvents()
|
||||
@showView.$el.empty()
|
||||
@showView = null
|
||||
|
||||
@editView = new ThreadResponseEditView(model: @model)
|
||||
@editView.bind "response:update", @update
|
||||
@editView.bind "response:cancel_edit", @cancelEdit
|
||||
|
||||
renderSubView: (view) ->
|
||||
view.setElement(@$('.discussion-response'))
|
||||
view.render()
|
||||
view.delegateEvents()
|
||||
|
||||
renderEditView: () ->
|
||||
@renderSubView(@editView)
|
||||
|
||||
hideCommentForm: () ->
|
||||
@$('.comment-form').closest('li').hide()
|
||||
|
||||
showCommentForm: () ->
|
||||
@$('.comment-form').closest('li').show()
|
||||
|
||||
createShowView: () ->
|
||||
|
||||
if @editView?
|
||||
@editView.undelegateEvents()
|
||||
@editView.$el.empty()
|
||||
@editView = null
|
||||
|
||||
@showView = new ThreadResponseShowView(model: @model)
|
||||
@showView.bind "response:delete", @delete
|
||||
@showView.bind "response:edit", @edit
|
||||
|
||||
renderShowView: () ->
|
||||
@renderSubView(@showView)
|
||||
|
||||
cancelEdit: (event) =>
|
||||
event.preventDefault()
|
||||
@createShowView()
|
||||
@renderShowView()
|
||||
@showCommentForm()
|
||||
|
||||
edit: (event) =>
|
||||
@createEditView()
|
||||
@renderEditView()
|
||||
@hideCommentForm()
|
||||
|
||||
update: (event) =>
|
||||
|
||||
newBody = @editView.$(".edit-post-body textarea").val()
|
||||
|
||||
url = DiscussionUtil.urlFor('update_comment', @model.id)
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
async: false # TODO when the rest of the stuff below is made to work properly..
|
||||
data:
|
||||
body: newBody
|
||||
error: DiscussionUtil.formErrorHandler(@$(".edit-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
@editView.$(".edit-post-body textarea").val("").attr("prev-text", "")
|
||||
@editView.$(".wmd-preview p").html("")
|
||||
|
||||
@model.set
|
||||
body: newBody
|
||||
|
||||
@createShowView()
|
||||
@renderShowView()
|
||||
@showCommentForm()
|
||||
|
||||
BIN
lms/static/images/browse-icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
lms/static/images/comment-icon-bottoms.png
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
lms/static/images/endorse-icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
lms/static/images/follow-dog-ear.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
lms/static/images/following-flag.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
lms/static/images/moderator-delete-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/moderator-edit-icon.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
lms/static/images/nested-icon.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
lms/static/images/new-post-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/new-post-icons-full.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
lms/static/images/new-post-icons.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
lms/static/images/show-hide-discussion-icon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
lms/static/images/sidebar-resolved-icons.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/small-grey-arrows.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
lms/static/images/staff-icons.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/vote-plus-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/white-error-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/wmd-buttons-transparent.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
350
lms/static/js/discussions-temp.js
Normal file
@@ -0,0 +1,350 @@
|
||||
var $body;
|
||||
var $browse;
|
||||
var $search;
|
||||
var $searchField;
|
||||
var $topicDrop;
|
||||
var $currentBoard;
|
||||
var $tooltip;
|
||||
var $newPost;
|
||||
var $thread;
|
||||
var $sidebar;
|
||||
var $sidebarWidthStyles;
|
||||
var $formTopicDropBtn;
|
||||
var $formTopicDropMenu;
|
||||
var $postListWrapper;
|
||||
var $dropFilter;
|
||||
var $topicFilter;
|
||||
var $discussionBody;
|
||||
var sidebarWidth;
|
||||
var sidebarHeight;
|
||||
var sidebarHeaderHeight;
|
||||
var sidebarXOffset;
|
||||
var scrollTop;
|
||||
var discussionsBodyTop;
|
||||
var discussionsBodyBottom;
|
||||
var tooltipTimer;
|
||||
var tooltipCoords;
|
||||
var SIDEBAR_PADDING = 10;
|
||||
var SIDEBAR_HEADER_HEIGHT = 87;
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
$body = $('body');
|
||||
//$browse = $('.browse-search .browse');
|
||||
//$search = $('.browse-search .search');
|
||||
$searchField = $('.post-search-field');
|
||||
//$topicDrop = $('.browse-topic-drop-menu-wrapper');
|
||||
$currentBoard = $('.current-board');
|
||||
$tooltip = $('<div class="tooltip"></div>');
|
||||
$newPost = $('.new-post-article');
|
||||
$sidebar = $('.sidebar');
|
||||
$discussionBody = $('.discussion-body');
|
||||
$postListWrapper = $('.post-list-wrapper');
|
||||
$formTopicDropBtn = $('.new-post-article .form-topic-drop-btn');
|
||||
$formTopicDropMenu = $('.new-post-article .form-topic-drop-menu-wrapper');
|
||||
// $dropFilter = $('.browse-topic-drop-search-input');
|
||||
// $topicFilter = $('.topic-drop-search-input');
|
||||
$sidebarWidthStyles = $('<style></style>');
|
||||
$body.append($sidebarWidthStyles);
|
||||
|
||||
sidebarWidth = $('.sidebar').width();
|
||||
sidebarXOffset = $sidebar.offset().top;
|
||||
|
||||
//$browse.bind('click', showTopicDrop);
|
||||
//$search.bind('click', showSearch);
|
||||
// $topicDrop.bind('click', setTopic);
|
||||
$formTopicDropBtn.bind('click', showFormTopicDrop);
|
||||
$formTopicDropMenu.bind('click', setFormTopic);
|
||||
$('.new-post-btn').bind('click', newPost);
|
||||
$('.new-post-cancel').bind('click', closeNewPost);
|
||||
|
||||
$body.delegate('[data-tooltip]', {
|
||||
'mouseover': showTooltip,
|
||||
'mousemove': moveTooltip,
|
||||
'mouseout': hideTooltip,
|
||||
'click': hideTooltip
|
||||
});
|
||||
|
||||
$body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop);
|
||||
});
|
||||
|
||||
function filterDrop(e) {
|
||||
/*
|
||||
* multiple queries
|
||||
*/
|
||||
|
||||
// var $drop = $(e.target).parents('.form-topic-drop-menu-wrapper, .browse-topic-drop-menu-wrapper');
|
||||
// var queries = $(this).val().split(' ');
|
||||
// var $items = $drop.find('a');
|
||||
|
||||
// if(queries.length == 0) {
|
||||
// $items.show();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// $items.hide();
|
||||
// $items.each(function(i) {
|
||||
// var thisText = $(this).children().not('.unread').text();
|
||||
// $(this).parents('ul').siblings('a').not('.unread').each(function(i) {
|
||||
// thisText = thisText + ' ' + $(this).text();
|
||||
// });
|
||||
|
||||
// var test = true;
|
||||
// var terms = thisText.split(' ');
|
||||
|
||||
// for(var i = 0; i < queries.length; i++) {
|
||||
// if(thisText.toLowerCase().search(queries[i].toLowerCase()) == -1) {
|
||||
// test = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if(test) {
|
||||
// $(this).show();
|
||||
|
||||
// // show children
|
||||
// $(this).parent().find('a').show();
|
||||
|
||||
// // show parents
|
||||
// $(this).parents('ul').siblings('a').show();
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* single query
|
||||
*/
|
||||
|
||||
var $drop = $(e.target).parents('.topic_menu_wrapper, .browse-topic-drop-menu-wrapper');
|
||||
var query = $(this).val();
|
||||
var $items = $drop.find('a');
|
||||
|
||||
if(query.length == 0) {
|
||||
$items.removeClass('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
$items.addClass('hidden');
|
||||
$items.each(function(i) {
|
||||
var thisText = $(this).not('.unread').text();
|
||||
$(this).parents('ul').siblings('a').not('.unread').each(function(i) {
|
||||
thisText = thisText + ' ' + $(this).text();
|
||||
});
|
||||
|
||||
var test = true;
|
||||
var terms = thisText.split(' ');
|
||||
|
||||
if(thisText.toLowerCase().search(query.toLowerCase()) == -1) {
|
||||
test = false;
|
||||
}
|
||||
|
||||
if(test) {
|
||||
$(this).removeClass('hidden');
|
||||
|
||||
// show children
|
||||
$(this).parent().find('a').removeClass('hidden');
|
||||
|
||||
// show parents
|
||||
$(this).parents('ul').siblings('a').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showTooltip(e) {
|
||||
var tooltipText = $(this).attr('data-tooltip');
|
||||
$tooltip.html(tooltipText);
|
||||
$body.append($tooltip);
|
||||
$(this).children().css('pointer-events', 'none');
|
||||
|
||||
tooltipCoords = {
|
||||
x: e.pageX - ($tooltip.outerWidth() / 2),
|
||||
y: e.pageY - ($tooltip.outerHeight() + 15)
|
||||
};
|
||||
|
||||
$tooltip.css({
|
||||
'left': tooltipCoords.x,
|
||||
'top': tooltipCoords.y
|
||||
});
|
||||
|
||||
tooltipTimer = setTimeout(function() {
|
||||
$tooltip.show().css('opacity', 1);
|
||||
|
||||
tooltipTimer = setTimeout(function() {
|
||||
hideTooltip();
|
||||
}, 3000);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function moveTooltip(e) {
|
||||
tooltipCoords = {
|
||||
x: e.pageX - ($tooltip.outerWidth() / 2),
|
||||
y: e.pageY - ($tooltip.outerHeight() + 15)
|
||||
};
|
||||
|
||||
$tooltip.css({
|
||||
'left': tooltipCoords.x,
|
||||
'top': tooltipCoords.y
|
||||
});
|
||||
}
|
||||
|
||||
function hideTooltip(e) {
|
||||
$tooltip.hide().css('opacity', 0);
|
||||
clearTimeout(tooltipTimer);
|
||||
}
|
||||
|
||||
function showBrowse(e) {
|
||||
$browse.addClass('is-open');
|
||||
$search.removeClass('is-open');
|
||||
$searchField.val('');
|
||||
}
|
||||
|
||||
function showSearch(e) {
|
||||
$search.addClass('is-open');
|
||||
$browse.removeClass('is-open');
|
||||
setTimeout(function() {
|
||||
$searchField.focus();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function showTopicDrop(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$browse.addClass('is-dropped');
|
||||
|
||||
if(!$topicDrop[0]) {
|
||||
$topicDrop = $('.browse-topic-drop-menu-wrapper');
|
||||
}
|
||||
|
||||
$topicDrop.show();
|
||||
$browse.unbind('click', showTopicDrop);
|
||||
$body.bind('keyup', setActiveDropItem);
|
||||
$browse.bind('click', hideTopicDrop);
|
||||
setTimeout(function() {
|
||||
$body.bind('click', hideTopicDrop);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function hideTopicDrop(e) {
|
||||
if(e.target == $('.browse-topic-drop-search-input')[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
$browse.removeClass('is-dropped');
|
||||
$topicDrop.hide();
|
||||
$body.unbind('click', hideTopicDrop);
|
||||
$browse.bind('click', showTopicDrop);
|
||||
}
|
||||
|
||||
function setTopic(e) {
|
||||
if(e.target == $('.browse-topic-drop-search-input')[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $item = $(e.target).closest('a');
|
||||
var boardName = $item.find('.board-name').html();
|
||||
|
||||
$item.parents('ul').not('.browse-topic-drop-menu').each(function(i) {
|
||||
boardName = $(this).siblings('a').find('.board-name').html() + ' / ' + boardName;
|
||||
});
|
||||
|
||||
if(!$currentBoard[0]) {
|
||||
$currentBoard = $('.current-board');
|
||||
}
|
||||
$currentBoard.html(boardName);
|
||||
|
||||
var fontSize = 16;
|
||||
$currentBoard.css('font-size', '16px');
|
||||
|
||||
while($currentBoard.width() > (sidebarWidth * .8) - 40) {
|
||||
fontSize--;
|
||||
if(fontSize < 11) {
|
||||
break;
|
||||
}
|
||||
$currentBoard.css('font-size', fontSize + 'px');
|
||||
}
|
||||
|
||||
showBrowse();
|
||||
}
|
||||
|
||||
function newPost(e) {
|
||||
$newPost.slideDown(300);
|
||||
$('.new-post-title').focus();
|
||||
}
|
||||
|
||||
function closeNewPost(e) {
|
||||
$newPost.slideUp(300);
|
||||
}
|
||||
|
||||
function showFormTopicDrop(e) {
|
||||
$formTopicDropBtn.addClass('is-dropped');
|
||||
$formTopicDropMenu.show();
|
||||
$formTopicDropBtn.unbind('click', showFormTopicDrop);
|
||||
$formTopicDropBtn.bind('click', hideFormTopicDrop);
|
||||
|
||||
setTimeout(function() {
|
||||
$body.bind('click', hideFormTopicDrop);
|
||||
}, 0);
|
||||
|
||||
}
|
||||
|
||||
function hideFormTopicDrop(e) {
|
||||
if(e.target == $('.topic-drop-search-input')[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formTopicDropBtn.removeClass('is-dropped');
|
||||
$formTopicDropMenu.hide();
|
||||
$body.unbind('click', hideFormTopicDrop);
|
||||
$formTopicDropBtn.unbind('click', hideFormTopicDrop);
|
||||
$formTopicDropBtn.bind('click', showFormTopicDrop);
|
||||
}
|
||||
|
||||
function setFormTopic(e) {
|
||||
if(e.target == $('.topic-drop-search-input')[0]) {
|
||||
return;
|
||||
}
|
||||
$formTopicDropBtn.removeClass('is-dropped');
|
||||
hideFormTopicDrop(e);
|
||||
|
||||
var $item = $(e.target);
|
||||
var boardName = $item.html();
|
||||
$item.parents('ul').not('.form-topic-drop-menu').each(function(i) {
|
||||
boardName = $(this).siblings('a').html() + ' / ' + boardName;
|
||||
});
|
||||
$formTopicDropBtn.html(boardName + ' <span class="drop-arrow">▾</span>');
|
||||
}
|
||||
|
||||
function updateSidebar(e) {
|
||||
// determine page scroll attributes
|
||||
scrollTop = $(window).scrollTop();
|
||||
discussionsBodyTop = $discussionBody.offset().top;
|
||||
discussionsBodyBottom = discussionsBodyTop + $discussionBody.height();
|
||||
var windowHeight = $(window).height();
|
||||
|
||||
// toggle fixed positioning
|
||||
if(scrollTop > discussionsBodyTop - SIDEBAR_PADDING) {
|
||||
$sidebar.addClass('fixed');
|
||||
$sidebar.css('top', SIDEBAR_PADDING + 'px');
|
||||
} else {
|
||||
$sidebar.removeClass('fixed');
|
||||
$sidebar.css('top', '0');
|
||||
}
|
||||
|
||||
// set sidebar width
|
||||
var sidebarWidth = .32 * $discussionBody.width() - 10;
|
||||
$sidebar.css('width', sidebarWidth + 'px');
|
||||
|
||||
// show the entire sidebar at all times
|
||||
var sidebarHeight = windowHeight - (scrollTop < discussionsBodyTop - SIDEBAR_PADDING ? discussionsBodyTop - scrollTop : SIDEBAR_PADDING) - SIDEBAR_PADDING - (scrollTop + windowHeight > discussionsBodyBottom + SIDEBAR_PADDING ? scrollTop + windowHeight - discussionsBodyBottom - SIDEBAR_PADDING : 0);
|
||||
$sidebar.css('height', sidebarHeight > 400 ? sidebarHeight : 400 + 'px');
|
||||
|
||||
// update the list height
|
||||
if(!$postListWrapper[0]) {
|
||||
$postListWrapper = $('.post-list-wrapper');
|
||||
}
|
||||
$postListWrapper.css('height', (sidebarHeight - SIDEBAR_HEADER_HEIGHT - 4) + 'px');
|
||||
|
||||
// update title wrappers
|
||||
var titleWidth = sidebarWidth - 115;
|
||||
$sidebarWidthStyles.html('.discussion-body .post-list a .title { width: ' + titleWidth + 'px !important; }');
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
|
||||
jQuery Tags Input Plugin 1.3.3
|
||||
|
||||
|
||||
Copyright (c) 2011 XOXCO, Inc
|
||||
|
||||
|
||||
Documentation for this plugin lives here:
|
||||
http://xoxco.com/clickable/jquery-tags-input
|
||||
|
||||
|
||||
Licensed under the MIT license:
|
||||
http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
val = '',
|
||||
input = $(this),
|
||||
testSubject = $('#'+$(this).data('tester_id'));
|
||||
|
||||
|
||||
if (val === (val = input.val())) {return;}
|
||||
|
||||
|
||||
// Enter new content into testSubject
|
||||
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
testSubject.html(escaped);
|
||||
@@ -36,7 +36,7 @@
|
||||
currentWidth = input.width(),
|
||||
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|
||||
|| (newWidth > minWidth && newWidth < maxWidth);
|
||||
|
||||
|
||||
// Animate width
|
||||
if (isValidWidthChange) {
|
||||
input.width(newWidth);
|
||||
@@ -72,19 +72,24 @@
|
||||
input.data('tester_id', testerId);
|
||||
input.css('width', minWidth);
|
||||
};
|
||||
|
||||
|
||||
$.fn.addTag = function(value,options) {
|
||||
options = jQuery.extend({focus:false,callback:true},options);
|
||||
this.each(function() {
|
||||
this.each(function() {
|
||||
var id = $(this).attr('id');
|
||||
|
||||
var tagslist = $(this).val().split(delimiter[id]);
|
||||
if (tagslist[0] == '') {
|
||||
if (tagslist[0] == '') {
|
||||
tagslist = new Array();
|
||||
}
|
||||
|
||||
value = jQuery.trim(value);
|
||||
|
||||
|
||||
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['preprocessTag']) {
|
||||
var f = tags_callbacks[id]['preprocessTag'];
|
||||
value = f.call(this, value);
|
||||
}
|
||||
|
||||
if (options.unique) {
|
||||
var skipTag = $(this).tagExist(value);
|
||||
if(skipTag == true) {
|
||||
@@ -92,15 +97,15 @@
|
||||
$('#'+id+'_tag').addClass('not_valid');
|
||||
}
|
||||
} else {
|
||||
var skipTag = false;
|
||||
var skipTag = false;
|
||||
}
|
||||
|
||||
if (value !='' && skipTag != true) {
|
||||
|
||||
if (value !='' && skipTag != true) {
|
||||
$('<span>').addClass('tag').append(
|
||||
$('<span>').text(value).append(' '),
|
||||
$('<a>', {
|
||||
href : '#',
|
||||
title : 'Removing tag',
|
||||
title : 'Remove tag',
|
||||
text : 'x'
|
||||
}).click(function () {
|
||||
return $('#' + id).removeTag(escape(value));
|
||||
@@ -108,16 +113,16 @@
|
||||
).insertBefore('#' + id + '_addTag');
|
||||
|
||||
tagslist.push(value);
|
||||
|
||||
|
||||
$('#'+id+'_tag').val('');
|
||||
if (options.focus) {
|
||||
$('#'+id+'_tag').focus();
|
||||
} else {
|
||||
} else {
|
||||
$('#'+id+'_tag').blur();
|
||||
}
|
||||
|
||||
|
||||
$.fn.tagsInput.updateTagsField(this,tagslist);
|
||||
|
||||
|
||||
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
|
||||
var f = tags_callbacks[id]['onAddTag'];
|
||||
f.call(this, value);
|
||||
@@ -127,29 +132,29 @@
|
||||
var i = tagslist.length;
|
||||
var f = tags_callbacks[id]['onChange'];
|
||||
f.call(this, $(this), tagslist[i-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.removeTag = function(value) {
|
||||
|
||||
$.fn.removeTag = function(value) {
|
||||
value = unescape(value);
|
||||
this.each(function() {
|
||||
this.each(function() {
|
||||
var id = $(this).attr('id');
|
||||
|
||||
|
||||
var old = $(this).val().split(delimiter[id]);
|
||||
|
||||
|
||||
$('#'+id+'_tagsinput .tag').remove();
|
||||
str = '';
|
||||
for (i=0; i< old.length; i++) {
|
||||
if (old[i]!=value) {
|
||||
for (i=0; i< old.length; i++) {
|
||||
if (old[i]!=value) {
|
||||
str = str + delimiter[id] +old[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$.fn.tagsInput.importTags(this,str);
|
||||
|
||||
if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
|
||||
@@ -157,24 +162,24 @@
|
||||
f.call(this, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
$.fn.tagExist = function(val) {
|
||||
var id = $(this).attr('id');
|
||||
var tagslist = $(this).val().split(delimiter[id]);
|
||||
return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
|
||||
};
|
||||
|
||||
|
||||
// clear all existing tags and import new ones from a string
|
||||
$.fn.importTags = function(str) {
|
||||
id = $(this).attr('id');
|
||||
$('#'+id+'_tagsinput .tag').remove();
|
||||
$.fn.tagsInput.importTags(this,str);
|
||||
}
|
||||
|
||||
$.fn.tagsInput = function(options) {
|
||||
|
||||
$.fn.tagsInput = function(options) {
|
||||
var settings = jQuery.extend({
|
||||
interactive:true,
|
||||
defaultText:'add a tag',
|
||||
@@ -192,15 +197,15 @@
|
||||
inputPadding: 6*2
|
||||
},options);
|
||||
|
||||
this.each(function() {
|
||||
if (settings.hide) {
|
||||
$(this).hide();
|
||||
this.each(function() {
|
||||
if (settings.hide) {
|
||||
$(this).hide();
|
||||
}
|
||||
var id = $(this).attr('id');
|
||||
if (!id || delimiter[$(this).attr('id')]) {
|
||||
id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
|
||||
}
|
||||
|
||||
|
||||
var data = jQuery.extend({
|
||||
pid:id,
|
||||
real_input: '#'+id,
|
||||
@@ -208,57 +213,58 @@
|
||||
input_wrapper: '#'+id+'_addTag',
|
||||
fake_input: '#'+id+'_tag'
|
||||
},settings);
|
||||
|
||||
|
||||
delimiter[id] = data.delimiter;
|
||||
|
||||
if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
|
||||
|
||||
if (settings.onAddTag || settings.onRemoveTag || settings.onChange || settings.preprocessTag) {
|
||||
tags_callbacks[id] = new Array();
|
||||
tags_callbacks[id]['onAddTag'] = settings.onAddTag;
|
||||
tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
|
||||
tags_callbacks[id]['onChange'] = settings.onChange;
|
||||
tags_callbacks[id]['preprocessTag'] = settings.preprocessTag;
|
||||
}
|
||||
|
||||
|
||||
var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
|
||||
|
||||
|
||||
if (settings.interactive) {
|
||||
markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
|
||||
}
|
||||
|
||||
|
||||
markup = markup + '</div><div class="tags_clear"></div></div>';
|
||||
|
||||
|
||||
$(markup).insertAfter(this);
|
||||
|
||||
|
||||
|
||||
$(data.holder).css('width',settings.width);
|
||||
$(data.holder).css('min-height',settings.height);
|
||||
$(data.holder).css('height','100%');
|
||||
|
||||
if ($(data.real_input).val()!='') {
|
||||
|
||||
if ($(data.real_input).val()!='') {
|
||||
$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
|
||||
}
|
||||
if (settings.interactive) {
|
||||
}
|
||||
if (settings.interactive) {
|
||||
$(data.fake_input).val($(data.fake_input).attr('data-default'));
|
||||
$(data.fake_input).css('color',settings.placeholderColor);
|
||||
$(data.fake_input).resetAutosize(settings);
|
||||
|
||||
|
||||
$(data.fake_input).doAutosize(settings);
|
||||
$(data.holder).bind('click',data,function(event) {
|
||||
$(event.data.fake_input).focus();
|
||||
});
|
||||
|
||||
|
||||
$(data.fake_input).bind('focus',data,function(event) {
|
||||
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
|
||||
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
|
||||
$(event.data.fake_input).val('');
|
||||
}
|
||||
$(event.data.fake_input).css('color','#000000');
|
||||
$(event.data.fake_input).css('color','#000000');
|
||||
});
|
||||
|
||||
|
||||
if (settings.autocomplete_url != undefined) {
|
||||
autocomplete_options = {source: settings.autocomplete_url};
|
||||
for (attrname in settings.autocomplete) {
|
||||
autocomplete_options[attrname] = settings.autocomplete[attrname];
|
||||
for (attrname in settings.autocomplete) {
|
||||
autocomplete_options[attrname] = settings.autocomplete[attrname];
|
||||
}
|
||||
|
||||
|
||||
if (jQuery.Autocompleter !== undefined) {
|
||||
onSelectCallback = settings.autocomplete.onItemSelect;
|
||||
settings.autocomplete.onItemSelect = function() {
|
||||
@@ -278,18 +284,18 @@
|
||||
$(data.fake_input).autocomplete(autocomplete_options);
|
||||
$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
|
||||
$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
// if a user tabs out of the field, create a new tag
|
||||
// this is only available if autocomplete is not used.
|
||||
$(data.fake_input).bind('blur',data,function(event) {
|
||||
$(data.fake_input).bind('blur',data,function(event) {
|
||||
var d = $(this).attr('data-default');
|
||||
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
|
||||
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
|
||||
if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
|
||||
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
|
||||
} else {
|
||||
@@ -298,7 +304,7 @@
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
// if user types a comma, create a new tag
|
||||
$(data.fake_input).bind('keypress',data,function(event) {
|
||||
@@ -326,7 +332,7 @@
|
||||
}
|
||||
});
|
||||
$(data.fake_input).blur();
|
||||
|
||||
|
||||
//Removes the not_valid class when user changes the value of the fake input
|
||||
if(data.unique) {
|
||||
$(data.fake_input).keydown(function(event){
|
||||
@@ -337,21 +343,21 @@
|
||||
}
|
||||
} // if settings.interactive
|
||||
});
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
|
||||
};
|
||||
|
||||
$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
|
||||
|
||||
$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
|
||||
var id = $(obj).attr('id');
|
||||
$(obj).val(tagslist.join(delimiter[id]));
|
||||
};
|
||||
|
||||
$.fn.tagsInput.importTags = function(obj,val) {
|
||||
|
||||
$.fn.tagsInput.importTags = function(obj,val) {
|
||||
$(obj).val('');
|
||||
var id = $(obj).attr('id');
|
||||
var tags = val.split(delimiter[id]);
|
||||
for (i=0; i<tags.length; i++) {
|
||||
for (i=0; i<tags.length; i++) {
|
||||
$(obj).addTag(tags[i],{focus:false,callback:false});
|
||||
}
|
||||
if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
|
||||
|
||||
477
lms/static/sass/_discussion-old.scss
Normal file
@@ -0,0 +1,477 @@
|
||||
@mixin blue-button {
|
||||
display: block;
|
||||
height: 33px;
|
||||
margin: 12px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #4697c1;
|
||||
background: -webkit-linear-gradient(top, #6dccf1, #38a8e5);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .4);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
border-color: #297095;
|
||||
background: -webkit-linear-gradient(top, #4fbbe4, #2090d0);
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-body {
|
||||
|
||||
.vote-btn {
|
||||
float: right;
|
||||
display: block;
|
||||
height: 27px;
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #b2b2b2;
|
||||
background: -webkit-linear-gradient(top, #fff 35%, #ebebeb);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .15);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 25px;
|
||||
color: #333;
|
||||
|
||||
.plus-icon {
|
||||
float: left;
|
||||
margin-right: 6px;
|
||||
font-size: 18px;
|
||||
color: #17b429;
|
||||
}
|
||||
|
||||
&.is-cast {
|
||||
border-color: #379a42;
|
||||
background: -webkit-linear-gradient(top, #50cc5e, #3db84b);
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .4) inset, 0 1px 2px rgba(0, 0, 0, .2);
|
||||
|
||||
.plus-icon {
|
||||
color: #336a39;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.new-post-btn {
|
||||
@include blue-button;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.new-post-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 16px;
|
||||
height: 17px;
|
||||
margin: 7px 7px 0 0;
|
||||
background: url(../images/new-post-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
.post-search {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.post-search-field {
|
||||
width: 280px;
|
||||
height: 30px;
|
||||
padding: 0 15px 0 30px;
|
||||
margin-top: 14px;
|
||||
border: 1px solid #acacac;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .1) inset, 0 1px 0 rgba(255, 255, 255, .5);
|
||||
background: url(../images/search-icon.png) no-repeat 8px center #fff;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 30px;
|
||||
color: #333;
|
||||
outline: 0;
|
||||
-webkit-transition: border-color .1s;
|
||||
|
||||
&:focus {
|
||||
border-color: #4697c1;
|
||||
}
|
||||
}
|
||||
|
||||
h1, ul, li, a, ol {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
ul, li {
|
||||
list-style-type: none;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #009fe2;
|
||||
}
|
||||
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #aaa;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.sidebar {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
width: 27.7%;
|
||||
background: #f6f6f6;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border-right: 1px solid #bcbcbc;
|
||||
.post-list {
|
||||
background-color: #ddd;
|
||||
|
||||
li:last-child a {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 1px;
|
||||
background: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 34px;
|
||||
color: #333;
|
||||
|
||||
&.read .title {
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
&.followed:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url(../images/following-flag.png) no-repeat;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: -webkit-linear-gradient(top, #96e0fd, #61c7fc);
|
||||
border-color: #4697c1;
|
||||
box-shadow: 0 1px 0 #4697c1, 0 -1px 0 #4697c1;
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.votes-count,
|
||||
.comments-count {
|
||||
background: -webkit-linear-gradient(top, #3994c7, #4da7d3);
|
||||
color: #fff;
|
||||
|
||||
&:after {
|
||||
color: #4da7d3;
|
||||
}
|
||||
}
|
||||
|
||||
&.followed:after {
|
||||
background-position: 0 -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 70%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.votes-count,
|
||||
.comments-count {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 32px;
|
||||
height: 16px;
|
||||
margin-top: 9px;
|
||||
border-radius: 2px;
|
||||
background: -webkit-linear-gradient(top, #d4d4d4, #dfdfdf);
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
.comments-count {
|
||||
position: relative;
|
||||
margin-left: 4px;
|
||||
|
||||
&:after {
|
||||
content: '◥';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 3px;
|
||||
font-size: 6px;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
&.new {
|
||||
background: -webkit-linear-gradient(top, #84d7fe, #99e0fe);
|
||||
color: #333;
|
||||
|
||||
&:after {
|
||||
color: #99e0fe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.board-drop-btn {
|
||||
display: block;
|
||||
height: 60px;
|
||||
border-bottom: 1px solid #a3a3a3;
|
||||
border-radius: 3px 0 0 0;
|
||||
background: -webkit-linear-gradient(top, #ebebeb, #d9d9d9);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 58px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .8);
|
||||
}
|
||||
|
||||
.sort-bar {
|
||||
height: 27px;
|
||||
border-bottom: 1px solid #a3a3a3;
|
||||
background: -webkit-linear-gradient(top, #cdcdcd, #b6b6b6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .4) inset;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
float: right;
|
||||
height: 27px;
|
||||
margin-right: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
line-height: 23px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
|
||||
|
||||
.sort-label {
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.global-discussion-actions {
|
||||
height: 60px;
|
||||
background: -webkit-linear-gradient(top, #ebebeb, #d9d9d9);
|
||||
border-radius: 0 3px 0 0;
|
||||
border-bottom: 1px solid #bcbcbc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.discussion-article {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
width: 72.3%;
|
||||
padding: 40px;
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.posted-details {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dogear {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -1px;
|
||||
width: 52px;
|
||||
height: 51px;
|
||||
background: url(../images/follow-dog-ear.png) 0 -51px no-repeat;
|
||||
|
||||
&.is-followed {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-post header,
|
||||
.responses li header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.responses {
|
||||
margin-top: 40px;
|
||||
|
||||
> li {
|
||||
margin: 0 -10px;
|
||||
padding: 30px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b2b2b2;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.posted-by {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.endorse-btn {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
margin-right: 10px;
|
||||
border-radius: 27px;
|
||||
border: 1px solid #a0a0a0;
|
||||
background: -webkit-linear-gradient(top, #fff 35%, #ebebeb);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
|
||||
|
||||
.check-icon {
|
||||
display: block;
|
||||
width: 13px;
|
||||
height: 12px;
|
||||
margin: 8px auto;
|
||||
background: url(../images/endorse-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
&.is-endorsed {
|
||||
border: 1px solid #4697c1;
|
||||
background: -webkit-linear-gradient(top, #6dccf1, #38a8e5);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, .4) inset;
|
||||
|
||||
.check-icon {
|
||||
background-position: 0 -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comments {
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
|
||||
li {
|
||||
background: #f6f6f6;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
padding: 10px 20px;
|
||||
|
||||
.posted-details {
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-form {
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
.comment-form-input {
|
||||
width: 100%;
|
||||
height: 31px;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #b2b2b2;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .1) inset;
|
||||
-webkit-transition: border-color .1s;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
border-color: #4697c1;
|
||||
}
|
||||
}
|
||||
|
||||
.moderator-actions {
|
||||
margin-top: 20px;
|
||||
@include clearfix;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 12px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b2b2b2;
|
||||
background: -webkit-linear-gradient(top, #fff 35%, #ebebeb);
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
color: #737373;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.delete-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 8px 4px 0 0;
|
||||
background: url(../images/moderator-delete-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 7px 4px 0 0;
|
||||
background: url(../images/moderator-edit-icon.png) no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,304 +1,1613 @@
|
||||
/*** Variables ***/
|
||||
@mixin blue-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #2d81ad;
|
||||
@include linear-gradient(top, #6dccf1, #38a8e5);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
$comment-margin-left: 30px;
|
||||
$discussion-title-size: 1.6em;
|
||||
$comment-title-size: 1.0em;
|
||||
$post-font-size: 0.9em;
|
||||
$comment-info-size: 0.75em;
|
||||
$comment-font-size: 0.8em;
|
||||
$discussion-input-width: 100%;
|
||||
|
||||
$tag-background-color: #e7ecdd;
|
||||
$tag-border-color: #babdb3;
|
||||
$tag-text-color: #5b614f;
|
||||
|
||||
|
||||
|
||||
/*** Mixins ***/
|
||||
|
||||
@mixin discussion-font {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
@mixin discussion-clickable {
|
||||
color: black;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
border-color: #297095;
|
||||
@include linear-gradient(top, #4fbbe4, #2090d0);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin standard-discussion-link {
|
||||
text-decoration: none;
|
||||
@mixin white-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #aaa;
|
||||
@include linear-gradient(top, #eee, #ccc);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
color: #1C71DD;
|
||||
text-decoration: none;
|
||||
@include linear-gradient(top, #fff, #ddd);
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-loading {
|
||||
background-image: url(../images/discussion/loading.gif);
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
@mixin dark-grey-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #222;
|
||||
background: -webkit-linear-gradient(top, #777, #555);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
background: -webkit-linear-gradient(top, #888, #666);
|
||||
}
|
||||
}
|
||||
|
||||
/*** Discussions ***/
|
||||
@mixin discussion-wmd-input {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
margin-top: 0;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) inset;
|
||||
}
|
||||
|
||||
.discussion {
|
||||
@mixin discussion-wmd-preview {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 25px 20px 10px 20px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #aaa;
|
||||
border-top: none;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #eee;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) inset;
|
||||
}
|
||||
|
||||
#open_close_accordion {
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
body.discussion {
|
||||
.new-post-form-errors {
|
||||
display: none;
|
||||
}
|
||||
background: $error-red;
|
||||
padding: 0;
|
||||
border: 1px solid #333;
|
||||
list-style: none;
|
||||
color: #fff;
|
||||
line-height: 1.6;
|
||||
border-radius: 3px;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, .2));
|
||||
|
||||
p + p, ul + p, ol + p {
|
||||
margin-top: 0;
|
||||
}
|
||||
li {
|
||||
padding: 10px 20px 12px 45px;
|
||||
border-bottom: 1px solid #dc4949;
|
||||
background: url(../images/white-error-icon.png) no-repeat 15px 14px;
|
||||
|
||||
/*** Sidebar ***/
|
||||
|
||||
.sidebar-module {
|
||||
@include clearfix;
|
||||
padding: 0 26px 24px;
|
||||
margin-bottom: 24px;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
font-size: 13px;
|
||||
|
||||
header {
|
||||
margin-bottom: 14px;
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
h4 {
|
||||
float: left;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-new-post-button, .sidebar-promote-moderator-button {
|
||||
@include button;
|
||||
}
|
||||
|
||||
.sidebar-revoke-moderator-button {
|
||||
@include button(simple, gray);
|
||||
}
|
||||
|
||||
.sidebar-new-post-button, .sidebar-promote-moderator-button, .sidebar-revoke-moderator-button {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
padding: 11px;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-new-post-button {
|
||||
margin: 40px 0 20px 0;
|
||||
}
|
||||
.course-tabs .right {
|
||||
float: right;
|
||||
|
||||
.sidebar-view-all {
|
||||
float: right;
|
||||
.new-post-btn {
|
||||
@include blue-button;
|
||||
font-size: 13px;
|
||||
line-height: 1.6em;
|
||||
@include standard-discussion-link;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.discussion-sidebar-following-list {
|
||||
li {
|
||||
@include clearfix;
|
||||
margin-bottom: 8px;
|
||||
border: none;
|
||||
.new-post-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 16px;
|
||||
height: 17px;
|
||||
margin: 8px 7px 0 0;
|
||||
background: url(../images/new-post-icon.png) no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-article {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
|
||||
.inner-wrapper {
|
||||
max-width: 1180px;
|
||||
min-width: 760px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
float: left;
|
||||
width: 32%;
|
||||
padding: 40px;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
label {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
@include standard-discussion-link;
|
||||
background: none;
|
||||
.form-topic-drop {
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
line-height: 1.3;
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.topic_dropdown_button {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
@include white-button;
|
||||
height: 40px;
|
||||
margin-top: 15px;
|
||||
border-color: #444;
|
||||
line-height: 36px;
|
||||
|
||||
.drop-arrow {
|
||||
float: right;
|
||||
color: #999;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic_menu_wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
background: #737373;
|
||||
border: 1px solid #333;
|
||||
box-shadow: 0 2px 50px rgba(0, 0, 0, .4);
|
||||
}
|
||||
|
||||
.topic_menu {
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #5f5f5f;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
color: #eee;
|
||||
@include transition(none);
|
||||
|
||||
&:hover,
|
||||
&.focused {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
li li {
|
||||
a {
|
||||
padding-left: 39px;
|
||||
background: url(../images/nested-icon.png) no-repeat 17px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
li li li {
|
||||
a {
|
||||
padding-left: 63px;
|
||||
background: url(../images/nested-icon.png) no-repeat 41px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topic_menu_search {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.form-topic-drop-search-input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 15px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 30px;
|
||||
border: 1px solid #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .25) inset;
|
||||
background: -webkit-linear-gradient(top, #eee, #fff);
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
color: #333;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-sidebar-tags-list li {
|
||||
@include clearfix;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sidebar-tag-count {
|
||||
color: #9a9a9a;
|
||||
font-size: .85em;
|
||||
line-height: 3em;
|
||||
}
|
||||
|
||||
.sidebar-following-name {
|
||||
.right-column {
|
||||
float: left;
|
||||
width: 80%;
|
||||
width: 68%;
|
||||
padding: 40px;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
.sidebar-vote-count {
|
||||
float: right;
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
color: #9a9a9a;
|
||||
.wmd-button {
|
||||
background: none;
|
||||
}
|
||||
|
||||
//user profile
|
||||
.wmd-button span {
|
||||
background: url(../images/new-post-icons-full.png) no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
@extend .sidebar;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.edit-post-form {
|
||||
width: 100%;
|
||||
margin-bottom: 40px;
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
.sidebar-username {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
line-height: 1.5em;
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sidebar-user-roles {
|
||||
color: darkGray;
|
||||
font-style: italic;
|
||||
margin-bottom: 15px;
|
||||
.post-cancel {
|
||||
@include white-button;
|
||||
float: left;
|
||||
margin: 10px 0 0 15px;
|
||||
}
|
||||
|
||||
.sidebar-threads-count, .sidebar-comments-count {
|
||||
|
||||
span {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
line-height: 1.5em;
|
||||
margin-right: 10px;
|
||||
.post-update {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
height: 37px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&:hover {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-post-title, .edit-post-tags {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 3px;
|
||||
border: 1px solid #aaa;
|
||||
font-size: 16px;
|
||||
font-family: $sans-serif;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) inset;
|
||||
}
|
||||
|
||||
.tagsinput {
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
|
||||
span.tag {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-non-content {
|
||||
margin-left: flex-gutter();
|
||||
}
|
||||
.new-post-form {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, .55);
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .5) inset, 0 1px 0 rgba(255, 255, 255, .5);
|
||||
@include clearfix;
|
||||
|
||||
/*** Post ***/
|
||||
.form-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.discussion-title {
|
||||
@include discussion-font;
|
||||
@include discussion-clickable;
|
||||
display: inline-block;
|
||||
font-size: $discussion-title-size;
|
||||
font-weight: bold;
|
||||
margin-bottom: flex-gutter(6);
|
||||
}
|
||||
.new-post-body .wmd-input {
|
||||
@include discussion-wmd-input;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
z-index: 1;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.discussion-title-wrapper {
|
||||
.discussion-watch-discussion, .discussion-unwatch-discussion {
|
||||
@include discussion-font;
|
||||
display: none;
|
||||
font-size: $comment-info-size;
|
||||
margin-left: 5px;
|
||||
.tagsinput {
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
|
||||
span.tag {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-body .wmd-preview {
|
||||
@include discussion-wmd-preview;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
//height: 50px;
|
||||
margin-top: -1px;
|
||||
padding: 25px 20px 10px 20px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-preview-label {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.new-post-title,
|
||||
.new-post-tags {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 3px;
|
||||
border: 1px solid #333;
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.submit {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
height: 37px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-cancel {
|
||||
@include white-button;
|
||||
border-color: #444;
|
||||
float: left;
|
||||
margin: 10px 0 0 15px;
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-top: 40px;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
margin-left: 8px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-right-wrapper {
|
||||
min-height: 40px;
|
||||
margin: 24px 0 24px 68px;
|
||||
|
||||
|
||||
.thread-tags {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.admin-actions {
|
||||
float: right;
|
||||
margin: 0.4em 1em 0 2em;
|
||||
padding: 0;
|
||||
.thread-tag {
|
||||
margin-right: 5px;
|
||||
padding: 3px 10px 6px;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #c5eeff;
|
||||
border: 1px solid #90c4d7;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.thread-title {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
font-size: 21px;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
section.user-profile {
|
||||
@extend .sidebar;
|
||||
display: table-cell;
|
||||
@include border-radius(3px 0 0 3px);
|
||||
border-right: 1px solid #ddd;
|
||||
@include box-shadow(none);
|
||||
background-color: $sidebar-color;
|
||||
|
||||
.user-profile {
|
||||
padding: 32px 36px;
|
||||
}
|
||||
|
||||
.sidebar-username {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-user-roles {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.sidebar-threads-count {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.sidebar-threads-count span,
|
||||
.sidebar-comments-count span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-toggle-moderator-button {
|
||||
@include blue-button;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.wmd-panel {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.wmd-button-bar {
|
||||
width: 100%;
|
||||
background-color: Silver;
|
||||
}
|
||||
|
||||
.wmd-input {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: #e9e9e9;
|
||||
border: 1px solid #c8c8c8;
|
||||
font-family: Monaco, 'Lucida Console', monospace;
|
||||
font-style: normal;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.6em;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-preview {
|
||||
position: relative;
|
||||
font-family: $sans-serif;
|
||||
padding: 25px 20px 10px 20px;
|
||||
margin-bottom: 5px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #c8c8c8;
|
||||
border-top-width: 0;
|
||||
@include border-radius(0 0 3px 3px);
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
|
||||
&:before {
|
||||
content: 'PREVIEW';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: $sans-serif;
|
||||
}
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.wmd-button-row {
|
||||
position: relative;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
}
|
||||
|
||||
.wmd-spacer {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin-left: 14px;
|
||||
|
||||
position: absolute;
|
||||
background-color: Silver;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-left: 2px;
|
||||
padding-right: 3px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wmd-button > span {
|
||||
display: inline-block;
|
||||
background-image: url('/static/images/wmd-buttons.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
left: 50px;
|
||||
}
|
||||
.wmd-spacer2 {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.wmd-spacer3 {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.wmd-prompt-background {
|
||||
background-color: Black;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
@extend .modal;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
padding: 20px;
|
||||
|
||||
> div {
|
||||
font-size: 0.8em;
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
b {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
> form > input[type="text"] {
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
> form > input[type="button"] {
|
||||
border: 1px solid #888;
|
||||
font-family: $sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> form > input[type="file"] {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container .discussion-body {
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
@include clearfix;
|
||||
|
||||
.sidebar {
|
||||
float: left;
|
||||
@include box-sizing(border-box);
|
||||
width: 31%;
|
||||
height: 550px;
|
||||
border: 1px solid #aaa;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
background: #f6f6f6;
|
||||
border-radius: 3px;
|
||||
border-right: 1px solid #bcbcbc;
|
||||
|
||||
&.fixed {
|
||||
@include box-sizing(border-box);
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
width: 32%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.browse-search {
|
||||
display: block;
|
||||
position: relative;
|
||||
height: 60px;
|
||||
border-bottom: 1px solid #a3a3a3;
|
||||
border-radius: 3px 0 0 0;
|
||||
|
||||
|
||||
.browse,
|
||||
.search {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .5), rgba(255, 255, 255, 0));
|
||||
background-color: #dcdcdc;
|
||||
@include transition(all .2s ease-out);
|
||||
|
||||
&:hover {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.browse {
|
||||
border-radius: 3px 0 0 0;
|
||||
box-shadow: -1px 0 0 #aaa inset;
|
||||
|
||||
&.is-open {
|
||||
.browse-topic-drop-btn span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.browse-topic-drop-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.is-dropped {
|
||||
.browse-topic-drop-btn {
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
border-color: #4b4b4b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-dropped {
|
||||
.browse-topic-drop-btn {
|
||||
background-color: #616161;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-dropped {
|
||||
.browse-topic-drop-icon {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
cursor: pointer;
|
||||
border-radius: 0 3px 0 0;
|
||||
|
||||
&.is-open {
|
||||
cursor: auto;
|
||||
|
||||
.post-search {
|
||||
padding: 0 10px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.post-search-field {
|
||||
cursor: text;
|
||||
pointer-events: auto;
|
||||
|
||||
&::-webkit-input-placeholder,
|
||||
&:-moz-placeholder,
|
||||
&:-ms-input-placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.browse-topic-drop-btn {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
z-index: 50;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px 0 0 0;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
@include transition(none);
|
||||
|
||||
.current-board {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 58px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .8);
|
||||
opacity: 0;
|
||||
@include transition(opacity .2s);
|
||||
}
|
||||
}
|
||||
|
||||
.browse-topic-drop-icon {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 21px;
|
||||
left: 50%;
|
||||
z-index: 100;
|
||||
width: 29px;
|
||||
height: 16px;
|
||||
margin-left: -12px;
|
||||
background: url(../images/browse-icon.png) no-repeat;
|
||||
opacity: 1;
|
||||
@include transition(none);
|
||||
}
|
||||
|
||||
.browse-topic-drop-menu-wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: -1px;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
background: #737373;
|
||||
border: 1px solid #4b4b4b;
|
||||
border-left: none;
|
||||
border-radius: 0 0 3px 3px;
|
||||
@include box-shadow(1px 0 0 #4b4b4b inset);
|
||||
|
||||
.browse-topic-drop-menu {
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
ul {
|
||||
position: inline;
|
||||
}
|
||||
|
||||
> li:first-child a {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0 20px;
|
||||
border-top: 1px solid #5f5f5f;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
color: #fff;
|
||||
@include clearfix;
|
||||
@include transition(none);
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.focused {
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
.board-name {
|
||||
float: left;
|
||||
width: 80%;
|
||||
margin: 13px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.unread {
|
||||
float: right;
|
||||
padding: 0 5px;
|
||||
margin-top: 13px;
|
||||
font-size: 11px;
|
||||
line-height: 22px;
|
||||
border-radius: 2px;
|
||||
@include linear-gradient(top, #4c4c4c, #5a5a5a);
|
||||
}
|
||||
}
|
||||
|
||||
li li {
|
||||
a {
|
||||
padding-left: 44px;
|
||||
background: url(../images/nested-icon.png) no-repeat 22px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
li li li {
|
||||
a {
|
||||
padding-left: 68px;
|
||||
background: url(../images/nested-icon.png) no-repeat 46px 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.browse-topic-drop-search {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.browse-topic-drop-search-input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 15px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 30px;
|
||||
border: 1px solid #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .25) inset;
|
||||
background: -webkit-linear-gradient(top, #eee, #fff);
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
color: #333;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.post-search {
|
||||
width: 100%;
|
||||
max-width: 30px;
|
||||
margin: auto;
|
||||
@include box-sizing(border-box);
|
||||
@include transition(all .2s);
|
||||
}
|
||||
|
||||
.post-search-field {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
margin: 14px auto;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #acacac;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .1) inset, 0 1px 0 rgba(255, 255, 255, .5);
|
||||
background: url(../images/search-icon.png) no-repeat 7px center #fff;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
text-indent: 30px;
|
||||
color: #333;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
@include transition(all .2s ease-out);
|
||||
|
||||
&::-webkit-input-placeholder,
|
||||
&:-moz-placeholder,
|
||||
&:-ms-input-placeholder {
|
||||
opacity: 0;
|
||||
@include transition(opacity .2s);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #4697c1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sort-bar {
|
||||
height: 27px;
|
||||
border-bottom: 1px solid #a3a3a3;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
|
||||
background-color: #aeaeae;
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset;
|
||||
|
||||
span,
|
||||
a {
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
line-height: 25px;
|
||||
color: #333;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .4);
|
||||
}
|
||||
|
||||
.sort-label {
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 6px !important;
|
||||
float: left;
|
||||
margin: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
height: 25px;
|
||||
padding-left: 25px;
|
||||
border-radius: 50%;
|
||||
background: url(../images/admin-actions-sprite.png) no-repeat;
|
||||
font-size: .8em;
|
||||
line-height: 25px;
|
||||
color: #b8b8b8;
|
||||
@include transition(color, .1s);
|
||||
display: block;
|
||||
height: 18px;
|
||||
padding: 0 9px;
|
||||
border-radius: 19px;
|
||||
color: #333;
|
||||
line-height: 17px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, .2));
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.admin-endorse {
|
||||
background-position: 0 0;
|
||||
&.active {
|
||||
@include linear-gradient(top, rgba(0, 0, 0, .3), rgba(0, 0, 0, 0));
|
||||
background-color: #999;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 1px rgba(0, 0, 0, .2) inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #63b141;
|
||||
background-position: 0 -75px;
|
||||
.post-list-wrapper {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
border-right: 1px solid transparent;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
background-color: #ddd;
|
||||
|
||||
.loading {
|
||||
padding: 15px 0;
|
||||
background: #f6f6f6;
|
||||
|
||||
.loading-animation {
|
||||
background-image: url(../images/spinner-on-grey.gif);
|
||||
}
|
||||
}
|
||||
|
||||
.more-pages a {
|
||||
background: #eee;
|
||||
font-size: 12px;
|
||||
line-height: 33px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-image: none;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 36px;
|
||||
padding: 0 10px 0 18px;
|
||||
margin-bottom: 1px;
|
||||
margin-right: -1px;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
|
||||
background-color: #fff;
|
||||
|
||||
&:hover {
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&.staff-post.staff-response {
|
||||
.staff-post-icon {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.staff-response-icon {
|
||||
top: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&.admin-edit {
|
||||
background-position: 0 -25px;
|
||||
|
||||
&:hover {
|
||||
color: #009fe2;
|
||||
background-position: 0 -100px;
|
||||
}
|
||||
.staff-post-icon,
|
||||
.staff-response-icon {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 3px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
background: url(../images/staff-icons.png) no-repeat;
|
||||
}
|
||||
|
||||
&.admin-delete {
|
||||
background-position: 0 -50px;
|
||||
.staff-post-icon {
|
||||
left: 2px;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #d45050;
|
||||
background-position: 0 -125px;
|
||||
.staff-response-icon {
|
||||
background-position: -13px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 34px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.read .title {
|
||||
font-weight: 400;
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
&.resolved:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 75px;
|
||||
width: 9px;
|
||||
height: 8px;
|
||||
background: url(../images/sidebar-resolved-icons.png) no-repeat;
|
||||
}
|
||||
|
||||
&.followed:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1px;
|
||||
width: 10px;
|
||||
height: 12px;
|
||||
background: url(../images/following-flag.png) no-repeat;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@include linear-gradient(top, #96e0fd, #61c7fc);
|
||||
border-color: #4697c1;
|
||||
box-shadow: 0 1px 0 #4697c1, 0 -1px 0 #4697c1;
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.staff-post-icon {
|
||||
background-position: 0 -13px;
|
||||
}
|
||||
|
||||
.staff-response-icon {
|
||||
background-position: -13px -13px;
|
||||
}
|
||||
|
||||
.votes-count,
|
||||
.comments-count {
|
||||
@include linear-gradient(top, #3994c7, #4da7d3);
|
||||
color: #fff;
|
||||
|
||||
&:after {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.followed:after {
|
||||
background-position: 0 -12px;
|
||||
}
|
||||
|
||||
&.resolved:before {
|
||||
background-position: 0 -8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 70%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.votes-count,
|
||||
.comments-count {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 32px;
|
||||
height: 16px;
|
||||
margin-top: 9px;
|
||||
border-radius: 2px;
|
||||
@include linear-gradient(top, #d4d4d4, #dfdfdf);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
.comments-count {
|
||||
position: relative;
|
||||
width: 25px;
|
||||
margin-left: 4px;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 3px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: url(../images/comment-icon-bottoms.png) no-repeat;
|
||||
background-position: 0 -5px;
|
||||
}
|
||||
|
||||
&.new {
|
||||
@include linear-gradient(top, #84d7fe, #99e0fe);
|
||||
color: #333;
|
||||
|
||||
&:after {
|
||||
color: #99e0fe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.bottom-post-status {
|
||||
padding: 30px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.discussion-column {
|
||||
float: right;
|
||||
@include box-sizing(border-box);
|
||||
width: 68%;
|
||||
max-width: 800px;
|
||||
min-height: 500px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
|
||||
&.sidebar-fixed {
|
||||
margin-left: 32%;
|
||||
}
|
||||
}
|
||||
|
||||
.blank-slate h1 {
|
||||
margin-top: 195px;
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.blank-slate,
|
||||
.discussion-article {
|
||||
position: relative;
|
||||
padding: 40px;
|
||||
min-height: 468px;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.posted-details {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
|
||||
.username {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
span {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.post-context{
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dogear {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
width: 52px;
|
||||
height: 51px;
|
||||
background: url(../images/follow-dog-ear.png) 0 -52px no-repeat;
|
||||
@include transition(none);
|
||||
|
||||
&.is-followed {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-post {
|
||||
padding: 10px 20px;
|
||||
|
||||
> header .vote-btn {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.post-tools {
|
||||
@include clearfix;
|
||||
margin-top: 15px;
|
||||
|
||||
.ui-icon {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin-right: 4px;
|
||||
background: url(../images/small-grey-arrows.png) no-repeat;
|
||||
|
||||
&.expand {
|
||||
margin-top: 5px;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
margin-top: 6px;
|
||||
background-position: -13px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-post header,
|
||||
.responses li header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.responses {
|
||||
list-style: none;
|
||||
margin-top: 40px;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
position: relative;
|
||||
margin: 0 -10px 30px;
|
||||
padding: 26px 30px 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b2b2b2;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
|
||||
@include animation(fadeIn .3s);
|
||||
|
||||
&.staff {
|
||||
padding-top: 38px;
|
||||
border-color: #009fe2;
|
||||
}
|
||||
|
||||
.staff-banner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
padding: 1px 5px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 2px 2px 0 0;
|
||||
background: #009fe2;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.posted-by {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.vote-btn {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
float: right;
|
||||
display: block;
|
||||
height: 27px;
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #b2b2b2;
|
||||
@include linear-gradient(top, #fff 35%, #ebebeb);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .15);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 25px;
|
||||
color: #333;
|
||||
|
||||
.plus-icon {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 8px 6px 0 0;
|
||||
background: url(../images/vote-plus-icon.png) no-repeat;
|
||||
font-size: 18px;
|
||||
text-indent: -9999px;
|
||||
color: #17b429;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.is-cast {
|
||||
border-color: #379a42;
|
||||
@include linear-gradient(top, #50cc5e, #3db84b);
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, .4) inset, 0 1px 2px rgba(0, 0, 0, .2);
|
||||
|
||||
.plus-icon {
|
||||
background-position: 0 -10px;
|
||||
color: #336a39;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.endorse-btn {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
margin-right: 10px;
|
||||
border-radius: 27px;
|
||||
border: 1px solid #a0a0a0;
|
||||
@include linear-gradient(top, #fff 35%, #ebebeb);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
|
||||
|
||||
.check-icon {
|
||||
display: block;
|
||||
width: 13px;
|
||||
height: 12px;
|
||||
margin: 8px auto;
|
||||
background: url(../images/endorse-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
&.is-endorsed {
|
||||
border: 1px solid #4697c1;
|
||||
@include linear-gradient(top, #6dccf1, #38a8e5);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, .4) inset;
|
||||
|
||||
.check-icon {
|
||||
background-position: 0 -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comments {
|
||||
.admin-actions {
|
||||
margin-top: 0;
|
||||
list-style: none;
|
||||
margin-top: 20px;
|
||||
padding: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
|
||||
li {
|
||||
margin-bottom: 2px !important;
|
||||
li {
|
||||
background: #f6f6f6;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.comment-form {
|
||||
background: #eee;
|
||||
@include clearfix;
|
||||
|
||||
.comment-form-input {
|
||||
padding: 5px 10px;
|
||||
background-color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-left: 0;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
.discussion-submit-comment {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&.admin-endorse {
|
||||
background-position: 0 -150px;
|
||||
.wmd-input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-position: 0 -225px;
|
||||
}
|
||||
}
|
||||
.discussion-errors {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&.admin-edit {
|
||||
background-position: 0 -175px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-position: 0 -250px;
|
||||
}
|
||||
}
|
||||
.response-body {
|
||||
font-size: 13px;
|
||||
padding: 10px 20px;
|
||||
|
||||
&.admin-delete {
|
||||
background-position: 0 -200px;
|
||||
p + p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-position: 0 -275px;
|
||||
}
|
||||
}
|
||||
.posted-details {
|
||||
margin: 0 20px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.staff-label {
|
||||
margin-left: 2px;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
background: #009FE2;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-form {
|
||||
padding: 8px 20px;
|
||||
|
||||
.wmd-input {
|
||||
@include transition(all .2s);
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
background: transparent;
|
||||
|
||||
span {
|
||||
background-image: url(../images/wmd-buttons-transparent.png);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-form-input {
|
||||
width: 100%;
|
||||
height: 31px;
|
||||
padding: 0 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #b2b2b2;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .1) inset;
|
||||
@include transition(border-color .1s);
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
border-color: #4697c1;
|
||||
}
|
||||
}
|
||||
|
||||
.moderator-actions {
|
||||
margin: 0;
|
||||
margin-top: 20px;
|
||||
padding: 0;
|
||||
@include clearfix;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 12px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b2b2b2;
|
||||
@include linear-gradient(top, #fff 35%, #ebebeb);
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
color: #737373;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.delete-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 8px 4px 0 0;
|
||||
background: url(../images/moderator-delete-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 7px 4px 0 0;
|
||||
background: url(../images/moderator-edit-icon.png) no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,744 +1615,746 @@ $tag-text-color: #5b614f;
|
||||
|
||||
|
||||
|
||||
/*** thread ***/
|
||||
|
||||
.thread {
|
||||
//display: none;
|
||||
.main-article.new {
|
||||
display: none;
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
background-color: lightyellow;
|
||||
.new-post-form {
|
||||
margin-top: 20px;
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
.new-post-form .submit {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.new-post-form .options {
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
|
||||
label {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.discussion-reply-new {
|
||||
padding: 20px;
|
||||
@include clearfix;
|
||||
@include transition(opacity .2s);
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-button-bar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.wmd-input {
|
||||
@include discussion-wmd-input;
|
||||
}
|
||||
|
||||
.wmd-preview {
|
||||
@include discussion-wmd-preview;
|
||||
}
|
||||
|
||||
.reply-post-control {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.discussion-submit-post {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.global-discussion-actions {
|
||||
height: 60px;
|
||||
@include linear-gradient(top, #ebebeb, #d9d9d9);
|
||||
border-radius: 0 3px 0 0;
|
||||
border-bottom: 1px solid #bcbcbc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.discussion-module {
|
||||
@extend .discussion-body;
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: #f6f6f6 !important;
|
||||
border-radius: 3px;
|
||||
|
||||
header {
|
||||
.anonymous{
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.responses {
|
||||
margin-top: 40px;
|
||||
|
||||
> li {
|
||||
margin: 0 20px 20px !important;
|
||||
padding: 26px 30px 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
background-image: url(../images/spinner-on-grey.gif);
|
||||
}
|
||||
|
||||
.discussion-show {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
|
||||
&.shown {
|
||||
.show-hide-discussion-icon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thread-title {
|
||||
@include discussion-font;
|
||||
@include discussion-clickable;
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
font-size: $comment-title-size;
|
||||
font-weight: bold;
|
||||
line-height: 1.4em;
|
||||
.show-hide-discussion-icon {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
margin-right: 6px;
|
||||
width: 21px;
|
||||
height: 19px;
|
||||
background: url(../images/show-hide-discussion-icon.png) no-repeat;
|
||||
background-position: -21px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-btn {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
section.discussion {
|
||||
margin-top: 30px;
|
||||
|
||||
.threads {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.thread-body, .content-body {
|
||||
@include discussion-font;
|
||||
font-size: $post-font-size;
|
||||
margin-bottom: 4px;
|
||||
margin-top: 3px;
|
||||
/* Course content p has a default margin-bottom of 1.416em, this is just to reset that */
|
||||
.discussion-thread {
|
||||
padding: 0;
|
||||
@include transition(all .25s);
|
||||
|
||||
.dogear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
padding: 20px 0;
|
||||
|
||||
.dogear{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.discussion-article {
|
||||
border: 1px solid #b2b2b2;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@include discussion-font;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.discussion-article {
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-width: 0;
|
||||
background: #fff;
|
||||
min-height: 0;
|
||||
padding: 10px 10px 15px 10px;
|
||||
box-shadow: 0 1px 0 #ddd;
|
||||
@include transition(all .2s);
|
||||
|
||||
.discussion-post {
|
||||
padding: 12px 20px 0 20px;
|
||||
@include clearfix;
|
||||
|
||||
.inline-comment-count {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
float: right;
|
||||
display: block;
|
||||
height: 27px;
|
||||
margin-top: 6px;
|
||||
margin-right: 8px;
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 25px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.posted-details {
|
||||
margin-top: 4px;
|
||||
|
||||
.username {
|
||||
display: inline;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-body {
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.post-tools {
|
||||
margin-left: 20px;
|
||||
margin-top: 5px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
|
||||
&.expand-post:before {
|
||||
content: '▾ ';
|
||||
}
|
||||
|
||||
&.collapse-post:before {
|
||||
content: '▴ ';
|
||||
}
|
||||
|
||||
&.collapse-post {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responses {
|
||||
margin-top: 10px;
|
||||
|
||||
header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.posted-by {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.response-body {
|
||||
margin-bottom: 0.2em;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-reply-new {
|
||||
.wmd-input {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
// Content that is hidden by default in the inline view
|
||||
.post-extended-content{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-article {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
|
||||
.inner-wrapper {
|
||||
max-width: 1180px;
|
||||
min-width: 760px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.new-post-form {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
padding: 30px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, .55);
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
.form-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.new-post-body .wmd-input {
|
||||
@include discussion-wmd-input;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
z-index: 1;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-body .wmd-preview {
|
||||
@include discussion-wmd-preview;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
//height: 50px;
|
||||
margin-top: -1px;
|
||||
padding: 25px 20px 10px 20px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-preview-label {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.new-post-title,
|
||||
.new-post-tags {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 3px;
|
||||
border: 1px solid #333;
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tagsinput {
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
|
||||
span.tag {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submit {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
height: 37px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-cancel {
|
||||
@include white-button;
|
||||
float: left;
|
||||
margin: 10px 0 0 15px;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-top: 5px;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
margin-left: 8px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thread-tags {
|
||||
display: inline-block;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.context{
|
||||
margin-top: 1em;
|
||||
font-size: $comment-font-size;
|
||||
.thread-tag {
|
||||
padding: 3px 10px 6px;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #c5eeff;
|
||||
border: 1px solid #90c4d7;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.info {
|
||||
@include discussion-font;
|
||||
color: gray;
|
||||
font-size: $comment-info-size;
|
||||
font-style: italic;
|
||||
margin-top: 1em;
|
||||
.thread-title {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
font-size: 21px;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: #1C71DD;
|
||||
}
|
||||
.new-post-btn {
|
||||
@include blue-button;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
display: inline;
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.new-post-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 16px;
|
||||
height: 17px;
|
||||
margin: 8px 7px 0 0;
|
||||
background: url(../images/new-post-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
.show-comments-wrapper {
|
||||
display: inline;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.moderator-actions {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.discussion-actions {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
section.pagination {
|
||||
margin-top: 30px;
|
||||
|
||||
nav.discussion-paginator {
|
||||
float: right;
|
||||
|
||||
ol {
|
||||
li {
|
||||
display: inline;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-link {
|
||||
@include discussion-font;
|
||||
color: #1d9dd9;
|
||||
display: inline;
|
||||
|
||||
&.discussion-unfollow-thread {
|
||||
color: #dea03e;
|
||||
}
|
||||
}
|
||||
|
||||
.author-moderator:after{
|
||||
content: " (moderator)"
|
||||
}
|
||||
|
||||
.author-administrator:after{
|
||||
content: " (instructor)"
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-content {
|
||||
border-top: lightgray 1px solid;
|
||||
overflow: hidden;
|
||||
// padding: 1.5% 0;
|
||||
|
||||
.discussion-reply-new {
|
||||
@include discussion-font;
|
||||
margin-left: 68px;
|
||||
|
||||
.reply-body {
|
||||
@include discussion-font;
|
||||
display: block;
|
||||
font-size: $post-font-size;
|
||||
margin-top: 10px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.reply-post-control {
|
||||
margin-top: 1%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Role based styles
|
||||
.role-moderator{
|
||||
background-color: #eafcfc;
|
||||
}
|
||||
.role-administrator{
|
||||
background-color: #eafcea;
|
||||
}
|
||||
//COMMENT STYLES
|
||||
.comments {
|
||||
overflow: hidden;
|
||||
|
||||
.discussion-votes {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.discussion-right-wrapper {
|
||||
margin: 10px 0 10px 68px;
|
||||
}
|
||||
|
||||
.comment {
|
||||
margin-left: 68px;
|
||||
.comment-body, .content-body {
|
||||
@include discussion-font;
|
||||
color: black;
|
||||
display: block;
|
||||
font-size: $comment-font-size;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&.endorsed {
|
||||
> .discussion-content {
|
||||
background-color: #fcfcea;
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding-right: 0.5em;
|
||||
a {
|
||||
@include white-button;
|
||||
}
|
||||
}
|
||||
|
||||
li.current-page{
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*** Sorting ***/
|
||||
|
||||
.discussion-sort {
|
||||
float: right;
|
||||
font-size: 0.8em;
|
||||
margin-top: -36px;
|
||||
|
||||
.discussion-label {
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 0 14px;
|
||||
line-height: 34px;
|
||||
.new-post-body {
|
||||
.wmd-panel {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.discussion-sort-link {
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 0 14px;
|
||||
line-height: 34px;
|
||||
|
||||
&:hover {
|
||||
color: #1C71DD;
|
||||
text-decoration: none;
|
||||
}
|
||||
.wmd-button-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.discussion-sort-link.sorted {
|
||||
color: #000;
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
}
|
||||
|
||||
/*** Search ***/
|
||||
|
||||
.search-wrapper-inline {
|
||||
display: inline-block;
|
||||
margin-top: 3%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
margin-bottom: 50px;
|
||||
margin-left: .5%;
|
||||
}
|
||||
|
||||
.discussion-search-form {
|
||||
display: inline-block;
|
||||
margin-bottom: 1%;
|
||||
width: flex-grid(12);
|
||||
|
||||
.discussion-link {
|
||||
@include button(simple, #999);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
margin-left: 1%;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 2px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.discussion-search-text {
|
||||
@include discussion-font;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
float: left;
|
||||
font: inherit;
|
||||
.wmd-input {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: #e9e9e9;
|
||||
border: 1px solid #c8c8c8;
|
||||
font-family: Monaco, 'Lucida Console', monospace;
|
||||
font-style: normal;
|
||||
// width: 72%;
|
||||
width: flex-grid(8);
|
||||
margin-left: flex-grid(1);
|
||||
}
|
||||
}
|
||||
font-size: 0.8em;
|
||||
line-height: 1.6em;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
|
||||
.search-within {
|
||||
display: block;
|
||||
margin-bottom: 3%;
|
||||
}
|
||||
|
||||
.discussion-search-within-board {
|
||||
font: inherit;
|
||||
font-size: $post-font-size;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/*** buttons ***/
|
||||
|
||||
.control-button {
|
||||
@include button;
|
||||
@include discussion-font;
|
||||
background-color: #959595;
|
||||
@include background-image(linear-gradient(top, #959595, #7B7B7B));
|
||||
border: 1px solid #6F6F6F;
|
||||
@include box-shadow(inset 0 1px 0 #A2A2A2, 0 0 3px #CCC);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
margin-bottom: 3%;
|
||||
padding-top: 9px;
|
||||
width: inherit;
|
||||
text-decoration: none;
|
||||
text-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #A2A2A2;
|
||||
@include background-image(linear-gradient(top, #A2A2A2, #7B7B7B));
|
||||
border: 1px solid #555;
|
||||
@include box-shadow(inset 0 1px 0 #BBB, 0 0 3px #CCC);
|
||||
}
|
||||
}
|
||||
|
||||
.follow-wrapper {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/*** votes ***/
|
||||
|
||||
.discussion-votes {
|
||||
float: left;
|
||||
width: 60px;
|
||||
margin-top: 18px;
|
||||
text-align: center;
|
||||
|
||||
.discussion-vote {
|
||||
display: block;
|
||||
width: 50px;
|
||||
height: 17px;
|
||||
margin: auto;
|
||||
background: url(../images/vote-arrows.png) no-repeat;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
@include hide-text;
|
||||
@include transition(all, 0, easeOut);
|
||||
&::-webkit-input-placeholder {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-vote-up {
|
||||
.wmd-preview {
|
||||
position: relative;
|
||||
font-family: $sans-serif;
|
||||
padding: 25px 20px 10px 20px;
|
||||
margin-bottom: 5px;
|
||||
background-position: -50px -3px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #c8c8c8;
|
||||
border-top-width: 0;
|
||||
@include border-radius(0 0 3px 3px);
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
|
||||
&:hover {
|
||||
background-position: -50px -5px;
|
||||
@include transition-duration(0.05s);
|
||||
&:before {
|
||||
content: 'PREVIEW';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
&.voted {
|
||||
background-position: 0 -3px;
|
||||
color: #1C71DD;
|
||||
@include transition-duration(0);
|
||||
p {
|
||||
font-family: $sans-serif;
|
||||
}
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.discussion-vote-down {
|
||||
margin-top: 7px;
|
||||
background-position: -50px -30px;
|
||||
|
||||
&:hover {
|
||||
background-position: -50px -28px;
|
||||
@include transition-duration(0.05s);
|
||||
}
|
||||
|
||||
&.voted {
|
||||
background-position: 0 -30px;
|
||||
color: #1C71DD;
|
||||
@include transition-duration(0);
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-vote-count {
|
||||
@include discussion-font;
|
||||
font-size: $post-font-size;
|
||||
.wmd-button-row {
|
||||
position: relative;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
}
|
||||
|
||||
.discussion-votes-point {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
color: #9a9a9a;
|
||||
.wmd-spacer {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin-left: 14px;
|
||||
|
||||
position: absolute;
|
||||
background-color: Silver;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-left: 2px;
|
||||
padding-right: 3px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.wmd-button > span {
|
||||
display: inline-block;
|
||||
background-image: url(../images/new-post-icons-full.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
left: 50px;
|
||||
}
|
||||
.wmd-spacer2 {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.wmd-spacer3 {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.wmd-prompt-background {
|
||||
background-color: Black;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
@extend .modal;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
padding: 20px;
|
||||
|
||||
> div {
|
||||
font-size: 0.8em;
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
b {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
> form > input[type="text"] {
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
> form > input[type="button"] {
|
||||
border: 1px solid #888;
|
||||
font-family: $sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> form > input[type="file"] {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*** new post ***/
|
||||
|
||||
.new-post-form, .discussion-thread-edit {
|
||||
|
||||
.title-input, .body-input {
|
||||
display: block !important;
|
||||
font: inherit;
|
||||
font-style: normal;
|
||||
//width: $discussion-input-width !important;
|
||||
|
||||
.wmd-button-row {
|
||||
// this is being hidden now because the inline styles to position the icons are not being written
|
||||
position: relative;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
span {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-image: url("/static/images/wmd-buttons.png");
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.wmd-spacer2 {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.wmd-spacer3 {
|
||||
left: 300px;
|
||||
}
|
||||
.edit-post-form {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
.form-row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.new-post-similar-posts-wrapper {
|
||||
@include border-radius(3px);
|
||||
border: 1px solid #EEE;
|
||||
font-size: $post-font-size;
|
||||
line-height: 150%;
|
||||
margin-top: 1%;
|
||||
padding: 1% 1.5%;
|
||||
.post-cancel {
|
||||
@include white-button;
|
||||
float: left;
|
||||
margin: 10px 0 0 15px;
|
||||
}
|
||||
|
||||
.hide-similar-posts {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.new-post-similar-posts {
|
||||
font: inherit;
|
||||
.similar-post {
|
||||
display: block;
|
||||
line-height: 150%;
|
||||
|
||||
.post-update {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
height: 37px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&:hover {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-errors {
|
||||
color: #8F0E0E;
|
||||
display: block;
|
||||
margin-left: -5%;
|
||||
}
|
||||
|
||||
.new-post-body {
|
||||
.edit-post-title, .edit-post-tags {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 3px;
|
||||
border: 1px solid #aaa;
|
||||
font-size: 16px;
|
||||
font-family: $sans-serif;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) inset;
|
||||
}
|
||||
|
||||
.tagsinput {
|
||||
background: #FAFAFA;
|
||||
border: 1px solid #C8C8C8;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
-webkit-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6), inset 0 0 3px 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6), inset 0 0 3px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6), inset 0 0 3px 0 rgba(0, 0, 0, 0.1);
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin-top: flex-gutter();
|
||||
vertical-align: top;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
|
||||
span.tag {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-content-edit, .discussion-reply-new, .new-post-form {
|
||||
margin: 10px 0 10px 0;
|
||||
|
||||
.discussion-errors {
|
||||
color: #8F0E0E;
|
||||
display: block;
|
||||
font: inherit;
|
||||
font-size: $post_font_size;
|
||||
list-style: none;
|
||||
margin-left: -3%;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #1C71DD;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.new-post-title {
|
||||
display: block;
|
||||
padding: 5px 12px;
|
||||
border-width: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.thread-title-edit {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
.new-post-title {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.wmd-button-row {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.wmd-input {
|
||||
height: 100px;
|
||||
@include border-radius(3px);
|
||||
}
|
||||
|
||||
.wmd-preview {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.post-options {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.post-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tagsinput {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-control {
|
||||
margin-left: 75%;
|
||||
margin-top: 1%;
|
||||
|
||||
.discussion-cancel-post {
|
||||
margin-right: 1.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-post-control {
|
||||
|
||||
.discussion-cancel-post {
|
||||
margin-right: 1.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-post-control {
|
||||
margin-top: 1%;
|
||||
|
||||
.discussion-cancel-update {
|
||||
margin-right: 1.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.control-button {
|
||||
@include button;
|
||||
@include discussion-font;
|
||||
margin-right: 16px;
|
||||
padding-top: 9px;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
width: inherit;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
font-family: $sans-serif;
|
||||
font-size: .8em;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-content-edit {
|
||||
margin: 3%;
|
||||
}
|
||||
|
||||
.new-post-form {
|
||||
margin: 10px 0 40px 0;
|
||||
}
|
||||
|
||||
.discussion-reply-new {
|
||||
|
||||
.discussion-auto-watch {
|
||||
margin-left: 2%;
|
||||
}
|
||||
.thread-tags {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.thread-tag {
|
||||
background: $tag-background-color;
|
||||
border: 1px solid $tag-border-color;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
color: $tag-text-color;
|
||||
float: left;
|
||||
padding: 3px 10px 6px;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #c5eeff;
|
||||
border: 1px solid #90c4d7;
|
||||
font-size: 13px;
|
||||
margin: 5px 7px 5px 0;
|
||||
padding: 5px 7px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #7b8761;
|
||||
color: #2f381c;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*** pagination ***/
|
||||
|
||||
.discussion-paginator {
|
||||
font-size: $post-font-size;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0 5px;
|
||||
|
||||
a {
|
||||
background: #EEE;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
color: black;
|
||||
font-weight: normal;
|
||||
padding: 4px 10px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: #DDD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-discussion, .forum-discussion, .user-discussion {
|
||||
.new-post-form {
|
||||
margin: 24px 60px;
|
||||
|
||||
.post-options {
|
||||
margin: 8px 0 16px 0;
|
||||
overflow: hidden;
|
||||
|
||||
label {
|
||||
margin-right: 15px;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.post-control {
|
||||
overflow: hidden;
|
||||
margin: 0 0 5% 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*** base editor styles ***/
|
||||
|
||||
.wmd-panel {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.wmd-button-bar {
|
||||
width: 100%;
|
||||
background-color: Silver;
|
||||
}
|
||||
|
||||
.wmd-input {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: #e9e9e9;
|
||||
border: 1px solid #c8c8c8;
|
||||
font-family: Monaco, 'Lucida Console', monospace;
|
||||
font-style: normal;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.6em;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-preview {
|
||||
position: relative;
|
||||
font-family: $sans-serif;
|
||||
padding: 25px 20px 10px 20px;
|
||||
margin-bottom: 5px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #c8c8c8;
|
||||
border-top-width: 0;
|
||||
@include border-radius(0 0 3px 3px);
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
|
||||
&:before {
|
||||
content: 'PREVIEW';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: $sans-serif;
|
||||
}
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.wmd-button-row {
|
||||
position: relative;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
}
|
||||
|
||||
.wmd-spacer {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin-left: 14px;
|
||||
|
||||
position: absolute;
|
||||
background-color: Silver;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-left: 2px;
|
||||
padding-right: 3px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wmd-button > span {
|
||||
background-image: url('/static/images/wmd-buttons.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
left: 50px;
|
||||
}
|
||||
.wmd-spacer2 {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.wmd-spacer3 {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.wmd-prompt-background {
|
||||
background-color: Black;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
border: 1px solid #999999;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog > div {
|
||||
font-size: 0.8em;
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
.wmd-prompt-dialog > form > input[type="text"] {
|
||||
border: 1px solid #999999;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog > form > input[type="button"] {
|
||||
border: 1px solid #888888;
|
||||
font-family: trebuchet MS, helvetica, sans-serif;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
.discussion-user-threads {
|
||||
@extend .discussion-module
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ span {
|
||||
font: normal 1em/1.6em $sans-serif;
|
||||
color: $base-font-color;
|
||||
}
|
||||
|
||||
/* Fix for CodeMirror: prevent top-level span from affecting deeply-embedded span in CodeMirror */
|
||||
.CodeMirror span {
|
||||
font: inherit;
|
||||
@@ -136,3 +137,23 @@ span.edx {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: -10px;
|
||||
background: url(../images/spinner.gif) no-repeat;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0 3px;
|
||||
border-radius: 2px;
|
||||
background-color: #f7e9a8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// Course base / layout styles
|
||||
@import 'course/layout/courseware_header';
|
||||
@import 'course/layout/footer';
|
||||
@import 'course/base/mixins';
|
||||
@import 'course/base/base';
|
||||
@import 'course/base/extends';
|
||||
@import 'module/module-styles.scss';
|
||||
|
||||
535
lms/static/sass/course/_discussions-inline.scss
Normal file
@@ -0,0 +1,535 @@
|
||||
.discussion-module {
|
||||
@extend .discussion-body;
|
||||
margin: 20px 0;
|
||||
padding: 20px 20px 28px 20px;
|
||||
background: #f6f6f6 !important;
|
||||
border-radius: 3px;
|
||||
|
||||
.responses {
|
||||
margin-top: 40px;
|
||||
|
||||
> li {
|
||||
margin: 0 20px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-show {
|
||||
display: block;
|
||||
width: 200px;
|
||||
margin: auto;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
|
||||
&.shown {
|
||||
.show-hide-discussion-icon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.show-hide-discussion-icon {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
margin-right: 6px;
|
||||
width: 21px;
|
||||
height: 19px;
|
||||
background: url(../images/show-hide-discussion-icon.png) no-repeat;
|
||||
background-position: -21px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
section.discussion {
|
||||
margin-top: 20px;
|
||||
|
||||
.threads {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Course content p has a default margin-bottom of 1.416em, this is just to reset that */
|
||||
.discussion-thread {
|
||||
padding: 0;
|
||||
@include transition(all .25s);
|
||||
|
||||
.dogear,
|
||||
.vote-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
padding: 20px 0;
|
||||
|
||||
.dogear,
|
||||
.vote-btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.discussion-article {
|
||||
border: 1px solid #b2b2b2;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.discussion-article {
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-width: 0;
|
||||
background: #fff;
|
||||
min-height: 0;
|
||||
padding: 10px 10px 15px 10px;
|
||||
box-shadow: 0 1px 0 #ddd;
|
||||
@include transition(all .2s);
|
||||
|
||||
.discussion-post {
|
||||
padding: 12px 20px 0 20px;
|
||||
@include clearfix;
|
||||
|
||||
header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
|
||||
h3 {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-body {
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.post-tools {
|
||||
margin-left: 20px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
|
||||
&.expand-post:before {
|
||||
content: '▾ ';
|
||||
}
|
||||
|
||||
&.collapse-post:before {
|
||||
content: '▴ ';
|
||||
}
|
||||
|
||||
&.collapse-post {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.responses {
|
||||
margin-top: 10px;
|
||||
|
||||
header {
|
||||
padding-bottom: 0em;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.posted-by {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
.response-body {
|
||||
margin-bottom: 0.2em;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-reply-new {
|
||||
.wmd-input {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
// Content that is hidden by default in the inline view
|
||||
.post-extended-content{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-article {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
|
||||
.inner-wrapper {
|
||||
max-width: 1180px;
|
||||
min-width: 760px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.new-post-form {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
padding: 30px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, .55);
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
.form-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.new-post-body .wmd-input {
|
||||
@include discussion-wmd-input;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
z-index: 1;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-body .wmd-preview {
|
||||
@include discussion-wmd-preview;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
//height: 50px;
|
||||
margin-top: -1px;
|
||||
padding: 25px 20px 10px 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #333;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-preview-label {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.new-post-title,
|
||||
.new-post-tags {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #333;
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: #333;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
}
|
||||
|
||||
.new-post-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tagsinput {
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
font-family: 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
|
||||
|
||||
span.tag {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submit {
|
||||
@include blue-button;
|
||||
float: left;
|
||||
height: 37px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 2px;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-cancel {
|
||||
@include white-button;
|
||||
float: left;
|
||||
margin: 10px 0 0 15px;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-top: 5px;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
margin-left: 8px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thread-tags {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.thread-tag {
|
||||
padding: 3px 10px 6px;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #c5eeff;
|
||||
border: 1px solid #90c4d7;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.thread-title {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
font-size: 21px;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-btn {
|
||||
@include blue-button;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.new-post-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 16px;
|
||||
height: 17px;
|
||||
margin: 8px 7px 0 0;
|
||||
background: url(../images/new-post-icon.png) no-repeat;
|
||||
}
|
||||
|
||||
.moderator-actions {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
section.pagination {
|
||||
margin-top: 30px;
|
||||
|
||||
nav.discussion-paginator {
|
||||
float: right;
|
||||
|
||||
ol {
|
||||
li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding-right: 0.5em;
|
||||
a {
|
||||
@include white-button;
|
||||
}
|
||||
}
|
||||
|
||||
li.current-page{
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-post-body {
|
||||
.wmd-panel {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.wmd-button-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wmd-input {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: #e9e9e9;
|
||||
border: 1px solid #c8c8c8;
|
||||
font-family: Monaco, 'Lucida Console', monospace;
|
||||
font-style: normal;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.6em;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-preview {
|
||||
position: relative;
|
||||
font-family: $sans-serif;
|
||||
padding: 25px 20px 10px 20px;
|
||||
margin-bottom: 5px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #c8c8c8;
|
||||
border-top-width: 0;
|
||||
@include border-radius(0 0 3px 3px);
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
|
||||
&:before {
|
||||
content: 'PREVIEW';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: $sans-serif;
|
||||
}
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.wmd-button-row {
|
||||
position: relative;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
@include transition(all, .2s, easeOut);
|
||||
}
|
||||
|
||||
.wmd-spacer {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin-left: 14px;
|
||||
|
||||
position: absolute;
|
||||
background-color: Silver;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding-left: 2px;
|
||||
padding-right: 3px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.wmd-button > span {
|
||||
display: inline-block;
|
||||
background-image: url(../images/new-post-icons-full.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px 0px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wmd-spacer1 {
|
||||
left: 50px;
|
||||
}
|
||||
.wmd-spacer2 {
|
||||
left: 175px;
|
||||
}
|
||||
|
||||
.wmd-spacer3 {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.wmd-prompt-background {
|
||||
background-color: Black;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
@extend .modal;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.wmd-prompt-dialog {
|
||||
padding: 20px;
|
||||
|
||||
> div {
|
||||
font-size: 0.8em;
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
b {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
> form > input[type="text"] {
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
> form > input[type="button"] {
|
||||
border: 1px solid #888;
|
||||
font-family: $sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> form > input[type="file"] {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wmd-button-row {
|
||||
// this is being hidden now because the inline styles to position the icons are not being written
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.wmd-button {
|
||||
span {
|
||||
background-image: url("/static/images/wmd-buttons.png");
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,11 +39,6 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
form {
|
||||
label {
|
||||
display: block;
|
||||
@@ -102,3 +97,90 @@ img {
|
||||
background: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99999;
|
||||
padding: 0 10px;
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, .85);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 26px;
|
||||
color: #fff;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
@include transition(opacity .1s);
|
||||
|
||||
&:after {
|
||||
content: '▾';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
left: 50%;
|
||||
margin-left: -7px;
|
||||
font-size: 20px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
}
|
||||
}
|
||||
|
||||
.test-class {
|
||||
border: 1px solid #f00;
|
||||
}
|
||||
|
||||
|
||||
.toast-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 99999;
|
||||
max-width: 350px;
|
||||
padding: 15px 20px 17px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #333;
|
||||
background: -webkit-linear-gradient(top, rgba(255, 255, 255, .1), rgba(255, 255, 255, 0)) rgba(30, 30, 30, .92);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 1px 0 rgba(255, 255, 255, .1) inset;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
-webkit-transition: all .2s;
|
||||
|
||||
p, span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 25px;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
|
||||
.close-icon {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@include dark-grey-button;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
57
lms/static/sass/course/base/_mixins.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
@mixin blue-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #2d81ad;
|
||||
@include linear-gradient(top, #6dccf1, #38a8e5);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
border-color: #297095;
|
||||
@include linear-gradient(top, #4fbbe4, #2090d0);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin white-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #444;
|
||||
@include linear-gradient(top, #eee, #ccc);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
@include linear-gradient(top, #fff, #ddd);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dark-grey-button {
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #222;
|
||||
background: -webkit-linear-gradient(top, #777, #555);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
background: -webkit-linear-gradient(top, #888, #666);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ def url_class(url):
|
||||
% if staff_access:
|
||||
<li class="instructor"><a href="${reverse('instructor_dashboard', args=[course.id])}" class="${url_class('instructor')}">Instructor</a></li>
|
||||
% endif
|
||||
<%block name="extratabs" />
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</%def>
|
||||
|
||||
<%def name="render_content_with_comments(content, *args, **kwargs)">
|
||||
<div class="${content['type'] | h}${helpers.show_if(' endorsed', content.get('endorsed')) | h}" _id="${content['id'] | h}" _discussion_id="${content.get('commentable_id', '') | h}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous')) | h}">
|
||||
<div class="${content['type'] | h}${' endorsed' if content.get('endorsed') else ''| h}" _id="${content['id'] | h}" _discussion_id="${content.get('commentable_id', '') | h}" _author_id="${content['user_id'] if (not content.get('anonymous')) else '' | h}">
|
||||
${render_content(content, *args, **kwargs)}
|
||||
${render_comments(content.get('children', []), *args, **kwargs)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<%inherit file="../courseware/course_navigation.html" />
|
||||
|
||||
<%block name="extratabs">
|
||||
<li class="right"><a href="#" class="new-post-btn"><span class="new-post-icon"></span>New Post</a></li>
|
||||
</%block>
|
||||
@@ -1,3 +1,6 @@
|
||||
<div class="discussion-module">
|
||||
<a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id | h}">Show Discussion</a>
|
||||
<%include file="_underscore_templates.html" />
|
||||
|
||||
<div class="discussion-module" data-discussion-id="${discussion_id | h}">
|
||||
<a class="discussion-show control-button" href="javascript:void(0)" data-discussion-id="${discussion_id | h}"><span class="show-hide-discussion-icon"></span><span class="button-text">Show Discussion</span></a>
|
||||
<a href="#" class="new-post-btn"><span class="new-post-icon"></span>New Post</a>
|
||||
</div>
|
||||
|
||||
38
lms/templates/discussion/_filter_dropdown.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<%! import json %>
|
||||
|
||||
<%def name="render_dropdown(map)">
|
||||
% for child in map["children"]:
|
||||
% if child in map["entries"]:
|
||||
${render_entry(map["entries"], child)}
|
||||
%else:
|
||||
${render_category(map["subcategories"], child)}
|
||||
%endif
|
||||
%endfor
|
||||
</%def>
|
||||
|
||||
<%def name="render_entry(entries, entry)">
|
||||
<li><a href="#"><span class="board-name" data-discussion_id='${json.dumps(entries[entry])}'>${entry}</span></a></li>
|
||||
</%def>
|
||||
|
||||
<%def name="render_category(categories, category)">
|
||||
<li>
|
||||
<a href="#"><span class="board-name">${category}</span></a>
|
||||
<ul>
|
||||
${render_dropdown(categories[category])}
|
||||
</ul>
|
||||
</li>
|
||||
</%def>
|
||||
|
||||
<div class="browse-topic-drop-menu-wrapper">
|
||||
<div class="browse-topic-drop-search">
|
||||
<input type="text" class="browse-topic-drop-search-input" placeholder="filter topics">
|
||||
</div>
|
||||
<ul class="browse-topic-drop-menu">
|
||||
<li>
|
||||
<a href="#">
|
||||
<span class="board-name" data-discussion_id='#all'>All</span>
|
||||
</a>
|
||||
</li>
|
||||
${render_dropdown(category_map)}
|
||||
</ul>
|
||||
</div>
|
||||
34
lms/templates/discussion/_inline_new_post.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<article class="new-post-article">
|
||||
<div class="inner-wrapper">
|
||||
<div class="new-post-form-errors">
|
||||
</div>
|
||||
<form class="new-post-form">
|
||||
<div class="left-column">
|
||||
<div class="options">
|
||||
<input type="checkbox" name="follow" class="discussion-follow" class="discussion-follow" id="new-post-follow" checked><label for="new-post-follow">follow this post</label>
|
||||
<br>
|
||||
% if course.metadata.get("allow_anonymous", True):
|
||||
<input type="checkbox" name="anonymous" class="discussion-anonymous" id="new-post-anonymous"><label for="new-post-anonymous">post anonymously</label>
|
||||
%elif course.metadata.get("allow_anonymous_to_peers", False):
|
||||
<input type="checkbox" name="anonymous_to_peers" class="discussion-anonymous-to-peers" id="new-post-anonymous-to-peers"><label for="new-post-anonymous-to-peers">post anonymously to classmates</label>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<div class="form-row">
|
||||
<input type="text" class="new-post-title" name="title" placeholder="Title">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="new-post-body" name="body" placeholder="Enter your question or comment…"></div>
|
||||
<!---<div class="new-post-preview"><span class="new-post-preview-label">Preview</span></div>-->
|
||||
</div>
|
||||
## TODO commenting out tags til we figure out what to do with them
|
||||
##<div class="form-row">
|
||||
## <input type="text" class="new-post-tags" name="tags" placeholder="Tags">
|
||||
##</div>
|
||||
<input type="submit" class="submit" value="Add post">
|
||||
<a href="#" class="new-post-cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
68
lms/templates/discussion/_new_post.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<%def name="render_form_filter_dropdown(map)">
|
||||
% for child in map["children"]:
|
||||
% if child in map["entries"]:
|
||||
${render_entry(map["entries"], child)}
|
||||
%else:
|
||||
${render_category(map["subcategories"], child)}
|
||||
%endif
|
||||
%endfor
|
||||
</%def>
|
||||
|
||||
<%def name="render_entry(entries, entry)">
|
||||
<li><a href="#" class="topic" data-discussion_id="${entries[entry]['id']}">${entry}</a></li>
|
||||
</%def>
|
||||
|
||||
<%def name="render_category(categories, category)">
|
||||
<li>
|
||||
<a href="#">${category}</a>
|
||||
<ul>
|
||||
${render_form_filter_dropdown(categories[category])}
|
||||
</ul>
|
||||
</li>
|
||||
</%def>
|
||||
|
||||
<article class="new-post-article">
|
||||
<div class="inner-wrapper">
|
||||
<form class="new-post-form">
|
||||
<div class="left-column">
|
||||
<label>Create new post about:</label>
|
||||
<div class="form-topic-drop">
|
||||
<a href="#" class="topic_dropdown_button">All<span class="drop-arrow">▾</span></a>
|
||||
<div class="topic_menu_wrapper">
|
||||
<div class="topic_menu_search">
|
||||
<input type="text" class="form-topic-drop-search-input" placeholder="filter topics">
|
||||
</div>
|
||||
<ul class="topic_menu">
|
||||
${render_form_filter_dropdown(category_map)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="options">
|
||||
<input type="checkbox" name="follow" class="discussion-follow" class="discussion-follow" id="new-post-follow" checked><label for="new-post-follow">follow this post</label>
|
||||
<br>
|
||||
% if course.metadata.get("allow_anonymous", True):
|
||||
<input type="checkbox" name="anonymous" class="discussion-anonymous" id="new-post-anonymous"><label for="new-post-anonymous">post anonymously</label>
|
||||
%elif course.metadata.get("allow_anonymous_to_peers", False):
|
||||
<input type="checkbox" name="anonymous_to_peers" class="discussion-anonymous-to-peers" id="new-post-anonymous-to-peers"><label for="new-post-anonymous-to-peers">post anonymously to classmates</label>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<ul class="new-post-form-errors"></ul>
|
||||
<div class="form-row">
|
||||
<input type="text" class="new-post-title" name="title" placeholder="Title">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="new-post-body" name="body" placeholder="Enter your question or comment…"></div>
|
||||
<!---<div class="new-post-preview"><span class="new-post-preview-label">Preview</span></div>-->
|
||||
</div>
|
||||
## TODO tags commenting out til we figure out what to do w/ tags
|
||||
##<div class="form-row">
|
||||
## <input type="text" class="new-post-tags" name="tags" placeholder="Tags">
|
||||
##</div>
|
||||
<input type="submit" class="submit" value="Add post">
|
||||
<a href="#" class="new-post-cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
@@ -1,10 +1,33 @@
|
||||
<%namespace name="renderer" file="_content_renderer.html"/>
|
||||
<%! from django_comment_client.mustache_helpers import url_for_user %>
|
||||
|
||||
<section class="discussion" _id="${discussion_id | h}">
|
||||
<a class="discussion-title" href="javascript:void(0)">Discussion</a>
|
||||
<div class="threads">
|
||||
${renderer.render_content_with_comments(thread)}
|
||||
<article class="discussion-article" data-id="${discussion_id| h}">
|
||||
<a href="#" class="dogear"></a>
|
||||
<div class="discussion-post">
|
||||
<header>
|
||||
<a href="#" class="vote-btn discussion-vote discussion-vote-up"><span class="plus-icon">+</span> <span class='votes-count-number'>${thread['votes']['up_count']}</span></a>
|
||||
<h1>${thread['title']}</h1>
|
||||
<p class="posted-details">
|
||||
<span class="timeago" title="${thread['created_at'] | h}">sometime</span> by
|
||||
<a href="${url_for_user(thread, thread['user_id'])}">${thread['username']}</a>
|
||||
</p>
|
||||
</header>
|
||||
<div class="post-body">
|
||||
${thread['body']}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<ol class="responses">
|
||||
% for reply in thread.get("children", []):
|
||||
<li>
|
||||
<div class="response-body">${reply['body']}</div>
|
||||
<ol class="comments">
|
||||
% for comment in reply.get("children", []):
|
||||
<li><div class="comment-body">${comment['body']}</div></li>
|
||||
% endfor
|
||||
</ol>
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
</article>
|
||||
|
||||
<%include file="_js_data.html" />
|
||||
<%include file="_js_data.html" />
|
||||
26
lms/templates/discussion/_thread_list_template.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<script type="text/template" id="thread-list-template">
|
||||
<div class="browse-search">
|
||||
<div class="browse is-open">
|
||||
<a href="#" class="browse-topic-drop-icon"></a>
|
||||
<a href="#" class="browse-topic-drop-btn"><span class="current-board">All</span> <span class="drop-arrow">▾</span></a>
|
||||
</div>
|
||||
<%include file="_filter_dropdown.html" />
|
||||
<div class="search">
|
||||
<form class="post-search">
|
||||
<input type="text" placeholder="Search all discussions" class="post-search-field">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sort-bar">
|
||||
<span class="sort-label">Sort by:</span>
|
||||
<ul>
|
||||
<li><a href="#" class="active" data-sort="date">date</a></li>
|
||||
<li><a href="#" data-sort="votes">votes</a></li>
|
||||
<li><a href="#" data-sort="comments">comments</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="post-list-wrapper">
|
||||
<ul class="post-list">
|
||||
</ul>
|
||||
</div>
|
||||
</script>
|
||||
132
lms/templates/discussion/_underscore_templates.html
Normal file
@@ -0,0 +1,132 @@
|
||||
<script type="text/template" id="thread-template">
|
||||
<article class="discussion-article" data-id="${'<%- id %>'}">
|
||||
<div class="thread-content-wrapper"></div>
|
||||
<ol class="responses">
|
||||
<li class="loading"><div class="loading-animation"></div></li>
|
||||
</ol>
|
||||
<div class="post-status-closed bottom-post-status" style="display: none">
|
||||
This thread is closed.
|
||||
</div>
|
||||
<form class="discussion-reply-new" data-id="${'<%- id %>'}">
|
||||
<h4>Post a response:</h4>
|
||||
<ul class="discussion-errors"></ul>
|
||||
<div class="reply-body" data-id="${'<%- id %>'}"></div>
|
||||
<div class="reply-post-control">
|
||||
<a class="discussion-submit-post control-button" href="#">Submit</a>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-show-template">
|
||||
<div class="discussion-post">
|
||||
<div><a href="javascript:void(0)" class="dogear action-follow" data-tooltip="follow"></a></div>
|
||||
<header>
|
||||
<a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote" data-tooltip="vote"><span class="plus-icon">+</span> <span class='votes-count-number'>${'<%- votes["up_count"] %>'}</span></a>
|
||||
<h1>${'<%- title %>'}</h1>
|
||||
<p class="posted-details">
|
||||
${"<% if (obj.username) { %>"}
|
||||
<a href="${'<%- user_url %>'}" class="username">${'<%- username %>'}</a>
|
||||
${"<% } else {print('anonymous');} %>"}
|
||||
<span class="timeago" title="${'<%- created_at %>'}">${'<%- created_at %>'}</span>
|
||||
|
||||
<span class="post-status-closed top-post-status" style="display: none">
|
||||
• This thread is closed.
|
||||
</span>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="post-body">${'<%- body %>'}</div>
|
||||
${'<% if (obj.courseware_url) { %>'}
|
||||
<div class="post-context">
|
||||
(this post is about <a href="${'<%- courseware_url%>'}">${'<%- courseware_title %>'}</a>)
|
||||
</div>
|
||||
${'<% } %>'}
|
||||
|
||||
<ul class="moderator-actions">
|
||||
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> Edit</a></li>
|
||||
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> Delete</a></li>
|
||||
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> Close</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-edit-template">
|
||||
<div class="discussion-post edit-post-form">
|
||||
<h1>Editing post</h1>
|
||||
<ul class="edit-post-form-errors"></ul>
|
||||
<div class="form-row">
|
||||
<input type="text" class="edit-post-title" name="title" value="${"<%-title %>"}" placeholder="Title">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="edit-post-body" name="body">${"<%- body %>"}</div>
|
||||
</div>
|
||||
## TODO tags
|
||||
## Until we decide what to do with tags, commenting them out.
|
||||
##<div class="form-row">
|
||||
## <input type="text" class="edit-post-tags" name="tags" placeholder="Tags" value="${"<%- tags %>"}">
|
||||
##</div>
|
||||
<input type="submit" class="post-update" value="Update post">
|
||||
<a href="#" class="post-cancel">Cancel</a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-response-template">
|
||||
<div class="discussion-response"></div>
|
||||
<ol class="comments">
|
||||
<li class="new-comment response-local">
|
||||
<form class="comment-form" data-id="${'<%- wmdId %>'}">
|
||||
<ul class="discussion-errors"></ul>
|
||||
<div class="comment-body" data-id="${'<%- wmdId %>'}"
|
||||
data-placeholder="Add a comment..."></div>
|
||||
<div class="comment-post-control">
|
||||
<a class="discussion-submit-comment control-button" href="#">Submit</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-response-show-template">
|
||||
<header class="response-local">
|
||||
<a href="javascript:void(0)" class="vote-btn" data-tooltip="vote"><span class="plus-icon"></span><span class="votes-count-number">${"<%- votes['up_count'] %>"}</span></a>
|
||||
<a href="javascript:void(0)" class="endorse-btn${'<% if (endorsed) { %> is-endorsed<% } %>'} action-endorse" style="cursor: default; display: none;" data-tooltip="endorse"><span class="check-icon" style="pointer-events: none; "></span></a>
|
||||
${"<% if (obj.username) { %>"}
|
||||
<a href="${'<%- user_url %>'}" class="posted-by">${'<%- username %>'}</a>
|
||||
${"<% } else {print('<span class=\"anonymous\"><em>anonymous</em></span>');} %>"}
|
||||
<p class="posted-details" title="${'<%- created_at %>'}">${'<%- created_at %>'}</p>
|
||||
</header>
|
||||
<div class="response-local"><div class="response-body">${"<%- body %>"}</div></div>
|
||||
<ul class="moderator-actions response-local">
|
||||
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> Edit</a></li>
|
||||
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> Delete</a></li>
|
||||
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> Close</a></li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-response-edit-template">
|
||||
<div class="edit-post-form">
|
||||
<h1>Editing response</h1>
|
||||
<ul class="edit-post-form-errors"></ul>
|
||||
<div class="form-row">
|
||||
<div class="edit-post-body" name="body">${"<%- body %>"}</div>
|
||||
</div>
|
||||
<input type="submit" class="post-update" value="Update response">
|
||||
<a href="#" class="post-cancel">Cancel</a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="response-comment-show-template">
|
||||
<div id="comment_${'<%- id %>'}">
|
||||
<div class="response-body">${'<%- body %>'}</div>
|
||||
<p class="posted-details">–posted <span class="timeago" title="${'<%- created_at %>'}">${'<%- created_at %>'}</span> by
|
||||
${"<% if (obj.username) { %>"}
|
||||
<a href="${'<%- user_url %>'}" class="profile-link">${'<%- username %>'}</a>
|
||||
${"<% } else {print('anonymous');} %>"}
|
||||
</p>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="thread-list-item-template">
|
||||
<a href="${'<%- id %>'}" data-id="${'<%- id %>'}"><span class="title">${"<%- title %>"}</span> <span class="comments-count">${"<%- comments_count %>"}</span><span class="votes-count">+${"<%- votes['up_count'] %>"}</span></a>
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="user-profile">
|
||||
<%
|
||||
role_names = sorted(map(attrgetter('name'), django_user.roles.all()))
|
||||
role_names = sorted(set(map(attrgetter('name'), django_user.roles.all())))
|
||||
%>
|
||||
<div class="sidebar-username">${django_user.username | h}</div>
|
||||
<div class="sidebar-user-roles">
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="sidebar-comments-count"><span>${profiled_user['comments_count'] | h}</span> ${pluralize('comment', profiled_user['comments_count']) | h}</div>
|
||||
% if check_permissions_by_view(user, course.id, content=None, name='update_moderator_status'):
|
||||
% if "Moderator" in role_names:
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator provileges</a>
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator rights</a>
|
||||
% else:
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-promote-moderator-button">Promote to Moderator</a>
|
||||
% endif
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<%! import django_comment_client.helpers as helpers %>
|
||||
<%! from django.template.defaultfilters import escapejs %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
|
||||
<%inherit file="../main.html" />
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
<%block name="bodyclass">discussion</%block>
|
||||
@@ -13,26 +17,23 @@
|
||||
<%static:js group='discussion'/>
|
||||
</%block>
|
||||
|
||||
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
|
||||
<%include file="_discussion_course_navigation.html" args="active_page='discussion'" />
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
<section aria-label="Course Navigation" class="course-index">
|
||||
<nav>
|
||||
|
||||
<article class="sidebar-module discussion-sidebar">
|
||||
<a href="#" class="sidebar-new-post-button">New Post</a>
|
||||
</article>
|
||||
<%include file="_new_post.html" />
|
||||
|
||||
<%include file="_recent_active_posts.html" />
|
||||
<script type="text/javascript" src="${static.url('js/discussions-temp.js')}"></script>
|
||||
|
||||
<%include file="_trending_tags.html" />
|
||||
|
||||
</nav>
|
||||
</section>
|
||||
<section class="discussion container" id="discussion-container" data-roles="${roles}" data-course-id="${course_id}" data-user-info="${user_info}" data-threads="${threads}" data-thread-pages="${thread_pages}">
|
||||
<div class="discussion-body">
|
||||
<div class="sidebar"></div>
|
||||
<div class="discussion-column">
|
||||
<div class="discussion-article blank-slate">
|
||||
<h1>${course.title} Discussion</h1>
|
||||
|
||||
<section class="course-content">
|
||||
${content.decode('utf-8')}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<%include file="_underscore_templates.html" />
|
||||
<%include file="_thread_list_template.html" />
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<section class="discussion" data-discussion-id="{{discussionId}}">
|
||||
|
||||
|
||||
<article class="new-post-article">
|
||||
<span class="topic" data-discussion-id="{{discussionId}}" />
|
||||
<div class="inner-wrapper">
|
||||
<div class="new-post-form-errors">
|
||||
</div>
|
||||
<form class="new-post-form">
|
||||
<div class="form-row">
|
||||
<input type="text" class="new-post-title" name="title" placeholder="Title">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="new-post-body" name="body" placeholder="Enter your question or comment…"></div>
|
||||
<!---<div class="new-post-preview"><span class="new-post-preview-label">Preview</span></div>-->
|
||||
</div>
|
||||
{{! TODO tags: Getting rid of tags for now. }}
|
||||
{{!<div class="form-row">}}
|
||||
{{! <input type="text" class="new-post-tags" name="tags" placeholder="Tags">}}
|
||||
{{!</div>}}
|
||||
<input type="submit" class="submit" value="Add post">
|
||||
<a href="#" class="new-post-cancel">Cancel</a>
|
||||
<div class="options">
|
||||
<input type="checkbox" name="follow" class="discussion-follow" class="discussion-follow" id="new-post-follow" checked><label for="new-post-follow">follow this post</label>
|
||||
<br>
|
||||
{{#allow_anonymous}}
|
||||
<input type="checkbox" name="anonymous" class="discussion-anonymous" id="new-post-anonymous"><label for="new-post-anonymous">post anonymously</label>
|
||||
{{/allow_anonymous}}
|
||||
{{#allow_anonymous_to_peers}}
|
||||
<input type="checkbox" name="anonymous" class="discussion-anonymous-to-peers" id="new-post-anonymous-to-peers"><label for="new-post-anonymous-to-peers">post anonymously to classmates</label>
|
||||
{{/allow_anonymous_to_peers}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section class="threads">
|
||||
{{#threads}}
|
||||
<article class="discussion-thread" id="thread_{{id}}">
|
||||
</article>
|
||||
{{/threads}}
|
||||
</section>
|
||||
|
||||
<section class="pagination">
|
||||
</section>
|
||||
</section>
|
||||
22
lms/templates/discussion/mustache/_inline_thread.mustache
Normal file
@@ -0,0 +1,22 @@
|
||||
<article class="discussion-article" data-id="{{id}}">
|
||||
|
||||
<div class="thread-content-wrapper"></div>
|
||||
|
||||
<ol class="responses post-extended-content">
|
||||
<li class="loading"><div class="loading-animation"></div></li>
|
||||
</ol>
|
||||
<form class="local discussion-reply-new post-extended-content" data-id="{{id}}">
|
||||
<h4>Post a response:</h4>
|
||||
<ul class="discussion-errors"></ul>
|
||||
<div class="reply-body" data-id="{{id}}"></div>
|
||||
<div class="reply-post-control">
|
||||
<a class="discussion-submit-post control-button" href="#">Submit</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="local post-tools">
|
||||
<a href="javascript:void(0)" class="expand-post">View discussion</a>
|
||||
<a href="javascript:void(0)" class="collapse-post">Hide discussion</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="discussion-post local">
|
||||
<div><a href="javascript:void(0)" class="dogear action-follow" data-tooltip="follow"></a></div>
|
||||
<header>
|
||||
<a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote" data-tooltip="vote"><span class="plus-icon">+</span> <span class='votes-count-number'>{{votes.up_count}}</span></a>
|
||||
<h3>{{title}}</h3>
|
||||
<p class="posted-details">
|
||||
{{#user}}
|
||||
<a href="{{user_url}}" class="username">{{username}}</a>
|
||||
{{/user}}
|
||||
{{^user}}
|
||||
anonymous
|
||||
{{/user}}
|
||||
|
||||
<span class="timeago" title="{{created_at}}">{{created_at}}</span>
|
||||
|
||||
<span class="post-status-closed top-post-status" style="display: none">
|
||||
• This thread is closed.
|
||||
</span>
|
||||
</p>
|
||||
</header>
|
||||
<div class="post-body">{{abbreviatedBody}}</div>
|
||||
|
||||
<ul class="moderator-actions post-extended-content">
|
||||
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> Edit</a></li>
|
||||
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> Delete</a></li>
|
||||
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> Close</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -3,7 +3,8 @@
|
||||
<input type="text" class="new-post-title title-input" placeholder="Title" />
|
||||
<div class="new-post-similar-posts-wrapper" style="display: none"></div>
|
||||
<div class="new-post-body reply-body"></div>
|
||||
<input class="new-post-tags" placeholder="Tags" />
|
||||
{{! TODO tags: Getting rid of tags for now. }}
|
||||
{{! <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>
|
||||
|
||||
32
lms/templates/discussion/mustache/_pagination.mustache
Normal file
@@ -0,0 +1,32 @@
|
||||
<nav class="discussion-{{discussiontype}}-paginator discussion-paginator local">
|
||||
<ol>
|
||||
{{#previous}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="{{number}}">< Previous</a></li>
|
||||
{{/previous}}
|
||||
{{#first}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="1">1</a></li>
|
||||
{{/first}}
|
||||
{{#leftdots}}
|
||||
<li>…</li>
|
||||
{{/leftdots}}
|
||||
|
||||
{{#lowPages}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="{{number}}">{{number}}</a></li>
|
||||
{{/lowPages}}
|
||||
<li class="current-page">{{page}}</li>
|
||||
{{#highPages}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="{{number}}">{{number}}</a></li>
|
||||
{{/highPages}}
|
||||
|
||||
{{#rightdots}}
|
||||
<li>…</li>
|
||||
{{/rightdots}}
|
||||
{{#last}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="{{number}}">{{number}}</a></li>
|
||||
{{/last}}
|
||||
{{#next}}
|
||||
<li><a class="discussion-pagination" href="{{url}}" data-page-number="{{number}}">Next ></a></li>
|
||||
{{/next}}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
32
lms/templates/discussion/mustache/_profile_thread.mustache
Normal file
@@ -0,0 +1,32 @@
|
||||
<article class="discussion-article" data-id="{{id}}">
|
||||
<div class="local"><a href="javascript:void(0)" class="dogear action-follow"></a></div>
|
||||
<div class="discussion-post local">
|
||||
<header>
|
||||
<a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote"><span class="plus-icon">+</span> <span class='votes-count-number'>{{votes.up_count}}</span></a>
|
||||
<h3>{{title}}</h3>
|
||||
<p class="posted-details">
|
||||
{{#user}}
|
||||
<a href="{{user_url}}" class="username">{{username}}</a>
|
||||
{{/user}}
|
||||
{{^user}}
|
||||
anonymous
|
||||
{{/user}}
|
||||
|
||||
<span class="timeago" title="{{created_at}}">{{created_at}}</span>
|
||||
<span class="post-status-closed top-post-status" style="display: none">
|
||||
• This thread is closed.
|
||||
</span>
|
||||
</p>
|
||||
</header>
|
||||
<div class="post-body">{{abbreviatedBody}}</div>
|
||||
</div>
|
||||
<ol class="responses post-extended-content">
|
||||
<li class="loading"></li>
|
||||
</ol>
|
||||
|
||||
<div class="local post-tools">
|
||||
<a href="{{permalink}}">View discussion</a>
|
||||
<!-- <a href="javascript:void(0)" class="collapse-post">Hide discussion</a> -->
|
||||
</div>
|
||||
|
||||
</article>
|
||||
10
lms/templates/discussion/mustache/_user_profile.mustache
Normal file
@@ -0,0 +1,10 @@
|
||||
<section class="discussion-user-threads" >
|
||||
<section class="discussion">
|
||||
{{#threads}}
|
||||
<article class="discussion-thread" id="thread_{{id}}">
|
||||
</article>
|
||||
{{/threads}}
|
||||
</div>
|
||||
<section class="pagination">
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,3 +1,8 @@
|
||||
<%! import django_comment_client.helpers as helpers %>
|
||||
<%! from django.template.defaultfilters import escapejs %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from courseware.access import has_access %>
|
||||
|
||||
<%inherit file="../main.html" />
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
<%block name="bodyclass">discussion</%block>
|
||||
@@ -11,26 +16,20 @@
|
||||
<%block name="js_extra">
|
||||
<%include file="_js_body_dependencies.html" />
|
||||
<%static:js group='discussion'/>
|
||||
<script type="text/javascript" src="${static.url('js/discussions-temp.js')}"></script>
|
||||
|
||||
</%block>
|
||||
|
||||
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
|
||||
<%include file="_discussion_course_navigation.html" args="active_page='discussion'"/>
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
<section aria-label="Course Navigation" class="course-index">
|
||||
<nav>
|
||||
|
||||
<article class="sidebar-module discussion-sidebar">
|
||||
</article>
|
||||
<%include file="_recent_active_posts.html" />
|
||||
<%include file="_new_post.html" />
|
||||
|
||||
<%include file="_trending_tags.html" />
|
||||
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="course-content">
|
||||
${content.decode('utf-8')}
|
||||
</section>
|
||||
<section class="discussion container" id="discussion-container" data-roles="${roles}" data-course-id="${course_id}" data-user-info="${user_info}" data-threads="${threads}" data-content-info="${annotated_content_info}" data-thread-pages="${thread_pages}">
|
||||
<div class="discussion-body">
|
||||
<div class="sidebar"></div>
|
||||
<div class="discussion-column"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<%include file="_underscore_templates.html" />
|
||||
<%include file="_thread_list_template.html" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="course-wrapper">
|
||||
<section aria-label="User Profile" class="user-profile">
|
||||
<nav>
|
||||
|
||||
|
||||
<article class="sidebar-module discussion-sidebar">
|
||||
<%include file="_user_profile.html" />
|
||||
</article>
|
||||
@@ -29,8 +29,8 @@
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="course-content">
|
||||
${content.decode('utf-8')}
|
||||
<section class="course-content container discussion-user-threads" data-user-id="${django_user.id | escapejs}" data-course-id="${course.id | escapejs}" data-threads="${threads}" data-user-info="${user_info}">
|
||||
<h2>Active Threads</h2>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
@@ -39,3 +39,4 @@
|
||||
var $$profiled_user_id = "${django_user.id | escapejs}";
|
||||
var $$course_id = "${course.id | escapejs}";
|
||||
</script>
|
||||
<%include file="_underscore_templates.html" />
|
||||
|
||||