Merge branch 'ps-fix-problem' of github.com:MITx/mitx into stable-edx4edx
This commit is contained in:
3
README
3
README
@@ -1,2 +1 @@
|
||||
This branch (re-)adds dynamic math and symbolicresponse.
|
||||
Test cases included.
|
||||
see doc/ for documentation.
|
||||
|
||||
@@ -10,6 +10,7 @@ import os.path
|
||||
from StringIO import StringIO
|
||||
from mako.template import Template
|
||||
from mako.lookup import TemplateLookup
|
||||
from collections import defaultdict
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from keystore.django import keystore
|
||||
@@ -42,9 +43,7 @@ class Command(BaseCommand):
|
||||
# Simple lists
|
||||
'chapter': 'Week',
|
||||
'course': 'Course',
|
||||
'sequential': 'LectureSequence',
|
||||
'vertical': 'ProblemSet',
|
||||
'section': {
|
||||
'section': defaultdict(lambda: 'Section', {
|
||||
'Lab': 'Lab',
|
||||
'Lecture Sequence': 'LectureSequence',
|
||||
'Homework': 'Homework',
|
||||
@@ -52,8 +51,13 @@ class Command(BaseCommand):
|
||||
'Video': 'VideoSegment',
|
||||
'Midterm': 'Exam',
|
||||
'Final': 'Exam',
|
||||
None: 'Section',
|
||||
},
|
||||
'Problems': 'ProblemSet',
|
||||
}),
|
||||
'videosequence': 'VideoSequence',
|
||||
'problemset': 'ProblemSet',
|
||||
'vertical': 'Section',
|
||||
'sequential': 'Section',
|
||||
'tab': 'Section',
|
||||
# True types
|
||||
'video': 'VideoSegment',
|
||||
'html': 'HTML',
|
||||
@@ -78,6 +82,8 @@ class Command(BaseCommand):
|
||||
e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format(
|
||||
category=category,
|
||||
name=name))
|
||||
else:
|
||||
print "Skipping element with tag", e.tag
|
||||
|
||||
|
||||
def handle_skip(e):
|
||||
@@ -150,6 +156,9 @@ class Command(BaseCommand):
|
||||
'sequential': handle_list,
|
||||
'vertical': handle_list,
|
||||
'section': handle_list,
|
||||
'videosequence': handle_list,
|
||||
'problemset': handle_list,
|
||||
'tab': handle_list,
|
||||
# True types
|
||||
'video': handle_video,
|
||||
'html': handle_html,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from keystore.django import keystore
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse
|
||||
import json
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
# TODO (cpennington): These need to be read in from the active user
|
||||
org = 'mit.edu'
|
||||
@@ -11,3 +14,20 @@ def index(request):
|
||||
course = keystore().get_item(['i4x', org, course, 'Course', name])
|
||||
weeks = course.get_children()
|
||||
return render_to_response('index.html', {'weeks': weeks})
|
||||
|
||||
|
||||
def edit_item(request):
|
||||
item_id = request.GET['id']
|
||||
item = keystore().get_item(item_id)
|
||||
return render_to_response('unit.html', {
|
||||
'contents': item.get_html(),
|
||||
'type': item.type,
|
||||
'name': item.name,
|
||||
})
|
||||
|
||||
|
||||
def save_item(request):
|
||||
item_id = request.POST['id']
|
||||
data = json.loads(request.POST['data'])
|
||||
keystore().update_item(item_id, data)
|
||||
return HttpResponse(json.dumps({}))
|
||||
|
||||
@@ -21,6 +21,9 @@ Longer TODO:
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import os.path
|
||||
import os
|
||||
import errno
|
||||
from path import path
|
||||
|
||||
############################ FEATURE CONFIGURATION #############################
|
||||
@@ -154,7 +157,38 @@ PIPELINE_CSS = {
|
||||
|
||||
PIPELINE_ALWAYS_RECOMPILE = ['sass/base-style.scss']
|
||||
|
||||
from x_module import XModuleDescriptor
|
||||
js_file_dir = PROJECT_ROOT / "static" / "coffee" / "module"
|
||||
try:
|
||||
os.makedirs(js_file_dir)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
module_js_sources = []
|
||||
for xmodule in XModuleDescriptor.load_classes():
|
||||
js = xmodule.get_javascript()
|
||||
for filetype in ('coffee', 'js'):
|
||||
for idx, fragment in enumerate(js.get(filetype, [])):
|
||||
path = os.path.join(js_file_dir, "{name}.{idx}.{type}".format(
|
||||
name=xmodule.__name__,
|
||||
idx=idx,
|
||||
type=filetype))
|
||||
with open(path, 'w') as js_file:
|
||||
js_file.write(fragment)
|
||||
module_js_sources.append(path.replace(PROJECT_ROOT / "static/", ""))
|
||||
|
||||
PIPELINE_JS = {
|
||||
'main': {
|
||||
'source_filenames': ['coffee/main.coffee', 'coffee/unit.coffee'],
|
||||
'output_filename': 'js/main.js',
|
||||
},
|
||||
'module-js': {
|
||||
'source_filenames': module_js_sources,
|
||||
'output_filename': 'js/modules.js',
|
||||
}
|
||||
}
|
||||
|
||||
PIPELINE_COMPILERS = [
|
||||
|
||||
2
cms/static/coffee/.gitignore
vendored
Normal file
2
cms/static/coffee/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.js
|
||||
module
|
||||
83
cms/static/coffee/main.coffee
Normal file
83
cms/static/coffee/main.coffee
Normal file
@@ -0,0 +1,83 @@
|
||||
class @CMS
|
||||
@bind = =>
|
||||
$('a.module-edit').click ->
|
||||
CMS.edit_item($(this).attr('id'))
|
||||
return false
|
||||
|
||||
@edit_item = (id) =>
|
||||
$.get('/edit_item', {id: id}, (data) =>
|
||||
$('#module-html').empty().append(data)
|
||||
CMS.bind()
|
||||
$('section.edit-pane').show()
|
||||
$('body').addClass('content')
|
||||
new Unit('unit-wrapper', id)
|
||||
)
|
||||
|
||||
$ ->
|
||||
$.ajaxSetup
|
||||
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
|
||||
$('section.main-content').children().hide()
|
||||
$('.editable').inlineEdit()
|
||||
$('.editable-textarea').inlineEdit({control: 'textarea'})
|
||||
|
||||
heighest = 0
|
||||
$('.cal ol > li').each ->
|
||||
heighest = if $(this).height() > heighest then $(this).height() else heighest
|
||||
|
||||
$('.cal ol > li').css('height',heighest + 'px')
|
||||
|
||||
$('.add-new-section').click -> return false
|
||||
|
||||
$('.new-week .close').click ->
|
||||
$(this).parents('.new-week').hide()
|
||||
$('p.add-new-week').show()
|
||||
return false
|
||||
|
||||
$('.save-update').click ->
|
||||
$(this).parent().parent().hide()
|
||||
return false
|
||||
|
||||
# $('html').keypress ->
|
||||
# $('.wip').css('visibility', 'visible')
|
||||
|
||||
setHeight = ->
|
||||
windowHeight = $(this).height()
|
||||
contentHeight = windowHeight - 29
|
||||
|
||||
$('section.main-content > section').css('min-height', contentHeight)
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
|
||||
$('.edit-week').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.week-edit').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.sequence-edit').click ->
|
||||
$('body').addClass('content')
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('a.module-edit').click ->
|
||||
$('body.content .cal').css('height', contentHeight)
|
||||
|
||||
$(document).ready(setHeight)
|
||||
$(window).bind('resize', setHeight)
|
||||
|
||||
$('.video-new a').click ->
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
$('.problem-new a').click ->
|
||||
$('section.edit-pane').show()
|
||||
return false
|
||||
|
||||
CMS.bind()
|
||||
|
||||
15
cms/static/coffee/unit.coffee
Normal file
15
cms/static/coffee/unit.coffee
Normal file
@@ -0,0 +1,15 @@
|
||||
class @Unit
|
||||
constructor: (@element_id, @module_id) ->
|
||||
@module = new window[$("##{@element_id}").attr('class')] 'module-html'
|
||||
|
||||
$("##{@element_id} .save-update").click (event) =>
|
||||
event.preventDefault()
|
||||
$.post("save_item", {
|
||||
id: @module_id
|
||||
data: JSON.stringify(@module.save())
|
||||
})
|
||||
|
||||
$("##{@element_id} .cancel").click (event) =>
|
||||
event.preventDefault()
|
||||
CMS.edit_item(@module_id)
|
||||
|
||||
47
cms/static/js/jquery.cookie.js
Normal file
47
cms/static/js/jquery.cookie.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* jQuery Cookie Plugin
|
||||
* https://github.com/carhartl/jquery-cookie
|
||||
*
|
||||
* Copyright 2011, Klaus Hartl
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.opensource.org/licenses/GPL-2.0
|
||||
*/
|
||||
(function($) {
|
||||
$.cookie = function(key, value, options) {
|
||||
|
||||
// key and at least value given, set cookie...
|
||||
if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
|
||||
options = $.extend({}, options);
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
options.expires = -1;
|
||||
}
|
||||
|
||||
if (typeof options.expires === 'number') {
|
||||
var days = options.expires, t = options.expires = new Date();
|
||||
t.setDate(t.getDate() + days);
|
||||
}
|
||||
|
||||
value = String(value);
|
||||
|
||||
return (document.cookie = [
|
||||
encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
|
||||
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
options.path ? '; path=' + options.path : '',
|
||||
options.domain ? '; domain=' + options.domain : '',
|
||||
options.secure ? '; secure' : ''
|
||||
].join(''));
|
||||
}
|
||||
|
||||
// key and possibly options given, get cookie...
|
||||
options = value || {};
|
||||
var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
|
||||
|
||||
var pairs = document.cookie.split('; ');
|
||||
for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
|
||||
if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
|
||||
}
|
||||
return null;
|
||||
};
|
||||
})(jQuery);
|
||||
4
cms/static/js/jquery.min.js
vendored
Normal file
4
cms/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,85 +0,0 @@
|
||||
$(document).ready(function(){
|
||||
$('section.main-content').children().hide();
|
||||
|
||||
$(function(){
|
||||
$('.editable').inlineEdit();
|
||||
$('.editable-textarea').inlineEdit({control: 'textarea'});
|
||||
});
|
||||
|
||||
var heighest = 0;
|
||||
$('.cal ol > li').each(function(){
|
||||
heighest = ($(this).height() > heighest) ? $(this).height() : heighest;
|
||||
|
||||
});
|
||||
|
||||
$('.cal ol > li').css('height',heighest + 'px');
|
||||
|
||||
$('.add-new-section').click(function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.new-week .close').click( function(){
|
||||
$(this).parents('.new-week').hide();
|
||||
$('p.add-new-week').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.save-update').click(function(){
|
||||
$(this).parent().parent().hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
setHeight = function(){
|
||||
var windowHeight = $(this).height();
|
||||
var contentHeight = windowHeight - 29;
|
||||
|
||||
$('section.main-content > section').css('min-height', contentHeight);
|
||||
$('body.content .cal').css('height', contentHeight);
|
||||
|
||||
$('.edit-week').click( function() {
|
||||
$('body').addClass('content');
|
||||
$('body.content .cal').css('height', contentHeight);
|
||||
$('section.week-new').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.cal ol li header h1 a').click( function() {
|
||||
$('body').addClass('content');
|
||||
$('body.content .cal').css('height', contentHeight);
|
||||
$('section.week-edit').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('a.sequence-edit').click(function(){
|
||||
$('body').addClass('content');
|
||||
$('body.content .cal').css('height', contentHeight);
|
||||
$('section.sequence-edit').show();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(setHeight);
|
||||
$(window).bind('resize', setHeight);
|
||||
|
||||
$('.video-new a').click(function(){
|
||||
$('section.video-new').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('a.video-edit').click(function(){
|
||||
$('section.video-edit').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.problem-new a').click(function(){
|
||||
$('section.problem-new').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('a.problem-edit').click(function(){
|
||||
$('section.problem-edit').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Sass Watch:
|
||||
|
||||
sass --watch cms/static/sass:cms/static/css -r ./cms/static/sass/bourbon/lib/bourbon.rb
|
||||
sass --watch cms/static/sass:cms/static/sass -r ./cms/static/sass/bourbon/lib/bourbon.rb
|
||||
|
||||
@@ -2,65 +2,14 @@ $fg-column: 70px;
|
||||
$fg-gutter: 26px;
|
||||
$fg-max-columns: 12;
|
||||
$body-font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
$body-font-size: 14px;
|
||||
$body-line-height: 20px;
|
||||
|
||||
// Base html styles
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
@include clearfix();
|
||||
height: 100%;
|
||||
font: 14px $body-font-family;
|
||||
|
||||
> section {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: none;
|
||||
padding: 6px 20px;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
nav {
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul {
|
||||
float: left;
|
||||
|
||||
&.user-nav {
|
||||
float: right;
|
||||
}
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.content {
|
||||
section.main-content {
|
||||
border-left: 2px solid #000;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9);
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 3px #ddd );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #888;
|
||||
@@ -77,6 +26,13 @@ input[type="submit"], .button {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Extends
|
||||
.new-module {
|
||||
position: relative;
|
||||
|
||||
@@ -111,3 +67,17 @@ input[type="submit"], .button {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.wip {
|
||||
outline: 1px solid #f00 !important;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "WIP";
|
||||
font-size: 8px;
|
||||
padding: 2px;
|
||||
background: #f00;
|
||||
color: #fff;
|
||||
@include position(absolute, 0px 0px 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ section.cal {
|
||||
ol {
|
||||
list-style: none;
|
||||
@include clearfix;
|
||||
@include box-sizing(border-box);
|
||||
border-left: 1px solid #333;
|
||||
border-top: 1px solid #333;
|
||||
width: 100%;
|
||||
|
||||
53
cms/static/sass/_layout.scss
Normal file
53
cms/static/sass/_layout.scss
Normal file
@@ -0,0 +1,53 @@
|
||||
body {
|
||||
@include clearfix();
|
||||
height: 100%;
|
||||
font: 14px $body-font-family;
|
||||
|
||||
> section {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: none;
|
||||
padding: 6px 20px;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
nav {
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul {
|
||||
float: left;
|
||||
|
||||
&.user-nav {
|
||||
float: right;
|
||||
}
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.content {
|
||||
section.main-content {
|
||||
border-left: 2px solid #000;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9);
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 3px #ddd );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
section.video-new, section.video-edit, section.problem-new, section.problem-edit {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
width: flex-grid(6);
|
||||
@include box-shadow(0 0 6px #666);
|
||||
border: 1px solid #333;
|
||||
border-right: 0;
|
||||
z-index: 4;
|
||||
|
||||
> header {
|
||||
background: #666;
|
||||
@include clearfix;
|
||||
color: #fff;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #333;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
h2 {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
||||
&.save-update {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
> section {
|
||||
padding: 20px;
|
||||
|
||||
> header {
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
section {
|
||||
&.status-settings {
|
||||
ul {
|
||||
list-style: none;
|
||||
@include border-radius(2px);
|
||||
border: 1px solid #999;
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid #999;
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
margin: 0 20px;
|
||||
border: 1px solid #999;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
select {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.meta {
|
||||
background: #eee;
|
||||
padding: 10px;
|
||||
margin: 20px 0;
|
||||
@include clearfix();
|
||||
|
||||
div {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
@include inline-block();
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.notes {
|
||||
margin-top: 20px;
|
||||
padding: 6px;
|
||||
background: #eee;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
input[type="submit"]{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
section.problem-new, section.problem-edit {
|
||||
> section {
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.preview {
|
||||
background: #eee;
|
||||
@include box-sizing(border-box);
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a.save {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
cms/static/sass/_section.scss
Normal file
182
cms/static/sass/_section.scss
Normal file
@@ -0,0 +1,182 @@
|
||||
section#unit-wrapper {
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
background: #efefef;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
|
||||
&.advanced {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.content {
|
||||
display: table;
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
|
||||
section {
|
||||
header {
|
||||
background: #eee;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.modules {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(6, 9);
|
||||
border-right: 1px solid #333;
|
||||
|
||||
&.empty {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&.group {
|
||||
padding: 0;
|
||||
|
||||
header {
|
||||
padding: 6px;
|
||||
background: none;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scratch-pad {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(3, 9) + flex-gutter(9);
|
||||
vertical-align: top;
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #999;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #999;
|
||||
background: #f9f9f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
cms/static/sass/_unit.scss
Normal file
199
cms/static/sass/_unit.scss
Normal file
@@ -0,0 +1,199 @@
|
||||
section#unit-wrapper {
|
||||
> header {
|
||||
border-bottom: 2px solid #333;
|
||||
@include clearfix();
|
||||
padding: 6px 20px;
|
||||
|
||||
section {
|
||||
float: left;
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
@include inline-block();
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
float: right;
|
||||
color: #666;
|
||||
|
||||
a {
|
||||
&.cancel {
|
||||
margin-right: 20px;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> section {
|
||||
padding: 20px;
|
||||
|
||||
section.meta {
|
||||
section {
|
||||
&.status-settings {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
@include border-radius(2px);
|
||||
border: 1px solid #999;
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid #999;
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
margin: 0 20px;
|
||||
border: 1px solid #999;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
select {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.author {
|
||||
float: right;
|
||||
|
||||
dl {
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd, dt {
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.tags {
|
||||
background: #eee;
|
||||
padding: 10px;
|
||||
margin: 0 0 20px;
|
||||
@include clearfix();
|
||||
clear: both;
|
||||
|
||||
div {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
@include inline-block();
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//general styles for main content
|
||||
div.preview {
|
||||
background: #eee;
|
||||
@include box-sizing(border-box);
|
||||
min-height: 40px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
//notes
|
||||
section.notes {
|
||||
margin-top: 20px;
|
||||
padding: 20px 0 0;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 20px;
|
||||
|
||||
input[type="submit"]{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
|
||||
p {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.author {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.actions {
|
||||
a.save-update {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
section.video-new, section.video-edit {
|
||||
> section {
|
||||
|
||||
section.upload {
|
||||
padding: 6px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
a.upload-button {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
|
||||
section.in-use {
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div {
|
||||
background: #eee;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
a.save-update {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
section.week-edit,
|
||||
section.week-new,
|
||||
section.sequence-edit {
|
||||
|
||||
> header {
|
||||
border-bottom: 2px solid #333;
|
||||
@include clearfix();
|
||||
|
||||
div {
|
||||
@include clearfix();
|
||||
padding: 6px 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
p {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.week {
|
||||
background: #eee;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
@include inline-block();
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
margin-right: 10px;
|
||||
|
||||
p {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.goals {
|
||||
background: #eee;
|
||||
padding: 6px 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
color: #999;
|
||||
|
||||
li {
|
||||
margin-bottom: 6px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> section.content {
|
||||
@include box-sizing(border-box);
|
||||
padding: 20px;
|
||||
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
background: #efefef;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
|
||||
&.advanced {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
display: table;
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
|
||||
section {
|
||||
header {
|
||||
background: #eee;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.modules {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(6, 9);
|
||||
border-right: 1px solid #333;
|
||||
|
||||
&.empty {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&.group {
|
||||
padding: 0;
|
||||
|
||||
header {
|
||||
padding: 6px;
|
||||
background: none;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scratch-pad {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(3, 9) + flex-gutter(9);
|
||||
vertical-align: top;
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #999;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #999;
|
||||
background: #f9f9f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'reset';
|
||||
|
||||
@import 'base';
|
||||
@import 'base', 'layout';
|
||||
@import 'calendar';
|
||||
@import 'week', 'video', 'problem', 'module-header';
|
||||
@import 'section', 'unit';
|
||||
|
||||
@@ -23,11 +23,18 @@
|
||||
|
||||
<%block name="content"></%block>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${ STATIC_URL}/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/jquery.markitup.js"></script>
|
||||
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/sets/wiki/set.js"></script>
|
||||
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
|
||||
<%static:js group='main'/>
|
||||
% else:
|
||||
<script src="${ STATIC_URL }/js/main.js"></script>
|
||||
% endif
|
||||
|
||||
<%static:js group='module-js'/>
|
||||
<script src="${ STATIC_URL }/js/jquery.inlineedit.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.cookie.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.leanModal.min.js"></script>
|
||||
<script src="${ STATIC_URL }/js/jquery.tablednd.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -7,13 +7,9 @@
|
||||
<%include file="widgets/navigation.html"/>
|
||||
|
||||
<section class="main-content">
|
||||
<%include file="widgets/week-edit.html"/>
|
||||
<%include file="widgets/week-new.html"/>
|
||||
<%include file="widgets/sequnce-edit.html"/>
|
||||
<%include file="widgets/video-edit.html"/>
|
||||
<%include file="widgets/video-new.html"/>
|
||||
<%include file="widgets/problem-edit.html"/>
|
||||
<%include file="widgets/problem-new.html"/>
|
||||
<section class="edit-pane">
|
||||
<div id="module-html"/>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
17
cms/templates/unit.html
Normal file
17
cms/templates/unit.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<section id="unit-wrapper" class="${type}">
|
||||
<header>
|
||||
<section>
|
||||
<h1 class="editable">${name}</h1>
|
||||
<p>${type}</p>
|
||||
</section>
|
||||
|
||||
<div class="actions">
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
${contents}
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,242 +0,0 @@
|
||||
<ul class="tabs">
|
||||
<li class="active">English (main)</li>
|
||||
<li>French</li>
|
||||
<li>English v2</li>
|
||||
<li>+</li>
|
||||
</ul>
|
||||
<textarea class="captions">
|
||||
{
|
||||
"start": [
|
||||
0, 2770, 5700, 7620, 10320, 12130, 13430, 15170, 17940, 20890, 22840, 26200, 28980, 30170, 32040, 33240, 36420, 37570, 41760, 44270, 48120, 50810, 52960, 54070, 56480, 58600, 59550, 62520, 67680, 69990, 74280, 77605, 81320, 85050, 88430, 92750, 96010, 99440, 103160, 106650, 110050, 114240, 118800, 120980, 122290, 125040, 127900, 130990, 133170, 134580, 139410, 143180, 144530, 147640, 150800, 153220, 156570, 161000, 162010, 162930, 164090, 165490, 167650, 170630, 172020, 174760, 178480, 181840, 185840, 188620, 194160, 196110, 199370, 201360, 204140, 209970, 211850, 215030, 218890, 221730, 225370, 228790, 231690, 234080, 236300, 237970, 240450, 244010, 247670, 251910, 253230, 260149, 263330, 266380, 269470, 273570, 278240, 280050, 282810, 288060, 292410, 297300, 298950, 300860, 302500, 304350, 309620, 312950, 318710, 322870, 323810, 328380, 332840, 334440, 338640, 341540, 345490, 348900, 350730, 354480, 357640, 362310, 365020, 366890, 368900, 373200, 374240, 379410, 381580, 381990, 385680, 389080, 390750, 393970, 395960, 397340, 401000, 403210, 405650, 408880, 411730, 415490, 421350, 425630, 427520, 430490, 435320, 436860, 439460, 443300, 447010, 450740, 453820, 456610, 460140, 463730, 466700, 471200, 472450, 475260, 476330, 480650, 483650, 486320, 489080, 491940, 496690, 501990, 502740, 507000, 511650, 513220, 517330, 519169, 524159, 528140, 529960, 531270, 535340, 541590, 543710, 545170, 550960, 551810, 555140, 556230, 557750, 560530, 564300, 566800, 567600, 569910, 573170, 578610, 580490, 585520, 586500, 589880, 591750, 596120, 597290, 600290, 602940, 606490, 608560, 610690, 612600, 613970, 616670, 621260, 622310, 624520, 626750, 629550, 632500, 635510, 637470, 638900, 640370, 644200, 647470, 648740, 652700, 653950 ],
|
||||
"end": [
|
||||
2770, 5700, 7620, 10320, 12130, 13430, 15170, 17940, 20890, 22840, 26200, 28980, 30170, 32040, 33240, 36420, 37570, 41760, 44269, 48120, 50809, 52960, 54070, 56480, 58599, 59550, 62519, 67680, 69990, 74280, 77605, 81320, 85050, 88429, 92750, 96010, 99440, 103160, 106649, 110050, 114240, 118800, 120980, 122290, 125040, 127900, 130990, 133170, 134579, 139410, 143180, 144530, 147640, 150799, 153220, 156570, 161000, 162010, 162929, 164090, 165490, 167650, 170630, 172019, 174760, 178480, 181840, 185840, 188620, 194160, 196109, 199370, 201360, 204140, 209970, 211850, 215030, 218890, 221730, 225369, 228790, 231690, 234079, 236300, 237970, 240450, 244010, 247670, 251910, 253230, 260149, 263330, 266380, 269469, 273570, 278240, 280050, 282810, 288060, 292410, 297300, 298950, 300860, 302500, 304350, 309620, 312950, 318710, 322870, 323810, 328380, 332840, 334440, 338640, 341539, 345490, 348900, 350729, 354480, 357640, 362310, 365020, 366890, 368900, 373200, 374240, 379410, 381580, 381990, 385680, 389080, 390750, 393970, 395960, 397340, 401000, 403210, 405650, 408880, 411730, 415490, 421350, 425630, 427520, 430490, 435320, 436860, 439460, 443299, 447010, 450740, 453820, 456610, 460140, 463729, 466700, 471200, 472450, 475260, 476330, 480650, 483650, 486320, 489080, 491940, 496690, 501990, 502740, 507000, 511650, 513220, 517330, 519169, 524159, 528140, 529960, 531270, 535340, 541590, 543710, 545170, 550959, 551810, 555140, 556230, 557750, 560530, 564300, 566800, 567599, 569910, 573170, 578610, 580490, 585520, 586500, 589880, 591750, 596120, 597290, 600290, 602939, 606490, 608560, 610689, 612600, 613970, 616670, 621260, 622310, 624520, 626750, 629550, 632500, 635510, 637470, 638900, 640370, 644200, 647470, 648740, 652700, 653950, 655050 ],
|
||||
"text": [
|
||||
"SUBJECT 1: The various methods\nI'm going to show you--",
|
||||
"in particular, the first method\none and method two for",
|
||||
"solving non-linear equations--",
|
||||
"are really just particular\nways of solving a pair of",
|
||||
"equations where at least\none of each have some",
|
||||
"non-linearity to them.",
|
||||
"So let's start with the\ngraphical method.",
|
||||
"And my circuit is on the\nright-hand side.",
|
||||
"And I'm showing you the same\nvoltage source resistor--",
|
||||
"the Thevenin pattern--",
|
||||
"connected to Device D. And\nbelow that, I have the",
|
||||
"equation iD equals ae\nraised to bvD, which",
|
||||
"is the device equation.",
|
||||
"So as before, let me\ngo ahead and do all",
|
||||
"the first few steps.",
|
||||
"I go ahead and write the\nnode equation at vD.",
|
||||
"And I got that.",
|
||||
"I go in and substitute, as I\nalways do, in the node method",
|
||||
"for the current, using\nthe device relation.",
|
||||
"And just for fun here, let me\nkeep that separate for now.",
|
||||
"In the analytical method, notice\nthat you ended up with",
|
||||
"these two equations, and\nyou had to solve",
|
||||
"for these two unknowns.",
|
||||
"And we did that using\nanalytical methods.",
|
||||
"In this video, I'm going to\nsolve these using the",
|
||||
"graphical method.",
|
||||
"In order to do so here's what\nI'm going to do I want to",
|
||||
"start by rearranging the terms\nin my equation 1 to make it a",
|
||||
"little bit more convenient\nto draw the graph.",
|
||||
"So notice in equation 2, I\nhave iD equals something.",
|
||||
"And then I have an expression\nin vD and iD.",
|
||||
"So what I'd like to do is let\nme start by taking this",
|
||||
"equation here and expressing it\nin more of a standard form",
|
||||
"so I can get something symmetric\nto equation 2.",
|
||||
"So let me express this by\npulling iD to the left-hand",
|
||||
"side all by itself, and so I\nget something like this.",
|
||||
"So I get iD on the left-hand\nside, and then I'm going to",
|
||||
"move vD minus V divided by\nr do the right hand side.",
|
||||
"So let's start with minus V\ndivided by R, and when I move",
|
||||
"that to the right-hand side, it\nbecomes V divided by R. And",
|
||||
"then when I move vD over R to\nthe right-hand side, I get",
|
||||
"minus vD over R. And I've simply\nnot done much here.",
|
||||
"I just have done some\nrearranging of the terms in",
|
||||
"equation 1.",
|
||||
"And since I haven't done\nanything unique and different,",
|
||||
"I'm just going to label\nthis as 1 prime.",
|
||||
"So I just got this from equation\n1, and I just labeled",
|
||||
"that as 1 prime.",
|
||||
"Continuing with the method--",
|
||||
"and now I'm summarizing for you\nequations 1 prime and 2--",
|
||||
"we want both of them for iD on\nthe left-hand side expressed",
|
||||
"as a function of vD.",
|
||||
"And in equation 2,\nit is non-linear.",
|
||||
"So the graphical method can\nbe summarized as follows.",
|
||||
"To start, I want you to\nnotice something.",
|
||||
"Essentially all we're trying to\ndo is find a solution, find",
|
||||
"a value for vD and iD that\nsatisfies both equations, 1",
|
||||
"prime and 2.",
|
||||
"that's all we're trying to do.",
|
||||
"It's just math here.",
|
||||
"There's no circuits here.",
|
||||
"We're just doing some relatively\nsimple math.",
|
||||
"We just have to figure out what\niD and vD are, and we'll",
|
||||
"use the graphical method.",
|
||||
"So to do the graphical method,\nwhat I'm going to do is I'm",
|
||||
"going to plot the two equations\nin a pair of graphs.",
|
||||
"And let me first start by\nplotting equation 2.",
|
||||
"And in equation 2, I'm going\nto plot iD equals a",
|
||||
"raised to ae bvD.",
|
||||
"I'm going to plot iD equals\na times e raised to bvD.",
|
||||
"That's my equation 2.",
|
||||
"And this is the plot\nthat I get for it.",
|
||||
"It's a plot that I had before.",
|
||||
"So that's my equation 2.",
|
||||
"Notice that this plot here is\nsimply the constraint on iD",
|
||||
"and vD imposed by the device.",
|
||||
"This little circuit here\ncontaining the Thevenin",
|
||||
"equivalent and connected to\nthe device, equation 2 is",
|
||||
"simply the constraint imposed\nby the device.",
|
||||
"The device properties are such\nthat it is going to constrain",
|
||||
"iD and vD into some\nrelationship.",
|
||||
"This constraint that I've\nplotted here as equation 2 is",
|
||||
"simply the constraint imposed\nby this device.",
|
||||
"OK, next, let me\ngo to 1 prime.",
|
||||
"And in 1 prime, let\nme go ahead and",
|
||||
"plot the 1 prime equation.",
|
||||
"And that equation, as we\nrewrite that here, is V",
|
||||
"divided by R minus vD divided\nby R. So I'm just going to",
|
||||
"plot that equation for you\nin a second graph.",
|
||||
"So how do I plot this?",
|
||||
"So notice here that when vD is\n0, then iD is V divided by R.",
|
||||
"So that is one point on\nthe straight line.",
|
||||
"Notice that this is an equation\nfor a straight line.",
|
||||
"It's a linear relationship\nbetween iD and vD.",
|
||||
"Next, when iD is 0--\nso when iD is 0--",
|
||||
"then notice that R and R can be\ncanceled out, and V will be",
|
||||
"equal to vD.",
|
||||
"So when iD is 0, V\nand vD are equal.",
|
||||
"So therefore, vD equals V. So\nthis is the line when iD is 0.",
|
||||
"And for that, vD equals V. So\nthen I get this [? long, ?]",
|
||||
"straight line for the\nrelationship between vD and iD",
|
||||
"according to 1 prime.",
|
||||
"So what's the slope\nof this line here?",
|
||||
"Can you tell me what the\nslope of this line is.",
|
||||
"Let me give you a few seconds\nto think about it.",
|
||||
null,
|
||||
"OK, from the equation 1 prime--\nfrom this equation--",
|
||||
"the slope is simply given by the\ncoefficient of vD, since",
|
||||
"the slope is negative, which\nis why the line is inclined",
|
||||
"the following way.",
|
||||
"So the slope here is simply\nminus 1 divided by R.",
|
||||
"So that is my constraint\nthat relates vD to iD.",
|
||||
"And where did that constraint\ncome from?",
|
||||
"That constraint is the\nconstraint on iD and vD that",
|
||||
"has been imposed by the\nrest of the circuit.",
|
||||
"So if the first constraint was\nimposed by the device, then",
|
||||
"the second constraint is\nimposed by the Thevenin",
|
||||
"equivalent that is connected\nto the device.",
|
||||
"The Thevenin equivalent was\nthat V and R in series and",
|
||||
"those two impose a Thevenin\nconstraint on the terminal",
|
||||
"pair that relates vD and iD.",
|
||||
"So now my next step is,\ngiven these two graphs",
|
||||
"for iD versus vD--",
|
||||
"and clearly those are\nmy two constraints.",
|
||||
"Graph 2 says that iD and vD\nmust be somewhere on this",
|
||||
"trajectory.",
|
||||
"Graph 1 prime says, well, I'm\nnot going to let vD and iD be",
|
||||
"anywhere else but\non this curve.",
|
||||
"That's it.",
|
||||
"Both of them are fighting with\neach other and telling each",
|
||||
"other, nope, I'm not going to\nallow you to do anything.",
|
||||
"You have to be on my curve.",
|
||||
"So in this case, I have two\ncurves, and I need a point",
|
||||
"that satisfies both curves.",
|
||||
"And that is easy enough to do.",
|
||||
"And I simply have to go\nand satisfy both these",
|
||||
"constraints, and that will\ngive me the answer",
|
||||
"for vD versus iD.",
|
||||
"Before I do that, so I can go\nand solve it for you, let me",
|
||||
"go and pick some values for\nthe various parameters.",
|
||||
"So as before, I'm going to pick\nV equals 1 volt, R equals",
|
||||
"1 ohm, a, a quarter of an amp,\nand b to be one volt inverse.",
|
||||
"So I'll pick the same parameters\nas I had done when",
|
||||
"I did the analytical method.",
|
||||
"Next, what I'll do is I'll\nsubstitute these parameters",
|
||||
"into the two equations 1 prime\nand 2 and rewrite them with",
|
||||
"the parameters substituted.",
|
||||
"So for 1 prime, I get iD.",
|
||||
"Since V is 1 and R is 1, I get\nV divided by R equals 1.",
|
||||
"And then since R is\n1, I get minus vD.",
|
||||
"So this is 1 prime, once I've\nsubstituted the values.",
|
||||
"And then for equation\n2, what do I get?",
|
||||
"I get iD equals ae\nraised to bvD.",
|
||||
"a is 1/4, so I write\nthat down.",
|
||||
"b is 1, and so I\nget vD up here.",
|
||||
"So I have my two equations in\nterms of the parameters I have",
|
||||
"chosen, and now I can go ahead\nand plot the two equations and",
|
||||
"see where they intersect.",
|
||||
"I've given you the form of the\ngraph here, and so let me go",
|
||||
"ahead and plot this.",
|
||||
"Let me go ahead and plot 2\nfirst. 2 looks like this,",
|
||||
"where this point is 1/4.",
|
||||
"That was the a point.",
|
||||
"As I said before, this curve\nis simply my Equation 2.",
|
||||
"Then let me go ahead and\nplot equation 1 prime.",
|
||||
"And as you recall, that looked\nsomething like this where the",
|
||||
"vD intercept was V and the\ny-intercept was given by V",
|
||||
"divided by R.",
|
||||
"And in this case, it was\n1, and V was also 1.",
|
||||
"So those are my two curves, and\nhere's the point where the",
|
||||
"two coincide.",
|
||||
"And then I just have to go and\nfind the values of iD and vD",
|
||||
"where they two intersect.",
|
||||
"So at this point, it will be\n0.56 volts, and this point",
|
||||
"will be 0.44 amps.",
|
||||
"It's the same as what\nI calculated in",
|
||||
"the analytical method.",
|
||||
"So let me go ahead and write\nthat down. iD equals, in this",
|
||||
"case, 0.44 amps, and vD\nequals 0.56 volts.",
|
||||
"So basically, I've just\ntaken the two graphs,",
|
||||
"superimposed them.",
|
||||
"This was 1 prime, and the\nnon-linear one was related to",
|
||||
"Equation 2.",
|
||||
"So before I jump off to one\nother thing, I can define",
|
||||
"something for you.",
|
||||
"Notice this curve here.",
|
||||
"This is a straight line that\nreflects the Thevenin",
|
||||
"constraint that I apply on\nmy non-linear device.",
|
||||
"And I mentioned earlier we're\ngoing to do this again and",
|
||||
"again and again.",
|
||||
"I think you will see this at\nleast 10 more times in this",
|
||||
"course where I take a Thevenin\nequivalent of the following",
|
||||
"form, some voltage V, some R,\nand apply that in series",
|
||||
"across something interesting.",
|
||||
"So this line that I see here is\nthe constraint imposed by",
|
||||
"the Thevenin equivalent.",
|
||||
"And you can see that from\nthe equation 1 prime.",
|
||||
"Now, there's a name\nfor this line.",
|
||||
"So this line is called the "load\nline." You will see more",
|
||||
"reasons for this later.",
|
||||
"But this line is called\nthe "load line."",
|
||||
"Let me show you one other little\ntrick, just in case you",
|
||||
"didn't completely get how I\nsolved the graphical method.",
|
||||
"But one little trick here.",
|
||||
"If you like PowerPoint,\nyou will enjoy this.",
|
||||
"And if you don't like\nPowerPoint, you",
|
||||
"will hate me for it.",
|
||||
"So if you look at these\ncurves, I have my two",
|
||||
"equations in iD-- the 1 prime\nand 2-- and I've plotted them",
|
||||
"for you here.",
|
||||
"On the left-hand side,\nI plotted equation 2.",
|
||||
"Right-hand side, I plotted\nequation 1.",
|
||||
"And fundamentally, all that the\ngraphical method is doing",
|
||||
"is simply superimposing\nthe two graphs.",
|
||||
"And by superimposing the two\ngraphs, it is finding the",
|
||||
"point where the two\ncurves intersect.",
|
||||
"And this is what you get.",
|
||||
"So let me do that again.",
|
||||
"So I take one of the curves and\nsuperimpose it on top of",
|
||||
"the other curve given the\nsame axes and the same",
|
||||
"scales for the axes.",
|
||||
"And then I go ahead and find\nthe solution for the point",
|
||||
"where the two intersect.",
|
||||
null
|
||||
]
|
||||
}
|
||||
|
||||
</textarea>
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
<li>
|
||||
<a href="#" class="new-module">New Section</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Module</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Unit</a>
|
||||
</li>
|
||||
|
||||
45
cms/templates/widgets/html-edit.html
Normal file
45
cms/templates/widgets/html-edit.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<section class="html-edit">
|
||||
<section class="meta wip">
|
||||
|
||||
<section class="status-settings">
|
||||
<ul>
|
||||
<li><a href="#" class="current">Scrap</a></li>
|
||||
<li><a href="#">Draft</a></li>
|
||||
<li><a href="#">Proofed</a></li>
|
||||
<li><a href="#">Published</a></li>
|
||||
</ul>
|
||||
<a href="#" class="settings">Settings</a>
|
||||
</section>
|
||||
|
||||
<section class="author">
|
||||
<dl>
|
||||
<dt>Last modified:</dt>
|
||||
<dd>mm/dd/yy</dd>
|
||||
<dt>By</dt>
|
||||
<dd>Anant Agarwal</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="tags">
|
||||
<div>
|
||||
<h2>Tags:</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Goal</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<textarea name="" class="edit-box" rows="8" cols="40">${module.definition['data']['text']}</textarea>
|
||||
<div class="preview">${module.definition['data']['text']}</div>
|
||||
|
||||
<div class="actions wip">
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
</div>
|
||||
|
||||
<%include file="notes.html"/>
|
||||
</section>
|
||||
@@ -1,4 +1,4 @@
|
||||
<li class="create-module">
|
||||
<li class="create-module wip">
|
||||
<a href="#" class="new-module">
|
||||
+ Add new module
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<section class="cal">
|
||||
<header>
|
||||
<header class="wip">
|
||||
<h2>Filter content:</h2>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -38,7 +38,7 @@
|
||||
% for week in weeks:
|
||||
<li>
|
||||
<header>
|
||||
<h1><a href="#">${week.name}</a></h1>
|
||||
<h1><a href="#" class="week-edit" id="${week.url}">${week.name}</a></h1>
|
||||
<ul>
|
||||
% if week.goals:
|
||||
% for goal in week.goals:
|
||||
@@ -53,7 +53,7 @@
|
||||
<ul>
|
||||
% for module in week.get_children():
|
||||
<li class="${module.type}">
|
||||
<a href="#" class="${module.type}-edit">${module.name}</a>
|
||||
<a href="#" class="module-edit" id="${module.url}">${module.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
% endfor
|
||||
|
||||
21
cms/templates/widgets/notes.html
Normal file
21
cms/templates/widgets/notes.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<section class="notes wip">
|
||||
<h2>Notes</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
|
||||
<p class="author">Anant Agarwal</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
|
||||
<p class="author">Anant Agarwal</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form>
|
||||
<h2>Add a note</h2>
|
||||
<textarea name="" id= rows="8" cols="40"></textarea>
|
||||
<input type="submit" name="" id="" value="post" />
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -1,73 +1,48 @@
|
||||
<section class="problem-edit">
|
||||
<header>
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
<a href="#" class="save-update">Save & Update</a>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<h1 class="editable">New Problem</h1>
|
||||
<section class="author">
|
||||
<div>
|
||||
<h2>Last modified:</h2>
|
||||
<p>mm/dd/yy</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>By</h2>
|
||||
<p>Anant Agarwal</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="status-settings">
|
||||
<ul>
|
||||
<li><a href="#" class="current">Scrap</a></li>
|
||||
<li><a href="#">Draft</a></li>
|
||||
<li><a href="#">Proofed</a></li>
|
||||
<li><a href="#">Published</a></li>
|
||||
</ul>
|
||||
<a href="#" class="settings">Settings</a>
|
||||
|
||||
<select name="" id="">
|
||||
<option>Global</option>
|
||||
</select>
|
||||
</section>
|
||||
<section class="meta">
|
||||
<div>
|
||||
<h2>Tags:</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Goal</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<textarea name="" id= rows="8" cols="40">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</textarea>
|
||||
<div class="preview">
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
|
||||
</div>
|
||||
<section class="meta">
|
||||
<section class="status-settings">
|
||||
<ul>
|
||||
<li><a href="#" class="current">Scrap</a></li>
|
||||
<li><a href="#">Draft</a></li>
|
||||
<li><a href="#">Proofed</a></li>
|
||||
<li><a href="#">Published</a></li>
|
||||
</ul>
|
||||
<a href="#" class="settings">Settings</a>
|
||||
</section>
|
||||
|
||||
<section class="notes">
|
||||
<h2>Add notes</h2>
|
||||
<textarea name="" id= rows="8" cols="40"></textarea>
|
||||
<input type="submit" name="" id="" value="post" />
|
||||
<section class="author">
|
||||
<dl>
|
||||
<dt>Last modified:</dt>
|
||||
<dd>mm/dd/yy</dd>
|
||||
<dt>By</dt>
|
||||
<dd>Anant Agarwal</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
|
||||
<p class="author">Anant Agarwal</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
|
||||
<p class="author">Anant Agarwal</p>
|
||||
</li>
|
||||
</ul> </section>
|
||||
<section class="tags">
|
||||
<div>
|
||||
<h2>Tags:</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
<div>
|
||||
<h2>Goal</h2>
|
||||
<p class="editable">Click to edit</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<textarea name="" id= rows="8" cols="40">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</textarea>
|
||||
<div class="preview">
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<%include file="notes.html"/>
|
||||
</section>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<li>
|
||||
<img src="http://placehold.it/300x180" alt="" /><h5>Video-file-name</h5>
|
||||
</li>
|
||||
@@ -1,4 +0,0 @@
|
||||
<section class="caption-save">
|
||||
<a href="#" class="close-box">Cancel</a>
|
||||
<button class="close-box">Save changes</button>
|
||||
</section>
|
||||
106
cms/templates/widgets/sequence-edit.html
Normal file
106
cms/templates/widgets/sequence-edit.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<section class="sequence-edit">
|
||||
<section class="filters">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="">Sort by</label>
|
||||
<select>
|
||||
<option value="">Recently Modified</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="">Display</label>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<select>
|
||||
<option value="">Internal Only</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li class="advanced">
|
||||
<a href="#">Advanced filters</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="search" name="" id="" value="" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="content">
|
||||
<section class="modules">
|
||||
<ol>
|
||||
<li>
|
||||
<ol>
|
||||
% for child in module.get_children():
|
||||
<li>
|
||||
<a href="#" class="module-edit" id="${child.url}">${child.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
%endfor
|
||||
</ol>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="scratch-pad">
|
||||
<ol>
|
||||
<li class="new-module">
|
||||
<%include file="new-module.html"/>
|
||||
</li>
|
||||
<li>
|
||||
<header>
|
||||
<h2>Section Scratch</h2>
|
||||
</header>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 13 </a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit"> Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<header>
|
||||
<h2>Course Scratch</h2>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 13 </a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit"> Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
<section class="sequence-edit">
|
||||
<header>
|
||||
<div class="week">
|
||||
<h2><a href="">Week 1</a></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="editable"><strong>Goal title:</strong> This is the goal body and is where the goal will be further explained</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="editable">Lecture sequence</h1>
|
||||
<p><strong>Group type:</strong> Ordered Sequence</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<section class="filters">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="">Sort by</label>
|
||||
<select>
|
||||
<option value="">Recently Modified</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="">Display</label>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<select>
|
||||
<option value="">Internal Only</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li class="advanced">
|
||||
<a href="#">Advanced filters</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="search" name="" id="" value="" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<section class="modules">
|
||||
<ol>
|
||||
<li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="sequence-edit">Problem Group</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li class="group">
|
||||
<header>
|
||||
<h3>
|
||||
<a href="#" class="problem-edit">Problem group</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</h3>
|
||||
</header>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 13</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="sequence-edit">Problem Group</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
|
||||
<!-- <li class="new-module"> -->
|
||||
<!-- <%include file="new-module.html"/> -->
|
||||
<!-- </li> -->
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="scratch-pad">
|
||||
<ol>
|
||||
<li>
|
||||
<header>
|
||||
<h2>Section Scratch</h2>
|
||||
</header>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 13 </a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit"> Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<header>
|
||||
<h2>Course Scratch</h2>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 11</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit">Problem title 13 </a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="problem-edit"> Problem title 14</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- <li class="new-module"> -->
|
||||
<!-- <%include file="new-module.html"/> -->
|
||||
<!-- </li> -->
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="tooltip">
|
||||
<ul>
|
||||
<li><a href="#view" rel="leanModal">View</a></li>
|
||||
<li><a href="#">Download</a></li>
|
||||
<li><a href="#" class="delete-speed">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
<li class="video-box">
|
||||
|
||||
<div class="thumb"><img src="http://placehold.it/100x65" /></div>
|
||||
|
||||
<div class="meta">
|
||||
<strong>video-name</strong> 236mb Uploaded 6 hours ago by <em>Anant Agrawal</em>
|
||||
<p>
|
||||
<ul class="speed-list">
|
||||
Speed
|
||||
<li class="speed">
|
||||
0.75x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">Normal
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">1.25x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">1.5x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li style="background: #eee;" ><a href="#upload" rel="leanModal" class="new-upload">+</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<a href="#">Download All</a> —
|
||||
<a href="#" style="color: brown;" class="remove">Delete All</a> —
|
||||
<a href="#" class="edit-captions"> Edit Captions </a> —
|
||||
<a href="#" class="use-video">Use clip ⬆</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="caption-box">
|
||||
<%include file="captions.html"/>
|
||||
<%include file="save-captions.html"/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<li class="video-box">
|
||||
<div class="thumb"><img src="http://placehold.it/155x90" /></div>
|
||||
|
||||
<div class="meta">
|
||||
<strong>video-name</strong> 236mb
|
||||
<p>Uploaded 6 hours ago by <em>Anant Agrawal</em></p>
|
||||
<p>
|
||||
<ul class="speed-list">
|
||||
Speed
|
||||
<li class="speed">
|
||||
0.75x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">Normal
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">1.25x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li class="speed">1.5x
|
||||
<%include file="speed-tooltip.html"/>
|
||||
</li>
|
||||
<li style="background: #eee;" ><a href="#upload" rel="leanModal" class="new-upload">+</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<a href="#">Download all</a> —
|
||||
<a href="#" stle="color: brown;" class="remove-video">Remove ⬇ </a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div style="margin-top: 30px;">
|
||||
<%include file="captions.html"/>
|
||||
</div>
|
||||
</li>
|
||||
@@ -6,4 +6,6 @@ from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'contentstore.views.index', name='index'),
|
||||
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
)
|
||||
|
||||
@@ -12,13 +12,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("mitx.common.lib.mitxmako")
|
||||
|
||||
from django.template import Context
|
||||
from django.http import HttpResponse
|
||||
|
||||
from . import middleware
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary, context=None, namespace='main'):
|
||||
context_instance = Context(dictionary)
|
||||
# add dictionary to context_instance
|
||||
@@ -27,8 +30,11 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
|
||||
context_dictionary = {}
|
||||
context_instance['settings'] = settings
|
||||
context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
|
||||
for d in middleware.requestcontext:
|
||||
context_dictionary.update(d)
|
||||
|
||||
# In various testing contexts, there might not be a current request context.
|
||||
if middleware.requestcontext is not None:
|
||||
for d in middleware.requestcontext:
|
||||
context_dictionary.update(d)
|
||||
for d in context_instance:
|
||||
context_dictionary.update(d)
|
||||
if context:
|
||||
|
||||
@@ -10,7 +10,8 @@ import StringIO
|
||||
from datetime import timedelta
|
||||
from lxml import etree
|
||||
|
||||
from x_module import XModule, XModuleDescriptor
|
||||
from x_module import XModule
|
||||
from mako_module import MakoModuleDescriptor
|
||||
from progress import Progress
|
||||
from capa.capa_problem import LoncapaProblem
|
||||
from capa.responsetypes import StudentInputError
|
||||
@@ -63,8 +64,14 @@ class ComplexEncoder(json.JSONEncoder):
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
class ModuleDescriptor(XModuleDescriptor):
|
||||
pass
|
||||
class CapaModuleDescriptor(MakoModuleDescriptor):
|
||||
"""
|
||||
Module implementing problems in the LON-CAPA format,
|
||||
as implemented by capa.capa_problem
|
||||
"""
|
||||
|
||||
mako_template = 'widgets/problem-edit.html'
|
||||
|
||||
|
||||
|
||||
class Module(XModule):
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from x_module import XModule, XModuleDescriptor
|
||||
from x_module import XModule
|
||||
from mako_module import MakoModuleDescriptor
|
||||
from lxml import etree
|
||||
from pkg_resources import resource_string
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class ModuleDescriptor(XModuleDescriptor):
|
||||
pass
|
||||
class HtmlModuleDescriptor(MakoModuleDescriptor):
|
||||
"""
|
||||
Module for putting raw html in a course
|
||||
"""
|
||||
mako_template = "widgets/html-edit.html"
|
||||
|
||||
# TODO (cpennington): Make this into a proper module
|
||||
js = {'coffee': [resource_string(__name__, 'js/module/html.coffee')]}
|
||||
|
||||
|
||||
class Module(XModule):
|
||||
id_attribute = 'filename'
|
||||
|
||||
9
common/lib/xmodule/js/module/html.coffee
Normal file
9
common/lib/xmodule/js/module/html.coffee
Normal file
@@ -0,0 +1,9 @@
|
||||
class @HTML
|
||||
constructor: (@id) ->
|
||||
@edit_box = $("##{@id} .edit-box")
|
||||
@preview = $("##{@id} .preview")
|
||||
@edit_box.on('input', =>
|
||||
@preview.empty().append(@edit_box.val())
|
||||
)
|
||||
|
||||
save: -> {text: @edit_box.val()}
|
||||
18
common/lib/xmodule/mako_module.py
Normal file
18
common/lib/xmodule/mako_module.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from x_module import XModuleDescriptor
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
|
||||
|
||||
class MakoModuleDescriptor(XModuleDescriptor):
|
||||
"""
|
||||
Module descriptor intended as a mixin that uses a mako template
|
||||
to specify the module html.
|
||||
|
||||
Expects the descriptor to have the `mako_template` attribute set
|
||||
with the name of the template to render, and it will pass
|
||||
the descriptor as the `module` parameter to that template
|
||||
"""
|
||||
|
||||
def get_html(self):
|
||||
return render_to_string(self.mako_template, {
|
||||
'module': self
|
||||
})
|
||||
@@ -3,7 +3,8 @@ import logging
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from x_module import XModule, XModuleDescriptor
|
||||
from x_module import XModule
|
||||
from mako_module import MakoModuleDescriptor
|
||||
from xmodule.progress import Progress
|
||||
|
||||
log = logging.getLogger("mitx.common.lib.seq_module")
|
||||
@@ -12,9 +13,6 @@ log = logging.getLogger("mitx.common.lib.seq_module")
|
||||
# OBSOLETE: This obsoletes 'type'
|
||||
class_priority = ['video', 'problem']
|
||||
|
||||
class ModuleDescriptor(XModuleDescriptor):
|
||||
pass
|
||||
|
||||
class Module(XModule):
|
||||
''' Layout module which lays out content in a temporal sequence
|
||||
'''
|
||||
@@ -118,5 +116,5 @@ class Module(XModule):
|
||||
self.rendered = False
|
||||
|
||||
|
||||
class SectionDescriptor(XModuleDescriptor):
|
||||
pass
|
||||
class SectionDescriptor(MakoModuleDescriptor):
|
||||
mako_template = 'widgets/sequence-edit.html'
|
||||
|
||||
@@ -5,6 +5,9 @@ setup(
|
||||
version="0.1",
|
||||
packages=find_packages(),
|
||||
install_requires=['distribute'],
|
||||
package_data={
|
||||
'': ['js/*']
|
||||
},
|
||||
|
||||
# See http://guide.python-distribute.org/creation.html#entry-points
|
||||
# for a description of entry_points
|
||||
@@ -19,6 +22,9 @@ setup(
|
||||
"TutorialIndex = seq_module:SectionDescriptor",
|
||||
"Exam = seq_module:SectionDescriptor",
|
||||
"VideoSegment = video_module:VideoSegmentDescriptor",
|
||||
"ProblemSet = seq_module:SectionDescriptor",
|
||||
"Problem = capa_module:CapaModuleDescriptor",
|
||||
"HTML = html_module:HtmlModuleDescriptor",
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import pkg_resources
|
||||
import logging
|
||||
|
||||
from keystore import Location
|
||||
from progress import Progress
|
||||
|
||||
log = logging.getLogger('mitx.' + __name__)
|
||||
|
||||
@@ -30,6 +29,12 @@ class Plugin(object):
|
||||
|
||||
return classes[0].load()
|
||||
|
||||
@classmethod
|
||||
def load_classes(cls):
|
||||
return [class_.load()
|
||||
for class_
|
||||
in pkg_resources.iter_entry_points(cls.entry_point)]
|
||||
|
||||
|
||||
class XModule(object):
|
||||
''' Implements a generic learning module.
|
||||
@@ -154,6 +159,7 @@ class XModuleDescriptor(Plugin):
|
||||
and can generate XModules (which do know about student state).
|
||||
"""
|
||||
entry_point = "xmodule.v1"
|
||||
js = {}
|
||||
|
||||
@staticmethod
|
||||
def load_from_json(json_data, system):
|
||||
@@ -178,6 +184,19 @@ class XModuleDescriptor(Plugin):
|
||||
"""
|
||||
return cls(system=system, **json_data)
|
||||
|
||||
@classmethod
|
||||
def get_javascript(cls):
|
||||
"""
|
||||
Return a dictionary containing some of the following keys:
|
||||
coffee: A list of coffeescript fragments that should be compiled and
|
||||
placed on the page
|
||||
js: A list of javascript fragments that should be included on the page
|
||||
|
||||
All of these will be loaded onto the page in the CMS
|
||||
"""
|
||||
return cls.js
|
||||
|
||||
|
||||
def __init__(self,
|
||||
system,
|
||||
definition=None,
|
||||
@@ -202,6 +221,7 @@ class XModuleDescriptor(Plugin):
|
||||
self.definition = definition if definition is not None else {}
|
||||
self.name = Location(kwargs.get('location')).name
|
||||
self.type = Location(kwargs.get('location')).category
|
||||
self.url = Location(kwargs.get('location')).url()
|
||||
|
||||
# For now, we represent goals as a list of strings, but this
|
||||
# is one of the things that we are going to be iterating on heavily
|
||||
@@ -220,21 +240,27 @@ class XModuleDescriptor(Plugin):
|
||||
else:
|
||||
return [child for child in self._child_instances if child.type in categories]
|
||||
|
||||
def get_html(self):
|
||||
"""
|
||||
Return the html used to edit this module
|
||||
"""
|
||||
raise NotImplementedError("get_html() must be provided by specific modules")
|
||||
|
||||
def get_xml(self):
|
||||
''' For conversions between JSON and legacy XML representations.
|
||||
'''
|
||||
if self.xml:
|
||||
if self.xml:
|
||||
return self.xml
|
||||
else:
|
||||
else:
|
||||
raise NotImplementedError("JSON->XML Translation not implemented")
|
||||
|
||||
def get_json(self):
|
||||
''' For conversions between JSON and legacy XML representations.
|
||||
'''
|
||||
if self.json:
|
||||
if self.json:
|
||||
raise NotImplementedError
|
||||
return self.json # TODO: Return context as well -- files, etc.
|
||||
else:
|
||||
return self.json # TODO: Return context as well -- files, etc.
|
||||
else:
|
||||
raise NotImplementedError("XML->JSON Translation not implemented")
|
||||
|
||||
#def handle_cms_json(self):
|
||||
|
||||
6
common/test/README.md
Normal file
6
common/test/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Common test infrastructure for LMS + CMS
|
||||
===========================
|
||||
|
||||
data/ has some test course data.
|
||||
|
||||
Once the course validation is separated from django, we should have scripts here that checks that a course consists only of xml that we understand.
|
||||
1
common/test/data/full/README.md
Normal file
1
common/test/data/full/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is a realistic course, with many different module types and a lot of structure. It is based on 6.002x.
|
||||
2
common/test/data/simple/README.md
Normal file
2
common/test/data/simple/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
This is a simple, but non-trivial, course using multiple module types and some nested structure.
|
||||
|
||||
1
common/test/data/toy/README.md
Normal file
1
common/test/data/toy/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is a very very simple course, useful for initial debugging of processing code.
|
||||
11
common/test/data/toy/toy_course.xml
Normal file
11
common/test/data/toy/toy_course.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<course name="Toy Course" graceperiod="1 day 5 hours 59 minutes 59 seconds" showanswer="always" rerandomize="never">
|
||||
<chapter name="Overview">
|
||||
<section format="Video" name="Welcome">
|
||||
<video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
|
||||
</section>
|
||||
<section format="Lecture Sequence" name="System Usage Sequence">
|
||||
<html id="Lab2A" filename="Lab2A.html"/>
|
||||
<video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/>
|
||||
</section>
|
||||
</chapter>
|
||||
</course>
|
||||
@@ -26,10 +26,13 @@ import xmodule
|
||||
|
||||
''' This file will eventually form an abstraction layer between the
|
||||
course XML file and the rest of the system.
|
||||
|
||||
TODO: Shift everything from xml.dom.minidom to XPath (or XQuery)
|
||||
'''
|
||||
|
||||
# ==== This section has no direct dependencies on django ====================================
|
||||
# NOTE: it does still have some indirect dependencies:
|
||||
# util.memcache.fasthash (which does not depend on memcache at all)
|
||||
#
|
||||
|
||||
class ContentException(Exception):
|
||||
pass
|
||||
|
||||
@@ -38,29 +41,6 @@ log = logging.getLogger("mitx.courseware")
|
||||
def format_url_params(params):
|
||||
return [ urllib.quote(string.replace(' ','_')) for string in params ]
|
||||
|
||||
def xpath(xml, query_string, **args):
|
||||
''' Safe xpath query into an xml tree:
|
||||
* xml is the tree.
|
||||
* query_string is the query
|
||||
* args are the parameters. Substitute for {params}.
|
||||
We should remove this with the move to lxml.
|
||||
We should also use lxml argument passing. '''
|
||||
doc = etree.fromstring(xml)
|
||||
#print type(doc)
|
||||
def escape(x):
|
||||
# TODO: This should escape the string. For now, we just assume it's made of valid characters.
|
||||
# Couldn't figure out how to escape for lxml in a few quick Googles
|
||||
valid_chars="".join(map(chr, range(ord('a'),ord('z')+1)+range(ord('A'),ord('Z')+1)+range(ord('0'), ord('9')+1)))+"_ "
|
||||
for e in x:
|
||||
if e not in valid_chars:
|
||||
raise Exception("Invalid char in xpath expression. TODO: Escape")
|
||||
return x
|
||||
|
||||
args=dict( ((k, escape(args[k])) for k in args) )
|
||||
#print args
|
||||
results = doc.xpath(query_string.format(**args))
|
||||
return results
|
||||
|
||||
def xpath_remove(tree, path):
|
||||
''' Remove all items matching path from lxml tree. Works in
|
||||
place.'''
|
||||
@@ -69,35 +49,34 @@ def xpath_remove(tree, path):
|
||||
item.getparent().remove(item)
|
||||
return tree
|
||||
|
||||
if __name__=='__main__':
|
||||
print xpath('<html><problem name="Bob"></problem></html>', '/{search}/problem[@name="{name}"]',
|
||||
search='html', name="Bob")
|
||||
|
||||
def id_tag(course):
|
||||
''' Tag all course elements with unique IDs '''
|
||||
default_ids = xmodule.get_default_ids()
|
||||
|
||||
# Tag elements with unique IDs
|
||||
elements = course.xpath("|".join(['//'+c for c in default_ids]))
|
||||
elements = course.xpath("|".join('//' + c for c in default_ids))
|
||||
for elem in elements:
|
||||
if elem.get('id'):
|
||||
pass
|
||||
elif elem.get(default_ids[elem.tag]):
|
||||
new_id = elem.get(default_ids[elem.tag])
|
||||
new_id = "".join([a for a in new_id if a.isalnum()]) # Convert to alphanumeric
|
||||
# Without this, a conflict may occur between an hmtl or youtube id
|
||||
new_id = elem.get(default_ids[elem.tag])
|
||||
# Convert to alphanumeric
|
||||
new_id = "".join(a for a in new_id if a.isalnum())
|
||||
|
||||
# Without this, a conflict may occur between an html or youtube id
|
||||
new_id = default_ids[elem.tag] + new_id
|
||||
elem.set('id', new_id)
|
||||
else:
|
||||
elem.set('id', "id"+fasthash(etree.tostring(elem)))
|
||||
elem.set('id', "id" + fasthash(etree.tostring(elem)))
|
||||
|
||||
def propogate_downward_tag(element, attribute_name, parent_attribute = None):
|
||||
''' This call is to pass down an attribute to all children. If an element
|
||||
has this attribute, it will be "inherited" by all of its children. If a
|
||||
child (A) already has that attribute, A will keep the same attribute and
|
||||
all of A's children will inherit A's attribute. This is a recursive call.'''
|
||||
|
||||
if (parent_attribute is None): #This is the entry call. Select all elements with this attribute
|
||||
|
||||
if (parent_attribute is None):
|
||||
#This is the entry call. Select all elements with this attribute
|
||||
all_attributed_elements = element.xpath("//*[@" + attribute_name +"]")
|
||||
for attributed_element in all_attributed_elements:
|
||||
attribute_value = attributed_element.get(attribute_name)
|
||||
@@ -118,6 +97,164 @@ def propogate_downward_tag(element, attribute_name, parent_attribute = None):
|
||||
#to its children later.
|
||||
return
|
||||
|
||||
|
||||
def course_xml_process(tree):
|
||||
''' Do basic pre-processing of an XML tree. Assign IDs to all
|
||||
items without. Propagate due dates, grace periods, etc. to child
|
||||
items.
|
||||
'''
|
||||
replace_custom_tags(tree)
|
||||
id_tag(tree)
|
||||
propogate_downward_tag(tree, "due")
|
||||
propogate_downward_tag(tree, "graded")
|
||||
propogate_downward_tag(tree, "graceperiod")
|
||||
propogate_downward_tag(tree, "showanswer")
|
||||
propogate_downward_tag(tree, "rerandomize")
|
||||
return tree
|
||||
|
||||
|
||||
def toc_from_xml(dom, active_chapter, active_section):
|
||||
'''
|
||||
Create a table of contents from the course xml.
|
||||
|
||||
Return format:
|
||||
[ {'name': name, 'sections': SECTIONS, 'active': bool}, ... ]
|
||||
|
||||
where SECTIONS is a list
|
||||
[ {'name': name, 'format': format, 'due': due, 'active' : bool}, ...]
|
||||
|
||||
active is set for the section and chapter corresponding to the passed
|
||||
parameters. Everything else comes from the xml, or defaults to "".
|
||||
|
||||
chapters with name 'hidden' are skipped.
|
||||
'''
|
||||
name = dom.xpath('//course/@name')[0]
|
||||
|
||||
chapters = dom.xpath('//course[@name=$name]/chapter', name=name)
|
||||
ch = list()
|
||||
for c in chapters:
|
||||
if c.get('name') == 'hidden':
|
||||
continue
|
||||
sections = list()
|
||||
for s in dom.xpath('//course[@name=$name]/chapter[@name=$chname]/section',
|
||||
name=name, chname=c.get('name')):
|
||||
|
||||
format = s.get("subtitle") if s.get("subtitle") else s.get("format") or ""
|
||||
active = (c.get("name") == active_chapter and
|
||||
s.get("name") == active_section)
|
||||
|
||||
sections.append({'name': s.get("name") or "",
|
||||
'format': format,
|
||||
'due': s.get("due") or "",
|
||||
'active': active})
|
||||
|
||||
ch.append({'name': c.get("name"),
|
||||
'sections': sections,
|
||||
'active': c.get("name") == active_chapter})
|
||||
return ch
|
||||
|
||||
|
||||
def replace_custom_tags_dir(tree, dir):
|
||||
'''
|
||||
Process tree to replace all custom tags defined in dir.
|
||||
'''
|
||||
tags = os.listdir(dir)
|
||||
for tag in tags:
|
||||
for element in tree.iter(tag):
|
||||
element.tag = 'customtag'
|
||||
impl = etree.SubElement(element, 'impl')
|
||||
impl.text = tag
|
||||
|
||||
def parse_course_file(filename, options, namespace):
|
||||
'''
|
||||
Parse a course file with the given options, and return the resulting
|
||||
xml tree object.
|
||||
|
||||
Options should be a dictionary including keys
|
||||
'dev_content': bool,
|
||||
'groups' : [list, of, user, groups]
|
||||
|
||||
namespace is used to in searching for the file. Could be e.g. 'course',
|
||||
'sections'.
|
||||
'''
|
||||
xml = etree.XML(render_to_string(filename, options, namespace=namespace))
|
||||
return course_xml_process(xml)
|
||||
|
||||
|
||||
def get_section(section, options, dirname):
|
||||
'''
|
||||
Given the name of a section, an options dict containing keys
|
||||
'dev_content' and 'groups', and a directory to look in,
|
||||
returns the xml tree for the section, or None if there's no
|
||||
such section.
|
||||
'''
|
||||
filename = section + ".xml"
|
||||
|
||||
if filename not in os.listdir(dirname):
|
||||
log.error(filename + " not in " + str(os.listdir(dirname)))
|
||||
return None
|
||||
|
||||
tree = parse_course_file(filename, options, namespace='sections')
|
||||
return tree
|
||||
|
||||
|
||||
def get_module(tree, module, id_tag, module_id, sections_dirname, options):
|
||||
'''
|
||||
Given the xml tree of the course, get the xml string for a module
|
||||
with the specified module type, id_tag, module_id. Looks in
|
||||
sections_dirname for sections.
|
||||
|
||||
id_tag -- use id_tag if the place the module stores its id is not 'id'
|
||||
'''
|
||||
# Sanitize input
|
||||
if not module.isalnum():
|
||||
raise Exception("Module is not alphanumeric")
|
||||
|
||||
if not module_id.isalnum():
|
||||
raise Exception("Module ID is not alphanumeric")
|
||||
|
||||
# Generate search
|
||||
xpath_search='//{module}[(@{id_tag} = "{id}") or (@id = "{id}")]'.format(
|
||||
module=module,
|
||||
id_tag=id_tag,
|
||||
id=module_id)
|
||||
|
||||
|
||||
result_set = tree.xpath(xpath_search)
|
||||
if len(result_set) < 1:
|
||||
# Not found in main tree. Let's look in the section files.
|
||||
section_list = (s[:-4] for s in os.listdir(sections_dirname) if s[-4:]=='.xml')
|
||||
for section in section_list:
|
||||
try:
|
||||
s = get_section(section, options, sections_dirname)
|
||||
except etree.XMLSyntaxError:
|
||||
ex = sys.exc_info()
|
||||
raise ContentException("Malformed XML in " + section +
|
||||
"(" + str(ex[1].msg) + ")")
|
||||
result_set = s.xpath(xpath_search)
|
||||
if len(result_set) != 0:
|
||||
break
|
||||
|
||||
if len(result_set) > 1:
|
||||
log.error("WARNING: Potentially malformed course file", module, module_id)
|
||||
|
||||
if len(result_set)==0:
|
||||
log.error('[content_parser.get_module] cannot find %s in course.xml tree',
|
||||
xpath_search)
|
||||
log.error('tree = %s' % etree.tostring(tree, pretty_print=True))
|
||||
return None
|
||||
|
||||
# log.debug('[courseware.content_parser.module_xml] found %s' % result_set)
|
||||
|
||||
return etree.tostring(result_set[0])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==== All Django-specific code below =============================================
|
||||
|
||||
def user_groups(user):
|
||||
if not user.is_authenticated():
|
||||
return []
|
||||
@@ -135,154 +272,94 @@ def user_groups(user):
|
||||
|
||||
return group_names
|
||||
|
||||
# return [u.name for u in UserTestGroup.objects.raw("select * from auth_user, student_usertestgroup, student_usertestgroup_users where auth_user.id = student_usertestgroup_users.user_id and student_usertestgroup_users.usertestgroup_id = student_usertestgroup.id and auth_user.id = %s", [user.id])]
|
||||
|
||||
def get_options(user):
|
||||
return {'dev_content': settings.DEV_CONTENT,
|
||||
'groups': user_groups(user)}
|
||||
|
||||
|
||||
def replace_custom_tags(tree):
|
||||
tags = os.listdir(settings.DATA_DIR+'/custom_tags')
|
||||
for tag in tags:
|
||||
for element in tree.iter(tag):
|
||||
element.tag = 'customtag'
|
||||
impl = etree.SubElement(element, 'impl')
|
||||
impl.text = tag
|
||||
'''Replace custom tags defined in our custom_tags dir'''
|
||||
replace_custom_tags_dir(tree, settings.DATA_DIR+'/custom_tags')
|
||||
|
||||
def course_xml_process(tree):
|
||||
''' Do basic pre-processing of an XML tree. Assign IDs to all
|
||||
items without. Propagate due dates, grace periods, etc. to child
|
||||
items.
|
||||
|
||||
def course_file(user, coursename=None):
|
||||
''' Given a user, return an xml tree object for the course file.
|
||||
|
||||
Handles getting the right file, and processing it depending on the
|
||||
groups the user is in. Does caching of the xml strings.
|
||||
'''
|
||||
replace_custom_tags(tree)
|
||||
id_tag(tree)
|
||||
propogate_downward_tag(tree, "due")
|
||||
propogate_downward_tag(tree, "graded")
|
||||
propogate_downward_tag(tree, "graceperiod")
|
||||
propogate_downward_tag(tree, "showanswer")
|
||||
propogate_downward_tag(tree, "rerandomize")
|
||||
return tree
|
||||
|
||||
def course_file(user,coursename=None):
|
||||
''' Given a user, return course.xml'''
|
||||
|
||||
if user.is_authenticated():
|
||||
filename = UserProfile.objects.get(user=user).courseware # user.profile_cache.courseware
|
||||
# use user.profile_cache.courseware?
|
||||
filename = UserProfile.objects.get(user=user).courseware
|
||||
else:
|
||||
filename = 'guest_course.xml'
|
||||
|
||||
# if a specific course is specified, then use multicourse to get the right path to the course XML directory
|
||||
# if a specific course is specified, then use multicourse to get
|
||||
# the right path to the course XML directory
|
||||
if coursename and settings.ENABLE_MULTICOURSE:
|
||||
xp = multicourse_settings.get_course_xmlpath(coursename)
|
||||
filename = xp + filename # prefix the filename with the path
|
||||
|
||||
groups = user_groups(user)
|
||||
options = {'dev_content':settings.DEV_CONTENT,
|
||||
'groups' : groups}
|
||||
options = get_options(user)
|
||||
|
||||
# Try the cache...
|
||||
cache_key = "{0}_processed?dev_content:{1}&groups:{2}".format(
|
||||
filename,
|
||||
options['dev_content'],
|
||||
sorted(groups))
|
||||
|
||||
cache_key = filename + "_processed?dev_content:" + str(options['dev_content']) + "&groups:" + str(sorted(groups))
|
||||
if "dev" not in settings.DEFAULT_GROUPS:
|
||||
tree_string = cache.get(cache_key)
|
||||
else:
|
||||
if "dev" in settings.DEFAULT_GROUPS:
|
||||
tree_string = None
|
||||
else:
|
||||
tree_string = cache.get(cache_key)
|
||||
|
||||
if settings.DEBUG:
|
||||
log.info('[courseware.content_parser.course_file] filename=%s, cache_key=%s' % (filename,cache_key))
|
||||
# print '[courseware.content_parser.course_file] tree_string = ',tree_string
|
||||
|
||||
if not tree_string:
|
||||
tree = course_xml_process(etree.XML(render_to_string(filename, options, namespace = 'course')))
|
||||
tree_string = etree.tostring(tree)
|
||||
|
||||
cache.set(cache_key, tree_string, 60)
|
||||
else:
|
||||
if tree_string:
|
||||
tree = etree.XML(tree_string)
|
||||
else:
|
||||
tree = parse_course_file(filename, options, namespace='course')
|
||||
# Cache it
|
||||
tree_string = etree.tostring(tree)
|
||||
cache.set(cache_key, tree_string, 60)
|
||||
|
||||
return tree
|
||||
|
||||
def section_file(user, section, coursename=None, dironly=False):
|
||||
|
||||
def sections_dir(coursename=None):
|
||||
''' Get directory where sections information is stored.
|
||||
'''
|
||||
# if a specific course is specified, then use multicourse to get the
|
||||
# right path to the course XML directory
|
||||
xp = ''
|
||||
if coursename and settings.ENABLE_MULTICOURSE:
|
||||
xp = multicourse_settings.get_course_xmlpath(coursename)
|
||||
|
||||
return settings.DATA_DIR + xp + '/sections/'
|
||||
|
||||
|
||||
|
||||
def section_file(user, section, coursename=None):
|
||||
'''
|
||||
Given a user and the name of a section, return that section.
|
||||
This is done specific to each course.
|
||||
If dironly=True then return the sections directory.
|
||||
TODO: This is a bit weird; dironly should be scrapped.
|
||||
|
||||
Returns the xml tree for the section, or None if there's no such section.
|
||||
'''
|
||||
filename = section+".xml"
|
||||
dirname = sections_dir(coursename)
|
||||
|
||||
# if a specific course is specified, then use multicourse to get the right path to the course XML directory
|
||||
xp = ''
|
||||
if coursename and settings.ENABLE_MULTICOURSE: xp = multicourse_settings.get_course_xmlpath(coursename)
|
||||
|
||||
dirname = settings.DATA_DIR + xp + '/sections/'
|
||||
|
||||
if dironly: return dirname
|
||||
|
||||
if filename not in os.listdir(dirname):
|
||||
log.error(filename+" not in "+str(os.listdir(dirname)))
|
||||
return None
|
||||
|
||||
options = {'dev_content':settings.DEV_CONTENT,
|
||||
'groups' : user_groups(user)}
|
||||
|
||||
tree = course_xml_process(etree.XML(render_to_string(filename, options, namespace = 'sections')))
|
||||
return tree
|
||||
return get_section(section, options, dirname)
|
||||
|
||||
|
||||
def module_xml(user, module, id_tag, module_id, coursename=None):
|
||||
''' Get XML for a module based on module and module_id. Assumes
|
||||
module occurs once in courseware XML file or hidden section. '''
|
||||
# Sanitize input
|
||||
if not module.isalnum():
|
||||
raise Exception("Module is not alphanumeric")
|
||||
if not module_id.isalnum():
|
||||
raise Exception("Module ID is not alphanumeric")
|
||||
# Generate search
|
||||
xpath_search='//{module}[(@{id_tag} = "{id}") or (@id = "{id}")]'.format(module=module,
|
||||
id_tag=id_tag,
|
||||
id=module_id)
|
||||
#result_set=doc.xpathEval(xpath_search)
|
||||
doc = course_file(user,coursename)
|
||||
sdirname = section_file(user,'',coursename,True) # get directory where sections information is stored
|
||||
section_list = (s[:-4] for s in os.listdir(sdirname) if s[-4:]=='.xml')
|
||||
module occurs once in courseware XML file or hidden section.
|
||||
'''
|
||||
tree = course_file(user, coursename)
|
||||
sdirname = sections_dir(coursename)
|
||||
options = get_options(user)
|
||||
|
||||
result_set=doc.xpath(xpath_search)
|
||||
if len(result_set)<1:
|
||||
for section in section_list:
|
||||
try:
|
||||
s = section_file(user, section, coursename)
|
||||
except etree.XMLSyntaxError:
|
||||
ex= sys.exc_info()
|
||||
raise ContentException("Malformed XML in " + section+ "("+str(ex[1].msg)+")")
|
||||
result_set = s.xpath(xpath_search)
|
||||
if len(result_set) != 0:
|
||||
break
|
||||
|
||||
if len(result_set)>1:
|
||||
log.error("WARNING: Potentially malformed course file", module, module_id)
|
||||
if len(result_set)==0:
|
||||
if settings.DEBUG:
|
||||
log.error('[courseware.content_parser.module_xml] cannot find %s in course.xml tree' % xpath_search)
|
||||
log.error('tree = %s' % etree.tostring(doc,pretty_print=True))
|
||||
return None
|
||||
if settings.DEBUG:
|
||||
log.info('[courseware.content_parser.module_xml] found %s' % result_set)
|
||||
return etree.tostring(result_set[0])
|
||||
#return result_set[0].serialize()
|
||||
|
||||
def toc_from_xml(dom, active_chapter, active_section):
|
||||
name = dom.xpath('//course/@name')[0]
|
||||
|
||||
chapters = dom.xpath('//course[@name=$name]/chapter', name=name)
|
||||
ch=list()
|
||||
for c in chapters:
|
||||
if c.get('name') == 'hidden':
|
||||
continue
|
||||
sections=list()
|
||||
for s in dom.xpath('//course[@name=$name]/chapter[@name=$chname]/section', name=name, chname=c.get('name')):
|
||||
sections.append({'name':s.get("name") or "",
|
||||
'format':s.get("subtitle") if s.get("subtitle") else s.get("format") or "",
|
||||
'due':s.get("due") or "",
|
||||
'active':(c.get("name")==active_chapter and \
|
||||
s.get("name")==active_section)})
|
||||
ch.append({'name':c.get("name"),
|
||||
'sections':sections,
|
||||
'active':(c.get("name")==active_chapter)})
|
||||
return ch
|
||||
return get_module(tree, module, id_tag, module_id, sdirname, options)
|
||||
|
||||
|
||||
@@ -10,46 +10,98 @@ from courseware.content_parser import course_file
|
||||
import courseware.module_render
|
||||
import xmodule
|
||||
|
||||
import mitxmako.middleware as middleware
|
||||
middleware.MakoMiddleware()
|
||||
|
||||
def check_names(user, course):
|
||||
'''
|
||||
Complain if any problems have alphanumeric names.
|
||||
TODO (vshnayder): there are some in 6.002x that don't. Is that actually a problem?
|
||||
'''
|
||||
all_ok = True
|
||||
print "Confirming all problems have alphanumeric names"
|
||||
for problem in course.xpath('//problem'):
|
||||
filename = problem.get('filename')
|
||||
if not filename.isalnum():
|
||||
print "==============> Invalid (non-alphanumeric) filename", filename
|
||||
all_ok = False
|
||||
return all_ok
|
||||
|
||||
def check_rendering(user, course):
|
||||
'''Check that all modules render'''
|
||||
all_ok = True
|
||||
print "Confirming all modules render. Nothing should print during this step. "
|
||||
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
|
||||
module_class = xmodule.modx_modules[module.tag]
|
||||
# TODO: Abstract this out in render_module.py
|
||||
try:
|
||||
module_class(etree.tostring(module),
|
||||
module.get('id'),
|
||||
ajax_url='',
|
||||
state=None,
|
||||
track_function = lambda x,y,z:None,
|
||||
render_function = lambda x: {'content':'','type':'video'})
|
||||
except Exception as ex:
|
||||
print "==============> Error in ", etree.tostring(module)
|
||||
print ""
|
||||
print ex
|
||||
all_ok = False
|
||||
print "Module render check finished"
|
||||
return all_ok
|
||||
|
||||
def check_sections(user, course):
|
||||
all_ok = True
|
||||
sections_dir = settings.DATA_DIR + "/sections"
|
||||
print "Checking that all sections exist and parse properly"
|
||||
if os.path.exists(sections_dir):
|
||||
print "Checking all section includes are valid XML"
|
||||
for f in os.listdir(sections_dir):
|
||||
sectionfile = sections_dir + '/' + f
|
||||
#print sectionfile
|
||||
# skip non-xml files:
|
||||
if not sectionfile.endswith('xml'):
|
||||
continue
|
||||
try:
|
||||
etree.parse(sectionfile)
|
||||
except Exception as ex:
|
||||
print "================> Error parsing ", sectionfile
|
||||
print ex
|
||||
all_ok = False
|
||||
print "checked all sections"
|
||||
else:
|
||||
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
|
||||
return all_ok
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Does basic validity tests on course.xml."
|
||||
def handle(self, *args, **options):
|
||||
check = True
|
||||
all_ok = True
|
||||
|
||||
# TODO (vshnayder): create dummy user objects. Anon, authenticated, staff.
|
||||
# Check that everything works for each.
|
||||
# The objects probably shouldn't be actual django users to avoid unneeded
|
||||
# dependency on django.
|
||||
|
||||
# TODO: use args as list of files to check. Fix loading to work for other files.
|
||||
|
||||
sample_user = User.objects.all()[0]
|
||||
|
||||
|
||||
print "Attempting to load courseware"
|
||||
course = course_file(sample_user)
|
||||
print "Confirming all problems have alphanumeric names"
|
||||
for problem in course.xpath('//problem'):
|
||||
filename = problem.get('filename')
|
||||
if not filename.isalnum():
|
||||
print "==============> Invalid (non-alphanumeric) filename", filename
|
||||
check = False
|
||||
print "Confirming all modules render. Nothing should print during this step. "
|
||||
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
|
||||
module_class = xmodule.modx_modules[module.tag]
|
||||
# TODO: Abstract this out in render_module.py
|
||||
try:
|
||||
module_class(etree.tostring(module),
|
||||
module.get('id'),
|
||||
ajax_url='',
|
||||
state=None,
|
||||
track_function = lambda x,y,z:None,
|
||||
render_function = lambda x: {'content':'','type':'video'})
|
||||
except:
|
||||
print "==============> Error in ", etree.tostring(module)
|
||||
check = False
|
||||
print "Module render check finished"
|
||||
sections_dir = settings.DATA_DIR+"sections"
|
||||
if os.path.exists(sections_dir):
|
||||
print "Checking all section includes are valid XML"
|
||||
for f in os.listdir(sections_dir):
|
||||
print f
|
||||
etree.parse(sections_dir+'/'+f)
|
||||
else:
|
||||
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
|
||||
|
||||
to_run = [check_names,
|
||||
# TODO (vshnayder) : make check_rendering work (use module_render.py),
|
||||
# turn it on
|
||||
# check_rendering,
|
||||
check_sections,
|
||||
]
|
||||
for check in to_run:
|
||||
all_ok = check(sample_user, course) and all_ok
|
||||
|
||||
# TODO: print "Checking course properly annotated with preprocess.py"
|
||||
|
||||
|
||||
if check:
|
||||
if all_ok:
|
||||
print 'Courseware passes all checks!'
|
||||
else:
|
||||
print "Courseware fails some checks"
|
||||
|
||||
@@ -20,10 +20,12 @@ jasmine.stubRequests = ->
|
||||
settings.success data: jasmine.stubbedMetadata[match[1]]
|
||||
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
|
||||
settings.success jasmine.stubbedCaption
|
||||
else if settings.url.match /modx\/problem\/.+\/problem_get$/
|
||||
settings.success html: readFixtures('problem_content.html')
|
||||
else if settings.url == '/calculate' ||
|
||||
settings.url == '/6002x/modx/sequence/1/goto_position' ||
|
||||
settings.url.match(/event$/) ||
|
||||
settings.url.match(/6002x\/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
|
||||
settings.url.match(/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
|
||||
# do nothing
|
||||
else
|
||||
throw "External request attempted for #{settings.url}, which is not defined."
|
||||
|
||||
@@ -13,6 +13,7 @@ describe 'Problem', ->
|
||||
spyOn($.fn, 'load').andCallFake (url, callback) ->
|
||||
$(@).html readFixtures('problem_content.html')
|
||||
callback()
|
||||
jasmine.stubRequests()
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
@@ -21,12 +22,6 @@ describe 'Problem', ->
|
||||
it 'set the element', ->
|
||||
expect(@problem.element).toBe '#problem_1'
|
||||
|
||||
it 'set the content url', ->
|
||||
expect(@problem.content_url).toEqual '/problem/url/problem_get?id=1'
|
||||
|
||||
it 'render the content', ->
|
||||
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @problem.bind
|
||||
|
||||
describe 'bind', ->
|
||||
beforeEach ->
|
||||
spyOn window, 'update_schematics'
|
||||
@@ -57,8 +52,11 @@ describe 'Problem', ->
|
||||
it 'bind the math input', ->
|
||||
expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath
|
||||
|
||||
it 'display the math input', ->
|
||||
expect(@stubbedJax.root.toMathML).toHaveBeenCalled()
|
||||
it 'replace math content on the page', ->
|
||||
expect(MathJax.Hub.Queue.mostRecentCall.args).toEqual [
|
||||
['Text', @stubbedJax, ''],
|
||||
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
|
||||
]
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
@@ -77,12 +75,19 @@ describe 'Problem', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
|
||||
describe 'with no content given', ->
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
|
||||
callback html: "Hello World"
|
||||
@problem.render()
|
||||
|
||||
it 'load the content via ajax', ->
|
||||
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @bind
|
||||
expect(@problem.element.html()).toEqual 'Hello World'
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
|
||||
describe 'check', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
@@ -116,7 +121,6 @@ describe 'Problem', ->
|
||||
|
||||
describe 'reset', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
|
||||
it 'log the problem_reset event', ->
|
||||
@@ -130,13 +134,13 @@ describe 'Problem', ->
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
|
||||
|
||||
it 'render the returned content', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback("Reset!")
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) ->
|
||||
callback html: "Reset!"
|
||||
@problem.reset()
|
||||
expect(@problem.element.html()).toEqual 'Reset!'
|
||||
|
||||
describe 'show', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
|
||||
|
||||
@@ -154,18 +158,19 @@ describe 'Problem', ->
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
|
||||
|
||||
it 'show the answers', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': 'One', '1_2': 'Two')
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
|
||||
callback answers: '1_1': 'One', '1_2': 'Two'
|
||||
@problem.show()
|
||||
expect($('#answer_1_1')).toHaveHtml 'One'
|
||||
expect($('#answer_1_2')).toHaveHtml 'Two'
|
||||
|
||||
it 'toggle the show answer button', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
|
||||
@problem.show()
|
||||
expect($('.show')).toHaveValue 'Hide Answer'
|
||||
|
||||
it 'add the showed class to element', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
|
||||
@problem.show()
|
||||
expect(@problem.element).toHaveClass 'showed'
|
||||
|
||||
@@ -179,7 +184,8 @@ describe 'Problem', ->
|
||||
'''
|
||||
|
||||
it 'set the correct_answer attribute on the choice', ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': [2, 3])
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
|
||||
callback answers: '1_1': [2, 3]
|
||||
@problem.show()
|
||||
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
|
||||
expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
|
||||
@@ -214,7 +220,6 @@ describe 'Problem', ->
|
||||
|
||||
describe 'save', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
@@ -236,23 +241,29 @@ describe 'Problem', ->
|
||||
describe 'refreshMath', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@stubbedJax.root.toMathML.andReturn '<MathML>'
|
||||
$('#input_example_1').val 'E=mc^2'
|
||||
@problem.refreshMath target: $('#input_example_1').get(0)
|
||||
|
||||
it 'should queue the conversion and MathML element update', ->
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith ['Text', @stubbedJax, 'E=mc^2'],
|
||||
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
|
||||
|
||||
describe 'updateMathML', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem 1, '/problem/url/'
|
||||
@stubbedJax.root.toMathML.andReturn '<MathML>'
|
||||
|
||||
describe 'when there is no exception', ->
|
||||
beforeEach ->
|
||||
@problem.refreshMath target: $('#input_example_1').get(0)
|
||||
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
|
||||
|
||||
it 'should convert and display the MathML object', ->
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith ['Text', @stubbedJax, 'E=mc^2']
|
||||
|
||||
it 'should display debug output in hidden div', ->
|
||||
it 'convert jax to MathML', ->
|
||||
expect($('#input_example_1_dynamath')).toHaveValue '<MathML>'
|
||||
|
||||
describe 'when there is an exception', ->
|
||||
beforeEach ->
|
||||
@stubbedJax.root.toMathML.andThrow {restart: true}
|
||||
@problem.refreshMath target: $('#input_example_1').get(0)
|
||||
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
|
||||
|
||||
it 'should queue up the exception', ->
|
||||
expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true
|
||||
|
||||
@@ -35,6 +35,9 @@ describe 'VideoCaption', ->
|
||||
it 'bind player resize event', ->
|
||||
expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
|
||||
|
||||
it 'bind player seek event', ->
|
||||
expect($(@player)).toHandleWith 'seek', @caption.onUpdatePlayTime
|
||||
|
||||
it 'bind player updatePlayTime event', ->
|
||||
expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class @Problem
|
||||
constructor: (@id, url) ->
|
||||
@element = $("#problem_#{id}")
|
||||
@content_url = "#{url}problem_get?id=#{@id}"
|
||||
@render()
|
||||
|
||||
$: (selector) ->
|
||||
@@ -17,7 +16,7 @@ class @Problem
|
||||
@$('section.action input.save').click @save
|
||||
@$('input.math').keyup(@refreshMath).each(@refreshMath)
|
||||
|
||||
update_progress: (response) =>
|
||||
updateProgress: (response) =>
|
||||
if response.progress_changed
|
||||
@element.attr progress: response.progress_status
|
||||
@element.trigger('progressChanged')
|
||||
@@ -27,10 +26,9 @@ class @Problem
|
||||
@element.html(content)
|
||||
@bind()
|
||||
else
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_get", '', (response) =>
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_get", (response) =>
|
||||
@element.html(response.html)
|
||||
@bind()
|
||||
|
||||
|
||||
check: =>
|
||||
Logger.log 'problem_check', @answers
|
||||
@@ -38,7 +36,7 @@ class @Problem
|
||||
switch response.success
|
||||
when 'incorrect', 'correct'
|
||||
@render(response.contents)
|
||||
@update_progress response
|
||||
@updateProgress response
|
||||
else
|
||||
alert(response.success)
|
||||
|
||||
@@ -46,7 +44,7 @@ class @Problem
|
||||
Logger.log 'problem_reset', @answers
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) =>
|
||||
@render(response.html)
|
||||
@update_progress response
|
||||
@updateProgress response
|
||||
|
||||
show: =>
|
||||
if !@element.hasClass 'showed'
|
||||
@@ -62,7 +60,7 @@ class @Problem
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
@$('.show').val 'Hide Answer'
|
||||
@element.addClass 'showed'
|
||||
@update_progress response
|
||||
@updateProgress response
|
||||
else
|
||||
@$('[id^=answer_], [id^=solution_]').text ''
|
||||
@$('[correct_answer]').attr correct_answer: null
|
||||
@@ -74,21 +72,22 @@ class @Problem
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) =>
|
||||
if response.success
|
||||
alert 'Saved'
|
||||
@update_progress response
|
||||
@updateProgress response
|
||||
|
||||
refreshMath: (event, element) =>
|
||||
element = event.target unless element
|
||||
target = "display_#{element.id.replace(/^input_/, '')}"
|
||||
|
||||
if jax = MathJax.Hub.getAllJax(target)[0]
|
||||
MathJax.Hub.Queue ['Text', jax, $(element).val()]
|
||||
MathJax.Hub.Queue ['Text', jax, $(element).val()],
|
||||
[@updateMathML, jax, element]
|
||||
|
||||
try
|
||||
output = jax.root.toMathML ''
|
||||
$("##{element.id}_dynamath").val(output)
|
||||
catch exception
|
||||
throw exception unless exception.restart
|
||||
MathJax.Callback.After [@refreshMath, jax], exception.restart
|
||||
updateMathML: (jax, element) =>
|
||||
try
|
||||
$("##{element.id}_dynamath").val(jax.root.toMathML '')
|
||||
catch exception
|
||||
throw exception unless exception.restart
|
||||
MathJax.Callback.After [@refreshMath, jax], exception.restart
|
||||
|
||||
refreshAnswers: =>
|
||||
@$('input.schematic').each (index, element) ->
|
||||
|
||||
@@ -10,6 +10,7 @@ class @VideoCaption
|
||||
$(window).bind('resize', @onWindowResize)
|
||||
$(@player).bind('resize', @onWindowResize)
|
||||
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
$(@player).bind('seek', @onUpdatePlayTime)
|
||||
$(@player).bind('play', @onPlay)
|
||||
@$('.hide-subtitles').click @toggle
|
||||
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
|
||||
|
||||
BIN
lms/static/images/sequence-nav/status/check.png
Normal file
BIN
lms/static/images/sequence-nav/status/check.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 211 B |
BIN
lms/static/images/sequence-nav/status/dash.png
Normal file
BIN
lms/static/images/sequence-nav/status/dash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 B |
BIN
lms/static/images/sequence-nav/status/not-started.png
Normal file
BIN
lms/static/images/sequence-nav/status/not-started.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 B |
BIN
lms/static/images/sequence-nav/status/wrong.png
Normal file
BIN
lms/static/images/sequence-nav/status/wrong.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
@@ -23,3 +23,7 @@ The dev server will automatically compile sass files that have changed. Simply s
|
||||
the server using:
|
||||
|
||||
$ rake runserver
|
||||
|
||||
To run it along side of development:
|
||||
|
||||
$ sass --watch lms/static/sass:lms/static/sass -r ./lms/static/sass/bourbon/lib/bourbon.rb
|
||||
|
||||
@@ -62,6 +62,8 @@ div.book-wrapper {
|
||||
@extend .clearfix;
|
||||
|
||||
li {
|
||||
background-color: darken($cream, 4%);
|
||||
|
||||
&.last {
|
||||
display: block;
|
||||
float: left;
|
||||
@@ -77,6 +79,10 @@ div.book-wrapper {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ h1.top-header {
|
||||
border-top: 1px solid #fff;
|
||||
// @include box-shadow(inset 0 1px 0 #fff, inset 1px 0 0 #fff);
|
||||
font-size: 12px;
|
||||
height:46px;
|
||||
// height:46px;
|
||||
line-height: 46px;
|
||||
margin: (-$body-line-height) (-$body-line-height) $body-line-height;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
@@ -224,7 +224,7 @@ h1.top-header {
|
||||
}
|
||||
|
||||
&.block-link {
|
||||
background: darken($cream, 5%);
|
||||
// background: darken($cream, 5%);
|
||||
border-left: 1px solid darken($cream, 20%);
|
||||
@include box-shadow(inset 1px 0 0 lighten($cream, 5%));
|
||||
display: block;
|
||||
|
||||
@@ -11,13 +11,14 @@ nav.sequence-nav {
|
||||
height: 100%;
|
||||
padding-right: flex-grid(1, 9);
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
|
||||
a {
|
||||
@extend .block-link;
|
||||
}
|
||||
|
||||
li {
|
||||
border-left: 1px solid darken($cream, 20%);
|
||||
border-left: 1px solid darken($cream, 10%);
|
||||
display: table-cell;
|
||||
min-width: 20px;
|
||||
|
||||
@@ -29,18 +30,17 @@ nav.sequence-nav {
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($cream, 3%);
|
||||
background-color: lighten($cream, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.visited {
|
||||
background-color: #DCCDA2;
|
||||
background-color: darken($cream, 4%);
|
||||
background-repeat: no-repeat;
|
||||
@include box-shadow(inset 0 0 3px darken(#dccda2, 10%));
|
||||
@include box-shadow(0);
|
||||
|
||||
&:hover {
|
||||
background-color: $cream;
|
||||
background-position: center center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ nav.sequence-nav {
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,86 +60,87 @@ nav.sequence-nav {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 17px;
|
||||
padding: 15px 0 14px;
|
||||
padding: 15px 0 17px;
|
||||
position: relative;
|
||||
@include transition(all, .4s, $ease-in-out-quad);
|
||||
width: 100%;
|
||||
|
||||
&.progress {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
|
||||
&.progress-none {
|
||||
@extend .progress;
|
||||
border-bottom-color: red;
|
||||
}
|
||||
|
||||
&.progress-some {
|
||||
@extend .progress;
|
||||
border-bottom-color: yellow;
|
||||
}
|
||||
|
||||
&.progress-done {
|
||||
@extend .progress;
|
||||
border-bottom-color: green;
|
||||
}
|
||||
background-position: center 8px;
|
||||
|
||||
//video
|
||||
&.seq_video_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('../images/sequence-nav/video-icon-normal.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/video-icon-visited.png');
|
||||
}
|
||||
|
||||
&.seq_video_visited {
|
||||
@extend .visited;
|
||||
background-image: url('../images/sequence-nav/video-icon-visited.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/video-icon-normal.png');
|
||||
}
|
||||
|
||||
&.seq_video_active {
|
||||
@extend .active;
|
||||
background-image: url('../images/sequence-nav/video-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//other
|
||||
&.seq_other_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('../images/sequence-nav/document-icon-normal.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/document-icon-visited.png');
|
||||
}
|
||||
|
||||
&.seq_other_visited {
|
||||
@extend .visited;
|
||||
background-image: url('../images/sequence-nav/document-icon-visited.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/document-icon-normal.png');
|
||||
}
|
||||
|
||||
&.seq_other_active {
|
||||
@extend .active;
|
||||
background-image: url('../images/sequence-nav/document-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//vertical & problems
|
||||
&.seq_vertical_inactive, &.seq_problem_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('../images/sequence-nav/list-icon-normal.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/list-icon-visited.png');
|
||||
}
|
||||
|
||||
&.seq_vertical_visited, &.seq_problem_visited {
|
||||
@extend .visited;
|
||||
background-image: url('../images/sequence-nav/list-icon-visited.png');
|
||||
background-position: center;
|
||||
background-image: url('../images/sequence-nav/list-icon-normal.png');
|
||||
}
|
||||
|
||||
&.seq_vertical_active, &.seq_problem_active {
|
||||
@extend .active;
|
||||
background-image: url('../images/sequence-nav/list-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background: url('../images/sequence-nav/status/dash.png') no-repeat center;
|
||||
@include position( absolute, 0 0 6px 50% );
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
//progress
|
||||
&.progress-none {
|
||||
&:after {
|
||||
background: url('../images/sequence-nav/status/not-started.png') no-repeat center;
|
||||
}
|
||||
}
|
||||
|
||||
&.progress-some {
|
||||
&:after {
|
||||
background: url('../images/sequence-nav/status/wrong.png') no-repeat center;
|
||||
}
|
||||
}
|
||||
|
||||
&.progress-done {
|
||||
&:after {
|
||||
background: url('../images/sequence-nav/status/check.png') no-repeat center;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -180,6 +180,8 @@ nav.sequence-nav {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-position: center 8px;
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
@@ -215,6 +217,7 @@ nav.sequence-nav {
|
||||
display: block;
|
||||
text-indent: -9999px;
|
||||
@include transition(all, .2s, $ease-in-out-quad);
|
||||
line-height: 49px;
|
||||
|
||||
&:hover {
|
||||
opacity: .5;
|
||||
@@ -277,6 +280,7 @@ section.course-content {
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%));
|
||||
@include inline-block();
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
|
||||
@@ -52,14 +52,14 @@ section.course-index {
|
||||
padding: 1em 1.5em;
|
||||
|
||||
li {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
@include border-radius(4px);
|
||||
margin-bottom: lh(.5);
|
||||
position: relative;
|
||||
padding: 5px 36px 5px 10px;
|
||||
|
||||
a {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
@include border-radius(4px);
|
||||
position: relative;
|
||||
padding: 5px 36px 5px 10px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: #666;
|
||||
@@ -74,67 +74,70 @@ section.course-index {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: transparent;
|
||||
border-top: 1px solid rgb(180,180,180);
|
||||
border-right: 1px solid rgb(180,180,180);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 30px;
|
||||
@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(all, 0.2s, linear);
|
||||
background: transparent;
|
||||
border-top: 1px solid rgb(180,180,180);
|
||||
border-right: 1px solid rgb(180,180,180);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 30px;
|
||||
@include transform(rotate(45deg));
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
&: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(all, 0.2s, linear);
|
||||
}
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
|
||||
top: 1px;
|
||||
&:active {
|
||||
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgb(240,240,240);
|
||||
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(230,230,230)));
|
||||
border-color: rgb(200,200,200);
|
||||
font-weight: bold;
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
> a {
|
||||
background: rgb(240,240,240);
|
||||
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(230,230,230)));
|
||||
border-color: rgb(200,200,200);
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
span.subtitle {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
rakefile
19
rakefile
@@ -58,6 +58,11 @@ task :pylint => REPORT_DIR do
|
||||
end
|
||||
end
|
||||
|
||||
default_options = {
|
||||
:lms => '8000',
|
||||
:cms => '8001',
|
||||
}
|
||||
|
||||
[:lms, :cms].each do |system|
|
||||
task_name = "test_#{system}"
|
||||
report_dir = File.join(REPORT_DIR, task_name)
|
||||
@@ -76,7 +81,7 @@ end
|
||||
Other useful environments are devplus (for dev testing with a real local database)
|
||||
desc
|
||||
task system, [:env, :options] => [] do |t, args|
|
||||
args.with_defaults(:env => 'dev', :options => '')
|
||||
args.with_defaults(:env => 'dev', :options => default_options[system])
|
||||
sh(django_admin(system, args.env, 'runserver', args.options))
|
||||
end
|
||||
end
|
||||
@@ -154,3 +159,15 @@ end
|
||||
task :publish => :package do
|
||||
sh("scp #{BUILD_DIR}/#{NORMALIZED_DEPLOY_NAME}_#{PKG_VERSION}*.deb #{PACKAGE_REPO}")
|
||||
end
|
||||
|
||||
namespace :cms do
|
||||
desc "Import course data within the given DATA_DIR variable"
|
||||
task :import do
|
||||
if ENV['DATA_DIR']
|
||||
sh(django_admin(:cms, :dev, :import, ENV['DATA_DIR']))
|
||||
else
|
||||
raise "Please specify a DATA_DIR variable that point to your data directory.\n" +
|
||||
"Example: \`rake cms:import DATA_DIR=../data\`"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user