Merge branch 'merge'
@@ -157,7 +157,7 @@ def edXauth_signup(request, eamap=None):
|
||||
|
||||
log.debug('ExtAuth: doing signup for %s' % eamap.external_email)
|
||||
|
||||
return student_views.main_index(request, extra_context=context)
|
||||
return student_views.index(request, extra_context=context)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# MIT SSL
|
||||
@@ -193,7 +193,7 @@ def edXauth_ssl_login(request):
|
||||
The certificate provides user email and fullname; this populates the ExternalAuthMap.
|
||||
The user is nevertheless still asked to complete the edX signup.
|
||||
|
||||
Else continues on with student.views.main_index, and no authentication.
|
||||
Else continues on with student.views.index, and no authentication.
|
||||
"""
|
||||
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use
|
||||
|
||||
@@ -207,7 +207,7 @@ def edXauth_ssl_login(request):
|
||||
pass
|
||||
if not cert:
|
||||
# no certificate information - go onward to main index
|
||||
return student_views.main_index(request)
|
||||
return student_views.index(request)
|
||||
|
||||
(user, email, fullname) = ssl_dn_extract_info(cert)
|
||||
|
||||
@@ -217,4 +217,4 @@ def edXauth_ssl_login(request):
|
||||
credentials=cert,
|
||||
email=email,
|
||||
fullname=fullname,
|
||||
retfun = functools.partial(student_views.main_index, request))
|
||||
retfun = functools.partial(student_views.index, request))
|
||||
|
||||
@@ -22,7 +22,6 @@ from django.db import IntegrityError
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from django.core.urlresolvers import reverse
|
||||
from bs4 import BeautifulSoup
|
||||
from django.core.cache import cache
|
||||
|
||||
@@ -30,7 +29,6 @@ from django_future.csrf import ensure_csrf_cookie
|
||||
from student.models import (Registration, UserProfile,
|
||||
PendingNameChange, PendingEmailChange,
|
||||
CourseEnrollment)
|
||||
from util.cache import cache_if_anonymous
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -54,23 +52,7 @@ def csrf_token(context):
|
||||
' name="csrfmiddlewaretoken" value="%s" /></div>' % (csrf_token))
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def index(request):
|
||||
|
||||
''' Redirects to main page -- info page if user authenticated, or marketing if not
|
||||
'''
|
||||
|
||||
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
|
||||
from external_auth.views import edXauth_ssl_login
|
||||
return edXauth_ssl_login(request)
|
||||
|
||||
return main_index(request, user=request.user)
|
||||
|
||||
def main_index(request, extra_context={}, user=None):
|
||||
def index(request, extra_context={}, user=None):
|
||||
'''
|
||||
Render the edX main page.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from functools import wraps
|
||||
|
||||
from django.core import cache
|
||||
|
||||
|
||||
# If we can't find a 'general' CACHE defined in settings.py, we simply fall back
|
||||
# to returning the default cache. This will happen with dev machines.
|
||||
try:
|
||||
@@ -41,7 +42,10 @@ def cache_if_anonymous(view_func):
|
||||
def _decorated(request, *args, **kwargs):
|
||||
if not request.user.is_authenticated():
|
||||
#Use the cache
|
||||
cache_key = "cache_if_anonymous." + request.path
|
||||
# same view accessed through different domain names may
|
||||
# return different things, so include the domain name in the key.
|
||||
domain = str(request.META.get('HTTP_HOST')) + '.'
|
||||
cache_key = domain + "cache_if_anonymous." + request.path
|
||||
response = cache.get(cache_key)
|
||||
if not response:
|
||||
response = view_func(request, *args, **kwargs)
|
||||
|
||||
@@ -112,11 +112,14 @@ def add_histogram(get_html, module, user):
|
||||
edit_link = "%s/%s/tree/master/%s" % (giturl,data_dir,filepath)
|
||||
else:
|
||||
edit_link = False
|
||||
source_file = module.metadata.get('source_file','') # source used to generate the problem XML, eg latex or word
|
||||
|
||||
staff_context = {'definition': module.definition.get('data'),
|
||||
'metadata': json.dumps(module.metadata, indent=4),
|
||||
'location': module.location,
|
||||
'xqa_key': module.metadata.get('xqa_key',''),
|
||||
'source_file' : source_file,
|
||||
'source_url': '%s/%s/tree/master/%s' % (giturl,data_dir,source_file),
|
||||
'category': str(module.__class__.__name__),
|
||||
'element_id': module.location.html_id().replace('-','_'),
|
||||
'edit_link': edit_link,
|
||||
|
||||
@@ -557,7 +557,7 @@ class ChoiceResponse(LoncapaResponse):
|
||||
return CorrectMap(self.answer_id, 'incorrect')
|
||||
|
||||
def get_answers(self):
|
||||
return {self.answer_id: self.correct_choices}
|
||||
return {self.answer_id: list(self.correct_choices)}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -390,9 +390,19 @@ class CapaModule(XModule):
|
||||
raise NotFoundError('Answer is not available')
|
||||
else:
|
||||
answers = self.lcp.get_question_answers()
|
||||
|
||||
# answers (eg <solution>) may have embedded images
|
||||
answers = dict( (k,self.system.replace_urls(answers[k], self.metadata['data_dir'])) for k in answers )
|
||||
return {'answers': answers}
|
||||
# but be careful, some problems are using non-string answer dicts
|
||||
new_answers = dict()
|
||||
for answer_id in answers:
|
||||
try:
|
||||
new_answer = {answer_id: self.system.replace_urls(answers[answer_id], self.metadata['data_dir'])}
|
||||
except TypeError:
|
||||
log.debug('Unable to perform URL substitution on answers[%s]: %s' % (answer_id, answers[answer_id]))
|
||||
new_answer = {answer_id: answers[answer_id]}
|
||||
new_answers.update(new_answer)
|
||||
|
||||
return {'answers': new_answers}
|
||||
|
||||
# Figure out if we should move these to capa_problem?
|
||||
def get_problem(self, get):
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
import logging
|
||||
import requests
|
||||
from lxml import etree
|
||||
from path import path # NOTE (THK): Only used for detecting presence of syllabus
|
||||
|
||||
from xmodule.util.decorators import lazyproperty
|
||||
from xmodule.graders import load_grading_policy
|
||||
@@ -60,6 +61,8 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def __init__(self, system, definition=None, **kwargs):
|
||||
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
|
||||
self.textbooks = self.definition['data']['textbooks']
|
||||
|
||||
self.wiki_slug = self.definition['data']['wiki_slug'] or self.location.course
|
||||
|
||||
msg = None
|
||||
if self.start is None:
|
||||
@@ -75,6 +78,10 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
# NOTE: relies on the modulestore to call set_grading_policy() right after
|
||||
# init. (Modulestore is in charge of figuring out where to load the policy from)
|
||||
|
||||
# NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
|
||||
# disable the syllabus content for courses that do not provide a syllabus
|
||||
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
|
||||
|
||||
|
||||
def set_grading_policy(self, policy_str):
|
||||
"""Parse the policy specified in policy_str, and save it"""
|
||||
@@ -94,8 +101,19 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
for textbook in xml_object.findall("textbook"):
|
||||
textbooks.append(cls.Textbook.from_xml_object(textbook))
|
||||
xml_object.remove(textbook)
|
||||
|
||||
#Load the wiki tag if it exists
|
||||
wiki_slug = None
|
||||
wiki_tag = xml_object.find("wiki")
|
||||
if wiki_tag is not None:
|
||||
wiki_slug = wiki_tag.attrib.get("slug", default=None)
|
||||
xml_object.remove(wiki_tag)
|
||||
|
||||
definition = super(CourseDescriptor, cls).definition_from_xml(xml_object, system)
|
||||
|
||||
definition.setdefault('data', {})['textbooks'] = textbooks
|
||||
definition['data']['wiki_slug'] = wiki_slug
|
||||
|
||||
return definition
|
||||
|
||||
def has_started(self):
|
||||
@@ -197,6 +215,19 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def start_date_text(self):
|
||||
return time.strftime("%b %d, %Y", self.start)
|
||||
|
||||
# An extra property is used rather than the wiki_slug/number because
|
||||
# there are courses that change the number for different runs. This allows
|
||||
# courses to share the same css_class across runs even if they have
|
||||
# different numbers.
|
||||
#
|
||||
# TODO get rid of this as soon as possible or potentially build in a robust
|
||||
# way to add in course-specific styling. There needs to be a discussion
|
||||
# about the right way to do this, but arjun will address this ASAP. Also
|
||||
# note that the courseware template needs to change when this is removed.
|
||||
@property
|
||||
def css_class(self):
|
||||
return self.metadata.get('css_class', '')
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.display_name
|
||||
@@ -205,10 +236,6 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def number(self):
|
||||
return self.location.course
|
||||
|
||||
@property
|
||||
def wiki_slug(self):
|
||||
return self.location.course
|
||||
|
||||
@property
|
||||
def org(self):
|
||||
return self.location.org
|
||||
|
||||
@@ -2,18 +2,54 @@ nav.sequence-nav {
|
||||
// TODO (cpennington): This doesn't work anymore. XModules aren't able to
|
||||
// import from external sources.
|
||||
@extend .topbar;
|
||||
border-bottom: 1px solid $border-color;
|
||||
@include border-top-right-radius(4px);
|
||||
margin: 0 0 lh() (-(lh()));
|
||||
margin: -4px 0 30px;
|
||||
position: relative;
|
||||
border-bottom: none;
|
||||
|
||||
.left-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 20px;
|
||||
height: 46px;
|
||||
@include linear-gradient(left, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0));
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.right-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
width: 20px;
|
||||
height: 46px;
|
||||
@include linear-gradient(right, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0));
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sequence-list-wrapper {
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
border: 1px solid #ccc;
|
||||
height: 44px;
|
||||
margin: 0 30px;
|
||||
@include linear-gradient(top, #ddd, #eee);
|
||||
overflow: hidden;
|
||||
@include box-shadow(0 1px 3px rgba(0, 0, 0, .1) inset);
|
||||
}
|
||||
|
||||
ol {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@include box-sizing(border-box);
|
||||
display: table;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding-left: 3px;
|
||||
padding-right: flex-grid(1, 9);
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
@@ -25,42 +61,49 @@ nav.sequence-nav {
|
||||
min-width: 20px;
|
||||
|
||||
a {
|
||||
background-position: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: 4px auto;
|
||||
background-position: center 10px;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
@include border-radius(35px);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 10px;
|
||||
padding: 15px 0 14px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
@include transition();
|
||||
width: 100%;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: #F3F3F3;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.visited {
|
||||
background-color: #F3F3F3;
|
||||
|
||||
&:hover {
|
||||
background-position: center center;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 10px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $border-color;
|
||||
@include box-shadow(0 2px 0 #fff);
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
|
||||
// &:after {
|
||||
// content: '▲';
|
||||
// position: absolute;
|
||||
// top: 28px;
|
||||
// left: 50%;
|
||||
// z-index: 9999;
|
||||
// margin-left: -5px;
|
||||
// font-size: 12px;
|
||||
// color: #aaa;
|
||||
// }
|
||||
|
||||
&:hover {
|
||||
background-position: center;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,20 +214,24 @@ nav.sequence-nav {
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
list-style: none !important;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: flex-grid(1, 9);
|
||||
border: 1px solid $border-color;
|
||||
border-bottom: 0;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
position: absolute;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
height: 44px;
|
||||
width: 70px;
|
||||
border: 1px solid #ccc;
|
||||
@include linear-gradient(top, #eee, #ddd);
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
|
||||
|
||||
&.prev, &.next {
|
||||
|
||||
@@ -192,14 +239,18 @@ nav.sequence-nav {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
height: 10px;
|
||||
padding: 15px 0 14px;
|
||||
height: 34px;
|
||||
width: 40px;
|
||||
text-indent: -9999px;
|
||||
@include transition(all, .2s, $ease-in-out-quad);
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: .5;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@@ -210,15 +261,23 @@ nav.sequence-nav {
|
||||
}
|
||||
|
||||
&.prev {
|
||||
left: -10px;
|
||||
border-radius: 35px 0 0 35px;
|
||||
|
||||
a {
|
||||
background-image: url('../images/sequence-nav/previous-icon.png');
|
||||
background-position: center 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&.next {
|
||||
right: -10px;
|
||||
border-radius: 0 35px 35px 0;
|
||||
|
||||
a {
|
||||
border-left: 1px solid lighten($border-color, 10%);
|
||||
margin-left: 30px;
|
||||
background-image: url('../images/sequence-nav/next-icon.png');
|
||||
background-position: center 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,3 +355,10 @@ nav.sequence-bottom {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.course-wrapper section.course-content ol.vert-mod > li ul.sequence-nav-buttons {
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
div.video {
|
||||
@include clearfix();
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
display: block;
|
||||
margin: 0 0 0 (-(lh()));
|
||||
padding: 6px lh();
|
||||
margin: 0 -12px;
|
||||
padding: 12px;
|
||||
border-radius: 5px;
|
||||
|
||||
article.video-wrapper {
|
||||
float: left;
|
||||
@@ -102,6 +101,11 @@ div.video {
|
||||
@include transition(background-color, opacity);
|
||||
width: 14px;
|
||||
background: url('../images/vcr.png') 15px 15px no-repeat;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
height: 46px;
|
||||
@@ -172,6 +176,11 @@ div.video {
|
||||
@include transition();
|
||||
-webkit-font-smoothing: antialiased;
|
||||
width: 116px;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #999;
|
||||
@@ -401,6 +410,7 @@ div.video {
|
||||
overflow: auto;
|
||||
width: flex-grid(3, 9);
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
|
||||
li {
|
||||
border: 0;
|
||||
|
||||
@@ -15,18 +15,39 @@ from xmodule.errortracker import exc_info_to_str
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# NOTE: This is not the most beautiful design in the world, but there's no good
|
||||
# way to tell if the module is being used in a staff context or not. Errors that get discovered
|
||||
# at course load time are turned into ErrorDescriptor objects, and automatically hidden from students.
|
||||
# Unfortunately, we can also have errors when loading modules mid-request, and then we need to decide
|
||||
# what to show, and the logic for that belongs in the LMS (e.g. in get_module), so the error handler
|
||||
# decides whether to create a staff or not-staff module.
|
||||
|
||||
class ErrorModule(XModule):
|
||||
def get_html(self):
|
||||
'''Show an error.
|
||||
'''Show an error to staff.
|
||||
TODO (vshnayder): proper style, divs, etc.
|
||||
'''
|
||||
# staff get to see all the details
|
||||
return self.system.render_template('module-error.html', {
|
||||
'staff_access' : True,
|
||||
'data' : self.definition['data']['contents'],
|
||||
'error' : self.definition['data']['error_msg'],
|
||||
})
|
||||
|
||||
|
||||
class NonStaffErrorModule(XModule):
|
||||
def get_html(self):
|
||||
'''Show an error to a student.
|
||||
TODO (vshnayder): proper style, divs, etc.
|
||||
'''
|
||||
# staff get to see all the details
|
||||
return self.system.render_template('module-error.html', {
|
||||
'staff_access' : False,
|
||||
'data' : "",
|
||||
'error' : "",
|
||||
})
|
||||
|
||||
|
||||
class ErrorDescriptor(EditingDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of broken xml.
|
||||
@@ -99,3 +120,9 @@ class ErrorDescriptor(EditingDescriptor):
|
||||
err_node = etree.SubElement(root, 'error_msg')
|
||||
err_node.text = self.definition['data']['error_msg']
|
||||
return etree.tostring(root)
|
||||
|
||||
class NonStaffErrorDescriptor(ErrorDescriptor):
|
||||
"""
|
||||
Module that provides non-staff error messages.
|
||||
"""
|
||||
module_class = NonStaffErrorModule
|
||||
|
||||
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 1.0 KiB |
66
common/static/js/vendor/backbone-min.js
vendored
@@ -4,35 +4,37 @@
|
||||
// Backbone may be freely distributed under the MIT license.
|
||||
// For all details and documentation:
|
||||
// http://backbonejs.org
|
||||
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
|
||||
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
|
||||
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
|
||||
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
|
||||
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
|
||||
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
|
||||
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
|
||||
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
|
||||
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
|
||||
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
|
||||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
|
||||
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
|
||||
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
|
||||
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
|
||||
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
|
||||
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
|
||||
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
|
||||
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
|
||||
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
|
||||
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
|
||||
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
|
||||
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
|
||||
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
|
||||
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
|
||||
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
|
||||
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
|
||||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
|
||||
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
|
||||
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
|
||||
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
|
||||
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
|
||||
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
|
||||
(function(){var k=this,y=k.Backbone,z=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:k.Backbone={};g.VERSION="0.9.2";var f=k._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=k.jQuery||k.Zepto||k.ender;g.noConflict=function(){k.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,h=g.Events={on:function(a,b,c){var d,e;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks={});e=a.shift();)e=d[e]||(d[e]=[]),e.push(b,c);return this},
|
||||
off:function(a,b,c){var d,e,m;if(!(e=this._callbacks))return this;if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(!(m=e[d])||!b&&!c)delete e[d];else for(d=m.length-2;0<=d;d-=2)b&&m[d]!==b||c&&m[d+1]!==c||m.splice(d,2);return this},trigger:function(a){var b,c,d,e,f,g,j;if(!(c=this._callbacks))return this;j=[];a=a.split(p);e=1;for(f=arguments.length;e<f;e++)j[e-1]=arguments[e];for(;b=a.shift();){if(g=c.all)g=g.slice();if(d=c[b])d=d.slice();if(d){e=0;for(f=
|
||||
d.length;e<f;e+=2)d[e].apply(d[e+1]||this,j)}if(g){b=[b].concat(j);e=0;for(f=g.length;e<f;e+=2)g[e].apply(g[e+1]||this,b)}}return this}};h.bind=h.on;h.unbind=h.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.collection&&(this.collection=b.collection);b&&b.parse&&(a=this.parse(a));if(c=l(this,"defaults"))a=f.extend({},c,a);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent={};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent=
|
||||
{};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,h,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},sync:function(){return g.sync.apply(this,arguments)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==b?"":""+b)},
|
||||
has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},g=this.attributes,i=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(g[e],a)||c.unset&&f.has(g,e))delete i[e],(c.silent?this._silent:b)[e]=!0;c.unset?
|
||||
delete g[e]:g[e]=a;!f.isEqual(j[e],a)||f.has(g,e)!==f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){b=f.extend({},b,{unset:!0});return this.set(a,null,b)},clear:function(a){a=f.extend({},a,{unset:!0});return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d,a);b.trigger("sync",
|
||||
b,d,a)};a.error=g.wrapError(a.error,b,a);return this.sync("read",this,a)},save:function(a,b,c){var d,e,m;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c)||!d&&!this.isValid())return!1;var i=this,j=c.success;c.success=function(a,b,e){m=true;b=i.parse(a,e);c.wait&&(b=f.extend(d||{},b));if(!i.set(b,c))return false;j&&j(i,a,c);i.trigger("sync",i,a,c)};c.error=
|
||||
g.wrapError(c.error,i,c);b=this.sync(this.isNew()?"create":"update",this,c);!m&&c.wait&&(this.clear(a),this.set(e,a));return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};a.success=function(e){(a.wait||b.isNew())&&d();c&&c(b,e,a);b.isNew()||b.trigger("sync",b,e,a)};if(this.isNew())return a.success(),!1;a.error=g.wrapError(a.error,b,a);var e=this.sync("delete",this,a);a.wait||d();return e},url:function(){var a=l(this,"urlRoot")||
|
||||
l(this.collection,"url")||s();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
|
||||
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return null==a?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return null==
|
||||
a||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate||!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var q=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);void 0!==b.comparator&&
|
||||
(this.comparator=b.comparator);this._reset();this.initialize.apply(this,arguments);a&&(b.parse&&(a=this.parse(a)),this.reset(a,{silent:!0,parse:b.parse}))};f.extend(q.prototype,h,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return g.sync.apply(this,arguments)},add:function(a,b){var c,d,e,g,i,j={},k={},h=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");
|
||||
g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?h.push(c):j[g]=k[i]=e}for(c=h.length;c--;)h[c]=a.splice(h[c],1)[0];c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;z.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));if(b.merge){c=0;for(d=h.length;c<d;c++)(e=this._byId[h[c].id])&&e.set(h[c],b)}this.comparator&&null==b.at&&this.sort({silent:!0});if(b.silent)return this;c=0;
|
||||
for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,
|
||||
b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==
|
||||
b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1===this.comparator.length?this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,
|
||||
f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d,a);b.trigger("sync",b,d,a)};a.error=g.wrapError(a.error,b,a);return this.sync("read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(a,b,f){f.wait&&c.add(a,
|
||||
f);d&&d(a,b,f)};a.save(null,b);return a},parse:function(a){return a},clone:function(){return new this.constructor(this.models)},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_prepareModel:function(a,b){if(a instanceof o)return a.collection||(a.collection=this),a;b||(b={});b.collection=this;var c=new this.model(a,b);return!c._validate(c.attributes,b)?!1:c},_removeReference:function(a){this===a.collection&&delete a.collection;
|
||||
a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"===a||"remove"===a)&&c!==this||("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=b)),this.trigger.apply(this,arguments))}});f.each("forEach each map collect reduce foldl inject reduceRight foldr find detect filter select reject every all some any include contains invoke max min sortBy sortedIndex toArray size first head take initial rest tail last without indexOf shuffle lastIndexOf isEmpty groupBy".split(" "),
|
||||
function(a){q.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var t=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},A=/:\w+/g,B=/\*\w+/g,C=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(t.prototype,h,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new n);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,
|
||||
d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(C,"\\$&").replace(A,"([^/]+)").replace(B,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});
|
||||
var n=g.History=function(a){this.handlers=[];f.bindAll(this,"checkUrl");this.location=a&&a.location||k.location;this.history=a&&a.history||k.history},r=/^[#\/]/,D=/msie [\w.]+/,u=/\/$/;n.started=!1;f.extend(n.prototype,h,{interval:50,getHash:function(a){return(a=(a||this).location.href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){var a=this.location.pathname,c=this.options.root.replace(u,"");a.indexOf(c)||(a=a.substr(c.length))}else a=
|
||||
this.getHash();return decodeURIComponent(a.replace(r,""))},start:function(a){if(n.started)throw Error("Backbone.history has already been started");n.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||!this.history||!this.history.pushState);var a=this.getFragment(),b=document.documentMode,b=D.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b);u.test(this.options.root)||
|
||||
(this.options.root+="/");b&&this._wantsHashChange&&(this.iframe=g.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a));this._hasPushState?g.$(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?g.$(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^/]$/,"$&/")===this.options.root&&
|
||||
!a.search;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.options.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(r,""),this.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).unbind("popstate",
|
||||
this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);n.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),
|
||||
!0})},navigate:function(a,b){if(!n.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(r,"");if(this.fragment!==c){this.fragment=c;var d=(0!==c.indexOf(this.options.root)?this.options.root:"")+c;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,d);else if(this._wantsHashChange)this._updateHash(this.location,c,b.replace),this.iframe&&c!==this.getFragment(this.getHash(this.iframe))&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,
|
||||
c,b.replace));else return this.location.assign(d);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?a.replace(a.href.replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},E=/^(\S+)\s*(.*)$/,w="model collection el id attributes className tagName".split(" ");f.extend(v.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},
|
||||
render:function(){return this},dispose:function(){this.undelegateEvents();this.model&&this.model.off(null,null,this);this.collection&&this.collection.off(null,null,this);return this},remove:function(){this.dispose();this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&g.$(a).attr(b);null!=c&&g.$(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];this.$delegateElement=this.$el;!1!==b&&
|
||||
this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=l(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(E),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$delegateElement.bind(e,c):this.$delegateElement.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=
|
||||
f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,!1);else{var a=f.extend({},l(this,"attributes"));this.id&&(a.id=l(this,"id"));this.className&&(a["class"]=l(this,"className"));this.setElement(this.make(l(this,"tagName"),a),!1)}}});o.extend=q.extend=t.extend=v.extend=function(a,b){var c=this,d;d=a&&a.hasOwnProperty("constructor")?a.constructor:function(){c.apply(this,arguments)};
|
||||
f.extend(d,c);x.prototype=c.prototype;d.prototype=new x;a&&f.extend(d.prototype,a);b&&f.extend(d,b);d.prototype.constructor=d;d.__super__=c.prototype;return d};var F={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=F[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=l(b,"url")||s());if(!c.data&&b&&("create"===a||"update"===a))e.contentType="application/json",e.data=JSON.stringify(b);g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=
|
||||
e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return g.ajax(f.extend(e,c))};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},l=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?
|
||||
a[b]():a[b]},s=function(){throw Error('A "url" property or function must be specified');}}).call(this);
|
||||
|
||||
52
lms/djangoapps/branding/__init__.py
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def get_subdomain(domain):
|
||||
return domain.split(".")[0]
|
||||
|
||||
|
||||
def get_visible_courses(domain=None):
|
||||
"""
|
||||
Return the set of CourseDescriptors that should be visible in this branded instance
|
||||
"""
|
||||
courses = [c for c in modulestore().get_courses()
|
||||
if isinstance(c, CourseDescriptor)]
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
|
||||
if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'):
|
||||
subdomain = get_subdomain(domain)
|
||||
if subdomain not in settings.COURSE_LISTINGS:
|
||||
subdomain = 'default'
|
||||
visible_ids = frozenset(settings.COURSE_LISTINGS[subdomain])
|
||||
return [course for course in courses if course.id in visible_ids]
|
||||
else:
|
||||
return courses
|
||||
|
||||
|
||||
def get_university(domain=None):
|
||||
"""
|
||||
Return the university name specified for the domain, or None
|
||||
if no university was specified
|
||||
"""
|
||||
if not settings.MITX_FEATURES['SUBDOMAIN_BRANDING'] or domain is None:
|
||||
return None
|
||||
|
||||
subdomain = get_subdomain(domain)
|
||||
return settings.SUBDOMAIN_BRANDING.get(subdomain)
|
||||
|
||||
|
||||
def get_logo_url(domain=None):
|
||||
"""
|
||||
Return the url for the branded logo image to be used
|
||||
"""
|
||||
university = get_university(domain)
|
||||
|
||||
if university is None:
|
||||
return '/static/images/header-logo.png'
|
||||
|
||||
return '/static/images/{uni}-on-edx-logo.png'.format(
|
||||
uni=university
|
||||
)
|
||||
45
lms/djangoapps/branding/views.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
|
||||
import student.views
|
||||
import branding
|
||||
import courseware.views
|
||||
from util.cache import cache_if_anonymous
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def index(request):
|
||||
'''
|
||||
Redirects to main page -- info page if user authenticated, or marketing if not
|
||||
'''
|
||||
|
||||
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
|
||||
from external_auth.views import edXauth_ssl_login
|
||||
return edXauth_ssl_login(request)
|
||||
|
||||
university = branding.get_university(request.META.get('HTTP_HOST'))
|
||||
if university is None:
|
||||
return student.views.index(request, user=request.user)
|
||||
|
||||
return courseware.views.university_profile(request, university)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def courses(request):
|
||||
"""
|
||||
Render the "find courses" page. If subdomain branding is on, this is the
|
||||
university profile page, otherwise it's the edX courseware.views.courses page
|
||||
"""
|
||||
|
||||
university = branding.get_university(request.META.get('HTTP_HOST'))
|
||||
if university is None:
|
||||
return courseware.views.courses(request)
|
||||
|
||||
return courseware.views.university_profile(request, university)
|
||||
@@ -5,6 +5,7 @@ from django.http import Http404
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from wiki.models import reverse as wiki_reverse
|
||||
from courseware.access import has_access
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
|
||||
@@ -135,7 +136,9 @@ def context_processor(request):
|
||||
|
||||
try:
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
return {'course' : course}
|
||||
staff_access = has_access(request.user, course, 'staff')
|
||||
return {'course' : course,
|
||||
'staff_access': staff_access}
|
||||
except Http404:
|
||||
# We couldn't access the course for whatever reason. It is too late to change
|
||||
# the URL here, so we just leave the course context. The middleware shouldn't
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Wikipath Extension for Python-Markdown
|
||||
======================================
|
||||
|
||||
Converts [Link Name](wiki:ArticleName) to relative links pointing to article. Requires Python-Markdown 2.0+
|
||||
|
||||
Basic usage:
|
||||
|
||||
>>> import markdown
|
||||
>>> text = "Some text with a [Link Name](wiki:ArticleName)."
|
||||
>>> html = markdown.markdown(text, ['wikipath(base_url="/wiki/view/")'])
|
||||
>>> html
|
||||
u'<p>Some text with a <a class="wikipath" href="/wiki/view/ArticleName/">Link Name</a>.</p>'
|
||||
|
||||
Dependencies:
|
||||
* [Python 2.3+](http://python.org)
|
||||
* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
|
||||
'''
|
||||
|
||||
|
||||
import markdown
|
||||
try:
|
||||
# Markdown 2.1.0 changed from 2.0.3. We try importing the new version first,
|
||||
# but import the 2.0.3 version if it fails
|
||||
from markdown.util import etree
|
||||
except:
|
||||
from markdown import etree
|
||||
|
||||
|
||||
class WikiPathExtension(markdown.Extension):
|
||||
def __init__(self, configs):
|
||||
# set extension defaults
|
||||
self.config = {
|
||||
'base_url' : ['/', 'String to append to beginning of URL.'],
|
||||
'html_class' : ['wikipath', 'CSS hook. Leave blank for none.']
|
||||
}
|
||||
|
||||
# Override defaults with user settings
|
||||
for key, value in configs :
|
||||
# self.config[key][0] = value
|
||||
self.setConfig(key, value)
|
||||
|
||||
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
self.md = md
|
||||
|
||||
# append to end of inline patterns
|
||||
WIKI_RE = r'\[(?P<linkTitle>.+?)\]\(wiki:(?P<wikiTitle>[a-zA-Z\d/_-]*)\)'
|
||||
wikiPathPattern = WikiPath(WIKI_RE, self.config)
|
||||
wikiPathPattern.md = md
|
||||
md.inlinePatterns.add('wikipath', wikiPathPattern, "<reference")
|
||||
|
||||
class WikiPath(markdown.inlinepatterns.Pattern):
|
||||
def __init__(self, pattern, config):
|
||||
markdown.inlinepatterns.Pattern.__init__(self, pattern)
|
||||
self.config = config
|
||||
|
||||
def handleMatch(self, m) :
|
||||
article_title = m.group('wikiTitle')
|
||||
if article_title.startswith("/"):
|
||||
article_title = article_title[1:]
|
||||
|
||||
url = self.config['base_url'][0] + article_title
|
||||
label = m.group('linkTitle')
|
||||
a = etree.Element('a')
|
||||
a.set('href', url)
|
||||
a.text = label
|
||||
|
||||
if self.config['html_class'][0]:
|
||||
a.set('class', self.config['html_class'][0])
|
||||
|
||||
return a
|
||||
|
||||
def _getMeta(self):
|
||||
""" Return meta data or config data. """
|
||||
base_url = self.config['base_url'][0]
|
||||
html_class = self.config['html_class'][0]
|
||||
if hasattr(self.md, 'Meta'):
|
||||
if self.md.Meta.has_key('wiki_base_url'):
|
||||
base_url = self.md.Meta['wiki_base_url'][0]
|
||||
if self.md.Meta.has_key('wiki_html_class'):
|
||||
html_class = self.md.Meta['wiki_html_class'][0]
|
||||
return base_url, html_class
|
||||
|
||||
def makeExtension(configs=None) :
|
||||
return WikiPathExtension(configs=configs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -5,18 +5,15 @@ from django.core.urlresolvers import reverse_lazy
|
||||
from wiki.core.plugins.base import BasePlugin
|
||||
from wiki.core.plugins import registry as plugin_registry
|
||||
|
||||
from course_wiki.plugins.markdownedx import mdx_circuit, mdx_wikipath, mdx_mathjax, mdx_video
|
||||
from course_wiki.plugins.markdownedx import mdx_circuit, mdx_mathjax, mdx_video
|
||||
|
||||
class ExtendMarkdownPlugin(BasePlugin):
|
||||
"""
|
||||
This plugin simply loads all of the markdown extensions we use in edX.
|
||||
"""
|
||||
|
||||
wiki_base_url = reverse_lazy("wiki:get", kwargs={'path' : ""})
|
||||
|
||||
|
||||
markdown_extensions = [mdx_circuit.CircuitExtension(configs={}),
|
||||
#mdx_image.ImageExtension() , #This one doesn't work. Tries to import simplewiki.settings
|
||||
mdx_wikipath.WikiPathExtension(configs={'base_url' : wiki_base_url}.iteritems() ) ,
|
||||
mdx_mathjax.MathJaxExtension(configs={}) ,
|
||||
mdx_video.VideoExtension(configs={})]
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ def course_wiki_redirect(request, course_id):
|
||||
urlpath = URLPath.create_article(
|
||||
root,
|
||||
course_slug,
|
||||
title=course.number,
|
||||
content="{0}\n===\nThis is the wiki for **{1}**'s _{2}_.".format(course.number, course.org, course.title),
|
||||
title=course_slug,
|
||||
content="This is the wiki for **{0}**'s _{1}_.".format(course.org, course.title),
|
||||
user_message="Course page automatically created.",
|
||||
user=None,
|
||||
ip_address=None,
|
||||
@@ -114,7 +114,7 @@ def get_or_create_root():
|
||||
"===",
|
||||
"Visit a course wiki to add an article."))
|
||||
|
||||
root = URLPath.create_root(title="edX Wiki",
|
||||
root = URLPath.create_root(title="Wiki",
|
||||
content=starting_content)
|
||||
article = root.article
|
||||
article.group = None
|
||||
|
||||
@@ -13,7 +13,6 @@ from xmodule.modulestore import Location
|
||||
from xmodule.timeparse import parse_time
|
||||
from xmodule.x_module import XModule, XModuleDescriptor
|
||||
|
||||
|
||||
DEBUG_ACCESS = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -13,6 +13,7 @@ from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from static_replace import replace_urls, try_staticfiles_lookup
|
||||
from courseware.access import has_access
|
||||
import branding
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -141,9 +142,10 @@ def get_course_info_section(course, section_key):
|
||||
|
||||
raise KeyError("Invalid about key " + str(section_key))
|
||||
|
||||
|
||||
# TODO: Fix this such that these are pulled in as extra course-specific tabs.
|
||||
# arjun will address this by the end of October if no one does so prior to
|
||||
# then.
|
||||
# then.
|
||||
def get_course_syllabus_section(course, section_key):
|
||||
"""
|
||||
This returns the snippet of html to be rendered on the syllabus page,
|
||||
@@ -178,24 +180,11 @@ def get_courses_by_university(user, domain=None):
|
||||
'''
|
||||
# TODO: Clean up how 'error' is done.
|
||||
# filter out any courses that errored.
|
||||
courses = [c for c in modulestore().get_courses()
|
||||
if isinstance(c, CourseDescriptor)]
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
|
||||
if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'):
|
||||
subdomain = domain.split(".")[0]
|
||||
if subdomain not in settings.COURSE_LISTINGS:
|
||||
subdomain = 'default'
|
||||
visible_courses = frozenset(settings.COURSE_LISTINGS[subdomain])
|
||||
else:
|
||||
visible_courses = frozenset(c.id for c in courses)
|
||||
visible_courses = branding.get_visible_courses(domain)
|
||||
|
||||
universities = defaultdict(list)
|
||||
for course in courses:
|
||||
for course in visible_courses:
|
||||
if not has_access(user, course, 'see_exists'):
|
||||
continue
|
||||
if course.id not in visible_courses:
|
||||
continue
|
||||
universities[course.org].append(course)
|
||||
return universities
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
@@ -15,10 +16,12 @@ from courseware.access import has_access
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from models import StudentModule, StudentModuleCache
|
||||
from static_replace import replace_urls
|
||||
from xmodule.errortracker import exc_info_to_str
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
|
||||
from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
@@ -73,6 +76,8 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
|
||||
course_id, user, course, depth=2)
|
||||
course = get_module(user, request, course.location, student_module_cache, course_id)
|
||||
if course is None:
|
||||
return None
|
||||
|
||||
chapters = list()
|
||||
for chapter in course.get_display_items():
|
||||
@@ -131,9 +136,9 @@ def get_section(course_module, chapter, section):
|
||||
|
||||
return section_module
|
||||
|
||||
|
||||
def get_module(user, request, location, student_module_cache, course_id, position=None):
|
||||
''' Get an instance of the xmodule class identified by location,
|
||||
"""
|
||||
Get an instance of the xmodule class identified by location,
|
||||
setting the state based on an existing StudentModule, or creating one if none
|
||||
exists.
|
||||
|
||||
@@ -146,9 +151,22 @@ def get_module(user, request, location, student_module_cache, course_id, positio
|
||||
- position : extra information from URL for user-specified
|
||||
position within module
|
||||
|
||||
Returns: xmodule instance
|
||||
Returns: xmodule instance, or None if the user does not have access to the
|
||||
module. If there's an error, will try to return an instance of ErrorModule
|
||||
if possible. If not possible, return None.
|
||||
"""
|
||||
try:
|
||||
return _get_module(user, request, location, student_module_cache, course_id, position)
|
||||
except:
|
||||
# Something has gone terribly wrong, but still not letting it turn into a 500.
|
||||
log.exception("Error in get_module")
|
||||
return None
|
||||
|
||||
'''
|
||||
def _get_module(user, request, location, student_module_cache, course_id, position=None):
|
||||
"""
|
||||
Actually implement get_module. See docstring there for details.
|
||||
"""
|
||||
location = Location(location)
|
||||
descriptor = modulestore().get_instance(course_id, location)
|
||||
|
||||
# Short circuit--if the user shouldn't have access, bail without doing any work
|
||||
@@ -198,7 +216,7 @@ def get_module(user, request, location, student_module_cache, course_id, positio
|
||||
'callback_url': xqueue_callback_url,
|
||||
'default_queuename': xqueue_default_queuename.replace(' ', '_')}
|
||||
|
||||
def _get_module(location):
|
||||
def inner_get_module(location):
|
||||
"""
|
||||
Delegate to get_module. It does an access check, so may return None
|
||||
"""
|
||||
@@ -214,7 +232,7 @@ def get_module(user, request, location, student_module_cache, course_id, positio
|
||||
xqueue=xqueue,
|
||||
# TODO (cpennington): Figure out how to share info between systems
|
||||
filestore=descriptor.system.resources_fs,
|
||||
get_module=_get_module,
|
||||
get_module=inner_get_module,
|
||||
user=user,
|
||||
# TODO (cpennington): This should be removed when all html from
|
||||
# a module is coming through get_html and is therefore covered
|
||||
@@ -226,7 +244,22 @@ def get_module(user, request, location, student_module_cache, course_id, positio
|
||||
system.set('position', position)
|
||||
system.set('DEBUG', settings.DEBUG)
|
||||
|
||||
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
|
||||
try:
|
||||
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
|
||||
except:
|
||||
log.exception("Error creating module from descriptor {0}".format(descriptor))
|
||||
|
||||
# make an ErrorDescriptor -- assuming that the descriptor's system is ok
|
||||
import_system = descriptor.system
|
||||
if has_access(user, location, 'staff'):
|
||||
err_descriptor = ErrorDescriptor.from_xml(str(descriptor), import_system,
|
||||
error_msg=exc_info_to_str(sys.exc_info()))
|
||||
else:
|
||||
err_descriptor = NonStaffErrorDescriptor.from_xml(str(descriptor), import_system,
|
||||
error_msg=exc_info_to_str(sys.exc_info()))
|
||||
|
||||
# Make an error module
|
||||
return err_descriptor.xmodule_constructor(system)(None, None)
|
||||
|
||||
module.get_html = replace_static_urls(
|
||||
wrap_xmodule(module.get_html, module, 'xmodule_display.html'),
|
||||
@@ -374,7 +407,7 @@ def modx_dispatch(request, dispatch, location, course_id):
|
||||
# ''' (fix emacs broken parsing)
|
||||
|
||||
# Check for submitted files and basic file size checks
|
||||
p = request.POST.dict()
|
||||
p = request.POST.copy()
|
||||
if request.FILES:
|
||||
for fileinput_id in request.FILES.keys():
|
||||
inputfiles = request.FILES.getlist(fileinput_id)
|
||||
|
||||
@@ -392,4 +392,46 @@ def instructor_dashboard(request, course_id):
|
||||
# For now, just a static page
|
||||
context = {'course': course,
|
||||
'staff_access': True,}
|
||||
|
||||
return render_to_response('courseware/instructor_dashboard.html', context)
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def enroll_students(request, course_id):
|
||||
''' Allows a staff member to enroll students in a course.
|
||||
|
||||
This is a short-term hack for Berkeley courses launching fall
|
||||
2012. In the long term, we would like functionality like this, but
|
||||
we would like both the instructor and the student to agree. Right
|
||||
now, this allows any instructor to add students to their course,
|
||||
which we do not want.
|
||||
|
||||
It is poorly written and poorly tested, but it's designed to be
|
||||
stripped out.
|
||||
'''
|
||||
|
||||
course = get_course_with_access(request.user, course_id, 'staff')
|
||||
existing_students = [ce.user.email for ce in CourseEnrollment.objects.filter(course_id = course_id)]
|
||||
|
||||
if 'new_students' in request.POST:
|
||||
new_students = request.POST['new_students'].split('\n')
|
||||
else:
|
||||
new_students = []
|
||||
new_students = [s.strip() for s in new_students]
|
||||
|
||||
added_students = []
|
||||
rejected_students = []
|
||||
|
||||
for student in new_students:
|
||||
try:
|
||||
nce = CourseEnrollment(user=User.objects.get(email = student), course_id = course_id)
|
||||
nce.save()
|
||||
added_students.append(student)
|
||||
except:
|
||||
rejected_students.append(student)
|
||||
|
||||
return render_to_response("enroll_students.html", {'course':course_id,
|
||||
'existing_students': existing_students,
|
||||
'added_students': added_students,
|
||||
'rejected_students': rejected_students,
|
||||
'debug':new_students})
|
||||
|
||||
@@ -51,7 +51,8 @@ def ajax_content_response(request, course_id, content, template_name):
|
||||
'content': content,
|
||||
}
|
||||
html = render_to_string(template_name, context)
|
||||
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user)
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
|
||||
return JsonResponse({
|
||||
'html': html,
|
||||
'content': content,
|
||||
|
||||
@@ -11,11 +11,11 @@ from courseware.courses import get_course_with_access
|
||||
from courseware.access import has_access
|
||||
|
||||
from urllib import urlencode
|
||||
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
|
||||
from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank
|
||||
|
||||
import json
|
||||
import dateutil
|
||||
import django_comment_client.utils as utils
|
||||
import comment_client as cc
|
||||
|
||||
@@ -64,22 +64,26 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
|
||||
'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])),
|
||||
}[discussion_type]()
|
||||
|
||||
annotated_content_infos = map(lambda x: utils.get_annotated_content_infos(course_id, x, request.user), threads)
|
||||
annotated_content_info = reduce(merge_dict, annotated_content_infos, {})
|
||||
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 = {
|
||||
'threads': threads,
|
||||
'discussion_id': discussion_id,
|
||||
'user_id': user_id,
|
||||
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
|
||||
'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_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text'])),
|
||||
'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))),
|
||||
'annotated_content_info': json.dumps(annotated_content_info),
|
||||
'discussion_data': json.dumps({ discussion_id: threads }),
|
||||
}
|
||||
context = dict(context.items() + query_params.items())
|
||||
return render_to_string(template, context)
|
||||
@@ -121,7 +125,11 @@ def inline_discussion(request, course_id, discussion_id):
|
||||
threads, query_params = get_threads(request, course_id, discussion_id)
|
||||
html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
|
||||
query_params=query_params)
|
||||
return utils.HtmlResponse(html)
|
||||
|
||||
return utils.JsonResponse({
|
||||
'html': html,
|
||||
'discussionData': threads,
|
||||
})
|
||||
|
||||
def render_search_bar(request, course_id, discussion_id=None, text=''):
|
||||
if not discussion_id:
|
||||
@@ -138,19 +146,21 @@ def forum_form_discussion(request, course_id):
|
||||
threads, query_params = get_threads(request, course_id)
|
||||
content = render_forum_discussion(request, course_id, threads, discussion_id=_general_discussion_id(course_id), query_params=query_params)
|
||||
|
||||
recent_active_threads = cc.search_recent_active_threads(
|
||||
course_id,
|
||||
recursive=False,
|
||||
query_params={'follower_id': request.user.id},
|
||||
)
|
||||
|
||||
trending_tags = cc.search_trending_tags(
|
||||
course_id,
|
||||
)
|
||||
|
||||
if request.is_ajax():
|
||||
return utils.HtmlResponse(content)
|
||||
return utils.JsonResponse({
|
||||
'html': content,
|
||||
'discussionData': 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,
|
||||
@@ -164,17 +174,19 @@ def forum_form_discussion(request, course_id):
|
||||
|
||||
def render_single_thread(request, discussion_id, course_id, thread_id):
|
||||
|
||||
thread = cc.Thread.find(thread_id).retrieve(recursive=True)
|
||||
thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict()
|
||||
|
||||
annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread.to_dict(), user=request.user)
|
||||
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)
|
||||
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'thread': thread,
|
||||
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
|
||||
'annotated_content_info': json.dumps(annotated_content_info),
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
'discussion_data': json.dumps({ discussion_id: [thread] }),
|
||||
}
|
||||
return render_to_string('discussion/_single_thread.html', context)
|
||||
|
||||
@@ -182,13 +194,15 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
|
||||
if request.is_ajax():
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
return utils.JsonResponse({
|
||||
'html': html,
|
||||
'content': thread.to_dict(),
|
||||
'annotated_content_info': annotated_content_info,
|
||||
})
|
||||
|
||||
@@ -200,7 +214,6 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'init': '',
|
||||
'content': render_single_thread(request, discussion_id, course_id, thread_id),
|
||||
'accordion': render_accordion(request, course, discussion_id),
|
||||
'course': course,
|
||||
'course_id': course.id,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from utils import *
|
||||
from mustache_helpers import mustache_helpers
|
||||
from django.core.urlresolvers import reverse
|
||||
from functools import partial
|
||||
|
||||
from utils import *
|
||||
|
||||
import pystache_custom as pystache
|
||||
import urllib
|
||||
import os
|
||||
|
||||
def pluralize(singular_term, count):
|
||||
if int(count) >= 2:
|
||||
@@ -18,12 +22,32 @@ def show_if(text, condition):
|
||||
else:
|
||||
return ''
|
||||
|
||||
# TODO there should be a better way to handle this
|
||||
def include_mustache_templates():
|
||||
mustache_dir = settings.PROJECT_ROOT / 'templates' / 'discussion' / 'mustache'
|
||||
valid_file_name = lambda file_name: file_name.endswith('.mustache')
|
||||
read_file = lambda file_name: (file_name, open(mustache_dir / file_name, "r").read())
|
||||
strip_file_name = lambda x: (x[0].rpartition('.')[0], x[1])
|
||||
wrap_in_tag = lambda x: "<script type='text/template' id='{0}'>{1}</script>".format(x[0], x[1])
|
||||
|
||||
file_contents = map(read_file, filter(valid_file_name, os.listdir(mustache_dir)))
|
||||
return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents)))
|
||||
|
||||
|
||||
|
||||
def render_content(content, additional_context={}):
|
||||
content_info = {
|
||||
'displayed_title': content.get('highlighted_title') or content.get('title', ''),
|
||||
'displayed_body': content.get('highlighted_body') or content.get('body', ''),
|
||||
'raw_tags': ','.join(content.get('tags', [])),
|
||||
}
|
||||
print content_info
|
||||
if content['type'] == 'thread':
|
||||
content_info['permalink'] = reverse('django_comment_client.forum.views.single_thread',
|
||||
args=[content['course_id'], content['commentable_id'], content['id']])
|
||||
else:
|
||||
content_info['permalink'] = reverse('django_comment_client.forum.views.single_thread',
|
||||
args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
|
||||
context = {
|
||||
'content': merge_dict(content, content_info),
|
||||
content['type']: True,
|
||||
@@ -31,4 +55,4 @@ def render_content(content, additional_context={}):
|
||||
context = merge_dict(context, additional_context)
|
||||
partial_mustache_helpers = {k: partial(v, content) for k, v in mustache_helpers.items()}
|
||||
context = merge_dict(context, partial_mustache_helpers)
|
||||
return render_mustache('discussion/_content.mustache', context)
|
||||
return render_mustache('discussion/mustache/_content.mustache', context)
|
||||
|
||||
@@ -6,9 +6,9 @@ import inspect
|
||||
def pluralize(content, text):
|
||||
num, word = text.split(' ')
|
||||
if int(num or '0') >= 2:
|
||||
return num + ' ' + word + 's'
|
||||
return word + 's'
|
||||
else:
|
||||
return num + ' ' + word
|
||||
return word
|
||||
|
||||
def url_for_user(content, user_id):
|
||||
return urlresolvers.reverse('django_comment_client.forum.views.user_profile', args=[content['course_id'], user_id])
|
||||
|
||||
@@ -18,6 +18,7 @@ class PermissionsTestCase(TestCase):
|
||||
return ''.join(random.choice(chars) for x in range(length))
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.course_id = "MITx/6.002x/2012_Fall"
|
||||
|
||||
self.moderator_role = Role.objects.get_or_create(name="Moderator", course_id=self.course_id)[0]
|
||||
|
||||
@@ -160,23 +160,32 @@ class QueryCountDebugMiddleware(object):
|
||||
logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time))
|
||||
return response
|
||||
|
||||
def get_annotated_content_info(course_id, content, user):
|
||||
def get_annotated_content_info(course_id, content, user, user_info):
|
||||
voted = ''
|
||||
if content['id'] in user_info['upvoted_ids']:
|
||||
voted = 'up'
|
||||
elif content['id'] in user_info['downvoted_ids']:
|
||||
voted = 'down'
|
||||
return {
|
||||
'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"),
|
||||
'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"),
|
||||
},
|
||||
}
|
||||
|
||||
def get_annotated_content_infos(course_id, thread, user):
|
||||
def get_annotated_content_infos(course_id, thread, user, user_info):
|
||||
infos = {}
|
||||
def _annotate(content):
|
||||
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user)
|
||||
def annotate(content):
|
||||
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user, user_info)
|
||||
for child in content.get('children', []):
|
||||
_annotate(child)
|
||||
_annotate(thread)
|
||||
annotate(child)
|
||||
annotate(thread)
|
||||
return infos
|
||||
|
||||
def render_mustache(template_name, dictionary, *args, **kwargs):
|
||||
|
||||
@@ -55,9 +55,10 @@ MITX_FEATURES = {
|
||||
# course_ids (see dev_int.py for an example)
|
||||
'SUBDOMAIN_COURSE_LISTINGS' : False,
|
||||
|
||||
# TODO: This will be removed once course-specific tabs are in place. see
|
||||
# courseware/courses.py
|
||||
'ENABLE_SYLLABUS' : True,
|
||||
# When True, will override certain branding with university specific values
|
||||
# Expects a SUBDOMAIN_BRANDING dictionary that maps the subdomain to the
|
||||
# university to use for branding purposes
|
||||
'SUBDOMAIN_BRANDING': False,
|
||||
|
||||
'ENABLE_TEXTBOOK' : True,
|
||||
'ENABLE_DISCUSSION' : False,
|
||||
@@ -66,7 +67,7 @@ MITX_FEATURES = {
|
||||
'ENABLE_SQL_TRACKING_LOGS': False,
|
||||
'ENABLE_LMS_MIGRATION': False,
|
||||
|
||||
'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL
|
||||
'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL
|
||||
|
||||
# extrernal access methods
|
||||
'ACCESS_REQUIRE_STAFF_FOR_COURSE': False,
|
||||
@@ -199,6 +200,11 @@ COURSE_SETTINGS = {'6.002x_Fall_2012': {'number' : '6.002x',
|
||||
# TODO (vshnayder): Will probably need to change as we get real access control in.
|
||||
LMS_MIGRATION_ALLOWED_IPS = []
|
||||
|
||||
######################## subdomain specific settings ###########################
|
||||
COURSE_LISTINGS = {}
|
||||
SUBDOMAIN_BRANDING = {}
|
||||
|
||||
|
||||
############################### XModule Store ##################################
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
@@ -317,6 +323,8 @@ SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False
|
||||
WIKI_ACCOUNT_HANDLING = False
|
||||
WIKI_EDITOR = 'course_wiki.editors.CodeMirror'
|
||||
WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb
|
||||
WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out
|
||||
WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.has_perm('wiki.assign')
|
||||
|
||||
################################# Jasmine ###################################
|
||||
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
|
||||
@@ -583,6 +591,7 @@ INSTALLED_APPS = (
|
||||
'mptt',
|
||||
'sekizai',
|
||||
#'wiki.plugins.attachments',
|
||||
'wiki.plugins.links',
|
||||
'wiki.plugins.notifications',
|
||||
'course_wiki.plugins.markdownedx',
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ TEMPLATE_DEBUG = True
|
||||
|
||||
MITX_FEATURES['DISABLE_START_DATES'] = True
|
||||
MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
|
||||
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = True
|
||||
MITX_FEATURES['SUBDOMAIN_BRANDING'] = True
|
||||
|
||||
WIKI_ENABLED = True
|
||||
|
||||
@@ -68,6 +70,28 @@ CACHE_TIMEOUT = 0
|
||||
# Dummy secret key for dev
|
||||
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
|
||||
|
||||
COURSE_LISTINGS = {
|
||||
'default': ['BerkeleyX/CS169.1x/2012_Fall',
|
||||
'BerkeleyX/CS188.1x/2012_Fall',
|
||||
'HarvardX/CS50x/2012',
|
||||
'HarvardX/PH207x/2012_Fall',
|
||||
'MITx/3.091x/2012_Fall',
|
||||
'MITx/6.002x/2012_Fall',
|
||||
'MITx/6.00x/2012_Fall'],
|
||||
'berkeley': ['BerkeleyX/CS169.1x/Cal_2012_Fall',
|
||||
'BerkeleyX/CS188.1x/Cal_2012_Fall'],
|
||||
'harvard': ['HarvardX/CS50x/2012H'],
|
||||
'mit': [],
|
||||
'sjsu': ['MITx/6.002x-EE98/2012_Fall_SJSU'],
|
||||
}
|
||||
|
||||
SUBDOMAIN_BRANDING = {
|
||||
'sjsu': 'MITx',
|
||||
'mit': 'MITx',
|
||||
'berkeley': 'BerkeleyX',
|
||||
'harvard': 'HarvardX',
|
||||
}
|
||||
|
||||
################################ LMS Migration #################################
|
||||
MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
|
||||
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from utils import *
|
||||
|
||||
from thread import Thread
|
||||
import models
|
||||
import settings
|
||||
|
||||
@@ -10,7 +11,7 @@ class Comment(models.Model):
|
||||
'endorsed', 'parent_id', 'thread_id',
|
||||
'username', 'votes', 'user_id', 'closed',
|
||||
'created_at', 'updated_at', 'depth',
|
||||
'at_position_list', 'type',
|
||||
'at_position_list', 'type', 'commentable_id',
|
||||
]
|
||||
|
||||
updatable_fields = [
|
||||
@@ -23,6 +24,10 @@ class Comment(models.Model):
|
||||
base_url = "{prefix}/comments".format(prefix=settings.PREFIX)
|
||||
type = 'comment'
|
||||
|
||||
@property
|
||||
def thread(self):
|
||||
return Thread(id=self.thread_id, type='thread')
|
||||
|
||||
@classmethod
|
||||
def url_for_comments(cls, params={}):
|
||||
if params.get('thread_id'):
|
||||
|
||||
@@ -43,6 +43,9 @@ class Model(object):
|
||||
raise KeyError("Field {0} does not exist".format(key))
|
||||
self.attributes.__setitem__(key, value)
|
||||
|
||||
def items(self, *args, **kwargs):
|
||||
return self.attributes.items(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.attributes.get(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class Thread(models.Model):
|
||||
'commentable_id', 'username', 'user_id',
|
||||
'created_at', 'updated_at', 'comments_count',
|
||||
'at_position_list', 'children', 'type',
|
||||
'highlighted_title', 'highlighted_body',
|
||||
]
|
||||
|
||||
updatable_fields = [
|
||||
|
||||
@@ -19,7 +19,7 @@ def extract(dic, keys):
|
||||
def merge_dict(dic1, dic2):
|
||||
return dict(dic1.items() + dic2.items())
|
||||
|
||||
def perform_request(method, url, data_or_params=None, *args, **kwargs):
|
||||
def perform_request(method, url, data_or_params={}, *args, **kwargs):
|
||||
data_or_params['api_key'] = settings.API_KEY
|
||||
if method in ['post', 'put', 'patch']:
|
||||
response = requests.request(method, url, data=data_or_params)
|
||||
|
||||
410
lms/static/coffee/src/backbone_discussion/content.coffee
Normal file
@@ -0,0 +1,410 @@
|
||||
class @Content extends Backbone.Model
|
||||
|
||||
template: -> DiscussionUtil.getTemplate('_content')
|
||||
|
||||
actions:
|
||||
editable: '.admin-edit'
|
||||
can_reply: '.discussion-reply'
|
||||
can_endorse: '.admin-endorse'
|
||||
can_delete: '.admin-delete'
|
||||
can_openclose: '.admin-openclose'
|
||||
|
||||
urlMappers: {}
|
||||
|
||||
urlFor: (name) ->
|
||||
@urlMappers[name].apply(@)
|
||||
|
||||
can: (action) ->
|
||||
DiscussionUtil.getContentInfo @id, action
|
||||
|
||||
updateInfo: (info) ->
|
||||
@set('ability', info.ability)
|
||||
@set('voted', info.voted)
|
||||
@set('subscribed', info.subscribed)
|
||||
|
||||
addComment: (comment, options) ->
|
||||
options ||= {}
|
||||
if not options.silent
|
||||
thread = @get('thread')
|
||||
comments_count = parseInt(thread.get('comments_count'))
|
||||
thread.set('comments_count', comments_count + 1)
|
||||
@get('children').push comment
|
||||
model = new Comment $.extend {}, comment, { thread: @get('thread') }
|
||||
@get('comments').add model
|
||||
model
|
||||
|
||||
removeComment: (comment) ->
|
||||
thread = @get('thread')
|
||||
comments_count = parseInt(thread.get('comments_count'))
|
||||
thread.set('comments_count', comments_count - 1 - comment.getCommentsCount())
|
||||
|
||||
resetComments: (children) ->
|
||||
@set 'children', []
|
||||
@set 'comments', new Comments()
|
||||
for comment in (children || [])
|
||||
@addComment comment, { silent: true }
|
||||
|
||||
initialize: ->
|
||||
DiscussionUtil.addContent @id, @
|
||||
@resetComments(@get('children'))
|
||||
|
||||
|
||||
class @ContentView extends Backbone.View
|
||||
|
||||
$: (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().remove()
|
||||
|
||||
$discussionContent: ->
|
||||
@_discussionContent ||= @$el.children(".discussion-content")
|
||||
|
||||
$showComments: ->
|
||||
@_showComments ||= @$(".discussion-show-comments")
|
||||
|
||||
updateShowComments: ->
|
||||
if @showed
|
||||
@$showComments().html @$showComments().html().replace "Show", "Hide"
|
||||
else
|
||||
@$showComments().html @$showComments().html().replace "Hide", "Show"
|
||||
|
||||
retrieved: ->
|
||||
@$showComments().hasClass("retrieved")
|
||||
|
||||
hideSingleThread: (event) ->
|
||||
@$el.children(".comments").hide()
|
||||
@showed = false
|
||||
@updateShowComments()
|
||||
|
||||
showSingleThread: (event) ->
|
||||
if @retrieved()
|
||||
@$el.children(".comments").show()
|
||||
@showed = true
|
||||
@updateShowComments()
|
||||
else
|
||||
$elem = $.merge @$(".thread-title"), @$showComments()
|
||||
url = @model.urlFor('retrieve')
|
||||
DiscussionUtil.get $elem, url, {}, (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
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
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
|
||||
@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.post $elem, url, {}, (response, textStatus) =>
|
||||
@model.set('voted', '')
|
||||
@model.set('votes_point', response.votes.point)
|
||||
|
||||
vote: (event, value) ->
|
||||
url = @model.urlFor("#{value}vote")
|
||||
$elem = @$(".discussion-vote")
|
||||
DiscussionUtil.post $elem, url, {}, (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.post $elem, url, data, (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.post $elem, url, {}, (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.post $elem, url, data, (response, textStatus) =>
|
||||
@model.set('closed', not closed)
|
||||
|
||||
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 = @$(".thread-raw-title").html()
|
||||
view.body = @$(".thread-raw-body").html()
|
||||
view.tags = @$(".thread-raw-tags").html()
|
||||
else
|
||||
view.body = @$(".comment-raw-body").html()
|
||||
@$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view
|
||||
Discussion.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)
|
||||
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)
|
||||
@model.set response.content
|
||||
@model.updateInfo response.annotated_content_info
|
||||
|
||||
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.post $elem, url, {}, (response, textStatus) =>
|
||||
@$el.remove()
|
||||
@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").timeago()
|
||||
|
||||
initPermalink: ->
|
||||
@$(".discussion-permanent-link").attr "href", @model.permalink()
|
||||
|
||||
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()
|
||||
|
||||
class @Thread extends @Content
|
||||
urlMappers:
|
||||
'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
|
||||
'reply' : -> DiscussionUtil.urlFor('create_comment', @id)
|
||||
'unvote' : -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
|
||||
'upvote' : -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
|
||||
'downvote' : -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
|
||||
'close' : -> DiscussionUtil.urlFor('openclose_thread', @id)
|
||||
'update' : -> DiscussionUtil.urlFor('update_thread', @id)
|
||||
'delete' : -> DiscussionUtil.urlFor('delete_thread', @id)
|
||||
'follow' : -> DiscussionUtil.urlFor('follow_thread', @id)
|
||||
'unfollow' : -> DiscussionUtil.urlFor('unfollow_thread', @id)
|
||||
|
||||
initialize: ->
|
||||
@set('thread', @)
|
||||
super()
|
||||
|
||||
permalink: ->
|
||||
discussion_id = @get('commentable_id')
|
||||
return Discussion.urlFor("permanent_link_thread", discussion_id, @id)
|
||||
|
||||
class @ThreadView extends @ContentView
|
||||
|
||||
class @Comment extends @Content
|
||||
urlMappers:
|
||||
'reply': -> DiscussionUtil.urlFor('create_sub_comment', @id)
|
||||
'unvote': -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
|
||||
'upvote': -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
|
||||
'downvote': -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
|
||||
'endorse': -> DiscussionUtil.urlFor('endorse_comment', @id)
|
||||
'update': -> DiscussionUtil.urlFor('update_comment', @id)
|
||||
'delete': -> DiscussionUtil.urlFor('delete_comment', @id)
|
||||
|
||||
permalink: ->
|
||||
thread_id = @get('thread').id
|
||||
discussion_id = @get('thread').get('commentable_id')
|
||||
return Discussion.urlFor("permanent_link_comment", discussion_id, thread_id, @id)
|
||||
|
||||
getCommentsCount: ->
|
||||
count = 0
|
||||
@get('comments').each (comment) ->
|
||||
count += comment.getCommentsCount() + 1
|
||||
count
|
||||
|
||||
class @CommentView extends @ContentView
|
||||
|
||||
class @Comments extends Backbone.Collection
|
||||
|
||||
model: Comment
|
||||
|
||||
initialize: ->
|
||||
@bind "add", (item) =>
|
||||
item.collection = @
|
||||
|
||||
find: (id) ->
|
||||
_.first @where(id: id)
|
||||
167
lms/static/coffee/src/backbone_discussion/discussion.coffee
Normal file
@@ -0,0 +1,167 @@
|
||||
class @Discussion extends Backbone.Collection
|
||||
model: Thread
|
||||
|
||||
initialize: ->
|
||||
DiscussionUtil.addDiscussion @id, @
|
||||
@bind "add", (item) =>
|
||||
item.discussion = @
|
||||
|
||||
find: (id) ->
|
||||
_.first @where(id: id)
|
||||
|
||||
addThread: (thread, options) ->
|
||||
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
|
||||
DiscussionUtil.get $elem, url, {}, (response, textStatus) =>
|
||||
$parent = @$el.parent()
|
||||
@$el.replaceWith(response.html)
|
||||
$discussion = $parent.find("section.discussion")
|
||||
@model.reset(response.discussionData, { silent: false })
|
||||
view = new DiscussionView el: $discussion[0], model: @model
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
|
||||
loadSimilarPost: (event) ->
|
||||
$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.get $elem, url, data, (response, textStatus) =>
|
||||
$similarPosts.empty()
|
||||
if $.type(response) == "array" and response.length
|
||||
$wrapper.show()
|
||||
for thread in response
|
||||
$similarPost = $("<a>").addClass("similar-post")
|
||||
.html(thread["title"])
|
||||
.attr("href", "javascript:void(0)") #TODO
|
||||
.appendTo($similarPosts)
|
||||
else
|
||||
$wrapper.hide()
|
||||
else
|
||||
$wrapper.hide()
|
||||
$title.attr("prev-text", text)
|
||||
|
||||
|
||||
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, @)
|
||||
|
||||
|
||||
@$(".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)
|
||||
url: url
|
||||
type: "POST"
|
||||
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)
|
||||
|
||||
@$(".new-post-title").val("")
|
||||
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "new-post-body", ""
|
||||
@$(".new-post-tags").val("")
|
||||
@$(".new-post-tags").importTags("")
|
||||
|
||||
thread = @model.addThread response.content
|
||||
threadView = new ThreadView el: $thread[0], model: thread
|
||||
thread.updateInfo response.annotated_content_info
|
||||
@cancelNewPost()
|
||||
|
||||
|
||||
cancelNewPost: (event) ->
|
||||
if @$el.hasClass("inline-discussion")
|
||||
@$(".new-post-form").addClass("collapsed")
|
||||
else if @$el.hasClass("forum-discussion")
|
||||
@$(".new-post-form").hide()
|
||||
|
||||
search: (event) ->
|
||||
event.preventDefault()
|
||||
$elem = $(event.target)
|
||||
url = URI($elem.attr("action")).addSearch({text: @$(".search-input").val()})
|
||||
@reload($elem, url)
|
||||
|
||||
sort: ->
|
||||
$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"
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
Discussion.safeAjax
|
||||
$elem: $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.discussionData, {silent: false})
|
||||
view = new DiscussionView(el: $discussion[0], model: discussion)
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@retrieved = true
|
||||
@showed = true
|
||||
15
lms/static/coffee/src/backbone_discussion/main.coffee
Normal file
@@ -0,0 +1,15 @@
|
||||
$ ->
|
||||
|
||||
window.$$contents = {}
|
||||
window.$$discussions = {}
|
||||
|
||||
$(".discussion-module").each (index, elem) ->
|
||||
view = new DiscussionModuleView(el: elem)
|
||||
|
||||
$("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)
|
||||
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@@ -0,0 +1,34 @@
|
||||
if not @Discussion?
|
||||
@Discussion = {}
|
||||
|
||||
Discussion = @Discussion
|
||||
|
||||
@Discussion = $.extend @Discussion,
|
||||
initializeUserProfile: ($userProfile) ->
|
||||
$local = Discussion.generateLocal $userProfile
|
||||
|
||||
handleUpdateModeratorStatus = (elem, isModerator) ->
|
||||
confirmValue = confirm("Are you sure?")
|
||||
if not confirmValue then return
|
||||
url = Discussion.urlFor('update_moderator_status', $$profiled_user_id)
|
||||
Discussion.safeAjax
|
||||
$elem: $(elem)
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
data:
|
||||
is_moderator: isModerator
|
||||
error: (response, textStatus, e) ->
|
||||
console.log e
|
||||
success: (response, textStatus) ->
|
||||
parent = $userProfile.parent()
|
||||
$userProfile.replaceWith(response.html)
|
||||
Discussion.initializeUserProfile parent.children(".user-profile")
|
||||
|
||||
Discussion.bindLocalEvents $local,
|
||||
"click .sidebar-revoke-moderator-button": (event) ->
|
||||
handleUpdateModeratorStatus(this, false)
|
||||
"click .sidebar-promote-moderator-button": (event) ->
|
||||
handleUpdateModeratorStatus(this, true)
|
||||
|
||||
initializeUserActiveDiscussion: ($discussion) ->
|
||||
249
lms/static/coffee/src/backbone_discussion/utils.coffee
Normal file
@@ -0,0 +1,249 @@
|
||||
class @DiscussionUtil
|
||||
|
||||
@wmdEditors: {}
|
||||
|
||||
@getTemplate: (id) ->
|
||||
$("script##{id}").html()
|
||||
|
||||
@getDiscussionData: (id) ->
|
||||
return $$discussion_data[id]
|
||||
|
||||
@addContent: (id, content) -> window.$$contents[id] = content
|
||||
|
||||
@getContent: (id) -> window.$$contents[id]
|
||||
|
||||
@addDiscussion: (id, discussion) -> window.$$discussions[id] = discussion
|
||||
|
||||
@getDiscussion: (id) -> window.$$discussions[id]
|
||||
|
||||
@bulkUpdateContentInfo: (infos) ->
|
||||
for id, info of infos
|
||||
@getContent(id).updateInfo(info)
|
||||
|
||||
@generateDiscussionLink: (cls, txt, handler) ->
|
||||
$("<a>").addClass("discussion-link")
|
||||
.attr("href", "javascript:void(0)")
|
||||
.addClass(cls).html(txt)
|
||||
.click -> handler(this)
|
||||
|
||||
@urlFor: (name, param, param1, param2) ->
|
||||
{
|
||||
follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow"
|
||||
unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow"
|
||||
create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create"
|
||||
search_similar_threads : "/courses/#{$$course_id}/discussion/#{param}/threads/search_similar"
|
||||
update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update"
|
||||
create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply"
|
||||
delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete"
|
||||
upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote"
|
||||
downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote"
|
||||
undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote"
|
||||
follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow"
|
||||
unfollow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unfollow"
|
||||
update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update"
|
||||
endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse"
|
||||
create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply"
|
||||
delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete"
|
||||
upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote"
|
||||
downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote"
|
||||
undo_vote_for_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/unvote"
|
||||
upload : "/courses/#{$$course_id}/discussion/upload"
|
||||
search : "/courses/#{$$course_id}/discussion/forum/search"
|
||||
tags_autocomplete : "/courses/#{$$course_id}/discussion/threads/tags/autocomplete"
|
||||
retrieve_discussion : "/courses/#{$$course_id}/discussion/forum/#{param}/inline"
|
||||
retrieve_single_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}"
|
||||
update_moderator_status : "/courses/#{$$course_id}/discussion/users/#{param}/update_moderator_status"
|
||||
openclose_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/close"
|
||||
permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}"
|
||||
permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}"
|
||||
}[name]
|
||||
|
||||
@safeAjax: (params) ->
|
||||
$elem = params.$elem
|
||||
if $elem.attr("disabled")
|
||||
return
|
||||
$elem.attr("disabled", "disabled")
|
||||
$.ajax(params).always ->
|
||||
$elem.removeAttr("disabled")
|
||||
|
||||
@get: ($elem, url, data, success) ->
|
||||
@safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "GET"
|
||||
dataType: "json"
|
||||
data: data
|
||||
success: success
|
||||
|
||||
@post: ($elem, url, data, success) ->
|
||||
@safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: "json"
|
||||
data: data
|
||||
success: success
|
||||
|
||||
@bindLocalEvents: ($local, eventsHandler) ->
|
||||
for eventSelector, handler of eventsHandler
|
||||
[event, selector] = eventSelector.split(' ')
|
||||
$local(selector).unbind(event)[event] handler
|
||||
|
||||
@tagsInputOptions: ->
|
||||
autocomplete_url: @urlFor('tags_autocomplete')
|
||||
autocomplete:
|
||||
remoteDataType: 'json'
|
||||
interactive: true
|
||||
height: '30px'
|
||||
width: '100%'
|
||||
defaultText: "Tag your post: press enter after each tag"
|
||||
removeWithBackspace: true
|
||||
|
||||
@formErrorHandler: (errorsField) ->
|
||||
(xhr, textStatus, error) ->
|
||||
response = JSON.parse(xhr.responseText)
|
||||
if response.errors? and response.errors.length > 0
|
||||
errorsField.empty()
|
||||
for error in response.errors
|
||||
errorsField.append($("<li>").addClass("new-post-form-error").html(error))
|
||||
|
||||
@clearFormErrors: (errorsField) ->
|
||||
errorsField.empty()
|
||||
|
||||
@postMathJaxProcessor: (text) ->
|
||||
RE_INLINEMATH = /^\$([^\$]*)\$/g
|
||||
RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g
|
||||
@processEachMathAndCode text, (s, type) ->
|
||||
if type == 'display'
|
||||
s.replace RE_DISPLAYMATH, ($0, $1) ->
|
||||
"\\[" + $1 + "\\]"
|
||||
else if type == 'inline'
|
||||
s.replace RE_INLINEMATH, ($0, $1) ->
|
||||
"\\(" + $1 + "\\)"
|
||||
else
|
||||
s
|
||||
|
||||
@makeWmdEditor: ($content, $local, cls_identifier) ->
|
||||
elem = $local(".#{cls_identifier}")
|
||||
id = $content.attr("_id")
|
||||
appended_id = "-#{cls_identifier}-#{id}"
|
||||
imageUploadUrl = @urlFor('upload')
|
||||
_processor = (_this) ->
|
||||
(text) -> _this.postMathJaxProcessor(text)
|
||||
editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, _processor(@)
|
||||
@wmdEditors["#{cls_identifier}-#{id}"] = editor
|
||||
editor
|
||||
|
||||
@getWmdEditor: ($content, $local, cls_identifier) ->
|
||||
id = $content.attr("_id")
|
||||
@wmdEditors["#{cls_identifier}-#{id}"]
|
||||
|
||||
@getWmdInput: ($content, $local, cls_identifier) ->
|
||||
id = $content.attr("_id")
|
||||
$local("#wmd-input-#{cls_identifier}-#{id}")
|
||||
|
||||
@getWmdContent: ($content, $local, cls_identifier) ->
|
||||
@getWmdInput($content, $local, cls_identifier).val()
|
||||
|
||||
@setWmdContent: ($content, $local, cls_identifier, text) ->
|
||||
@getWmdInput($content, $local, cls_identifier).val(text)
|
||||
@getWmdEditor($content, $local, cls_identifier).refreshPreview()
|
||||
|
||||
@subscriptionLink: (type, id) ->
|
||||
followLink = ->
|
||||
@generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow)
|
||||
|
||||
unfollowLink = ->
|
||||
@generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow)
|
||||
|
||||
handleFollow = (elem) ->
|
||||
@safeAjax
|
||||
$elem: $(elem)
|
||||
url: @urlFor("follow_#{type}", id)
|
||||
type: "POST"
|
||||
success: (response, textStatus) ->
|
||||
if textStatus == "success"
|
||||
$(elem).replaceWith unfollowLink()
|
||||
dataType: 'json'
|
||||
|
||||
handleUnfollow = (elem) ->
|
||||
@safeAjax
|
||||
$elem: $(elem)
|
||||
url: @urlFor("unfollow_#{type}", id)
|
||||
type: "POST"
|
||||
success: (response, textStatus) ->
|
||||
if textStatus == "success"
|
||||
$(elem).replaceWith followLink()
|
||||
dataType: 'json'
|
||||
|
||||
if @isSubscribed(id, type)
|
||||
unfollowLink()
|
||||
else
|
||||
followLink()
|
||||
|
||||
@processEachMathAndCode: (text, processor) ->
|
||||
|
||||
codeArchive = []
|
||||
|
||||
RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m
|
||||
RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m
|
||||
|
||||
ESCAPED_DOLLAR = '@@ESCAPED_D@@'
|
||||
ESCAPED_BACKSLASH = '@@ESCAPED_B@@'
|
||||
|
||||
processedText = ""
|
||||
|
||||
$div = $("<div>").html(text)
|
||||
|
||||
$div.find("code").each (index, code) ->
|
||||
codeArchive.push $(code).html()
|
||||
$(code).html(codeArchive.length - 1)
|
||||
|
||||
text = $div.html()
|
||||
text = text.replace /\\\$/g, ESCAPED_DOLLAR
|
||||
|
||||
while true
|
||||
if RE_INLINEMATH.test(text)
|
||||
text = text.replace RE_INLINEMATH, ($0, $1, $2, $3) ->
|
||||
processedText += $1 + processor("$" + $2 + "$", 'inline')
|
||||
$3
|
||||
else if RE_DISPLAYMATH.test(text)
|
||||
text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) ->
|
||||
processedText += $1 + processor("$$" + $2 + "$$", 'display')
|
||||
$3
|
||||
else
|
||||
processedText += text
|
||||
break
|
||||
|
||||
text = processedText
|
||||
text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$')
|
||||
|
||||
text = text.replace /\\\\\\\\/g, ESCAPED_BACKSLASH
|
||||
text = text.replace /\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, ($0, $1, $2) ->
|
||||
processor("\\begin{#{$1}}" + $2 + "\\end{#{$1}}")
|
||||
text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\')
|
||||
|
||||
$div = $("<div>").html(text)
|
||||
cnt = 0
|
||||
$div.find("code").each (index, code) ->
|
||||
$(code).html(processor(codeArchive[cnt], 'code'))
|
||||
cnt += 1
|
||||
|
||||
text = $div.html()
|
||||
|
||||
text
|
||||
|
||||
@unescapeHighlightTag: (text) ->
|
||||
text.replace(/\<\;highlight\>\;/g, "<span class='search-highlight'>")
|
||||
.replace(/\<\;\/highlight\>\;/g, "</span>")
|
||||
|
||||
@stripHighlight: (text) ->
|
||||
text.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "")
|
||||
.replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "")
|
||||
|
||||
@stripLatexHighlight: (text) ->
|
||||
@processEachMathAndCode text, @stripHighlight
|
||||
|
||||
@markdownWithHighlight: (text) ->
|
||||
converter = Markdown.getMathCompatibleConverter()
|
||||
@unescapeHighlightTag @stripLatexHighlight converter.makeHtml text
|
||||
@@ -85,6 +85,7 @@ initializeFollowThread = (thread) ->
|
||||
Discussion.extendContentInfo response.content['id'], response['annotated_content_info']
|
||||
Discussion.initializeContent($comment)
|
||||
Discussion.bindContentEvents($comment)
|
||||
@cancelReply()
|
||||
$local(".discussion-reply-new").hide()
|
||||
$local(".discussion-reply").show()
|
||||
$local(".discussion-edit").show()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
$ ->
|
||||
|
||||
toggle = ->
|
||||
$('.course-wrapper').toggleClass('closed')
|
||||
#toggle = ->
|
||||
# $('.course-wrapper').toggleClass('closed')
|
||||
|
||||
Discussion = window.Discussion
|
||||
if $('#accordion').length
|
||||
active = $('#accordion ul:has(li.active)').index('#accordion ul')
|
||||
$('#accordion').bind('accordionchange', @log).accordion
|
||||
active: if active >= 0 then active else 1
|
||||
header: 'h3'
|
||||
autoHeight: false
|
||||
$('#open_close_accordion a').click toggle
|
||||
$('#accordion').show()
|
||||
#Discussion = window.Discussion
|
||||
#if $('#accordion').length
|
||||
# active = $('#accordion ul:has(li.active)').index('#accordion ul')
|
||||
# $('#accordion').bind('accordionchange', @log).accordion
|
||||
# active: if active >= 0 then active else 1
|
||||
# header: 'h3'
|
||||
# autoHeight: false
|
||||
# $('#open_close_accordion a').click toggle
|
||||
# $('#accordion').show()
|
||||
|
||||
$(".discussion-module").each (index, elem) ->
|
||||
Discussion.initializeDiscussionModule(elem)
|
||||
#$(".discussion-module").each (index, elem) ->
|
||||
# Discussion.initializeDiscussionModule(elem)
|
||||
|
||||
$("section.discussion").each (index, discussion) ->
|
||||
Discussion.initializeDiscussion(discussion)
|
||||
Discussion.bindDiscussionEvents(discussion)
|
||||
#$("section.discussion").each (index, discussion) ->
|
||||
# Discussion.initializeDiscussion(discussion)
|
||||
# Discussion.bindDiscussionEvents(discussion)
|
||||
|
||||
Discussion.initializeUserProfile($(".discussion-sidebar>.user-profile"))
|
||||
#Discussion.initializeUserProfile($(".discussion-sidebar>.user-profile"))
|
||||
|
||||
BIN
lms/static/images/BerkeleyX-on-edx-logo.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
lms/static/images/HarvardX-on-edx-logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
lms/static/images/MITx-on-edx-logo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
lms/static/images/bg-texture.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
lms/static/images/small-header-home-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/small-header-logo.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
104
lms/static/js/jquery.sequence.js
Normal file
@@ -0,0 +1,104 @@
|
||||
|
||||
|
||||
|
||||
var SequenceNav = function($element) {
|
||||
var _this = this;
|
||||
var $element = $element;
|
||||
var $wrapper = $element.find('.sequence-list-wrapper');
|
||||
var $list = $element.find('#sequence-list');
|
||||
var $arrows = $element.find('.sequence-nav-buttons');
|
||||
var maxScroll = $list.width() - $wrapper.width() + 20;
|
||||
var $leftShadow = $('<div class="left-shadow"></div>');
|
||||
var $rightShadow = $('<div class="right-shadow"></div>');
|
||||
var $body = $('body');
|
||||
var listOrigin;
|
||||
var mouseOrigin;
|
||||
|
||||
var startDrag = function(e) {
|
||||
updateWidths();
|
||||
mouseOrigin = e.pageX;
|
||||
listOrigin = $list.position().left;
|
||||
$body.css('-webkit-user-select', 'none');
|
||||
$body.bind('mousemove', moveDrag);
|
||||
$body.bind('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
var moveDrag = function(e) {
|
||||
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);
|
||||
};
|
||||
|
||||
var stopDrag = function(e) {
|
||||
$body.css('-webkit-user-select', 'auto');
|
||||
$body.unbind('mousemove', moveDrag);
|
||||
$body.unbind('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
var setShadows = function(left) {
|
||||
var left = left || $list.position().left;
|
||||
var padding = 30;
|
||||
|
||||
var leftPercent = clamp(-left / padding, 0, 1);
|
||||
$leftShadow.css('opacity', leftPercent);
|
||||
|
||||
var rightPercent = clamp((maxScroll + left) / padding, 0, 1);
|
||||
$rightShadow.css('opacity', rightPercent);
|
||||
};
|
||||
|
||||
var clamp = function(val, min, max) {
|
||||
if(val > max) return max;
|
||||
if(val < min) return min;
|
||||
return val;
|
||||
};
|
||||
|
||||
var updateWidths = function(e) {
|
||||
maxScroll = $list.width() - $wrapper.width() + 20;
|
||||
var targetLeft = clamp($list.position().left, -maxScroll, 0);
|
||||
updateHorizontalPosition(targetLeft);
|
||||
setShadows(targetLeft);
|
||||
};
|
||||
|
||||
var updateHorizontalPosition = function(left) {
|
||||
$list.css({
|
||||
'left': left + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
var checkPosition = function(e) {
|
||||
var $active = $element.find('.active');
|
||||
if(!$active[0]) {
|
||||
return;
|
||||
}
|
||||
if($active.position().left + $active.width() > $wrapper.width() - $list.position().left) {
|
||||
$list.animate({
|
||||
'left': (-$active.position().left + $wrapper.width() - $active.width() - 10) + 'px'
|
||||
}, {
|
||||
step: setShadows
|
||||
});
|
||||
} else if($active.position().left < -$list.position().left) {
|
||||
$list.animate({
|
||||
'left': (-$active.position().left + 10) + 'px'
|
||||
}, {
|
||||
step: setShadows
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$wrapper.append($leftShadow).append($rightShadow);
|
||||
setShadows(0);
|
||||
$wrapper.bind('mousedown', startDrag);
|
||||
$arrows.bind('click', checkPosition);
|
||||
$(window).bind('resize', updateWidths);
|
||||
setTimeout(function() {
|
||||
checkPosition();
|
||||
}, 200);
|
||||
}
|
||||
3
lms/static/sass/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Sass Watch:
|
||||
|
||||
sass --watch lms/static/sass:lms/static/sass -r ./lms/static/sass/bourbon/lib/bourbon.rb
|
||||
@@ -55,10 +55,10 @@ $tag-text-color: #5b614f;
|
||||
|
||||
.sidebar-module {
|
||||
@include clearfix;
|
||||
padding: 0 24px 24px 0;
|
||||
padding: 0 26px 24px;
|
||||
margin-bottom: 24px;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
font-size: 0.8em;
|
||||
font-size: 13px;
|
||||
|
||||
header {
|
||||
margin-bottom: 14px;
|
||||
@@ -67,16 +67,18 @@ $tag-text-color: #5b614f;
|
||||
|
||||
h4 {
|
||||
float: left;
|
||||
font-size: 1.1em;
|
||||
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;
|
||||
@@ -91,9 +93,13 @@ $tag-text-color: #5b614f;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-new-post-button {
|
||||
margin: 40px 0 20px 0;
|
||||
}
|
||||
|
||||
.sidebar-view-all {
|
||||
float: right;
|
||||
font-size: 0.9em;
|
||||
font-size: 13px;
|
||||
line-height: 1.6em;
|
||||
@include standard-discussion-link;
|
||||
}
|
||||
@@ -108,6 +114,10 @@ $tag-text-color: #5b614f;
|
||||
a {
|
||||
@include standard-discussion-link;
|
||||
background: none;
|
||||
|
||||
span {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +350,7 @@ $tag-text-color: #5b614f;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.comment-count {
|
||||
.show-comments-wrapper {
|
||||
display: inline;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
@@ -15,16 +15,19 @@ $monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
|
||||
$body-font-size: em(14);
|
||||
$body-line-height: golden-ratio(.875em, 1);
|
||||
$base-font-color: rgb(60,60,60);
|
||||
$lighter-base-font-color: rgb(160,160,160);
|
||||
$lighter-base-font-color: rgb(100,100,100);
|
||||
|
||||
$blue: rgb(29,157,217);
|
||||
$pink: rgb(182,37,104);
|
||||
$yellow: rgb(255, 252, 221);
|
||||
$error-red: rgb(253, 87, 87);
|
||||
$border-color: #C8C8C8;
|
||||
$sidebar-color: #f6f6f6;
|
||||
$outer-border-color: #aaa;
|
||||
|
||||
// old variables
|
||||
|
||||
$light-gray: #ddd;
|
||||
$dark-gray: #333;
|
||||
$text-color: $dark-gray;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
// Course base / layout styles
|
||||
@import 'course/layout/courseware_header';
|
||||
@import 'course/layout/footer';
|
||||
@import 'course/base/base';
|
||||
@import 'course/base/extends';
|
||||
@import 'module/module-styles.scss';
|
||||
@@ -34,6 +35,9 @@
|
||||
@import "course/profile";
|
||||
@import "course/gradebook";
|
||||
|
||||
// instructor
|
||||
@import "course/instructor/instructor";
|
||||
|
||||
// Askbot / Discussion styles
|
||||
@import "course/discussion/askbot-original";
|
||||
@import "course/discussion/discussion";
|
||||
|
||||
@@ -6,6 +6,9 @@ div.gradebook-wrapper {
|
||||
|
||||
section.gradebook-content {
|
||||
@extend .content;
|
||||
display: block;
|
||||
width: 100%;
|
||||
@include clearfix;
|
||||
|
||||
.student-search {
|
||||
padding: 0 20px 0 15px;
|
||||
@@ -15,7 +18,7 @@ div.gradebook-wrapper {
|
||||
width: 100%;
|
||||
height: 27px;
|
||||
padding: 0 15px 0 35px;
|
||||
box-sizing: border-box;
|
||||
@include box-sizing(border-box);
|
||||
border-radius: 13px;
|
||||
border: 1px solid $table-border-color;
|
||||
background: url(../images/search-icon.png) no-repeat 9px center #f6f6f6;
|
||||
@@ -37,7 +40,6 @@ div.gradebook-wrapper {
|
||||
|
||||
.student-table {
|
||||
float: left;
|
||||
// width: 264px;
|
||||
width: 24%;
|
||||
border-radius: 3px 0 0 3px;
|
||||
color: #3c3c3c;
|
||||
@@ -88,12 +90,20 @@ div.gradebook-wrapper {
|
||||
|
||||
.left-shadow {
|
||||
left: 0;
|
||||
background: -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -webkit-gradient(linear, left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-gradient(linear, left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -moz-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -moz-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -ms-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -ms-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -o-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -o-linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.right-shadow {
|
||||
right: 0;
|
||||
background: -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -webkit-gradient(linear, right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-gradient(linear, right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -webkit-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -moz-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -moz-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -ms-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -ms-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
background-image: -o-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 20%), -o-linear-gradient(right, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,9 +113,8 @@ div.gradebook-wrapper {
|
||||
left: 0;
|
||||
width: 1000px;
|
||||
cursor: move;
|
||||
-webkit-transition: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
@include transition(none);
|
||||
@include user-select(none);
|
||||
|
||||
td,
|
||||
th {
|
||||
@@ -116,32 +125,30 @@ div.gradebook-wrapper {
|
||||
thead th {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
background: -webkit-linear-gradient(top, $cell-border-color, #ddd);
|
||||
@include linear-gradient(top, $cell-border-color, #ddd);
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
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: 100%;
|
||||
background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, .15));
|
||||
}
|
||||
// &: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;
|
||||
|
||||
&:before {
|
||||
display: hidden;
|
||||
}
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@@ -161,7 +168,7 @@ div.gradebook-wrapper {
|
||||
|
||||
.max {
|
||||
height: 12px;
|
||||
background: -webkit-linear-gradient(top, #c6c6c6, #bababa);
|
||||
@include linear-gradient(top, #c6c6c6, #bababa);
|
||||
font-size: 9px;
|
||||
line-height: 12px;
|
||||
color: #fff;
|
||||
|
||||
@@ -68,10 +68,10 @@ div.info-wrapper {
|
||||
|
||||
section.handouts {
|
||||
@extend .sidebar;
|
||||
border-left: 1px solid $border-color;
|
||||
border-right: 0;
|
||||
@include border-radius(0 4px 4px 0);
|
||||
border-left: 1px solid #ddd;
|
||||
@include box-shadow(none);
|
||||
font-size: 14px;
|
||||
|
||||
&:after {
|
||||
left: -1px;
|
||||
@@ -79,31 +79,51 @@ div.info-wrapper {
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend .bottom-border;
|
||||
margin-bottom: 0;
|
||||
padding: lh(.5) lh(.5);
|
||||
padding: 32px 26px 20px 26px;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ol {
|
||||
li {
|
||||
margin: 0 26px 14px 26px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding-left: lh(.5);
|
||||
padding-right: 0;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.expandable,
|
||||
&.collapsable {
|
||||
margin: 0 16px 14px 16px;
|
||||
@include transition(all .2s);
|
||||
|
||||
h4 {
|
||||
color: $blue;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
padding: lh(.25) 0 lh(.25) lh(1.5);
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsable {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 14px 0;
|
||||
@include box-shadow(0 0 1px 1px rgba(0, 0, 0, .1), 0 1px 3px rgba(0, 0, 0, .25));
|
||||
|
||||
h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.multiple {
|
||||
padding: lh(.5) 0 lh(.5) lh(.5);
|
||||
|
||||
a {
|
||||
@include inline-block;
|
||||
@@ -121,10 +141,10 @@ div.info-wrapper {
|
||||
|
||||
li {
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid $border-color;
|
||||
@include box-shadow(inset 0 1px 0 #eee);
|
||||
font-size: 1em;
|
||||
padding: lh(.5) 0 lh(.5) lh(.5);
|
||||
border-top: 1px solid #e6e6e6;
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
padding: 15px 30px;
|
||||
|
||||
a {
|
||||
@include inline-block;
|
||||
@@ -138,11 +158,11 @@ div.info-wrapper {
|
||||
}
|
||||
|
||||
div.hitarea {
|
||||
background-image: url('../images/treeview-default.gif');
|
||||
background-image: url('../images/treeview-default.gif') no-repeat;
|
||||
display: block;
|
||||
height: 100%;
|
||||
margin-left: 0;
|
||||
max-height: 30px;
|
||||
max-height: 20px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@@ -157,20 +177,20 @@ div.info-wrapper {
|
||||
}
|
||||
|
||||
&.expandable-hitarea {
|
||||
background-position: -72px 7px;
|
||||
background-position: -72px 0px;
|
||||
}
|
||||
|
||||
&.collapsable-hitarea {
|
||||
background-position: -55px -15px;
|
||||
background-position: -55px -23px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: 0;
|
||||
@include box-shadow(none);
|
||||
color: #aaa;
|
||||
color: #888;
|
||||
font-size: 1em;
|
||||
margin-bottom: em(6);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
div.syllabus {
|
||||
|
||||
padding: 0px 10px;
|
||||
padding: 2em 2.5em;
|
||||
|
||||
text-align: center;
|
||||
|
||||
@@ -9,15 +9,14 @@ div.syllabus {
|
||||
}
|
||||
|
||||
.notes {
|
||||
width: 740px;
|
||||
margin: 0px auto 10px;
|
||||
margin: 0px auto 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
|
||||
text-align: left;
|
||||
|
||||
margin: 10px auto;
|
||||
margin: 10px 0;
|
||||
|
||||
thead {
|
||||
font-weight: bold;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
div.book-wrapper {
|
||||
@extend .table-wrapper;
|
||||
|
||||
#open_close_accordion {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section.book-sidebar {
|
||||
@extend .sidebar;
|
||||
@extend .tran;
|
||||
@include box-sizing(border-box);
|
||||
padding: 10px 0;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border-right: 1px solid #ccc;
|
||||
|
||||
ul#booknav {
|
||||
font-size: em(14);
|
||||
@@ -32,7 +39,7 @@ div.book-wrapper {
|
||||
li {
|
||||
background: none;
|
||||
border-bottom: 0;
|
||||
padding-left: lh();
|
||||
padding-left: lh();
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
@@ -49,7 +56,7 @@ div.book-wrapper {
|
||||
|
||||
div.hitarea {
|
||||
background-image: url('../images/treeview-default.gif');
|
||||
margin-left: -22px;
|
||||
|
||||
position: relative;
|
||||
top: 4px;
|
||||
|
||||
@@ -62,26 +69,23 @@ div.book-wrapper {
|
||||
ul {
|
||||
background: none;
|
||||
margin-top: lh(.25);
|
||||
border-top: 1px solid $border-color;
|
||||
padding-top: lh(.25);
|
||||
|
||||
li {
|
||||
padding-bottom: lh(.25);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> li {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding: 7px 7px 7px 30px;
|
||||
padding: 5px 6px;
|
||||
margin: 0 16px 5px 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.book {
|
||||
@extend .content;
|
||||
padding-right: 0;
|
||||
padding-left: lh();
|
||||
|
||||
nav {
|
||||
@extend .clearfix;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
body {
|
||||
min-width: 980px;
|
||||
min-height: 100%;
|
||||
background: url(../images/bg-texture.png) #d6d6d6;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label {
|
||||
@@ -11,16 +13,37 @@ table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
color: $pink;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: lh(2);
|
||||
padding: 20px 0 0 0;
|
||||
|
||||
> div {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $outer-border-color;
|
||||
background: #fff;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
form {
|
||||
label {
|
||||
display: block;
|
||||
@@ -75,11 +98,7 @@ img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: em(40) 0;
|
||||
}
|
||||
|
||||
::selection, ::-moz-selection, ::-webkit-selection {
|
||||
background:#444;
|
||||
color:#fff;
|
||||
background: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ h1.top-header {
|
||||
.content {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
padding-right: lh();
|
||||
padding: 2em 2.5em;
|
||||
vertical-align: top;
|
||||
width: flex-grid(9) + flex-gutter();
|
||||
|
||||
@@ -47,34 +47,28 @@ h1.top-header {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
border-right: 1px solid #C8C8C8;
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
font-family: $sans-serif;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
width: flex-grid(3);
|
||||
|
||||
&:after {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
@include position(absolute, 0px -1px 0px 0);
|
||||
content: "";
|
||||
@include background-image(linear-gradient(top, #fff, rgba(#fff, 0)), linear-gradient(top, rgba(#fff, 0), #fff));
|
||||
background-position: top, bottom;
|
||||
@include background-size(1px 20px);
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
background: $sidebar-color;
|
||||
|
||||
h1, h2 {
|
||||
font-size: em(20);
|
||||
font-weight: 100;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
font-family: $sans-serif;
|
||||
text-align: left;
|
||||
font-style: italic;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
padding: 32px 26px 20px 26px;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -106,7 +100,7 @@ h1.top-header {
|
||||
}
|
||||
|
||||
&.active {
|
||||
@extend .bottom-border;
|
||||
// @extend .bottom-border;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -122,7 +116,7 @@ h1.top-header {
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
@extend .bottom-border;
|
||||
// @extend .bottom-border;
|
||||
@extend .clearfix;
|
||||
background: none;
|
||||
position: relative;
|
||||
|
||||
@@ -12,8 +12,7 @@ div.course-wrapper {
|
||||
|
||||
section.course-content {
|
||||
@extend .content;
|
||||
padding-right: 0;
|
||||
padding-left: lh();
|
||||
padding: 40px;
|
||||
|
||||
h1 {
|
||||
margin: 0 0 lh();
|
||||
@@ -46,6 +45,7 @@ div.course-wrapper {
|
||||
ol.vert-mod {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
|
||||
> li {
|
||||
@extend .clearfix;
|
||||
@@ -224,3 +224,12 @@ div.course-wrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.xmodule_VideoModule {
|
||||
margin-bottom: 30px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
section.course-index {
|
||||
@extend .sidebar;
|
||||
@extend .tran;
|
||||
@include border-radius(3px 0 0 3px);
|
||||
border-right: 1px solid #ddd;
|
||||
|
||||
#open_close_accordion {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header {
|
||||
max-height: 47px;
|
||||
@@ -11,10 +17,11 @@ section.course-index {
|
||||
}
|
||||
|
||||
div#accordion {
|
||||
width: auto;
|
||||
font-size: 14px;
|
||||
|
||||
h3 {
|
||||
@include border-radius(0);
|
||||
border-top: 1px solid lighten($border-color, 10%);
|
||||
font-size: em(16, 18);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -24,7 +31,6 @@ section.course-index {
|
||||
|
||||
&:hover {
|
||||
color: #666;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
&.ui-state-hover {
|
||||
@@ -40,6 +46,7 @@ section.course-index {
|
||||
a {
|
||||
@include border-radius(0);
|
||||
@include box-shadow(none);
|
||||
padding-left: 19px;
|
||||
}
|
||||
|
||||
&.ui-state-active {
|
||||
@@ -52,28 +59,50 @@ section.course-index {
|
||||
}
|
||||
|
||||
span.ui-icon {
|
||||
left: 0;
|
||||
background-image: url("/static/images/ui-icons_222222_256x240.png");
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
padding: 11px 14px;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .6), rgba(255, 255, 255, 0));
|
||||
background-color: #eee;
|
||||
@include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset);
|
||||
@include transition(background-color .1s);
|
||||
|
||||
&:first-child {
|
||||
border-radius: 3px 0 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 0 3px;
|
||||
@include box-shadow(0 1px 0 #fff inset);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fff
|
||||
}
|
||||
}
|
||||
|
||||
ul.ui-accordion-content {
|
||||
background: transparent;
|
||||
border: none;
|
||||
@include border-radius(0);
|
||||
font-size: em(14, 18);
|
||||
margin: 0;
|
||||
padding: 1em 1.5em;
|
||||
padding: 9px 0 9px 9px;
|
||||
|
||||
li {
|
||||
border-bottom: 0;
|
||||
@include border-radius(0);
|
||||
margin-bottom: lh(.5);
|
||||
margin-bottom: 4px;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
@include border-radius(4px);
|
||||
display: block;
|
||||
padding: 5px 36px 5px 10px;
|
||||
@@ -84,39 +113,18 @@ section.course-index {
|
||||
font-weight: bold;
|
||||
font-family: $sans-serif;
|
||||
margin-bottom: 0;
|
||||
line-height: 1.3;
|
||||
|
||||
span.subtitle {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: transparent;
|
||||
border-right: 1px solid rgb(180,180,180);
|
||||
border-top: 1px solid rgb(180,180,180);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 50%;
|
||||
@include transform(rotate(45deg));
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include background-image(linear-gradient(-90deg, rgba(245,245,245, 0.4), rgba(230,230,230, 0.4)));
|
||||
border-color: rgb(200,200,200);
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
@include transition();
|
||||
}
|
||||
background: rgba(0, 0, 0, .1);
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
@@ -136,8 +144,23 @@ section.course-index {
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
|
||||
&:after {
|
||||
content: '›';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
margin-top: -13px;
|
||||
font-size: 30px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
opacity: 0;
|
||||
@include transition();
|
||||
}
|
||||
|
||||
> a {
|
||||
border-color: rgb(200,200,200);
|
||||
border: 1px solid #bbb;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .35) inset);
|
||||
@include linear-gradient(top, #e6e6e6, #d6d6d6);
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
|
||||
@@ -3,15 +3,16 @@ body.askbot {
|
||||
section.container {
|
||||
div.discussion-wrapper {
|
||||
@extend .table-wrapper;
|
||||
display: table;
|
||||
|
||||
div.discussion-content {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
min-width: 650px;
|
||||
padding-right: lh();
|
||||
vertical-align: top;
|
||||
padding: 40px;
|
||||
width: flex-grid(9) + flex-gutter();
|
||||
|
||||
|
||||
a.tabula-rasa, .tabula-rasa{
|
||||
@extend .light-button;
|
||||
@include border-radius(5px);
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
div.discussion-wrapper aside {
|
||||
@extend .sidebar;
|
||||
border-left: 1px solid $border-color;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 0;
|
||||
width: flex-grid(3);
|
||||
border-radius: 0 3px 3px 0;
|
||||
|
||||
&:after {
|
||||
left: -1px;
|
||||
@@ -16,9 +17,7 @@ div.discussion-wrapper aside {
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend .bottom-border;
|
||||
padding: lh(.5) lh();
|
||||
margin-bottom: em(16, 20);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -54,7 +53,7 @@ div.discussion-wrapper aside {
|
||||
|
||||
div.box {
|
||||
display: block;
|
||||
padding: lh(.5) lh();
|
||||
padding: 18px 26px;
|
||||
border-top: 1px solid lighten($border-color, 10%);
|
||||
|
||||
&:first-child {
|
||||
@@ -67,7 +66,7 @@ div.discussion-wrapper aside {
|
||||
|
||||
li {
|
||||
border-bottom: 0;
|
||||
background: #eee;
|
||||
background: #ddd;
|
||||
padding: 6px 10px 6px 5px;
|
||||
|
||||
a {
|
||||
@@ -298,6 +297,7 @@ div.discussion-wrapper aside {
|
||||
|
||||
div.view-profile {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
|
||||
a {
|
||||
@extend .gray-button;
|
||||
|
||||
@@ -10,7 +10,7 @@ ul.tags {
|
||||
}
|
||||
|
||||
li {
|
||||
background: #eee;
|
||||
background: #ddd;
|
||||
color: #555;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
@@ -19,7 +19,7 @@ ul.tags {
|
||||
padding: 6px 10px 6px 5px;
|
||||
|
||||
&:before {
|
||||
border-color:transparent #eee transparent transparent;
|
||||
border-color:transparent #ddd transparent transparent;
|
||||
border-style:solid;
|
||||
border-width:12px 10px 12px 0;
|
||||
content:"";
|
||||
|
||||
15
lms/static/sass/course/instructor/_instructor.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.instructor-dashboard-wrapper {
|
||||
@extend .table-wrapper;
|
||||
display: table;
|
||||
|
||||
section.instructor-dashboard-content {
|
||||
@extend .content;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
|
||||
h1 {
|
||||
@extend .top-header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
nav.course-material {
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
background: #f6f6f6;
|
||||
border-bottom: 1px solid rgb(200,200,200);
|
||||
border-bottom: none;
|
||||
margin: 0px auto 0px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
@@ -16,33 +15,37 @@ nav.course-material {
|
||||
ol.course-tabs {
|
||||
@include border-top-radius(4px);
|
||||
@include clearfix;
|
||||
padding: 10px 0 0 0;
|
||||
padding: 28px 0 10px 0;
|
||||
margin-left: 10px;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
margin-right: 6px;
|
||||
|
||||
a {
|
||||
color: darken($lighter-base-font-color, 20%);
|
||||
border-radius: 3px;
|
||||
color: #555;
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 8px 13px 12px;
|
||||
padding: 10px 13px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px rgb(255,255,255);
|
||||
// text-shadow: 0 1px 0 rgba(0, 0, 0, .4);
|
||||
|
||||
&:hover {
|
||||
color: $base-font-color;
|
||||
color: #333;
|
||||
background: rgba(255, 255, 255, .6);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgb(255,255,255);
|
||||
border: 1px solid rgb(200,200,200);
|
||||
border-bottom: 0px;
|
||||
@include border-top-radius(4px);
|
||||
@include box-shadow(0 2px 0 0 rgba(255,255,255, 1));
|
||||
color: $blue;
|
||||
// background: rgba(0, 0, 0, .2);
|
||||
@include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25));
|
||||
background-color: transparent;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset);
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +60,48 @@ nav.course-material {
|
||||
}
|
||||
}
|
||||
|
||||
.global {
|
||||
header.global.slim {
|
||||
border-bottom: 1px solid $outer-border-color;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
height: 50px;
|
||||
@include linear-gradient(top, #fff, #eee);
|
||||
|
||||
.guest .secondary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
h1.logo {
|
||||
margin-left: 13px;
|
||||
margin-right: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
&::before {
|
||||
@extend .faded-vertical-divider;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: -8px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@extend .faded-vertical-divider-light;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: -12px;
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.find-courses-button {
|
||||
display: none;
|
||||
}
|
||||
@@ -68,8 +112,9 @@ nav.course-material {
|
||||
float: left;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
color: #777;
|
||||
letter-spacing: 0;
|
||||
margin-top: 9px;
|
||||
text-transform: none;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
white-space: nowrap;
|
||||
@@ -79,7 +124,16 @@ nav.course-material {
|
||||
.provider {
|
||||
font: inherit;
|
||||
font-weight: bold;
|
||||
color: #6d6d6d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a#signup {
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
padding: 6px 12px 8px;
|
||||
text-transform: none;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
nav.course-material {
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
background: #f6f6f6;
|
||||
border-bottom: 1px solid rgb(200,200,200);
|
||||
background: none;
|
||||
margin: 0px auto 0px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
@@ -37,11 +36,6 @@ nav.course-material {
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgb(255,255,255);
|
||||
border: 1px solid rgb(200,200,200);
|
||||
border-bottom: 0px;
|
||||
@include border-top-radius(4px);
|
||||
@include box-shadow(0 2px 0 0 rgba(255,255,255, 1));
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
4
lms/static/sass/course/layout/_footer.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
footer {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
section.wiki {
|
||||
padding-top: 25px;
|
||||
|
||||
> header {
|
||||
height: 33px;
|
||||
margin-bottom: 36px;
|
||||
padding-bottom: 26px;
|
||||
border-bottom: 1px solid $light-gray;
|
||||
}
|
||||
|
||||
.pull-left {
|
||||
float: left;
|
||||
}
|
||||
@@ -16,6 +9,18 @@ section.wiki {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.wiki-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
> header {
|
||||
height: 33px;
|
||||
padding: 24px 0 26px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background-color: $sidebar-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
@@ -27,28 +32,32 @@ section.wiki {
|
||||
.breadcrumb {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0 0 0 flex-gutter();
|
||||
margin: 0 0 0 40px;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
font-size: 0.9em;
|
||||
line-height: 31px;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
float: left;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
height: 30px;
|
||||
height: 30px;
|
||||
line-height: 31px;
|
||||
max-width: 200px;
|
||||
height: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '›';
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
margin-left: 10px;
|
||||
color: $base-font-color;
|
||||
height: 30px;
|
||||
line-height: 31px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +77,7 @@ section.wiki {
|
||||
.global-functions {
|
||||
display: block;
|
||||
width: auto;
|
||||
margin-right: flex-gutter();
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.add-article-btn {
|
||||
@@ -129,8 +138,9 @@ section.wiki {
|
||||
.main-article {
|
||||
float: left;
|
||||
width: flex-grid(9);
|
||||
margin-left: flex-gutter();
|
||||
padding: 40px 0 40px 40px;
|
||||
color: $base-font-color;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
&.view .main-article {
|
||||
@@ -193,6 +203,17 @@ section.wiki {
|
||||
font-size: 0.9em;
|
||||
font-family: Monaco, monospace;
|
||||
}
|
||||
|
||||
.toc {
|
||||
background-color: $sidebar-color;
|
||||
padding: 9px;
|
||||
margin: 10px 0;
|
||||
@include border-radius(5px);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,13 +227,14 @@ section.wiki {
|
||||
|
||||
.article-functions {
|
||||
float: left;
|
||||
width: flex-grid(2) + flex-gutter();
|
||||
margin-left: flex-grid(1);
|
||||
width: flex-grid(3);
|
||||
padding: 40px 40px;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
.timestamp {
|
||||
margin: 4px 0 15px;
|
||||
padding: 0 0 15px 5px;
|
||||
border-bottom: 1px solid $light-gray;
|
||||
.timestamp{
|
||||
margin-top: 15px;
|
||||
padding: 15px 0 0 10px;
|
||||
border-top: 1px solid $light-gray;
|
||||
|
||||
.label {
|
||||
font-size: 0.7em;
|
||||
@@ -223,6 +245,26 @@ section.wiki {
|
||||
.date {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.see-children {
|
||||
padding: 15px 0 0;
|
||||
border-top: 1px solid $light-gray;
|
||||
margin-top: 15px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 2px 4px 2px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
line-height: 25px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f6f6f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +278,8 @@ section.wiki {
|
||||
a {
|
||||
color: $blue;
|
||||
|
||||
.icon-view {
|
||||
.icon-view,
|
||||
.icon-home {
|
||||
background-position: -25px 0;
|
||||
}
|
||||
|
||||
@@ -244,11 +287,13 @@ section.wiki {
|
||||
background-position: -25px -25px;
|
||||
}
|
||||
|
||||
.icon-changes {
|
||||
.icon-changes,
|
||||
.icon-time {
|
||||
background-position: -25px -49px;
|
||||
}
|
||||
|
||||
.icon-attachments {
|
||||
.icon-attachments,
|
||||
.icon-file {
|
||||
background-position: -25px -73px;
|
||||
}
|
||||
|
||||
@@ -280,7 +325,8 @@ section.wiki {
|
||||
background: url(../images/wiki-icons.png) no-repeat;
|
||||
}
|
||||
|
||||
.icon-view {
|
||||
.icon-view,
|
||||
.icon-home {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
@@ -288,11 +334,13 @@ section.wiki {
|
||||
background-position: 0 -25px;
|
||||
}
|
||||
|
||||
.icon-changes {
|
||||
.icon-changes,
|
||||
.icon-time {
|
||||
background-position: 0 -49px;
|
||||
}
|
||||
|
||||
.icon-attachments {
|
||||
.icon-attachments,
|
||||
.icon-file {
|
||||
background-position: 0 -73px;
|
||||
}
|
||||
|
||||
@@ -346,7 +394,6 @@ section.wiki {
|
||||
.CodeMirror {
|
||||
background: #fafafa;
|
||||
border: 1px solid #c8c8c8;
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(0 1px 0 0 rgba(255, 255, 255, 0.6), inset 0 0 3px 0 rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
@@ -646,6 +693,82 @@ section.wiki {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-size: 1em;
|
||||
color: $pink;
|
||||
|
||||
.help-block {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
border-color: $pink;
|
||||
}
|
||||
}
|
||||
|
||||
.back {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
|
||||
Directory
|
||||
|
||||
-----------------*/
|
||||
.directory-toolbar {
|
||||
background-color: $sidebar-color;
|
||||
padding: 9px;
|
||||
margin: 0 -9px 20px;
|
||||
@include border-radius(5px);
|
||||
|
||||
.well-small {
|
||||
@include clearfix;
|
||||
|
||||
a {
|
||||
@include inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
+ p {
|
||||
font-size: 0.9em;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-clear {
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
font-size: .9em;
|
||||
|
||||
a {
|
||||
color: #aaa;
|
||||
|
||||
&:hover {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table.table-striped {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
|
||||
th, td {
|
||||
border-bottom: 1px solid $light-gray;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background: #F6F6F6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -706,6 +829,10 @@ section.wiki {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.attachment-actions {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.attachment-actions .btn {
|
||||
float: right;
|
||||
}
|
||||
@@ -738,6 +865,10 @@ section.wiki {
|
||||
}
|
||||
}
|
||||
|
||||
section.delete {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -767,6 +898,39 @@ section.wiki {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.missing {
|
||||
max-width: 400px;
|
||||
margin: lh(2) auto;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
background: $pink;
|
||||
padding: lh();
|
||||
@include box-shadow(inset 0 0 0 1px lighten($pink, 10%));
|
||||
border: 1px solid darken($pink, 15%);
|
||||
|
||||
p {
|
||||
color: #fff;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
background: darken($pink, 8%);
|
||||
margin: lh() (-(lh())) (-(lh()));
|
||||
padding: lh();
|
||||
border-top: 1px solid darken($pink, 15%);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: em(18);
|
||||
@include transition;
|
||||
text-align: center;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&:hover {
|
||||
background: darken($pink, 12%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
|
||||
@@ -48,7 +48,6 @@ footer {
|
||||
|
||||
a:link, a:visited {
|
||||
color: $lighter-base-font-color;
|
||||
letter-spacing: 1px;
|
||||
padding: 6px 0px;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +69,7 @@ footer {
|
||||
position: relative;
|
||||
width: 47px;
|
||||
vertical-align: middle;
|
||||
@include transition(none);
|
||||
|
||||
&:hover {
|
||||
background-position: 0 0;
|
||||
@@ -90,7 +90,6 @@ footer {
|
||||
a {
|
||||
color: $lighter-base-font-color;
|
||||
@include inline-block;
|
||||
letter-spacing: 1px;
|
||||
margin-right: 20px;
|
||||
padding-top: 2px;
|
||||
vertical-align: middle;
|
||||
@@ -165,7 +164,6 @@ footer {
|
||||
color: $lighter-base-font-color;
|
||||
font-family: $serif;
|
||||
font-style: italic;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.6em;
|
||||
margin-left: 20px;
|
||||
text-transform: lowercase;
|
||||
|
||||
@@ -19,7 +19,7 @@ header.global {
|
||||
|
||||
h1.logo {
|
||||
float: left;
|
||||
margin: 6px 15px 0px 0px;
|
||||
margin: 0px 15px 0px 0px;
|
||||
padding-right: 20px;
|
||||
position: relative;
|
||||
|
||||
@@ -46,12 +46,7 @@ header.global {
|
||||
}
|
||||
|
||||
a {
|
||||
@include background-image(url('/static/images/header-logo.png'));
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
height: 31px;
|
||||
width: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +131,7 @@ header.global {
|
||||
|
||||
&.user {
|
||||
float: right;
|
||||
margin-top: 4px;
|
||||
|
||||
> li.primary {
|
||||
display: block;
|
||||
@@ -151,22 +147,22 @@ header.global {
|
||||
> a {
|
||||
@include border-radius(0 4px 4px 0);
|
||||
border-left: none;
|
||||
padding: 5px 8px 7px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.user-link {
|
||||
padding: 10px 12px 10px 42px;
|
||||
padding: 6px 12px 8px 35px;
|
||||
position: relative;
|
||||
text-transform: none;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0;
|
||||
|
||||
.avatar {
|
||||
//background: rgb(220,220,220);
|
||||
@include background-image(url('../images/portal-icons/home-icon.png'));
|
||||
background-size: cover;
|
||||
//@include border-radius(3px);
|
||||
//border: 1px solid rgb(80,80,80);
|
||||
//@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
|
||||
@include background-image(url('../images/small-header-home-icon.png'));
|
||||
background-repeat: no-repeat;
|
||||
height: 26px;
|
||||
@include inline-block;
|
||||
left: 8px;
|
||||
@@ -194,7 +190,7 @@ header.global {
|
||||
padding: 5px 10px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 50px;
|
||||
top: 34px;
|
||||
width: 170px;
|
||||
z-index: 3;
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
|
||||
<%def name="make_chapter(chapter)">
|
||||
<h3 ${' class="active"' if 'active' in chapter and chapter['active'] else ''}><a href="#">${chapter['display_name']}</a>
|
||||
</h3>
|
||||
<div class="chapter">
|
||||
<h3 ${' class="active"' if 'active' in chapter and chapter['active'] else ''}><a href="#">${chapter['display_name']}</a>
|
||||
</h3>
|
||||
|
||||
<ul>
|
||||
% for section in chapter['sections']:
|
||||
<li${' class="active"' if 'active' in section and section['active'] else ''}>
|
||||
<a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
|
||||
<p>${section['display_name']}
|
||||
<span class="subtitle">
|
||||
${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}
|
||||
</span>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
<ul>
|
||||
% for section in chapter['sections']:
|
||||
<li${' class="active"' if 'active' in section and section['active'] else ''}>
|
||||
<a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
|
||||
<p>${section['display_name']}
|
||||
<span class="subtitle">
|
||||
${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}
|
||||
</span>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
% for chapter in toc:
|
||||
|
||||
@@ -19,7 +19,7 @@ def url_class(url):
|
||||
<ol class="course-tabs">
|
||||
<li class="courseware"><a href="${reverse('courseware', args=[course.id])}" class="${url_class('courseware')}">Courseware</a></li>
|
||||
<li class="info"><a href="${reverse('info', args=[course.id])}" class="${url_class('info')}">Course Info</a></li>
|
||||
% if settings.MITX_FEATURES.get('ENABLE_SYLLABUS'):
|
||||
% if hasattr(course,'syllabus_present') and course.syllabus_present:
|
||||
<li class="syllabus"><a href="${reverse('syllabus', args=[course.id])}" class="${url_class('syllabus')}">Syllabus</a></li>
|
||||
% endif
|
||||
% if user.is_authenticated():
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<%inherit file="/main.html" />
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%block name="bodyclass">courseware</%block>
|
||||
<%block name="bodyclass">courseware ${course.css_class}</%block>
|
||||
<%block name="title"><title>${course.number} Courseware</title></%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='course'/>
|
||||
<%include file="../discussion/_js_head_dependencies.html" />
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery.scrollTo-1.4.2-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.sequence.js')}"></script>
|
||||
|
||||
## codemirror
|
||||
<script type="text/javascript" src="${static.url('js/vendor/codemirror-compressed.js')}"></script>
|
||||
@@ -19,10 +21,10 @@
|
||||
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script>
|
||||
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script>
|
||||
|
||||
<%static:js group='courseware'/>
|
||||
<%static:js group='courseware'/>
|
||||
|
||||
<%include file="../discussion/_js_dependencies.html" />
|
||||
<%include file="/mathjax_include.html" />
|
||||
<%include file="../discussion/_js_body_dependencies.html" />
|
||||
|
||||
|
||||
<!-- TODO: http://docs.jquery.com/Plugins/Validation -->
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
<thead>
|
||||
<tr> <!-- Header Row -->
|
||||
%for section in templateSummary['section_breakdown']:
|
||||
<th>${section['label']}</th>
|
||||
<th><div class="assignment-label">${section['label']}</div></th>
|
||||
%endfor
|
||||
<th>Total</th>
|
||||
<th><div class="assignment-label">Total</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
%>
|
||||
|
||||
<section class="container">
|
||||
<div class="syllabus">
|
||||
<h1> Syllabus </h1>
|
||||
% if user.is_authenticated():
|
||||
${get_course_syllabus_section(course, 'syllabus')}
|
||||
% else:
|
||||
${get_course_syllabus_section(course, 'guest_syllabus')}
|
||||
% endif
|
||||
<div class="syllabus_wrapper">
|
||||
<div class="syllabus">
|
||||
<h1> Syllabus </h1>
|
||||
% if user.is_authenticated():
|
||||
${get_course_syllabus_section(course, 'syllabus')}
|
||||
% else:
|
||||
${get_course_syllabus_section(course, 'guest_syllabus')}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
</%def>
|
||||
|
||||
<%def name="render_content_with_comments(content)">
|
||||
<div class="${content['type']}" _id="${content['id']}" _discussion_id="${content.get('commentable_id')}" _author_id="${helpers.show_if(content['user_id'], content.get('anonymous'))}">
|
||||
<div class="${content['type']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}">
|
||||
${render_content(content)}
|
||||
% if content.get('children') is not None:
|
||||
${render_comments(content['children'])}
|
||||
% endif
|
||||
${render_comments(content.get('children', []))}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<section class="discussion forum-discussion" _id="${discussion_id}">
|
||||
|
||||
<div class="discussion-non-content discussion-local">
|
||||
<div class="discussion-non-content local">
|
||||
<div class="search-wrapper">
|
||||
<%include file="_search_bar.html" />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<section class="discussion inline-discussion" _id="${discussion_id}">
|
||||
|
||||
<div class="discussion-non-content discussion-local"></div>
|
||||
<div class="discussion-non-content local"></div>
|
||||
|
||||
<div class="threads">
|
||||
% for thread in threads:
|
||||
|
||||
4
lms/templates/discussion/_js_body_dependencies.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<%! from django_comment_client.helpers import include_mustache_templates %>
|
||||
|
||||
<%include file="/mathjax_include.html" />
|
||||
${include_mustache_templates()}
|
||||
@@ -1,10 +1,13 @@
|
||||
<%! from django.template.defaultfilters import escapejs %>
|
||||
|
||||
<script type="text/javascript">
|
||||
var $$user_info = JSON.parse("${user_info | escapejs}");
|
||||
var $$course_id = "${course_id | escapejs}";
|
||||
if (typeof $$annotated_content_info === undefined || $$annotated_content_info === null) {
|
||||
if (typeof $$annotated_content_info === undefined) {
|
||||
var $$annotated_content_info = {};
|
||||
}
|
||||
$$annotated_content_info = $.extend($$annotated_content_info, JSON.parse("${annotated_content_info | escapejs}"));
|
||||
if (typeof $$discussion_data === undefined) {
|
||||
var $$discussion_data = {};
|
||||
}
|
||||
$$discussion_data = $.extend($$discussion_data, JSON.parse("${discussion_data | escapejs}"));
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<script type="text/x-mathjax-config">
|
||||
MathJax.Hub.Config({
|
||||
tex2jax: {
|
||||
inlineMath: [
|
||||
["\\(","\\)"],
|
||||
],
|
||||
displayMath: [
|
||||
["\\[","\\]"],
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
## This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
|
||||
## It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of MathJax extension libraries
|
||||
<script type="text/javascript" src="/static/js/vendor/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"></script>
|
||||
<!---<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js"> </script>-->
|
||||
<script type="text/javascript" src="${static.url('js/split.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.ajaxfileupload.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/Markdown.Converter.js')}"></script>
|
||||
@@ -27,5 +10,8 @@
|
||||
<script type="text/javascript" src="${static.url('js/jquery.tagsinput.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/mustache.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/URI.min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
|
||||
<link href="${static.url('css/vendor/jquery.tagsinput.css')}" rel="stylesheet" type="text/css">
|
||||
<link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css">
|
||||
|
||||
17
lms/templates/discussion/_js_head_dependencies.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<script type="text/javascript" src="${static.url('js/split.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.ajaxfileupload.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/Markdown.Converter.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/Markdown.Sanitizer.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/Markdown.Editor.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.autocomplete.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.timeago.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.tagsinput.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/mustache.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/URI.min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
|
||||
|
||||
<link href="${static.url('css/vendor/jquery.tagsinput.css')}" rel="stylesheet" type="text/css">
|
||||
<link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css">
|
||||
@@ -36,7 +36,7 @@
|
||||
% endfor
|
||||
</%def>
|
||||
|
||||
<div class="discussion-${discussion_type}-paginator discussion-paginator">
|
||||
<div class="discussion-${discussion_type}-paginator discussion-paginator local">
|
||||
<div class="prev-page">
|
||||
% if page > 1:
|
||||
${link_to_page(page - 1, "< Previous page")}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
<section class="discussion" _id="${discussion_id}">
|
||||
<a class="discussion-title" href="javascript:void(0)">Discussion</a>
|
||||
${renderer.render_content_with_comments(thread)}
|
||||
<div class="threads">
|
||||
${renderer.render_content_with_comments(thread)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<%include file="_js_data.html" />
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<a class="discussion-sort-link ${cls}" href="javascript:void(0)" sort-url="${url_for_sort(key, order)}">${title}</a>
|
||||
</%def>
|
||||
|
||||
<div class="discussion-sort discussion-local">
|
||||
<div class="discussion-sort local">
|
||||
<span class="discussion-label">Sort by:</span>
|
||||
${link_to_sort('activity', 'top')}
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='course'/>
|
||||
<%include file="_js_head_dependencies.html" />
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<%include file="_js_dependencies.html" />
|
||||
<%include file="_js_body_dependencies.html" />
|
||||
</%block>
|
||||
|
||||
<%include file="/courseware/course_navigation.html" args="active_page='discussion'" />
|
||||
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="discussion-content">
|
||||
<div class="discussion-content local">
|
||||
<div class="discussion-content-wrapper">
|
||||
<div class="discussion-votes">
|
||||
<a class="discussion-vote discussion-vote-up" href="javascript:void(0)">▲</a>
|
||||
<a class="discussion-vote discussion-vote-up" href="javascript:void(0)" value="up">▲</a>
|
||||
<div class="discussion-votes-point">{{content.votes.point}}</div>
|
||||
<a class="discussion-vote discussion-vote-down" href="javascript:void(0)">▼</a>
|
||||
<a class="discussion-vote discussion-vote-down" href="javascript:void(0)" value="down">▼</a>
|
||||
</div>
|
||||
<div class="discussion-right-wrapper">
|
||||
<ul class="admin-actions">
|
||||
@@ -15,12 +15,12 @@
|
||||
{{/thread}}
|
||||
</ul>
|
||||
{{#thread}}
|
||||
<a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{{content.displayed_title}}}</a>
|
||||
<a class="thread-title" name="{{content.id}}" href="javascript:void(0)">{{content.displayed_title}}</a>
|
||||
<div class="thread-raw-title" style="display: none">{{{content.title}}}</div>
|
||||
{{/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-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">
|
||||
@@ -40,20 +40,22 @@
|
||||
<a href="{{##url_for_user}}{{content.user_id}}{{/url_for_user}}">{{content.username}}</a>
|
||||
{{/content.anonymous}}
|
||||
</div>
|
||||
<div class="comment-count">
|
||||
<div class="show-comments-wrapper">
|
||||
{{#thread}}
|
||||
{{#partial_comments}}
|
||||
<a href="javascript:void(0)" class="discussion-show-comments first-time">Show all comments ({{content.comments_count}} total)</a>
|
||||
<a href="javascript:void(0)" class="discussion-show-comments first-time">Show all comments (<span class="comments-count">{{content.comments_count}}</span> total)</a>
|
||||
{{/partial_comments}}
|
||||
{{^partial_comments}}
|
||||
<a href="javascript:void(0)" class="discussion-show-comments">Show {{##pluralize}}{{content.comments_count}} comment{{/pluralize}}</a>
|
||||
<a href="javascript:void(0)" class="discussion-show-comments">Show <span class="comments-count">{{content.comments_count}}</span> {{##pluralize}}{{content.comments_count}} comment{{/pluralize}}</a>
|
||||
{{/partial_comments}}
|
||||
{{/thread}}
|
||||
</div>
|
||||
<ul class="discussion-actions">
|
||||
<li><a class="discussion-link discussion-reply discussion-reply-{{content.type}}" href="javascript:void(0)">Reply</a></li>
|
||||
<li><div class="follow-wrapper"></div></li>
|
||||
<li><a class="discussion-link discussion-permanent-link" href="javascript:void(0)">Permanent Link</a></li>
|
||||
{{#thread}}
|
||||
<li><div class="follow-wrapper"><a class="discussion-link discussion-follow-thread" href="javascript:void(0)">Follow</a></div></li>
|
||||
{{/thread}}
|
||||
<li><a class="discussion-link discussion-permanent-link" href="{{content.permalink}}">Permanent Link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
8
lms/templates/discussion/mustache/_edit_comment.mustache
Normal file
@@ -0,0 +1,8 @@
|
||||
<form class="discussion-content-edit discussion-comment-edit" _id="{{id}}">
|
||||
<ul class="discussion-errors discussion-update-errors"></ul>
|
||||
<div class="comment-body-edit body-input">{{body}}</div>
|
||||
<div class = "edit-post-control">
|
||||
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
|
||||
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
|
||||
</div>
|
||||
</form>
|
||||
10
lms/templates/discussion/mustache/_edit_thread.mustache
Normal file
@@ -0,0 +1,10 @@
|
||||
<form class="discussion-content-edit discussion-thread-edit" _id="{{id}}">
|
||||
<ul class="discussion-errors discussion-update-errors"></ul>
|
||||
<input type="text" class="thread-title-edit title-input" placeholder="Title" value="{{title}}"/>
|
||||
<div class="thread-body-edit body-input">{{body}}</div>
|
||||
<input class="thread-tags-edit" placeholder="Tags" value="{{tags}}" />
|
||||
<div class = "edit-post-control">
|
||||
<a class="discussion-cancel-update" href="javascript:void(0)">Cancel</a>
|
||||
<a class="discussion-submit-update control-button" href="javascript:void(0)">Update</a>
|
||||
</div>
|
||||
</form>
|
||||
21
lms/templates/discussion/mustache/_new_post.mustache
Normal file
@@ -0,0 +1,21 @@
|
||||
<form class="new-post-form collapsed" id="new-post-form" style="display: block; ">
|
||||
<ul class="new-post-form-errors discussion-errors"></ul>
|
||||
<input type="text" class="new-post-title title-input" placeholder="Title" />
|
||||
<div class="new-post-similar-posts-wrapper" style="display: none">
|
||||
Similar Posts:
|
||||
<a class="hide-similar-posts" href="javascript:void(0)">Hide</a>
|
||||
<div class="new-post-similar-posts"></div>
|
||||
</div>
|
||||
<div class="new-post-body reply-body"></div>
|
||||
<input class="new-post-tags" placeholder="Tags" />
|
||||
<div class="post-options">
|
||||
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-${discussion_id}">
|
||||
<label for="discussion-post-anonymously-${discussion_id}">post anonymously</label>
|
||||
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-${discussion_id}" checked="">
|
||||
<label for="discussion-auto-watch-${discussion_id}">follow this thread</label>
|
||||
</div>
|
||||
<div class="new-post-control post-control">
|
||||
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
|
||||
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
|
||||
</div>
|
||||
</form>
|
||||
15
lms/templates/discussion/mustache/_reply.mustache
Normal file
@@ -0,0 +1,15 @@
|
||||
<form class="discussion-reply-new">
|
||||
<ul class="discussion-errors"></ul>
|
||||
<div class="reply-body"></div>
|
||||
<input type="checkbox" class="discussion-post-anonymously" id="discussion-post-anonymously-{{id}}" />
|
||||
<label for="discussion-post-anonymously-{{id}}">post anonymously</label>
|
||||
{{#showWatchCheckbox}}
|
||||
<input type="checkbox" class="discussion-auto-watch" id="discussion-autowatch-{{id}}" checked />
|
||||
<label for="discussion-auto-watch-{{id}}">follow this thread</label>
|
||||
{{/showWatchCheckbox}}
|
||||
<br />
|
||||
<div class="reply-post-control">
|
||||
<a class="discussion-cancel-post" href="javascript:void(0)">Cancel</a>
|
||||
<a class="discussion-submit-post control-button" href="javascript:void(0)">Submit</a>
|
||||
</div>
|
||||
</form>
|
||||
29
lms/templates/enroll_students.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<h1>Student Enrollment Form </h1>
|
||||
|
||||
<p> Course: ${ course }
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
|
||||
<h2> Add new students </h2>
|
||||
<textarea name="new_students">
|
||||
</textarea>
|
||||
<input type="submit">
|
||||
</form>
|
||||
|
||||
<p> Existing students:
|
||||
|
||||
<p> ${ existing_students }
|
||||
|
||||
<p> New students added:
|
||||
${ added_students }
|
||||
|
||||
<p> Students rejected:
|
||||
${ rejected_students }
|
||||
|
||||
<p> Debug:
|
||||
<p> ${ debug }
|
||||
|
||||
|
||||
<p> foo
|
||||
<p> bar
|
||||
<p> biff
|
||||
@@ -6,7 +6,7 @@
|
||||
<nav>
|
||||
<section class="top">
|
||||
<section class="primary">
|
||||
<a href="${reverse('root')}" class="logo"></a>
|
||||
<a href="https://www.edx.org" class="logo"></a>
|
||||
<a href="${reverse('courses')}">Find Courses</a>
|
||||
<a href="${reverse('about_edx')}">About</a>
|
||||
<a href="http://edxonline.tumblr.com/">Blog</a>
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
<h1>There has been an error on the <em>MITx</em> servers</h1>
|
||||
<p>We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at <a href="mailto:technical@mitx.mit.edu">technical@mitx.mit.edu</a> to report any problems or downtime.</p>
|
||||
|
||||
<h1>Details below:</h1>
|
||||
% if staff_access:
|
||||
<h1>Details</h1>
|
||||
|
||||
<p>Error: ${error | h}</p>
|
||||
<p>Error:
|
||||
<pre>
|
||||
${error | h}
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>Raw data: ${data | h}</p>
|
||||
<p>Raw data:
|
||||
|
||||
<pre>${data | h}</pre>
|
||||
</p>
|
||||
|
||||
% endif
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<%inherit file="main.html" />
|
||||
<%include file="navigation.html" args="active_page=''" />
|
||||
<script>
|
||||
function name_confirm(id) {
|
||||
postJSON('/accept_name_change',{"id":id},
|
||||
|
||||
@@ -3,11 +3,20 @@
|
||||
## one for people who aren't. Assume a Course object is passed to the former,
|
||||
## instead of using settings.COURSE_TITLE
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
<header class="global" aria-label="Global Navigation">
|
||||
# App that handles subdomain specific branding
|
||||
import branding
|
||||
%>
|
||||
|
||||
%if course:
|
||||
<header class="global slim" aria-label="Global Navigation">
|
||||
%else:
|
||||
<header class="global" aria-label="Global Navigation">
|
||||
%endif
|
||||
<nav>
|
||||
<h1 class="logo"><a href="${reverse('root')}"></a></h1>
|
||||
<h1 class="logo"><a href="${reverse('root')}"><img src="${static.url(branding.get_logo_url(request.META.get('HTTP_HOST')))}"/></a></h1>
|
||||
|
||||
%if course:
|
||||
<h2><span class="provider">${course.org}:</span> ${course.number} ${course.title}</h2>
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<div id="sequence_${element_id}" class="sequence" data-id="${item_id}" data-position="${position}" data-course_modx_root="/course/modx" >
|
||||
<nav aria-label="Section Navigation" class="sequence-nav">
|
||||
<ol id="sequence-list">
|
||||
% for idx, item in enumerate(items):
|
||||
## TODO (vshnayder): add item.progress_detail either to the title or somewhere else.
|
||||
## Make sure it gets updated after ajax calls.
|
||||
## implementation note: will need to figure out how to handle combining detail
|
||||
## statuses of multiple modules in js.
|
||||
<li>
|
||||
<a class="seq_${item['type']} inactive progress-${item['progress_status']}" data-element="${idx+1}">
|
||||
<p>${item['title']}</p>
|
||||
</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
<div class="sequence-list-wrapper">
|
||||
<ol id="sequence-list">
|
||||
% for idx, item in enumerate(items):
|
||||
## TODO (vshnayder): add item.progress_detail either to the title or somewhere else.
|
||||
## Make sure it gets updated after ajax calls.
|
||||
## implementation note: will need to figure out how to handle combining detail
|
||||
## statuses of multiple modules in js.
|
||||
<li>
|
||||
<a class="seq_${item['type']} inactive progress-${item['progress_status']}" data-element="${idx+1}">
|
||||
<p>${item['title']}</p>
|
||||
</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<ul class="sequence-nav-buttons">
|
||||
<li class="prev"><a href="#">Previous</a></li>
|
||||
@@ -21,7 +23,7 @@
|
||||
</nav>
|
||||
|
||||
% for item in items:
|
||||
<div class="seq_contents tex2jax_ignore">${item['content'] | h}</div>
|
||||
<div class="seq_contents tex2jax_ignore asciimath2jax_ignore">${item['content'] | h}</div>
|
||||
% endfor
|
||||
<div id="seq_content"></div>
|
||||
|
||||
@@ -32,3 +34,15 @@
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var sequenceNav;
|
||||
$(document).ready(function() {
|
||||
// console.log($('.sequence-nav'));
|
||||
|
||||
sequenceNav = new SequenceNav($('.sequence-nav'));
|
||||
console.log(sequenceNav);
|
||||
});
|
||||
</script>
|
||||
|
||||