Commit 9bb85992 by ichuang

Merge branch 'ps-fix-problem' of github.com:MITx/mitx into stable-edx4edx

parents 51b47a50 24fcdad4
This branch (re-)adds dynamic math and symbolicresponse. see doc/ for documentation.
Test cases included.
...@@ -10,6 +10,7 @@ import os.path ...@@ -10,6 +10,7 @@ import os.path
from StringIO import StringIO from StringIO import StringIO
from mako.template import Template from mako.template import Template
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
from collections import defaultdict
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from keystore.django import keystore from keystore.django import keystore
...@@ -42,9 +43,7 @@ class Command(BaseCommand): ...@@ -42,9 +43,7 @@ class Command(BaseCommand):
# Simple lists # Simple lists
'chapter': 'Week', 'chapter': 'Week',
'course': 'Course', 'course': 'Course',
'sequential': 'LectureSequence', 'section': defaultdict(lambda: 'Section', {
'vertical': 'ProblemSet',
'section': {
'Lab': 'Lab', 'Lab': 'Lab',
'Lecture Sequence': 'LectureSequence', 'Lecture Sequence': 'LectureSequence',
'Homework': 'Homework', 'Homework': 'Homework',
...@@ -52,8 +51,13 @@ class Command(BaseCommand): ...@@ -52,8 +51,13 @@ class Command(BaseCommand):
'Video': 'VideoSegment', 'Video': 'VideoSegment',
'Midterm': 'Exam', 'Midterm': 'Exam',
'Final': 'Exam', 'Final': 'Exam',
None: 'Section', 'Problems': 'ProblemSet',
}, }),
'videosequence': 'VideoSequence',
'problemset': 'ProblemSet',
'vertical': 'Section',
'sequential': 'Section',
'tab': 'Section',
# True types # True types
'video': 'VideoSegment', 'video': 'VideoSegment',
'html': 'HTML', 'html': 'HTML',
...@@ -78,6 +82,8 @@ class Command(BaseCommand): ...@@ -78,6 +82,8 @@ class Command(BaseCommand):
e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format( e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format(
category=category, category=category,
name=name)) name=name))
else:
print "Skipping element with tag", e.tag
def handle_skip(e): def handle_skip(e):
...@@ -150,6 +156,9 @@ class Command(BaseCommand): ...@@ -150,6 +156,9 @@ class Command(BaseCommand):
'sequential': handle_list, 'sequential': handle_list,
'vertical': handle_list, 'vertical': handle_list,
'section': handle_list, 'section': handle_list,
'videosequence': handle_list,
'problemset': handle_list,
'tab': handle_list,
# True types # True types
'video': handle_video, 'video': handle_video,
'html': handle_html, 'html': handle_html,
......
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from keystore.django import keystore 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): def index(request):
# TODO (cpennington): These need to be read in from the active user # TODO (cpennington): These need to be read in from the active user
org = 'mit.edu' org = 'mit.edu'
...@@ -11,3 +14,20 @@ def index(request): ...@@ -11,3 +14,20 @@ def index(request):
course = keystore().get_item(['i4x', org, course, 'Course', name]) course = keystore().get_item(['i4x', org, course, 'Course', name])
weeks = course.get_children() weeks = course.get_children()
return render_to_response('index.html', {'weeks': weeks}) 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: ...@@ -21,6 +21,9 @@ Longer TODO:
import sys import sys
import tempfile import tempfile
import os.path
import os
import errno
from path import path from path import path
############################ FEATURE CONFIGURATION ############################# ############################ FEATURE CONFIGURATION #############################
...@@ -154,7 +157,38 @@ PIPELINE_CSS = { ...@@ -154,7 +157,38 @@ PIPELINE_CSS = {
PIPELINE_ALWAYS_RECOMPILE = ['sass/base-style.scss'] 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 = { 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 = [ PIPELINE_COMPILERS = [
......
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()
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)
/*!
* 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);
$(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;
});
});
Sass Watch: 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; ...@@ -2,65 +2,14 @@ $fg-column: 70px;
$fg-gutter: 26px; $fg-gutter: 26px;
$fg-max-columns: 12; $fg-max-columns: 12;
$body-font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; $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 { html {
height: 100%; 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 { a {
text-decoration: none; text-decoration: none;
color: #888; color: #888;
...@@ -77,6 +26,13 @@ input[type="submit"], .button { ...@@ -77,6 +26,13 @@ input[type="submit"], .button {
padding: 6px; padding: 6px;
} }
textarea {
@include box-sizing(border-box);
display: block;
width: 100%;
}
// Extends
.new-module { .new-module {
position: relative; position: relative;
...@@ -111,3 +67,17 @@ input[type="submit"], .button { ...@@ -111,3 +67,17 @@ input[type="submit"], .button {
display: block; display: block;
float: right; 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 { ...@@ -49,7 +49,6 @@ section.cal {
ol { ol {
list-style: none; list-style: none;
@include clearfix; @include clearfix;
@include box-sizing(border-box);
border-left: 1px solid #333; border-left: 1px solid #333;
border-top: 1px solid #333; border-top: 1px solid #333;
width: 100%; width: 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 );
}
}
}
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;
}
}
}
section.week-edit, section#unit-wrapper {
section.week-new, section.filters {
section.sequence-edit { @include clearfix;
margin-bottom: 10px;
> header { background: #efefef;
border-bottom: 2px solid #333; border: 1px solid #ddd;
@include clearfix();
ul {
div {
@include clearfix(); @include clearfix();
padding: 6px 20px; list-style: none;
padding: 6px;
h1 { li {
font-size: 18px; @include inline-block();
text-transform: uppercase;
letter-spacing: 1px;
float: left;
}
p { &.advanced {
float: right; float: right;
}
} }
}
}
div.content {
display: table;
border: 1px solid;
width: 100%;
&.week { section {
header {
background: #eee; background: #eee;
font-size: 12px; padding: 6px;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
@include clearfix;
h2 { h2 {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 12px; font-size: 12px;
@include inline-block(); float: left;
margin-right: 20px;
}
ul {
list-style: none;
@include inline-block();
li {
@include inline-block();
margin-right: 10px;
p {
float: none;
}
}
} }
} }
}
section.goals { &.modules {
background: #eee; @include box-sizing(border-box);
padding: 6px 20px; display: table-cell;
border-top: 1px solid #ccc; width: flex-grid(6, 9);
border-right: 1px solid #333;
ul { &.empty {
list-style: none; text-align: center;
color: #999; vertical-align: middle;
li { a {
margin-bottom: 6px; @extend .button;
@include inline-block();
&:last-child { margin-top: 10px;
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 { ol {
display: table; list-style: none;
border: 1px solid; border-bottom: 1px solid #333;
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 { li {
@include box-sizing(border-box); border-bottom: 1px solid #333;
display: table-cell;
width: flex-grid(6, 9);
border-right: 1px solid #333;
&.empty { &:last-child{
text-align: center; border-bottom: 0;
vertical-align: middle; }
a { a {
@extend .button; color: #000;
@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 { ol {
list-style: none; list-style: none;
li { li {
padding: 6px; padding: 6px;
&:hover {
a.draggable {
opacity: 1;
}
}
&:hover {
a.draggable { a.draggable {
float: right; opacity: 1;
opacity: .5;
} }
}
&.group { a.draggable {
padding: 0; float: right;
opacity: .5;
}
header { &.group {
padding: 6px; padding: 0;
background: none;
h3 { header {
font-size: 14px; padding: 6px;
} background: none;
h3 {
font-size: 14px;
} }
}
ol { ol {
border-left: 4px solid #999; border-left: 4px solid #999;
border-bottom: 0; border-bottom: 0;
li { li {
&:last-child { &:last-child {
border-bottom: 0; border-bottom: 0;
}
} }
} }
} }
...@@ -191,63 +117,63 @@ section.sequence-edit { ...@@ -191,63 +117,63 @@ section.sequence-edit {
} }
} }
} }
}
&.scratch-pad { &.scratch-pad {
@include box-sizing(border-box); @include box-sizing(border-box);
display: table-cell; display: table-cell;
width: flex-grid(3, 9) + flex-gutter(9); width: flex-grid(3, 9) + flex-gutter(9);
vertical-align: top; vertical-align: top;
ol { ol {
list-style: none; list-style: none;
border-bottom: 1px solid #999; border-bottom: 1px solid #999;
li { li {
border-bottom: 1px solid #999; border-bottom: 1px solid #999;
background: #f9f9f9; background: #f9f9f9;
&:last-child { &:last-child {
border-bottom: 0; border-bottom: 0;
} }
ul { ul {
list-style: none; list-style: none;
li { li {
padding: 6px; padding: 6px;
&:last-child { &:last-child {
border-bottom: 0; border-bottom: 0;
} }
&:hover { &:hover {
a.draggable { a.draggable {
opacity: 1; opacity: 1;
}
} }
}
&.empty { &.empty {
padding: 12px; padding: 12px;
a { a {
@extend .button; @extend .button;
display: block; display: block;
text-align: center; text-align: center;
}
} }
}
a.draggable { a.draggable {
float: right; float: right;
opacity: .3; opacity: .3;
} }
a { a {
color: #000; color: #000;
}
} }
} }
} }
} }
} }
} }
......
section.video-new, section.video-edit, section.problem-new, section.problem-edit { section#unit-wrapper {
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 { > header {
background: #666; border-bottom: 2px solid #333;
@include clearfix; @include clearfix();
color: #fff; padding: 6px 20px;
padding: 6px;
border-bottom: 1px solid #333;
-webkit-font-smoothing: antialiased;
h2 {
float: left;
font-size: 14px;
}
a { section {
color: #fff; float: left;
&.save-update { h1 {
float: right; font-size: 16px;
text-transform: uppercase;
letter-spacing: 1px;
@include inline-block();
} }
&.cancel { p {
float: left; @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 { > section {
padding: 20px; padding: 20px;
> header { section.meta {
h1 {
font-size: 24px;
margin: 12px 0;
}
section { section {
&.status-settings { &.status-settings {
float: left;
margin-bottom: 10px;
ul { ul {
list-style: none; list-style: none;
@include border-radius(2px); @include border-radius(2px);
...@@ -80,11 +79,26 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit ...@@ -80,11 +79,26 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit
} }
} }
&.meta { &.author {
float: right;
dl {
dt {
font-weight: bold;
}
dd, dt {
@include inline-block();
}
}
}
&.tags {
background: #eee; background: #eee;
padding: 10px; padding: 10px;
margin: 20px 0; margin: 0 0 20px;
@include clearfix(); @include clearfix();
clear: both;
div { div {
float: left; float: left;
...@@ -103,25 +117,82 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit ...@@ -103,25 +117,82 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit
} }
} }
section.notes { //general styles for main content
margin-top: 20px; div.preview {
padding: 6px;
background: #eee; background: #eee;
border: 1px solid #ccc; @include box-sizing(border-box);
min-height: 40px;
padding: 10px;
width: 100%;
margin-top: 10px;
textarea { h1 {
@include box-sizing(border-box); font-size: 24px;
display: block; margin-bottom: 1em;
width: 100%;
} }
h2 { 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; font-size: 14px;
margin-bottom: 6px; margin-bottom: 6px;
} }
input[type="submit"]{ form {
margin-top: 10px; 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;
} }
} }
} }
......
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;
}
}
}
@import 'bourbon/bourbon'; @import 'bourbon/bourbon';
@import 'reset'; @import 'reset';
@import 'base'; @import 'base', 'layout';
@import 'calendar'; @import 'calendar';
@import 'week', 'video', 'problem', 'module-header'; @import 'section', 'unit';
...@@ -23,11 +23,18 @@ ...@@ -23,11 +23,18 @@
<%block name="content"></%block> <%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/jquery.markitup.js"></script>
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/sets/wiki/set.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> <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.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.leanModal.min.js"></script>
<script src="${ STATIC_URL }/js/jquery.tablednd.js"></script> <script src="${ STATIC_URL }/js/jquery.tablednd.js"></script>
</body> </body>
......
...@@ -7,13 +7,9 @@ ...@@ -7,13 +7,9 @@
<%include file="widgets/navigation.html"/> <%include file="widgets/navigation.html"/>
<section class="main-content"> <section class="main-content">
<%include file="widgets/week-edit.html"/> <section class="edit-pane">
<%include file="widgets/week-new.html"/> <div id="module-html"/>
<%include file="widgets/sequnce-edit.html"/> </section>
<%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> </section>
</section> </section>
......
<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 &amp; Update</a>
</div>
</header>
<section>
${contents}
</section>
</section>
...@@ -6,9 +6,6 @@ ...@@ -6,9 +6,6 @@
<a href="#" class="new-module">New Section</a> <a href="#" class="new-module">New Section</a>
</li> </li>
<li> <li>
<a href="#" class="new-module">New Module</a>
</li>
<li>
<a href="#" class="new-module">New Unit</a> <a href="#" class="new-module">New Unit</a>
</li> </li>
</ul> </ul>
......
<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 &amp; Update</a>
<a href="#" class="cancel">Cancel</a>
</div>
<%include file="notes.html"/>
</section>
<li class="create-module"> <li class="create-module wip">
<a href="#" class="new-module"> <a href="#" class="new-module">
+ Add new module + Add new module
</a> </a>
......
<section class="cal"> <section class="cal">
<header> <header class="wip">
<h2>Filter content:</h2> <h2>Filter content:</h2>
<ul> <ul>
<li> <li>
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
% for week in weeks: % for week in weeks:
<li> <li>
<header> <header>
<h1><a href="#">${week.name}</a></h1> <h1><a href="#" class="week-edit" id="${week.url}">${week.name}</a></h1>
<ul> <ul>
% if week.goals: % if week.goals:
% for goal in week.goals: % for goal in week.goals:
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<ul> <ul>
% for module in week.get_children(): % for module in week.get_children():
<li class="${module.type}"> <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> <a href="#" class="draggable">handle</a>
</li> </li>
% endfor % endfor
......
<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>
<section class="problem-edit"> <section class="problem-edit">
<header> <section class="meta">
<a href="#" class="cancel">Cancel</a> <section class="status-settings">
<a href="#" class="save-update">Save &amp; Update</a> <ul>
</header> <li><a href="#" class="current">Scrap</a></li>
<li><a href="#">Draft</a></li>
<section> <li><a href="#">Proofed</a></li>
<header> <li><a href="#">Published</a></li>
<h1 class="editable">New Problem</h1> </ul>
<section class="author"> <a href="#" class="settings">Settings</a>
<div> </section>
<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=""> <section class="author">
<option>Global</option> <dl>
</select> <dt>Last modified:</dt>
</section> <dd>mm/dd/yy</dd>
<section class="meta"> <dt>By</dt>
<div> <dd>Anant Agarwal</dd>
<h2>Tags:</h2> </dl>
<p class="editable">Click to edit</p> </section>
</div>
<div> <section class="tags">
<h2>Goal</h2> <div>
<p class="editable">Click to edit</p> <h2>Tags:</h2>
</div> <p class="editable">Click to edit</p>
</section> </div>
</header>
<section> <div>
<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> <h2>Goal</h2>
<div class="preview"> <p class="editable">Click to edit</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.
</div> </div>
</section> </section>
</section>
<section class="notes"> <section>
<h2>Add notes</h2> <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>
<textarea name="" id= rows="8" cols="40"></textarea> <div class="preview">
<input type="submit" name="" id="" value="post" /> 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>
<ul>
<li> <div class="actions">
<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> <a href="#" class="cancel">Cancel</a>
<p class="author">Anant Agarwal</p> <a href="" class="save-update">Save &amp; Update</a>
</li> </div>
<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>
<a href="" class="save-update">Save &amp; Update</a>
</section> </section>
<%include file="notes.html"/>
</section> </section>
<li>
<img src="http://placehold.it/300x180" alt="" /><h5>Video-file-name</h5>
</li>
<section class="caption-save">
<a href="#" class="close-box">Cancel</a>
<button class="close-box">Save changes</button>
</section>
<section class="sequence-edit"> <section class="sequence-edit">
<header> <section class="filters">
<div class="week"> <ul>
<h2><a href="">Week 1</a></h2> <li>
<ul> <label for="">Sort by</label>
<li> <select>
<p class="editable"><strong>Goal title:</strong> This is the goal body and is where the goal will be further explained</p> <option value="">Recently Modified</option>
</li> </select>
</ul> </li>
</div>
<div>
<h1 class="editable">Lecture sequence</h1>
<p><strong>Group type:</strong> Ordered Sequence</p>
</div>
</header>
<section class="content"> <li>
<section class="filters"> <label for="">Display</label>
<ul> <select>
<li> <option value="">All content</option>
<label for="">Sort by</label> </select>
<select> </li>
<option value="">Recently Modified</option> <li>
</select> <select>
</li> <option value="">Internal Only</option>
</select>
</li>
<li> <li class="advanced">
<label for="">Display</label> <a href="#">Advanced filters</a>
<select> </li>
<option value="">All content</option>
</select>
</li>
<li>
<select>
<option value="">Internal Only</option>
</select>
</li>
<li class="advanced"> <li>
<a href="#">Advanced filters</a> <input type="search" name="" id="" value="" />
</li> </li>
</ul>
</section>
<div class="content">
<section class="modules">
<ol>
<li> <li>
<input type="search" name="" id="" value="" /> <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> </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"> --> </ol>
<!-- <%include file="new-module.html"/> --> </section>
<!-- </li> -->
</ol>
</section>
<section class="scratch-pad"> <section class="scratch-pad">
<ol> <ol>
<li> <li class="new-module">
<header> <%include file="new-module.html"/>
<h2>Section Scratch</h2> </li>
</header> <li>
<ul> <header>
<li> <h2>Section Scratch</h2>
<a href="#" class="problem-edit">Problem title 11</a> </header>
<a href="#" class="draggable">handle</a> <ul>
</li> <li>
<li> <a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="problem-edit">Problem title 13 </a>
<a href="#" class="draggable">handle</a> <a href="#" class="draggable">handle</a>
</li> </li>
<li> <li>
<a href="#" class="problem-edit"> Problem title 14</a> <a href="#" class="problem-edit">Problem title 13 </a>
<a href="#" class="draggable">handle</a> <a href="#" class="draggable">handle</a>
</li> </li>
<li> <li>
<a href="" class="video-edit">Video 3</a> <a href="#" class="problem-edit"> Problem title 14</a>
<a href="#" class="draggable">handle</a> <a href="#" class="draggable">handle</a>
</li> </li>
</ul> <li>
</li> <a href="" class="video-edit">Video 3</a>
<li> <a href="#" class="draggable">handle</a>
<header> </li>
<h2>Course Scratch</h2> </ul>
</header> </li>
<li>
<ul> <header>
<li> <h2>Course Scratch</h2>
<a href="#" class="problem-edit">Problem title 11</a> </header>
<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"> --> <ul>
<!-- <%include file="new-module.html"/> --> <li>
<!-- </li> --> <a href="#" class="problem-edit">Problem title 11</a>
</ol> <a href="#" class="draggable">handle</a>
</section> </li>
</div> <li>
</section> <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> </section>
<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>
<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>
<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 ...@@ -6,4 +6,6 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', 'contentstore.views.index', name='index'), 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 @@ ...@@ -12,13 +12,16 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
log = logging.getLogger("mitx.common.lib.mitxmako")
from django.template import Context from django.template import Context
from django.http import HttpResponse from django.http import HttpResponse
from . import middleware from . import middleware
from django.conf import settings from django.conf import settings
def render_to_string(template_name, dictionary, context=None, namespace='main'): def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_instance = Context(dictionary) context_instance = Context(dictionary)
# add dictionary to context_instance # add dictionary to context_instance
...@@ -27,8 +30,11 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'): ...@@ -27,8 +30,11 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_dictionary = {} context_dictionary = {}
context_instance['settings'] = settings context_instance['settings'] = settings
context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL 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: for d in context_instance:
context_dictionary.update(d) context_dictionary.update(d)
if context: if context:
......
...@@ -10,7 +10,8 @@ import StringIO ...@@ -10,7 +10,8 @@ import StringIO
from datetime import timedelta from datetime import timedelta
from lxml import etree 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 progress import Progress
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError from capa.responsetypes import StudentInputError
...@@ -63,8 +64,14 @@ class ComplexEncoder(json.JSONEncoder): ...@@ -63,8 +64,14 @@ class ComplexEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)
class ModuleDescriptor(XModuleDescriptor): class CapaModuleDescriptor(MakoModuleDescriptor):
pass """
Module implementing problems in the LON-CAPA format,
as implemented by capa.capa_problem
"""
mako_template = 'widgets/problem-edit.html'
class Module(XModule): class Module(XModule):
......
import json import json
import logging import logging
from x_module import XModule, XModuleDescriptor from x_module import XModule
from mako_module import MakoModuleDescriptor
from lxml import etree from lxml import etree
from pkg_resources import resource_string
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class ModuleDescriptor(XModuleDescriptor): class HtmlModuleDescriptor(MakoModuleDescriptor):
pass """
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): class Module(XModule):
id_attribute = 'filename' id_attribute = 'filename'
......
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()}
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 ...@@ -3,7 +3,8 @@ import logging
from lxml import etree 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 from xmodule.progress import Progress
log = logging.getLogger("mitx.common.lib.seq_module") log = logging.getLogger("mitx.common.lib.seq_module")
...@@ -12,9 +13,6 @@ log = logging.getLogger("mitx.common.lib.seq_module") ...@@ -12,9 +13,6 @@ log = logging.getLogger("mitx.common.lib.seq_module")
# OBSOLETE: This obsoletes 'type' # OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem'] class_priority = ['video', 'problem']
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule): class Module(XModule):
''' Layout module which lays out content in a temporal sequence ''' Layout module which lays out content in a temporal sequence
''' '''
...@@ -118,5 +116,5 @@ class Module(XModule): ...@@ -118,5 +116,5 @@ class Module(XModule):
self.rendered = False self.rendered = False
class SectionDescriptor(XModuleDescriptor): class SectionDescriptor(MakoModuleDescriptor):
pass mako_template = 'widgets/sequence-edit.html'
...@@ -5,6 +5,9 @@ setup( ...@@ -5,6 +5,9 @@ setup(
version="0.1", version="0.1",
packages=find_packages(), packages=find_packages(),
install_requires=['distribute'], install_requires=['distribute'],
package_data={
'': ['js/*']
},
# See http://guide.python-distribute.org/creation.html#entry-points # See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points # for a description of entry_points
...@@ -19,6 +22,9 @@ setup( ...@@ -19,6 +22,9 @@ setup(
"TutorialIndex = seq_module:SectionDescriptor", "TutorialIndex = seq_module:SectionDescriptor",
"Exam = seq_module:SectionDescriptor", "Exam = seq_module:SectionDescriptor",
"VideoSegment = video_module:VideoSegmentDescriptor", "VideoSegment = video_module:VideoSegmentDescriptor",
"ProblemSet = seq_module:SectionDescriptor",
"Problem = capa_module:CapaModuleDescriptor",
"HTML = html_module:HtmlModuleDescriptor",
] ]
} }
) )
...@@ -3,7 +3,6 @@ import pkg_resources ...@@ -3,7 +3,6 @@ import pkg_resources
import logging import logging
from keystore import Location from keystore import Location
from progress import Progress
log = logging.getLogger('mitx.' + __name__) log = logging.getLogger('mitx.' + __name__)
...@@ -30,6 +29,12 @@ class Plugin(object): ...@@ -30,6 +29,12 @@ class Plugin(object):
return classes[0].load() 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): class XModule(object):
''' Implements a generic learning module. ''' Implements a generic learning module.
...@@ -154,6 +159,7 @@ class XModuleDescriptor(Plugin): ...@@ -154,6 +159,7 @@ class XModuleDescriptor(Plugin):
and can generate XModules (which do know about student state). and can generate XModules (which do know about student state).
""" """
entry_point = "xmodule.v1" entry_point = "xmodule.v1"
js = {}
@staticmethod @staticmethod
def load_from_json(json_data, system): def load_from_json(json_data, system):
...@@ -178,6 +184,19 @@ class XModuleDescriptor(Plugin): ...@@ -178,6 +184,19 @@ class XModuleDescriptor(Plugin):
""" """
return cls(system=system, **json_data) 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, def __init__(self,
system, system,
definition=None, definition=None,
...@@ -202,6 +221,7 @@ class XModuleDescriptor(Plugin): ...@@ -202,6 +221,7 @@ class XModuleDescriptor(Plugin):
self.definition = definition if definition is not None else {} self.definition = definition if definition is not None else {}
self.name = Location(kwargs.get('location')).name self.name = Location(kwargs.get('location')).name
self.type = Location(kwargs.get('location')).category 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 # 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 # is one of the things that we are going to be iterating on heavily
...@@ -220,21 +240,27 @@ class XModuleDescriptor(Plugin): ...@@ -220,21 +240,27 @@ class XModuleDescriptor(Plugin):
else: else:
return [child for child in self._child_instances if child.type in categories] 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): def get_xml(self):
''' For conversions between JSON and legacy XML representations. ''' For conversions between JSON and legacy XML representations.
''' '''
if self.xml: if self.xml:
return self.xml return self.xml
else: else:
raise NotImplementedError("JSON->XML Translation not implemented") raise NotImplementedError("JSON->XML Translation not implemented")
def get_json(self): def get_json(self):
''' For conversions between JSON and legacy XML representations. ''' For conversions between JSON and legacy XML representations.
''' '''
if self.json: if self.json:
raise NotImplementedError raise NotImplementedError
return self.json # TODO: Return context as well -- files, etc. return self.json # TODO: Return context as well -- files, etc.
else: else:
raise NotImplementedError("XML->JSON Translation not implemented") raise NotImplementedError("XML->JSON Translation not implemented")
#def handle_cms_json(self): #def handle_cms_json(self):
......
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.
This is a realistic course, with many different module types and a lot of structure. It is based on 6.002x.
This is a simple, but non-trivial, course using multiple module types and some nested structure.
This is a very very simple course, useful for initial debugging of processing code.
<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>
...@@ -10,46 +10,98 @@ from courseware.content_parser import course_file ...@@ -10,46 +10,98 @@ from courseware.content_parser import course_file
import courseware.module_render import courseware.module_render
import xmodule 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): class Command(BaseCommand):
help = "Does basic validity tests on course.xml." help = "Does basic validity tests on course.xml."
def handle(self, *args, **options): 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] sample_user = User.objects.all()[0]
print "Attempting to load courseware" print "Attempting to load courseware"
course = course_file(sample_user) course = course_file(sample_user)
print "Confirming all problems have alphanumeric names"
for problem in course.xpath('//problem'): to_run = [check_names,
filename = problem.get('filename') # TODO (vshnayder) : make check_rendering work (use module_render.py),
if not filename.isalnum(): # turn it on
print "==============> Invalid (non-alphanumeric) filename", filename # check_rendering,
check = False check_sections,
print "Confirming all modules render. Nothing should print during this step. " ]
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'): for check in to_run:
module_class = xmodule.modx_modules[module.tag] all_ok = check(sample_user, course) and all_ok
# 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+")"
# TODO: print "Checking course properly annotated with preprocess.py" # TODO: print "Checking course properly annotated with preprocess.py"
if all_ok:
if check:
print 'Courseware passes all checks!' print 'Courseware passes all checks!'
else: else:
print "Courseware fails some checks" print "Courseware fails some checks"
...@@ -20,10 +20,12 @@ jasmine.stubRequests = -> ...@@ -20,10 +20,12 @@ jasmine.stubRequests = ->
settings.success data: jasmine.stubbedMetadata[match[1]] settings.success data: jasmine.stubbedMetadata[match[1]]
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/ else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
settings.success jasmine.stubbedCaption 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' || else if settings.url == '/calculate' ||
settings.url == '/6002x/modx/sequence/1/goto_position' || settings.url == '/6002x/modx/sequence/1/goto_position' ||
settings.url.match(/event$/) || 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 # do nothing
else else
throw "External request attempted for #{settings.url}, which is not defined." throw "External request attempted for #{settings.url}, which is not defined."
......
...@@ -13,6 +13,7 @@ describe 'Problem', -> ...@@ -13,6 +13,7 @@ describe 'Problem', ->
spyOn($.fn, 'load').andCallFake (url, callback) -> spyOn($.fn, 'load').andCallFake (url, callback) ->
$(@).html readFixtures('problem_content.html') $(@).html readFixtures('problem_content.html')
callback() callback()
jasmine.stubRequests()
describe 'constructor', -> describe 'constructor', ->
beforeEach -> beforeEach ->
...@@ -21,12 +22,6 @@ describe 'Problem', -> ...@@ -21,12 +22,6 @@ describe 'Problem', ->
it 'set the element', -> it 'set the element', ->
expect(@problem.element).toBe '#problem_1' 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', -> describe 'bind', ->
beforeEach -> beforeEach ->
spyOn window, 'update_schematics' spyOn window, 'update_schematics'
...@@ -57,8 +52,11 @@ describe 'Problem', -> ...@@ -57,8 +52,11 @@ describe 'Problem', ->
it 'bind the math input', -> it 'bind the math input', ->
expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath
it 'display the math input', -> it 'replace math content on the page', ->
expect(@stubbedJax.root.toMathML).toHaveBeenCalled() expect(MathJax.Hub.Queue.mostRecentCall.args).toEqual [
['Text', @stubbedJax, ''],
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
]
describe 'render', -> describe 'render', ->
beforeEach -> beforeEach ->
...@@ -77,12 +75,19 @@ describe 'Problem', -> ...@@ -77,12 +75,19 @@ describe 'Problem', ->
expect(@problem.bind).toHaveBeenCalled() expect(@problem.bind).toHaveBeenCalled()
describe 'with no content given', -> describe 'with no content given', ->
beforeEach ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
callback html: "Hello World"
@problem.render()
it 'load the content via ajax', -> 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', -> describe 'check', ->
beforeEach -> beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/' @problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2' @problem.answers = 'foo=1&bar=2'
...@@ -116,7 +121,6 @@ describe 'Problem', -> ...@@ -116,7 +121,6 @@ describe 'Problem', ->
describe 'reset', -> describe 'reset', ->
beforeEach -> beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/' @problem = new Problem 1, '/problem/url/'
it 'log the problem_reset event', -> it 'log the problem_reset event', ->
...@@ -130,13 +134,13 @@ describe 'Problem', -> ...@@ -130,13 +134,13 @@ describe 'Problem', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function) expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
it 'render the returned content', -> it 'render the returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback("Reset!") spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) ->
callback html: "Reset!"
@problem.reset() @problem.reset()
expect(@problem.element.html()).toEqual 'Reset!' expect(@problem.element.html()).toEqual 'Reset!'
describe 'show', -> describe 'show', ->
beforeEach -> beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/' @problem = new Problem 1, '/problem/url/'
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />' @problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
...@@ -154,18 +158,19 @@ describe 'Problem', -> ...@@ -154,18 +158,19 @@ describe 'Problem', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function) expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
it 'show the answers', -> 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() @problem.show()
expect($('#answer_1_1')).toHaveHtml 'One' expect($('#answer_1_1')).toHaveHtml 'One'
expect($('#answer_1_2')).toHaveHtml 'Two' expect($('#answer_1_2')).toHaveHtml 'Two'
it 'toggle the show answer button', -> it 'toggle the show answer button', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({}) spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
@problem.show() @problem.show()
expect($('.show')).toHaveValue 'Hide Answer' expect($('.show')).toHaveValue 'Hide Answer'
it 'add the showed class to element', -> it 'add the showed class to element', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({}) spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
@problem.show() @problem.show()
expect(@problem.element).toHaveClass 'showed' expect(@problem.element).toHaveClass 'showed'
...@@ -179,7 +184,8 @@ describe 'Problem', -> ...@@ -179,7 +184,8 @@ describe 'Problem', ->
''' '''
it 'set the correct_answer attribute on the choice', -> 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() @problem.show()
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true' expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true' expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
...@@ -214,7 +220,6 @@ describe 'Problem', -> ...@@ -214,7 +220,6 @@ describe 'Problem', ->
describe 'save', -> describe 'save', ->
beforeEach -> beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/' @problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2' @problem.answers = 'foo=1&bar=2'
...@@ -236,23 +241,29 @@ describe 'Problem', -> ...@@ -236,23 +241,29 @@ describe 'Problem', ->
describe 'refreshMath', -> describe 'refreshMath', ->
beforeEach -> beforeEach ->
@problem = new Problem 1, '/problem/url/' @problem = new Problem 1, '/problem/url/'
@stubbedJax.root.toMathML.andReturn '<MathML>'
$('#input_example_1').val 'E=mc^2' $('#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', -> describe 'when there is no exception', ->
beforeEach -> 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>' expect($('#input_example_1_dynamath')).toHaveValue '<MathML>'
describe 'when there is an exception', -> describe 'when there is an exception', ->
beforeEach -> beforeEach ->
@stubbedJax.root.toMathML.andThrow {restart: true} @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', -> it 'should queue up the exception', ->
expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true
......
...@@ -35,6 +35,9 @@ describe 'VideoCaption', -> ...@@ -35,6 +35,9 @@ describe 'VideoCaption', ->
it 'bind player resize event', -> it 'bind player resize event', ->
expect($(@player)).toHandleWith 'resize', @caption.onWindowResize expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
it 'bind player seek event', ->
expect($(@player)).toHandleWith 'seek', @caption.onUpdatePlayTime
it 'bind player updatePlayTime event', -> it 'bind player updatePlayTime event', ->
expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
......
class @Problem class @Problem
constructor: (@id, url) -> constructor: (@id, url) ->
@element = $("#problem_#{id}") @element = $("#problem_#{id}")
@content_url = "#{url}problem_get?id=#{@id}"
@render() @render()
$: (selector) -> $: (selector) ->
...@@ -17,7 +16,7 @@ class @Problem ...@@ -17,7 +16,7 @@ class @Problem
@$('section.action input.save').click @save @$('section.action input.save').click @save
@$('input.math').keyup(@refreshMath).each(@refreshMath) @$('input.math').keyup(@refreshMath).each(@refreshMath)
update_progress: (response) => updateProgress: (response) =>
if response.progress_changed if response.progress_changed
@element.attr progress: response.progress_status @element.attr progress: response.progress_status
@element.trigger('progressChanged') @element.trigger('progressChanged')
...@@ -27,10 +26,9 @@ class @Problem ...@@ -27,10 +26,9 @@ class @Problem
@element.html(content) @element.html(content)
@bind() @bind()
else else
$.postWithPrefix "/modx/problem/#{@id}/problem_get", '', (response) => $.postWithPrefix "/modx/problem/#{@id}/problem_get", (response) =>
@element.html(response.html) @element.html(response.html)
@bind() @bind()
check: => check: =>
Logger.log 'problem_check', @answers Logger.log 'problem_check', @answers
...@@ -38,7 +36,7 @@ class @Problem ...@@ -38,7 +36,7 @@ class @Problem
switch response.success switch response.success
when 'incorrect', 'correct' when 'incorrect', 'correct'
@render(response.contents) @render(response.contents)
@update_progress response @updateProgress response
else else
alert(response.success) alert(response.success)
...@@ -46,7 +44,7 @@ class @Problem ...@@ -46,7 +44,7 @@ class @Problem
Logger.log 'problem_reset', @answers Logger.log 'problem_reset', @answers
$.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) => $.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) =>
@render(response.html) @render(response.html)
@update_progress response @updateProgress response
show: => show: =>
if !@element.hasClass 'showed' if !@element.hasClass 'showed'
...@@ -62,7 +60,7 @@ class @Problem ...@@ -62,7 +60,7 @@ class @Problem
MathJax.Hub.Queue ["Typeset", MathJax.Hub] MathJax.Hub.Queue ["Typeset", MathJax.Hub]
@$('.show').val 'Hide Answer' @$('.show').val 'Hide Answer'
@element.addClass 'showed' @element.addClass 'showed'
@update_progress response @updateProgress response
else else
@$('[id^=answer_], [id^=solution_]').text '' @$('[id^=answer_], [id^=solution_]').text ''
@$('[correct_answer]').attr correct_answer: null @$('[correct_answer]').attr correct_answer: null
...@@ -74,21 +72,22 @@ class @Problem ...@@ -74,21 +72,22 @@ class @Problem
$.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) => $.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) =>
if response.success if response.success
alert 'Saved' alert 'Saved'
@update_progress response @updateProgress response
refreshMath: (event, element) => refreshMath: (event, element) =>
element = event.target unless element element = event.target unless element
target = "display_#{element.id.replace(/^input_/, '')}" target = "display_#{element.id.replace(/^input_/, '')}"
if jax = MathJax.Hub.getAllJax(target)[0] 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 updateMathML: (jax, element) =>
output = jax.root.toMathML '' try
$("##{element.id}_dynamath").val(output) $("##{element.id}_dynamath").val(jax.root.toMathML '')
catch exception catch exception
throw exception unless exception.restart throw exception unless exception.restart
MathJax.Callback.After [@refreshMath, jax], exception.restart MathJax.Callback.After [@refreshMath, jax], exception.restart
refreshAnswers: => refreshAnswers: =>
@$('input.schematic').each (index, element) -> @$('input.schematic').each (index, element) ->
......
...@@ -10,6 +10,7 @@ class @VideoCaption ...@@ -10,6 +10,7 @@ class @VideoCaption
$(window).bind('resize', @onWindowResize) $(window).bind('resize', @onWindowResize)
$(@player).bind('resize', @onWindowResize) $(@player).bind('resize', @onWindowResize)
$(@player).bind('updatePlayTime', @onUpdatePlayTime) $(@player).bind('updatePlayTime', @onUpdatePlayTime)
$(@player).bind('seek', @onUpdatePlayTime)
$(@player).bind('play', @onPlay) $(@player).bind('play', @onPlay)
@$('.hide-subtitles').click @toggle @$('.hide-subtitles').click @toggle
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave) @$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
......
...@@ -23,3 +23,7 @@ The dev server will automatically compile sass files that have changed. Simply s ...@@ -23,3 +23,7 @@ The dev server will automatically compile sass files that have changed. Simply s
the server using: the server using:
$ rake runserver $ 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 { ...@@ -62,6 +62,8 @@ div.book-wrapper {
@extend .clearfix; @extend .clearfix;
li { li {
background-color: darken($cream, 4%);
&.last { &.last {
display: block; display: block;
float: left; float: left;
...@@ -77,6 +79,10 @@ div.book-wrapper { ...@@ -77,6 +79,10 @@ div.book-wrapper {
display: block; display: block;
float: right; float: right;
} }
&:hover {
background: none;
}
} }
} }
......
...@@ -205,7 +205,7 @@ h1.top-header { ...@@ -205,7 +205,7 @@ h1.top-header {
border-top: 1px solid #fff; border-top: 1px solid #fff;
// @include box-shadow(inset 0 1px 0 #fff, inset 1px 0 0 #fff); // @include box-shadow(inset 0 1px 0 #fff, inset 1px 0 0 #fff);
font-size: 12px; font-size: 12px;
height:46px; // height:46px;
line-height: 46px; line-height: 46px;
margin: (-$body-line-height) (-$body-line-height) $body-line-height; margin: (-$body-line-height) (-$body-line-height) $body-line-height;
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
...@@ -224,7 +224,7 @@ h1.top-header { ...@@ -224,7 +224,7 @@ h1.top-header {
} }
&.block-link { &.block-link {
background: darken($cream, 5%); // background: darken($cream, 5%);
border-left: 1px solid darken($cream, 20%); border-left: 1px solid darken($cream, 20%);
@include box-shadow(inset 1px 0 0 lighten($cream, 5%)); @include box-shadow(inset 1px 0 0 lighten($cream, 5%));
display: block; display: block;
......
...@@ -11,13 +11,14 @@ nav.sequence-nav { ...@@ -11,13 +11,14 @@ nav.sequence-nav {
height: 100%; height: 100%;
padding-right: flex-grid(1, 9); padding-right: flex-grid(1, 9);
width: 100%; width: 100%;
padding-left: 0;
a { a {
@extend .block-link; @extend .block-link;
} }
li { li {
border-left: 1px solid darken($cream, 20%); border-left: 1px solid darken($cream, 10%);
display: table-cell; display: table-cell;
min-width: 20px; min-width: 20px;
...@@ -29,18 +30,17 @@ nav.sequence-nav { ...@@ -29,18 +30,17 @@ nav.sequence-nav {
background-repeat: no-repeat; background-repeat: no-repeat;
&:hover { &:hover {
background-color: lighten($cream, 3%); background-color: lighten($cream, 8%);
} }
} }
.visited { .visited {
background-color: #DCCDA2; background-color: darken($cream, 4%);
background-repeat: no-repeat; background-repeat: no-repeat;
@include box-shadow(inset 0 0 3px darken(#dccda2, 10%)); @include box-shadow(0);
&:hover { &:hover {
background-color: $cream; background-color: $cream;
background-position: center center;
} }
} }
...@@ -51,7 +51,6 @@ nav.sequence-nav { ...@@ -51,7 +51,6 @@ nav.sequence-nav {
&:hover { &:hover {
background-color: #fff; background-color: #fff;
background-position: center;
} }
} }
...@@ -61,86 +60,87 @@ nav.sequence-nav { ...@@ -61,86 +60,87 @@ nav.sequence-nav {
cursor: pointer; cursor: pointer;
display: block; display: block;
height: 17px; height: 17px;
padding: 15px 0 14px; padding: 15px 0 17px;
position: relative; position: relative;
@include transition(all, .4s, $ease-in-out-quad); @include transition(all, .4s, $ease-in-out-quad);
width: 100%; width: 100%;
background-position: center 8px;
&.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;
}
//video //video
&.seq_video_inactive { &.seq_video_inactive {
@extend .inactive; @extend .inactive;
background-image: url('../images/sequence-nav/video-icon-normal.png'); background-image: url('../images/sequence-nav/video-icon-visited.png');
background-position: center;
} }
&.seq_video_visited { &.seq_video_visited {
@extend .visited; @extend .visited;
background-image: url('../images/sequence-nav/video-icon-visited.png'); background-image: url('../images/sequence-nav/video-icon-normal.png');
background-position: center;
} }
&.seq_video_active { &.seq_video_active {
@extend .active; @extend .active;
background-image: url('../images/sequence-nav/video-icon-current.png'); background-image: url('../images/sequence-nav/video-icon-current.png');
background-position: center;
} }
//other //other
&.seq_other_inactive { &.seq_other_inactive {
@extend .inactive; @extend .inactive;
background-image: url('../images/sequence-nav/document-icon-normal.png'); background-image: url('../images/sequence-nav/document-icon-visited.png');
background-position: center;
} }
&.seq_other_visited { &.seq_other_visited {
@extend .visited; @extend .visited;
background-image: url('../images/sequence-nav/document-icon-visited.png'); background-image: url('../images/sequence-nav/document-icon-normal.png');
background-position: center;
} }
&.seq_other_active { &.seq_other_active {
@extend .active; @extend .active;
background-image: url('../images/sequence-nav/document-icon-current.png'); background-image: url('../images/sequence-nav/document-icon-current.png');
background-position: center;
} }
//vertical & problems //vertical & problems
&.seq_vertical_inactive, &.seq_problem_inactive { &.seq_vertical_inactive, &.seq_problem_inactive {
@extend .inactive; @extend .inactive;
background-image: url('../images/sequence-nav/list-icon-normal.png'); background-image: url('../images/sequence-nav/list-icon-visited.png');
background-position: center;
} }
&.seq_vertical_visited, &.seq_problem_visited { &.seq_vertical_visited, &.seq_problem_visited {
@extend .visited; @extend .visited;
background-image: url('../images/sequence-nav/list-icon-visited.png'); background-image: url('../images/sequence-nav/list-icon-normal.png');
background-position: center;
} }
&.seq_vertical_active, &.seq_problem_active { &.seq_vertical_active, &.seq_problem_active {
@extend .active; @extend .active;
background-image: url('../images/sequence-nav/list-icon-current.png'); 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 { p {
...@@ -180,6 +180,8 @@ nav.sequence-nav { ...@@ -180,6 +180,8 @@ nav.sequence-nav {
} }
&:hover { &:hover {
background-position: center 8px;
p { p {
display: block; display: block;
margin-top: 4px; margin-top: 4px;
...@@ -215,6 +217,7 @@ nav.sequence-nav { ...@@ -215,6 +217,7 @@ nav.sequence-nav {
display: block; display: block;
text-indent: -9999px; text-indent: -9999px;
@include transition(all, .2s, $ease-in-out-quad); @include transition(all, .2s, $ease-in-out-quad);
line-height: 49px;
&:hover { &:hover {
opacity: .5; opacity: .5;
...@@ -277,6 +280,7 @@ section.course-content { ...@@ -277,6 +280,7 @@ section.course-content {
@include border-radius(3px); @include border-radius(3px);
@include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%)); @include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%));
@include inline-block(); @include inline-block();
padding-left: 0;
li { li {
float: left; float: left;
......
...@@ -52,14 +52,14 @@ section.course-index { ...@@ -52,14 +52,14 @@ section.course-index {
padding: 1em 1.5em; padding: 1em 1.5em;
li { li {
background: transparent;
border: 1px solid transparent;
@include border-radius(4px);
margin-bottom: lh(.5); margin-bottom: lh(.5);
position: relative;
padding: 5px 36px 5px 10px;
a { a {
border: 1px solid transparent;
background: transparent;
@include border-radius(4px);
position: relative;
padding: 5px 36px 5px 10px;
text-decoration: none; text-decoration: none;
display: block; display: block;
color: #666; color: #666;
...@@ -74,67 +74,70 @@ section.course-index { ...@@ -74,67 +74,70 @@ section.course-index {
display: block; 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 { &:after {
opacity: 1; background: transparent;
right: 15px; border-top: 1px solid rgb(180,180,180);
@include transition(all, 0.2s, linear); 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 { &:hover {
color: #333; @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 { &:active {
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1)); @include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
top: 1px;
&:after { &:after {
opacity: 1; opacity: 1;
right: 15px; right: 15px;
}
} }
} }
&.active { &.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; font-weight: bold;
> a p { > a {
color: #333; 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 { span.subtitle {
font-weight: normal; font-weight: normal;
} }
&:after {
opacity: 1;
right: 15px;
}
} }
} }
} }
......
...@@ -58,6 +58,11 @@ task :pylint => REPORT_DIR do ...@@ -58,6 +58,11 @@ task :pylint => REPORT_DIR do
end end
end end
default_options = {
:lms => '8000',
:cms => '8001',
}
[:lms, :cms].each do |system| [:lms, :cms].each do |system|
task_name = "test_#{system}" task_name = "test_#{system}"
report_dir = File.join(REPORT_DIR, task_name) report_dir = File.join(REPORT_DIR, task_name)
...@@ -76,7 +81,7 @@ end ...@@ -76,7 +81,7 @@ end
Other useful environments are devplus (for dev testing with a real local database) Other useful environments are devplus (for dev testing with a real local database)
desc desc
task system, [:env, :options] => [] do |t, args| 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)) sh(django_admin(system, args.env, 'runserver', args.options))
end end
end end
...@@ -154,3 +159,15 @@ end ...@@ -154,3 +159,15 @@ end
task :publish => :package do task :publish => :package do
sh("scp #{BUILD_DIR}/#{NORMALIZED_DEPLOY_NAME}_#{PKG_VERSION}*.deb #{PACKAGE_REPO}") sh("scp #{BUILD_DIR}/#{NORMALIZED_DEPLOY_NAME}_#{PKG_VERSION}*.deb #{PACKAGE_REPO}")
end 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment