Commit 9b5285ad by Tom Giannattasio

Merge branch 'master' into feature/rocha/wiki-cheatsheet

parents d1760f9a 25c83bbb
......@@ -46,4 +46,4 @@ class MakoMiddleware(object):
global requestcontext
requestcontext = RequestContext(request)
requestcontext['is_secure'] = request.is_secure()
requestcontext['site'] = settings.SITE_NAME
requestcontext['site'] = request.get_host()
......@@ -273,7 +273,7 @@ def add_user_to_default_group(user, group):
utg.users.add(User.objects.get(username=user))
utg.save()
# @receiver(post_save, sender=User)
@receiver(post_save, sender=User)
def update_user_information(sender, instance, created, **kwargs):
try:
cc_user = cc.User.from_django_user(instance)
......
......@@ -511,7 +511,7 @@ def password_reset(request):
form.save(use_https = request.is_secure(),
from_email = settings.DEFAULT_FROM_EMAIL,
request = request,
domain_override = settings.SITE_NAME)
domain_override = request.get_host())
return HttpResponse(json.dumps({'success':True,
'value': render_to_string('registration/password_reset_done.html', {})}))
else:
......
......@@ -73,10 +73,12 @@ class CorrectMap(object):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key
def get_npoints(self, answer_id):
if self.is_correct(answer_id):
npoints = self.cmap[answer_id].get('npoints', 1) # default to 1 point if correct
return npoints or 1
return 0 # if not correct, return 0
npoints = self.get_property(answer_id, 'npoints')
if npoints is not None:
return npoints
elif self.is_correct(answer_id):
return 1
return 0 # if not correct and no points have been assigned, return 0
def set_property(self, answer_id, property, value):
if answer_id in self.cmap: self.cmap[answer_id][property] = value
......
......@@ -61,13 +61,12 @@ nav.sequence-nav {
min-width: 20px;
a {
width: 34px;
height: 34px;
margin: 4px auto;
background-position: center 10px;
width: 100%;
height: 42px;
margin: 0;
background-position: center 14px;
background-repeat: no-repeat;
border: 1px solid transparent;
@include border-radius(35px);
cursor: pointer;
display: block;
padding: 0;
......@@ -82,7 +81,7 @@ nav.sequence-nav {
&:hover {
background-color: #fff;
background-repeat: no-repeat;
background-position: center 10px;
background-position: center 14px;
}
&.active {
......@@ -103,7 +102,7 @@ nav.sequence-nav {
&:hover {
background-color: #fff;
background-repeat: no-repeat;
background-position: center 10px;
background-position: center 14px;
}
}
......@@ -298,14 +297,16 @@ nav.sequence-bottom {
ul {
@extend .clearfix;
border: 1px solid $border-color;
@include border-radius(3px);
@include inline-block();
width: 100px;
width: 103px;
li {
float: left;
width: 50%;
width: 50px;
height: 44px;
border: 1px solid #ccc;
@include linear-gradient(top, #eee, #ddd);
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
&.prev, &.next {
margin-bottom: 0;
......@@ -318,38 +319,39 @@ nav.sequence-bottom {
padding: lh(.5) 4px;
text-indent: -9999px;
@include transition(all, .2s, $ease-in-out-quad);
outline: 0;
&:hover {
background-color: #ddd;
color: #000;
opacity: .5;
text-decoration: none;
background-position: center 15px;
}
&.disabled {
opacity: .4;
}
&:focus {
outline: 0;
}
}
}
&.prev {
border-radius: 35px 0 0 35px;
a {
background-image: url('../images/sequence-nav/previous-icon.png');
border-right: 1px solid lighten(#c6c6c6, 10%);
&:hover {
background-color: none;
}
background-position: center 15px;
}
}
&.next {
border-radius: 0 35px 35px 0;
border-left: none;
a {
background-image: url('../images/sequence-nav/next-icon.png');
&:hover {
background-color: none;
}
background-position: center 15px;
}
}
}
......@@ -360,5 +362,12 @@ div.course-wrapper section.course-content ol.vert-mod > li ul.sequence-nav-butto
list-style: none !important;
}
.xmodule_SequenceModule nav.sequence-bottom ul li.next a,
.xmodule_SequenceModule nav.sequence-bottom ul li.prev a {
outline: 0;
&:focus {
outline: 0;
}
}
......@@ -55,6 +55,7 @@ class @Sequence
element.removeClass('progress-none')
.removeClass('progress-some')
.removeClass('progress-done')
switch progress
when 'none' then element.addClass('progress-none')
when 'in_progress' then element.addClass('progress-some')
......
......@@ -5,6 +5,7 @@ import logging
from path import path
from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404
from xmodule.course_module import CourseDescriptor
......@@ -133,8 +134,13 @@ def get_course_info_section(course, section_key):
if section_key in ['handouts', 'guest_handouts', 'updates', 'guest_updates']:
try:
with course.system.resources_fs.open(path("info") / section_key + ".html") as htmlFile:
return replace_urls(htmlFile.read().decode('utf-8'),
course.metadata['data_dir'])
# Replace '/static/' urls
info_html = replace_urls(htmlFile.read().decode('utf-8'), course.metadata['data_dir'])
# Replace '/course/' urls
course_root = reverse('course_root', args=[course.id])[:-1] # Remove trailing slash
info_html = replace_urls(info_html, course_root, '/course/')
return info_html
except ResourceNotFoundError:
log.exception("Missing info section {key} in course {url}".format(
key=section_key, url=course.location.url()))
......
......@@ -143,8 +143,9 @@ def get_module(user, request, location, student_module_cache, course_id, positio
exists.
Arguments:
- user : current django User
- request : current django HTTPrequest
- user : User for whom we're getting the module
- request : current django HTTPrequest -- used in particular for auth
(This is important e.g. for prof impersonation of students in progress view)
- location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache
- course_id : the course_id in the context of which to load module
......@@ -170,7 +171,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
descriptor = modulestore().get_instance(course_id, location)
# Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load'):
# NOTE: Do access check on request.user -- that's who actually needs access (e.g. could be prof
# impersonating a user)
if not has_access(request.user, descriptor, 'load'):
return None
#TODO Only check the cache if this module can possibly have state
......@@ -199,7 +202,10 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
)
# Fully qualified callback URL for external queueing system
xqueue_callback_url = request.build_absolute_uri('/')[:-1] # Trailing slash provided by reverse
xqueue_callback_url = '{proto}://{host}'.format(
host=request.get_host(),
proto=request.META.get('HTTP_X_FORWARDED_PROTO', 'https' if request.is_secure() else 'http')
)
xqueue_callback_url += reverse('xqueue_callback',
kwargs=dict(course_id=course_id,
userid=str(user.id),
......@@ -346,10 +352,10 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
get = request.POST.copy()
for key in ['xqueue_header', 'xqueue_body']:
if not get.has_key(key):
return Http404
raise Http404
header = json.loads(get['xqueue_header'])
if not isinstance(header, dict) or not header.has_key('lms_key'):
return Http404
raise Http404
# Retrieve target StudentModule
user = User.objects.get(id=userid)
......
......@@ -325,14 +325,17 @@ def progress(request, course_id, student_id=None):
raise Http404
student = User.objects.get(id=int(student_id))
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, course)
course_module = get_module(request.user, request, course.location,
course_id, student, course)
course_module = get_module(student, request, course.location,
student_module_cache, course_id)
courseware_summary = grades.progress_summary(student, course_module,
course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache)
grade_summary = grades.grade(student, request, course, student_module_cache)
context = {'course': course,
'courseware_summary': courseware_summary,
......
......@@ -51,11 +51,11 @@ def ajax_content_response(request, course_id, content, template_name):
'content': content,
}
html = render_to_string(template_name, context)
user_info = cc.User.from_django_user(request.user).safe_attributes()
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
return JsonResponse({
'html': html,
'content': content,
'content': utils.safe_content(content),
'annotated_content_info': annotated_content_info,
})
......@@ -78,7 +78,7 @@ def create_thread(request, course_id, commentable_id):
if request.is_ajax():
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html')
else:
return JsonResponse(thread.to_dict())
return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST
@login_required
......@@ -90,7 +90,7 @@ def update_thread(request, course_id, thread_id):
if request.is_ajax():
return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html')
else:
return JsonResponse(thread.to_dict())
return JsonResponse(utils.safe_content(thread.to_dict()))
def _create_comment(request, course_id, thread_id=None, parent_id=None):
post = request.POST
......@@ -109,7 +109,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None):
if request.is_ajax():
return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html')
else:
return JsonResponse(comment.to_dict())
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -126,7 +126,7 @@ def create_comment(request, course_id, thread_id):
def delete_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id)
thread.delete()
return JsonResponse(thread.to_dict())
return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST
@login_required
......@@ -138,7 +138,7 @@ def update_comment(request, course_id, comment_id):
if request.is_ajax():
return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html')
else:
return JsonResponse(comment.to_dict()),
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -147,7 +147,7 @@ def endorse_comment(request, course_id, comment_id):
comment = cc.Comment.find(comment_id)
comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true'
comment.save()
return JsonResponse(comment.to_dict())
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -156,7 +156,11 @@ def openclose_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id)
thread.closed = request.POST.get('closed', 'false').lower() == 'true'
thread.save()
return JsonResponse(thread.to_dict())
thread = thread.to_dict()
return JsonResponse({
'content': utils.safe_content(thread),
'ability': utils.get_ability(course_id, thread, request.user),
})
@require_POST
@login_required
......@@ -173,7 +177,7 @@ def create_sub_comment(request, course_id, comment_id):
def delete_comment(request, course_id, comment_id):
comment = cc.Comment.find(comment_id)
comment.delete()
return JsonResponse(comment.to_dict())
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -182,7 +186,7 @@ def vote_for_comment(request, course_id, comment_id, value):
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
user.vote(comment, value)
return JsonResponse(comment.to_dict())
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -191,7 +195,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
user.unvote(comment)
return JsonResponse(comment.to_dict())
return JsonResponse(utils.safe_content(comment.to_dict()))
@require_POST
@login_required
......@@ -200,7 +204,7 @@ def vote_for_thread(request, course_id, thread_id, value):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
user.vote(thread, value)
return JsonResponse(thread.to_dict())
return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST
@login_required
......@@ -209,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
user.unvote(thread)
return JsonResponse(thread.to_dict())
return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST
......
......@@ -64,7 +64,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'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).safe_attributes()
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)
......@@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'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): threads })
'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)
......@@ -128,7 +128,7 @@ def inline_discussion(request, course_id, discussion_id):
return utils.JsonResponse({
'html': html,
'discussionData': threads,
'discussion_data': map(utils.safe_content, threads),
})
def render_search_bar(request, course_id, discussion_id=None, text=''):
......@@ -149,7 +149,7 @@ def forum_form_discussion(request, course_id):
if request.is_ajax():
return utils.JsonResponse({
'html': content,
'discussionData': threads,
'discussion_data': map(utils.safe_content, threads),
})
else:
recent_active_threads = cc.search_recent_active_threads(
......@@ -176,7 +176,7 @@ 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).safe_attributes()
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)
......@@ -186,7 +186,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
'annotated_content_info': json.dumps(annotated_content_info),
'course_id': course_id,
'request': request,
'discussion_data': json.dumps({ discussion_id: [thread] }),
'discussion_data': json.dumps({ discussion_id: [utils.safe_content(thread)] }),
}
return render_to_string('discussion/_single_thread.html', context)
......@@ -194,7 +194,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax():
user_info = cc.User.from_django_user(request.user).safe_attributes()
user_info = cc.User.from_django_user(request.user).to_dict()
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}
......@@ -202,7 +202,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
return utils.JsonResponse({
'html': html,
'content': thread.to_dict(),
'content': utils.safe_content(thread.to_dict()),
'annotated_content_info': annotated_content_info,
})
......@@ -252,7 +252,7 @@ def user_profile(request, course_id, user_id):
if request.is_ajax():
return utils.JsonResponse({
'html': content,
'discussionData': threads,
'discussion_data': map(utils.safe_content, threads),
})
else:
context = {
......
......@@ -164,6 +164,16 @@ class QueryCountDebugMiddleware(object):
logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time))
return response
def get_ability(course_id, content, user):
return {
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
}
def get_annotated_content_info(course_id, content, user, user_info):
voted = ''
if content['id'] in user_info['upvoted_ids']:
......@@ -173,14 +183,7 @@ def get_annotated_content_info(course_id, content, user, user_info):
return {
'voted': voted,
'subscribed': content['id'] in user_info['subscribed_thread_ids'],
'ability': {
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
},
'ability': get_ability(course_id, content, user),
}
def get_annotated_content_infos(course_id, thread, user, user_info):
......@@ -216,3 +219,17 @@ def extend_content(content):
'permalink': permalink(content),
}
return merge_dict(content, 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',
]
if content.get('anonymous') is False:
fields += ['username', 'user_id']
return strip_none(extract(content, fields))
......@@ -23,9 +23,16 @@ DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
MITX_FEATURES['ENABLE_DISCUSSION'] = False
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
# IMPORTANT: With this enabled, the server must always be behind a proxy that
# strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise,
# a user can fool our server into thinking it was an https connection.
# See https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
# for other warnings.
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
########################### NON-SECURE ENV CONFIG ##############################
# Things like server locations, ports, etc.
with open(ENV_ROOT / "env.json") as env_file:
ENV_TOKENS = json.load(env_file)
......@@ -49,6 +56,8 @@ LOGGING = get_logger_config(LOG_DIR,
COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {})
SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {})
COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL",'')
COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY",'')
############################## SECURE AUTH ITEMS ###############################
# Secret things: passwords, access keys, etc.
......@@ -67,5 +76,3 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
if 'COURSE_ID' in ENV_TOKENS:
ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID'])
COMMENTS_SERVICE_URL = ENV_TOKENS["COMMENTS_SERVICE_URL"]
COMMENTS_SERVICE_KEY = ENV_TOKENS["COMMENTS_SERVICE_KEY"]
......@@ -332,6 +332,8 @@ WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.is_staff or user.is_sup
WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser
WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
WIKI_LINK_LIVE_LOOKUPS = False
WIKI_LINK_DEFAULT_LEVEL = 2
################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
......
......@@ -16,6 +16,8 @@ WIKI_ENABLED = False
MITX_FEATURES['ENABLE_TEXTBOOK'] = False
MITX_FEATURES['ENABLE_DISCUSSION'] = False
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = True # require that user be in the staff_* group to be able to enroll
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False
MITX_FEATURES['SUBDOMAIN_BRANDING'] = False
MITX_FEATURES['DISABLE_START_DATES'] = True
# MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss
......
......@@ -33,16 +33,6 @@ class User(models.Model):
params = {'source_type': source.type, 'source_id': source.id}
response = perform_request('delete', _url_for_subscription(self.id), params)
# TODO this is a hack to compensate for the fact that synchronization isn't
# happening properly.
def safe_attributes(self):
try:
return self.to_dict()
except:
self.save()
self._retrieve()
return self.to_dict()
def vote(self, voteable, value):
if voteable.type == 'thread':
url = _url_for_vote_thread(voteable.id)
......
......@@ -90,7 +90,9 @@ if Backbone?
ability: (ability) ->
for action, elemSelector of @model.actions
if not ability[action]
@$(elemSelector).parent().remove()
@$(elemSelector).parent().hide()
else
@$(elemSelector).parent().show()
$discussionContent: ->
@_discussionContent ||= @$el.children(".discussion-content")
......@@ -122,7 +124,7 @@ if Backbone?
url = @model.urlFor('retrieve')
DiscussionUtil.safeAjax
$elem: $elem
$loading: $(event.target) if event
$loading: @$(".discussion-show-comments")
type: "GET"
url: url
success: (response, textStatus) =>
......@@ -191,6 +193,8 @@ if Backbone?
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: ->
......@@ -269,6 +273,7 @@ if Backbone?
data: data
success: (response, textStatus) =>
@model.set('closed', not closed)
@model.set('ability', response.ability)
edit: (event) ->
@$(".discussion-content-wrapper").hide()
......@@ -279,11 +284,11 @@ if Backbone?
view = {}
view.id = @model.id
if @model.get('type') == 'thread'
view.title = @$(".thread-raw-title").html()
view.body = @$(".thread-raw-body").html()
view.tags = @$(".thread-raw-tags").html()
view.title = @model.get('title')
view.body = @model.get('body')
view.tags = @model.get('tags')
else
view.body = @$(".comment-raw-body").html()
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()
......@@ -311,8 +316,12 @@ if Backbone?
success: (response, textStatus) =>
DiscussionUtil.clearFormErrors @$(".discussion-update-errors")
@$discussionContent().replaceWith(response.html)
@model.set response.content
@model.updateInfo response.annotated_content_info
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()
......@@ -330,9 +339,11 @@ if Backbone?
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
success: (response, textStatus) =>
@$el.remove()
@model.get('thread').removeComment(@model)
if @model.get('type') == 'comment'
@model.get('thread').removeComment(@model)
events:
"click .discussion-follow-thread": "toggleFollow"
......@@ -381,6 +392,14 @@ if Backbone?
@initTitle()
@initBody()
@initCommentViews()
reconstruct: ->
@initBindings()
@initLocal()
@initTimeago()
@initTitle()
@initBody()
@delegateEvents()
class @Thread extends @Content
urlMappers:
......
......@@ -42,13 +42,17 @@ if Backbone?
DiscussionUtil.safeAjax
$elem: $elem
$loading: $elem
loadingCallback: ->
$(this).parent().append("<span class='discussion-loading'></span>")
loadedCallback: ->
$(this).parent().children(".discussion-loading").remove()
url: url
type: "GET"
success: (response, textStatus) =>
$parent = @$el.parent()
@$el.replaceWith(response.html)
$discussion = $parent.find("section.discussion")
@model.reset(response.discussionData, { silent: false })
@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)
......@@ -109,6 +113,7 @@ if Backbone?
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
@$el.children(".blank").hide()
@$(".new-post-form").show()
submitNewPost: (event) ->
......@@ -136,6 +141,8 @@ if Backbone?
$thread = $(response.html)
@$el.children(".threads").prepend($thread)
@$el.children(".blank").remove()
@$(".new-post-similar-posts").empty()
@$(".new-post-similar-posts-wrapper").hide()
@$(".new-post-title").val("").attr("prev-text", "")
......@@ -154,6 +161,7 @@ if Backbone?
@$(".new-post-form").addClass("collapsed")
else if @$el.hasClass("forum-discussion")
@$(".new-post-form").hide()
@$el.children(".blank").show()
search: (event) ->
event.preventDefault()
......
......@@ -27,7 +27,7 @@ if Backbone?
$discussion = @$el.find("section.discussion")
$(event.target).html("Hide Discussion")
discussion = new Discussion()
discussion.reset(response.discussionData, {silent: false})
discussion.reset(response.discussion_data, {silent: false})
view = new DiscussionView(el: $discussion[0], model: discussion)
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
@retrieved = true
......
$ ->
$.fn.extend
loading: ->
$(this).after("<span class='discussion-loading'></span>")
@$_loading = $("<span class='discussion-loading'></span>")
$(this).after(@$_loading)
loaded: ->
$(this).parent().children(".discussion-loading").remove()
@$_loading.remove()
class @DiscussionUtil
......@@ -72,13 +73,17 @@ class @DiscussionUtil
params["beforeSend"] = ->
$elem.attr("disabled", "disabled")
if params["$loading"]
console.log "loading"
params["$loading"].loading()
if params["loadingCallback"]?
params["loadingCallback"].apply(params["$loading"])
else
params["$loading"].loading()
$.ajax(params).always ->
$elem.removeAttr("disabled")
if params["$loading"]
console.log "loaded"
params["$loading"].loaded()
if params["loadedCallback"]?
params["loadedCallback"].apply(params["$loading"])
else
params["$loading"].loaded()
@get: ($elem, url, data, success) ->
@safeAjax
......
......@@ -13,9 +13,10 @@ class @Navigation
active: active
header: 'h3'
autoHeight: false
$('#accordion .ui-state-active').closest('.chapter').addClass('is-open')
$('#open_close_accordion a').click @toggle
$('#accordion').show()
$('#accordion a').click @setChapter
log: (event, ui) ->
log_event 'accordion',
......@@ -24,3 +25,8 @@ class @Navigation
toggle: ->
$('.course-wrapper').toggleClass('closed')
setChapter: ->
$('#accordion .is-open').removeClass('is-open')
$(this).closest('.chapter').addClass('is-open')
\ No newline at end of file
......@@ -4,13 +4,16 @@
var Gradebook = function($element) {
var _this = this;
var $element = $element;
var $body = $('body');
var $grades = $element.find('.grades');
var $studentTable = $element.find('.student-table');
var $gradeTable = $element.find('.grade-table');
var $search = $element.find('.student-search-field');
var $leftShadow = $('<div class="left-shadow"></div>');
var $rightShadow = $('<div class="right-shadow"></div>');
var tableHeight = $gradeTable.height();
var maxScroll = $gradeTable.width() - $grades.width();
var $body = $('body');
var mouseOrigin;
var tableOrigin;
......@@ -58,12 +61,35 @@ var Gradebook = function($element) {
var targetLeft = clamp($gradeTable.position().left, -maxScroll, 0);
updateHorizontalPosition(targetLeft);
setShadows(targetLeft);
}
};
var updateHorizontalPosition = function(left) {
$gradeTable.css({
'left': left + 'px'
});
};
var highlightRow = function(e) {
$element.find('.highlight').removeClass('highlight');
var index = $(this).index();
$studentTable.find('tr').eq(index + 1).addClass('highlight');
$gradeTable.find('tr').eq(index + 1).addClass('highlight');
};
var filter = function(e) {
var term = $(this).val();
if(term.length > 0) {
$studentTable.find('tbody tr').hide();
$gradeTable.find('tbody tr').hide();
$studentTable.find('tbody tr:contains(' + term + ')').each(function(i) {
$(this).show();
$gradeTable.find('tr').eq($(this).index() + 1).show();
});
} else {
$studentTable.find('tbody tr').show();
$gradeTable.find('tbody tr').show();
}
}
$leftShadow.css('height', tableHeight + 'px');
......@@ -72,5 +98,11 @@ var Gradebook = function($element) {
setShadows(0);
$grades.css('height', tableHeight);
$gradeTable.bind('mousedown', startDrag);
$element.find('tr').bind('mouseover', highlightRow);
$search.bind('keyup', filter);
$(window).bind('resize', updateWidths);
}
\ No newline at end of file
}
......@@ -27,11 +27,6 @@ var SequenceNav = function($element) {
var offset = e.pageX - mouseOrigin;
var targetLeft = clamp(listOrigin + offset, -maxScroll, 0);
console.log('---------------');
console.log('offset: ' + offset);
console.log('target left: ' + targetLeft);
console.log('max: ' + maxScroll);
updateHorizontalPosition(targetLeft);
setShadows(targetLeft);
......
......@@ -150,10 +150,16 @@ $tag-text-color: #5b614f;
//user profile
.user-profile {
@extend .sidebar;
margin-top: 24px;
}
.sidebar-username {
font-size: 1.5em;
font-weight: bold;
line-height: 1.5em;
margin-top: 20px;
}
.sidebar-user-roles {
......
......@@ -133,18 +133,6 @@ div.gradebook-wrapper {
box-shadow: 0 1px 0 $table-border-color inset, 0 2px 0 rgba(255, 255, 255, .7) inset;
border-left: 1px solid #ccc;
// &:before {
// content: '';
// display: block;
// position: absolute;
// left: 0;
// top: 0;
// z-index: 9999;
// width: 1px;
// height: 50px;
// @include linear-gradient(top, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, .15));
// }
&:first-child {
border-radius: 5px 0 0 0;
box-shadow: 1px 1px 0 $table-border-color inset, 1px 2px 0 rgba(255, 255, 255, .7) inset;
......@@ -205,6 +193,19 @@ div.gradebook-wrapper {
@extend .top-header;
}
}
.student-table tr:hover td,
.grade-table tr:hover td,
.student-table tr.highlight td,
.grade-table tr.highlight td {
border-color: #74b7d6;
@include linear-gradient(#8ed6f7, #76cbf4);
color: #333;
a {
color: #333;
}
}
}
......@@ -13,6 +13,7 @@ div.course-wrapper {
section.course-content {
@extend .content;
padding: 40px;
line-height: 1.6;
h1 {
margin: 0 0 lh();
......
......@@ -75,6 +75,10 @@ section.course-index {
@include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset);
@include transition(background-color .1s);
&.is-open {
background: #fff;
}
&:first-child {
border-radius: 3px 0 0 0;
}
......
......@@ -67,7 +67,39 @@ header.global.slim {
@include linear-gradient(top, #fff, #eee);
.guest .secondary {
margin-right: 0;
}
.guest .secondary a {
display: none;
&#login {
display: block;
@include background-image(linear-gradient(-90deg, lighten($blue, 8%), lighten($blue, 5%) 50%, $blue 50%, darken($blue, 10%) 100%));
border: 1px solid transparent;
border-color: darken($blue, 10%);
@include border-radius(3px);
@include box-sizing(border-box);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
color: #fff;
display: inline-block;
font-family: $sans-serif;
font-size: 14px;
font-weight: bold;
@include inline-block;
letter-spacing: 0;
line-height: 1em;
margin: 4px;
padding: 6px 12px 8px;
text-decoration: none;
text-transform: none;
text-shadow: 0 -1px rgba(0, 0, 0, 0.6);
vertical-align: middle;
&:hover, &.active {
@include background-image(linear-gradient(-90deg, $blue, $blue 50%, $blue 50%, $blue 100%));
}
}
}
nav {
......@@ -86,7 +118,7 @@ header.global.slim {
height: 40px;
position: absolute;
right: 3px;
top: -8px;
top: 0;
width: 1px;
}
......@@ -97,7 +129,7 @@ header.global.slim {
height: 40px;
position: absolute;
right: 0px;
top: -12px;
top: 0;
width: 1px;
}
}
......@@ -129,7 +161,7 @@ header.global.slim {
a#signup {
position: relative;
margin-top: 4px;
margin-top: 3px;
padding: 6px 12px 8px;
text-transform: none;
font-size: 14px;
......
......@@ -954,8 +954,7 @@ section.wiki {
.alert {
position: relative;
top: -15px;
margin-bottom: 24px;
margin: 24px 40px;
padding: 8px 12px;
border: 1px solid #EBE8BF;
border-radius: 3px;
......@@ -972,6 +971,10 @@ section.wiki {
}
}
.main-article .alert {
margin: 0 0 24px;
}
.missing {
max-width: 400px;
margin: lh(2) auto;
......
......@@ -68,8 +68,8 @@
@include clearfix;
border-bottom: 1px dotted rgb(220,220,220);
list-style: none;
margin-bottom: 20px;
padding-bottom: 10px;
margin-bottom: 15px;
padding-bottom: 17px;
&:hover {
.title .icon {
......@@ -77,16 +77,20 @@
}
}
span {
display: block;
}
span.title {
color: $lighter-base-font-color;
float: left;
font-family: $sans-serif;
font-size: 13px;
.icon {
background-size: cover;
float: left;
height: 19px;
margin: 2px 8px 0 0;
margin: 0 6px 0 0;
opacity: 0.6;
@include transition(all, 0.15s, linear);
width: 19px;
......@@ -112,7 +116,10 @@
span.data {
color: $lighter-base-font-color;
font-weight: 700;
margin-left: 12px;
margin-left: 26px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
......
......@@ -5,7 +5,7 @@
</%def>
<%def name="render_content_with_comments(content, *args, **kwargs)">
<div class="${content['type']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}">
<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}">
${render_content(content, *args, **kwargs)}
${render_comments(content.get('children', []), *args, **kwargs)}
</div>
......
<div class="discussion-module">
<a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id}">Show Discussion</a>
<a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id | h}">Show Discussion</a>
</div>
<%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion forum-discussion" _id="${discussion_id}">
<section class="discussion forum-discussion" _id="${discussion_id | h}">
<div class="discussion-non-content local">
<div class="search-wrapper">
......
<%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion inline-discussion" _id="${discussion_id}">
<section class="discussion inline-discussion" _id="${discussion_id | h}">
<div class="discussion-non-content local"></div>
......
......@@ -9,7 +9,7 @@
%>
<%def name="link_to_page(_page, text)">
<a class="discussion-page-link" href="javascript:void(0)" page-url="${url_for_page(_page)}">${text}</a>
<a class="discussion-page-link" href="javascript:void(0)" page-url="${url_for_page(_page) | h}">${text}</a>
</%def>
<%def name="div_page(_page)">
......@@ -36,7 +36,7 @@
% endfor
</%def>
<div class="discussion-${discussion_type}-paginator discussion-paginator local">
<div class="discussion-${discussion_type | h}-paginator discussion-paginator local">
<div class="prev-page">
% if page > 1:
${link_to_page(page - 1, "&lt; Previous page")}
......
......@@ -8,7 +8,7 @@
</header>
<ol class="discussion-sidebar-following-list">
% for thread in recent_active_threads:
<li><a href="${helpers.permalink(thread)}"><span class="sidebar-following-name">${thread['title']}</span> <span class="sidebar-vote-count">${thread['votes']['point']}</span></a></li>
<li><a href="${helpers.permalink(thread) | h}"><span class="sidebar-following-name">${thread['title'] | h}</span> <span class="sidebar-vote-count">${thread['votes']['point'] | h}</span></a></li>
% endfor
<ol>
</article>
......
......@@ -10,9 +10,9 @@ def base_url_for_search():
<form action="${base_url_for_search()}" method="get" class="discussion-search-form">
% if query_params.get('tags', None):
<input class="search-input" type="text" value="[${tags}]${text}" id="keywords" autocomplete="off"/>
<input class="search-input" type="text" value="[${tags | h}]${text | h}" id="keywords" autocomplete="off"/>
% else:
<input class="search-input" type="text" value="${text}" id="keywords" autocomplete="off"/>
<input class="search-input" type="text" value="${text | h}" id="keywords" autocomplete="off"/>
% endif
<div class="discussion-link discussion-search-link" href="javascript:void(0)">Search posts</div>
</form>
......@@ -3,7 +3,7 @@
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
<div class="new-post-similar-posts">
% for thread in threads:
<a class="similar-post" href="${thread['permalink']}">${thread['title']}</a>
<a class="similar-post" href="${thread['permalink'] | h}">${thread['title'] | h}</a>
% endfor
</div>
% endif
<%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion" _id="${discussion_id}">
<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)}
......
......@@ -26,7 +26,7 @@
else:
return base_url + '?' + urlencode(merge(query_params, {'page': 1, 'sort_key': key, 'sort_order': order}))
%>
<a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a>
<a class="discussion-sort-link ${cls | h}" href="javascript:void(0)" sort-url="${url_for_sort(key, order) | h}">${title}</a>
</%def>
<div class="discussion-sort local">
......
......@@ -7,7 +7,7 @@
</header>
<ol class="discussion-sidebar-tags-list">
% for tag, count in trending_tags:
<li><a href="${helpers.url_for_tags(course.id, tag)}" class="thread-tag">${tag}</a><span class="sidebar-tag-count">&times;${count}</span></li>
<li><a href="${helpers.url_for_tags(course.id, tag) | h}" class="thread-tag">${tag | h}</a><span class="sidebar-tag-count">&times;${count | h}</span></li>
% endfor
<ol>
</article>
......
<%namespace name="renderer" file="_content_renderer.html"/>
<section class="discussion user-active-discussion" _id="${user_id}">
<section class="discussion user-active-discussion" _id="${user_id | h}">
<div class="discussion-non-content local"></div>
......
......@@ -7,12 +7,12 @@
<%
role_names = sorted(map(attrgetter('name'), django_user.roles.all()))
%>
<div class="sidebar-username">${django_user.username}</div>
<div class="sidebar-username">${django_user.username | h}</div>
<div class="sidebar-user-roles">
${", ".join(role_names)}
</div>
<div class="sidebar-threads-count"><span>${profiled_user['threads_count']}</span> ${pluralize('discussion', profiled_user['threads_count'])} started</div>
<div class="sidebar-comments-count"><span>${profiled_user['comments_count']}</span> ${pluralize('comment', profiled_user['comments_count'])}</div>
<div class="sidebar-threads-count"><span>${profiled_user['threads_count'] | h}</span> ${pluralize('discussion', profiled_user['threads_count']) | h} started</div>
<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>
......
<%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="bodyclass">discussion</%block>
<%block name="title"><title>Discussion – ${course.number}</title></%block>
<%block name="title"><title>Discussion – ${course.number | h}</title></%block>
<%block name="headextra">
<%static:css group='course'/>
......
......@@ -7,28 +7,25 @@
</div>
<div class="discussion-right-wrapper">
<ul class="admin-actions">
<li><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
<li><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-endorse">Endorse</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-edit">Edit</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-delete">Delete</a></li>
{{#thread}}
<li><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li>
<li style="display: none;"><a href="javascript:void(0)" class="admin-openclose">{{close_thread_text}}</a></li>
{{/thread}}
</ul>
{{#thread}}
<a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{content.displayed_title}}</a>
<div class="thread-raw-title" style="display: none">{{{content.title}}}</div>
{{/thread}}
<div class="discussion-content-view">
<a name="{{content.id}}" style="width: 0; height: 0; padding: 0; border: none;"></a>
<div class="content-body {{content.type}}-body" id="content-body-{{content.id}}">{{content.displayed_body}}</div>
<div class="content-raw-body {{content.type}}-raw-body" style="display: none">{{{content.body}}}</div>
{{#thread}}
<div class="thread-tags">
{{#content.tags}}
<a class="thread-tag" href="{{##url_for_tags}}{{.}}{{/url_for_tags}}">{{.}}</a>
{{/content.tags}}
</div>
<div class="thread-raw-tags" style="display: none">{{content.raw_tags}}</div>
{{/thread}}
<div class="info">
<div class="comment-time">
......
......@@ -3,7 +3,7 @@
<%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="bodyclass">discussion</%block>
<%block name="title"><title>Discussion – ${course.number}</title></%block>
<%block name="title"><title>Discussion – ${course.number | h}</title></%block>
<%block name="headextra">
<%static:css group='course'/>
......@@ -36,6 +36,6 @@
</section>
<script type="text/javascript">
var $$profiled_user_id = "${user.id | escapejs}";
var $$profiled_user_id = "${django_user.id | escapejs}";
var $$course_id = "${course.id | escapejs}";
</script>
......@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
https://www.edx.org{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
https://{{domain}}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
......
......@@ -40,9 +40,6 @@
<script type="text/javascript">
var sequenceNav;
$(document).ready(function() {
// console.log($('.sequence-nav'));
sequenceNav = new SequenceNav($('.sequence-nav'));
console.log(sequenceNav);
});
</script>
......@@ -33,11 +33,9 @@
</div>
{% if urlpath %}
<!--
<div class="see-children">
<a href="{% url 'wiki:dir' path=urlpath.path %}">See all children</a>
</div>
-->
{% endif %}
</div>
</div>
......
......@@ -124,6 +124,8 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.course_about', name="about_course"),
#Inside the course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
'courseware.views.course_info', name="course_root"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$',
'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/syllabus$',
......@@ -197,7 +199,6 @@ if settings.QUICKEDIT:
if settings.ASKBOT_ENABLED:
urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \
url(r'^admin/', include(admin.site.urls)), \
url(r'^settings/', include('askbot.deps.livesettings.urls')), \
url(r'^followit/', include('followit.urls')), \
# url(r'^robots.txt$', include('robots.urls')),
......@@ -206,8 +207,10 @@ if settings.ASKBOT_ENABLED:
if settings.DEBUG:
## Jasmine
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
## Jasmine and admin
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),
url(r'^admin/', include(admin.site.urls)),
)
if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
urlpatterns += (
......
-e git://github.com/MITx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
-e git://github.com/benjaoming/django-wiki.git@533c7fc#egg=django-wiki
-e git://github.com/MITx/django-wiki.git@e2e84558#egg=django-wiki
-e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
-e common/lib/capa
-e common/lib/xmodule
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment