From 78b0e86334a69c1699d785893e6705152b744f3e Mon Sep 17 00:00:00 2001 From: Galen Frechette Date: Wed, 20 Jun 2012 18:37:07 -0400 Subject: [PATCH 001/123] cleans up login modal and adds js for toggling login modal --- lms/static/js/toggle_login_modal.js | 8 ++ lms/static/sass/_shared_login_modal.scss | 124 ++++++++-------- lms/static/sass/application.css | 172 +++++++++++------------ lms/templates/login_modal.html | 10 +- lms/templates/main.html | 1 + lms/templates/navigation.html | 2 +- 6 files changed, 154 insertions(+), 163 deletions(-) create mode 100644 lms/static/js/toggle_login_modal.js diff --git a/lms/static/js/toggle_login_modal.js b/lms/static/js/toggle_login_modal.js new file mode 100644 index 0000000000..f1e53ea14f --- /dev/null +++ b/lms/static/js/toggle_login_modal.js @@ -0,0 +1,8 @@ +$(document).ready(function () { + $('a.login').click(function() { + $('.modal-wrapper').addClass("visible"); + }); + $('div.close-modal').click(function() { + $('.modal-wrapper').removeClass("visible"); + }); +}); diff --git a/lms/static/sass/_shared_login_modal.scss b/lms/static/sass/_shared_login_modal.scss index 9155464a57..b2dbb3994e 100644 --- a/lms/static/sass/_shared_login_modal.scss +++ b/lms/static/sass/_shared_login_modal.scss @@ -2,7 +2,7 @@ @include background-image(radial-gradient(50% 50%, circle cover, rgba(0,0,0, 0.3), rgba(0,0,0, 0.7))); bottom: 0; content: ""; - //display: none; + display: none; left: 0; position: fixed; right: 0; @@ -14,27 +14,43 @@ } .login-modal { - background: rgba(200, 200, 200, 1); + background: rgba(0,0,0, 0.6); + border: 1px solid rgba(0, 0, 0, 0.9); @include border-radius(4px); - @include box-shadow(0 5px 35px 0px rgba(0,0,0, 0.2)); + @include box-shadow(0 10px 60px 10px rgba(0,0,0, 0.5)); color: #fff; - padding: 6px; - width: grid-width(8); - @include vertically-and-horizontally-centered(430px, grid-width(8)); + padding: 10px; + width: grid-width(7); + @include vertically-and-horizontally-centered(400px, grid-width(7)); z-index: 10; .inner-wrapper { - background: rgba(235, 235, 235, 1); - border: 1px solid rgba(180, 180, 180, 1); + @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); @include border-radius(4px); - @include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.6), 0 1px 0 0 rgba(255, 255, 255, 0.3)); - height: 410px; + border: 1px solid rgba(0, 0, 0, 0.9); + @include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.7)); + height: 380px; + overflow: hidden; padding: 30px; position: relative; + &::before { + @include background-image(radial-gradient(50% 50%, circle closest-side, rgba(255,255,255, 1) 0%, rgba(255,255,255, 0) 100%)); + content: ""; + display: block; + height: 400px; + left: 0px; + margin: 0 auto; + position: absolute; + bottom: -140px; + width: 100%; + } + header { margin-bottom: 30px; + padding-top: 5px; position: relative; + z-index: 2; &::after { @extend .faded-hr-divider; @@ -45,27 +61,21 @@ } h3 { - color: $base-font-color; - font: normal 2.2rem/2.4rem $sans-serif; + color: $lighter-base-font-color; + font: normal 1.4rem/1.8rem $serif; padding-bottom: 20px; text-align: center; text-shadow: 0 1px rgba(255,255,255, 0.4); text-transform: uppercase; vertical-align: middle; - - img { - @include inline-block; - display: none; - height: 60px; - margin-right: 10px; - vertical-align: middle; - } } } form { @include clearfix; + position: relative; + z-index: 2; label { display: none; @@ -75,9 +85,19 @@ background: rgb(230,230,230); border: 1px solid rgb(200,200,200); @include border-radius(3px); + @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); display: block; - margin-bottom: 40px; - padding: 6px 10px; + margin-bottom: 60px; + padding: 8px 10px; + position: relative; + + &::before { + @extend .faded-hr-divider; + bottom: -30px; + content: ""; + display: block; + position: absolute; + } } input[type="checkbox"] { @@ -96,34 +116,18 @@ display: block; height: 45px; margin: 0 auto; - width: 50%; + width: 70%; } } .login-extra { - margin-top: 40px; - padding: 20px 0px; + padding: 15px 0px; position: relative; - @include transition(opacity 0.15s linear); - &:hover { - p { - opacity: 1; - } - } - - &::before { - @extend .faded-hr-divider; - content: ""; - display: block; - position: absolute; - top: 0px; - } p { color: $lighter-base-font-color; font: normal italic 1.2rem/1.6rem $serif; - opacity: 0.7; text-align: center; a { @@ -133,50 +137,40 @@ } span + a { - border-left: 1px solid rgb(200,200,200); - margin-left: 10px; - padding-left: 10px; + margin-left: 15px; } } } .close-modal { - background: rgba(200,200,200, 0.7); - border: 1px solid rgba(180,180,180, 0.7); - @include border-radius(50px); + //background: rgb(240,240,240); + //border: 1px solid rgba(180,180,180, 0.7); + @include border-radius(2px); + //@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.4), inset 0 0 2px 0 rgba(0,0,0, 0.1)); + cursor: pointer; @include inline-block; - padding: 4px; + padding: 5px 10px; position: absolute; - right: -16px; - top: -16px; + right: 5px; + top: 5px; @include transition(all, 0.15s, ease-out); .inner { - background: rgb(255,255,255); - border: 1px solid rgb(180,180,180); - @include border-radius(50px); - @include box-shadow(0 1px 0 0 rgba(255, 255, 255, 0.3)); - height: 26px; - width: 26px; - p { - color: rgb(210,210,210); - font: normal 15px/17px $sans-serif; - padding-top: 4px; + color: $lighter-base-font-color; + font: normal 1.2rem/1.2rem $sans-serif; text-align: center; - text-shadow: 0 -1px rgba(0,0,0, 0.3); + text-shadow: 0 1px rgba(255,255,255, 0.8); @include transition(all, 0.15s, ease-out); } } &:hover { - background: rgba(200,200,200, 1); - border: 1px solid rgba(180,180,180, 1); - cursor: pointer; - @include box-shadow(0 1px 3px 0 rgba(0,0,0, 0.15)); + //background: rgb(235,235,235); + //@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.4), inset 0 0 2px 0 rgba(0,0,0, 0.125)); p { - color: lighten(red, 15%); + color: $base-font-color; } } } diff --git a/lms/static/sass/application.css b/lms/static/sass/application.css index 26f4300492..0c86acd807 100755 --- a/lms/static/sass/application.css +++ b/lms/static/sass/application.css @@ -340,7 +340,7 @@ a:link, a:visited { .container:after { clear: both; } -.faded-hr-divider, .modal-wrapper .login-modal .inner-wrapper header::after, .modal-wrapper .login-modal .inner-wrapper .login-extra::before, .home .university-partners:before, .home .university-partners:after { +.faded-hr-divider, .modal-wrapper .login-modal .inner-wrapper header::after, .modal-wrapper .login-modal .inner-wrapper form label.remember-me::before, .home .university-partners:before, .home .university-partners:after { background-image: -webkit-linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(200, 200, 200, 0.8) 15%, #c8c8c8 50%, rgba(200, 200, 200, 0.8) 85%, rgba(255, 255, 255, 0)); background-image: -moz-linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(200, 200, 200, 0.8) 15%, #c8c8c8 50%, rgba(200, 200, 200, 0.8) 85%, rgba(255, 255, 255, 0)); background-image: -ms-linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(200, 200, 200, 0.8) 15%, #c8c8c8 50%, rgba(200, 200, 200, 0.8) 85%, rgba(255, 255, 255, 0)); @@ -1637,6 +1637,7 @@ header.app { background-image: radial-gradient(50% 50%, circle cover, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)); bottom: 0; content: ""; + display: none; left: 0; position: fixed; right: 0; @@ -1645,64 +1646,78 @@ header.app { .modal-wrapper.visible { display: block; } .modal-wrapper .login-modal { - background: #c8c8c8; + background: rgba(0, 0, 0, 0.6); + border: 1px solid rgba(0, 0, 0, 0.9); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; - -webkit-box-shadow: 0 5px 35px 0px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 35px 0px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 35px 0px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 10px 60px 10px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 10px 60px 10px rgba(0, 0, 0, 0.5); + box-shadow: 0 10px 60px 10px rgba(0, 0, 0, 0.5); color: #fff; - padding: 6px; - width: 655px; + padding: 10px; + width: 570px; left: 50%; - margin-left: -327.5px; - margin-top: -215px; - min-height: 430px; - min-width: 655px; + margin-left: -285px; + margin-top: -200px; + min-height: 400px; + min-width: 570px; position: fixed; top: 35%; z-index: 10; } .modal-wrapper .login-modal .inner-wrapper { - background: #ebebeb; - border: 1px solid #b4b4b4; + background-image: -webkit-linear-gradient(-90deg, #f5f5f5, #e1e1e1); + background-image: -moz-linear-gradient(-90deg, #f5f5f5, #e1e1e1); + background-image: -ms-linear-gradient(-90deg, #f5f5f5, #e1e1e1); + background-image: -o-linear-gradient(-90deg, #f5f5f5, #e1e1e1); + background-image: linear-gradient(-90deg, #f5f5f5, #e1e1e1); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.6), 0 1px 0 0 rgba(255, 255, 255, 0.3); - -moz-box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.6), 0 1px 0 0 rgba(255, 255, 255, 0.3); - box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.6), 0 1px 0 0 rgba(255, 255, 255, 0.3); - height: 410px; + border: 1px solid rgba(0, 0, 0, 0.9); + -webkit-box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.7); + -moz-box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.7); + box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.7); + height: 380px; + overflow: hidden; padding: 30px; position: relative; } + .modal-wrapper .login-modal .inner-wrapper::before { + background-image: -webkit-radial-gradient(50% 50%, circle closest-side, white 0%, rgba(255, 255, 255, 0) 100%); + background-image: -moz-radial-gradient(50% 50%, circle closest-side, white 0%, rgba(255, 255, 255, 0) 100%); + background-image: -ms-radial-gradient(50% 50%, circle closest-side, white 0%, rgba(255, 255, 255, 0) 100%); + background-image: -o-radial-gradient(50% 50%, circle closest-side, white 0%, rgba(255, 255, 255, 0) 100%); + background-image: radial-gradient(50% 50%, circle closest-side, white 0%, rgba(255, 255, 255, 0) 100%); + content: ""; + display: block; + height: 400px; + left: 0px; + margin: 0 auto; + position: absolute; + bottom: -140px; + width: 100%; } .modal-wrapper .login-modal .inner-wrapper header { margin-bottom: 30px; - position: relative; } + padding-top: 5px; + position: relative; + z-index: 2; } .modal-wrapper .login-modal .inner-wrapper header::after { bottom: 0px; content: ""; display: block; position: absolute; } .modal-wrapper .login-modal .inner-wrapper header h3 { - color: #3c3c3c; - font: normal 2.2rem/2.4rem "Open Sans", Verdana, Geneva, sans-serif; + color: #a0a0a0; + font: normal 1.4rem/1.8rem Georgia, Cambria, "Times New Roman", Times, serif; padding-bottom: 20px; text-align: center; text-shadow: 0 1px rgba(255, 255, 255, 0.4); text-transform: uppercase; vertical-align: middle; } - .modal-wrapper .login-modal .inner-wrapper header h3 img { - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - display: none; - height: 60px; - margin-right: 10px; - vertical-align: middle; } .modal-wrapper .login-modal .inner-wrapper form { - zoom: 1; } + zoom: 1; + position: relative; + z-index: 2; } .modal-wrapper .login-modal .inner-wrapper form:before, .modal-wrapper .login-modal .inner-wrapper form:after { content: ""; display: table; } @@ -1716,9 +1731,18 @@ header.app { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; + -webkit-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6); + -moz-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6); + box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.6); display: block; - margin-bottom: 40px; - padding: 6px 10px; } + margin-bottom: 60px; + padding: 8px 10px; + position: relative; } + .modal-wrapper .login-modal .inner-wrapper form label.remember-me::before { + bottom: -30px; + content: ""; + display: block; + position: absolute; } .modal-wrapper .login-modal .inner-wrapper form input[type="checkbox"] { margin-right: 5px; } .modal-wrapper .login-modal .inner-wrapper form input[type="email"], @@ -1731,87 +1755,51 @@ header.app { display: block; height: 45px; margin: 0 auto; - width: 50%; } + width: 70%; } .modal-wrapper .login-modal .inner-wrapper .login-extra { - margin-top: 40px; - padding: 20px 0px; - position: relative; - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -ms-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; } - .modal-wrapper .login-modal .inner-wrapper .login-extra:hover p { - opacity: 1; } - .modal-wrapper .login-modal .inner-wrapper .login-extra::before { - content: ""; - display: block; - position: absolute; - top: 0px; } + padding: 15px 0px; + position: relative; } .modal-wrapper .login-modal .inner-wrapper .login-extra p { color: #a0a0a0; font: normal italic 1.2rem/1.6rem Georgia, Cambria, "Times New Roman", Times, serif; - opacity: 0.7; text-align: center; } .modal-wrapper .login-modal .inner-wrapper .login-extra p a { color: #a0a0a0; font: normal italic 1.2rem/1.6rem Georgia, Cambria, "Times New Roman", Times, serif; text-decoration: underline; } .modal-wrapper .login-modal .inner-wrapper .login-extra p span + a { - border-left: 1px solid #c8c8c8; - margin-left: 10px; - padding-left: 10px; } + margin-left: 15px; } .modal-wrapper .login-modal .inner-wrapper .close-modal { - background: rgba(200, 200, 200, 0.7); - border: 1px solid rgba(180, 180, 180, 0.7); - -webkit-border-radius: 50px; - -moz-border-radius: 50px; - border-radius: 50px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + cursor: pointer; display: inline-block; vertical-align: baseline; zoom: 1; *display: inline; *vertical-align: auto; - padding: 4px; + padding: 5px 10px; position: absolute; - right: -16px; - top: -16px; + right: 5px; + top: 5px; -webkit-transition: all, 0.15s, ease-out; -moz-transition: all, 0.15s, ease-out; -ms-transition: all, 0.15s, ease-out; -o-transition: all, 0.15s, ease-out; transition: all, 0.15s, ease-out; } - .modal-wrapper .login-modal .inner-wrapper .close-modal .inner { - background: white; - border: 1px solid #b4b4b4; - -webkit-border-radius: 50px; - -moz-border-radius: 50px; - border-radius: 50px; - -webkit-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.3); - -moz-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.3); - box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.3); - height: 26px; - width: 26px; } - .modal-wrapper .login-modal .inner-wrapper .close-modal .inner p { - color: #d2d2d2; - font: normal 15px/17px "Open Sans", Verdana, Geneva, sans-serif; - padding-top: 4px; - text-align: center; - text-shadow: 0 -1px rgba(0, 0, 0, 0.3); - -webkit-transition: all, 0.15s, ease-out; - -moz-transition: all, 0.15s, ease-out; - -ms-transition: all, 0.15s, ease-out; - -o-transition: all, 0.15s, ease-out; - transition: all, 0.15s, ease-out; } - .modal-wrapper .login-modal .inner-wrapper .close-modal:hover { - background: #c8c8c8; - border: 1px solid #b4b4b4; - cursor: pointer; - -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15); - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15); } - .modal-wrapper .login-modal .inner-wrapper .close-modal:hover p { - color: #ff4d4d; } + .modal-wrapper .login-modal .inner-wrapper .close-modal .inner p { + color: #a0a0a0; + font: normal 1.2rem/1.2rem "Open Sans", Verdana, Geneva, sans-serif; + text-align: center; + text-shadow: 0 1px rgba(255, 255, 255, 0.8); + -webkit-transition: all, 0.15s, ease-out; + -moz-transition: all, 0.15s, ease-out; + -ms-transition: all, 0.15s, ease-out; + -o-transition: all, 0.15s, ease-out; + transition: all, 0.15s, ease-out; } + .modal-wrapper .login-modal .inner-wrapper .close-modal:hover p { + color: #3c3c3c; } .home { margin: 50px 10px 100px; } diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 3bb9801435..7d6b173a0e 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -4,7 +4,7 @@
-

Log In

+

Log In

@@ -16,19 +16,19 @@ Remember me - +
-

+

diff --git a/lms/templates/main.html b/lms/templates/main.html index b5c80bea09..d611a73ddd 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -68,6 +68,7 @@ <%block name="js_extra"/> + diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 5d96fb9bed..a609167558 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -39,7 +39,7 @@ Jobs
  • - Log In +
  • Sign Up From 6297e1df233ca502618b9ce7e1a0b507b97ff3e0 Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 09:51:03 -0400 Subject: [PATCH 002/123] wip modal --- lms/static/coffee/src/modal.coffee | 6 ++++++ lms/static/js/sticky_filter.js | 20 +++++++++++--------- lms/templates/login_modal.html | 3 +++ 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 lms/static/coffee/src/modal.coffee diff --git a/lms/static/coffee/src/modal.coffee b/lms/static/coffee/src/modal.coffee new file mode 100644 index 0000000000..3e77b43b78 --- /dev/null +++ b/lms/static/coffee/src/modal.coffee @@ -0,0 +1,6 @@ +class @Modal + initialize: (options) -> + @el = options['el'] + render: -> + @el.hide() + diff --git a/lms/static/js/sticky_filter.js b/lms/static/js/sticky_filter.js index 742d147782..895f024eb2 100644 --- a/lms/static/js/sticky_filter.js +++ b/lms/static/js/sticky_filter.js @@ -1,12 +1,14 @@ $(function() { - var offset = $('.filter nav').offset().top; + if ($('.filter nav').length > 0) { + var offset = $('.filter nav').offset().top; - $(window).scroll(function() { - if (offset <= window.pageYOffset) { - return $('.filter nav').addClass('fixed-top'); - } - else if (offset >= window.pageYOffset) { - return $('.filter nav').removeClass('fixed-top'); - } - }); + $(window).scroll(function() { + if (offset <= window.pageYOffset) { + return $('.filter nav').addClass('fixed-top'); + } + else if (offset >= window.pageYOffset) { + return $('.filter nav').removeClass('fixed-top'); + } + }); + } }); diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 7d6b173a0e..adfe509d73 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -34,3 +34,6 @@
  • + From d757054460e5d106070315b24f0080c1d1d491b3 Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 10:38:33 -0400 Subject: [PATCH 003/123] Modal wip --- lms/templates/index.html | 3 --- lms/templates/navigation.html | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lms/templates/index.html b/lms/templates/index.html index 3ed8f1326c..07967c8668 100644 --- a/lms/templates/index.html +++ b/lms/templates/index.html @@ -53,8 +53,5 @@
    - - <%include file="login_modal.html" /> - diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index a609167558..e249464751 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -49,3 +49,7 @@ %endif + +%if not user.is_authenticated(): + <%include file="login_modal.html" /> +%endif From dc5cf5f319da929605f88ded1e0b05d2783ad7cf Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 10:43:38 -0400 Subject: [PATCH 004/123] Point login form to login. --- lms/templates/login_modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index adfe509d73..9ac281f1aa 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -7,7 +7,7 @@

    Log In

    -
    + From 11e19d126074d2a43b703a78ebb085479d3106e8 Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 10:50:55 -0400 Subject: [PATCH 005/123] wip --- lms/templates/login_modal.html | 51 ++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 9ac281f1aa..7647962897 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -7,7 +7,7 @@

    Log In

    - + @@ -35,5 +35,52 @@ From 3606ddbfdb51090c04fa623d2e00210535e1ad1d Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 10:59:13 -0400 Subject: [PATCH 006/123] Got basic login working --- lms/templates/login_modal.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 7647962897..756f86869f 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -61,13 +61,8 @@ function postJSON(url, data, callback) { }); } $('form#login_form').submit(function(e) { - console.log("WOMBAT") e.preventDefault(); - var submit_data={}; - $.each($("[id^=li_]"), function(index,value){ - submit_data[value.name]=value.value; - }); - submit_data["remember"] = ($('#remember').attr("checked")? true : false); + var submit_data = $('#login_form').serialize(); postJSON('/login', submit_data, From e845221f4aa51f536491b8bb40843a533dff042d Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 8 Jun 2012 11:27:22 -0400 Subject: [PATCH 007/123] Make mitxmako available to both the lms and the cms --- {lms => common}/lib/mitxmako/README | 0 {lms => common}/lib/mitxmako/__init__.py | 0 {lms => common}/lib/mitxmako/middleware.py | 10 +++++----- {lms => common}/lib/mitxmako/shortcuts.py | 10 +++------- {lms => common}/lib/mitxmako/template.py | 9 +++++---- 5 files changed, 13 insertions(+), 16 deletions(-) rename {lms => common}/lib/mitxmako/README (100%) rename {lms => common}/lib/mitxmako/__init__.py (100%) rename {lms => common}/lib/mitxmako/middleware.py (88%) rename {lms => common}/lib/mitxmako/shortcuts.py (84%) rename {lms => common}/lib/mitxmako/template.py (87%) diff --git a/lms/lib/mitxmako/README b/common/lib/mitxmako/README similarity index 100% rename from lms/lib/mitxmako/README rename to common/lib/mitxmako/README diff --git a/lms/lib/mitxmako/__init__.py b/common/lib/mitxmako/__init__.py similarity index 100% rename from lms/lib/mitxmako/__init__.py rename to common/lib/mitxmako/__init__.py diff --git a/lms/lib/mitxmako/middleware.py b/common/lib/mitxmako/middleware.py similarity index 88% rename from lms/lib/mitxmako/middleware.py rename to common/lib/mitxmako/middleware.py index 1d175abbf7..50f2840a05 100644 --- a/lms/lib/mitxmako/middleware.py +++ b/common/lib/mitxmako/middleware.py @@ -20,10 +20,10 @@ from django.conf import settings requestcontext = None lookup = {} + class MakoMiddleware(object): def __init__(self): """Setup mako variables and lookup object""" - from django.conf import settings # Set all mako variables based on django settings template_locations = settings.MAKO_TEMPLATES module_directory = getattr(settings, 'MAKO_MODULE_DIR', None) @@ -32,17 +32,17 @@ class MakoMiddleware(object): module_directory = tempfile.mkdtemp() for location in template_locations: - lookup[location] = TemplateLookup(directories=template_locations[location], + lookup[location] = TemplateLookup(directories=template_locations[location], module_directory=module_directory, - output_encoding='utf-8', - input_encoding='utf-8', + output_encoding='utf-8', + input_encoding='utf-8', encoding_errors='replace', ) import mitxmako mitxmako.lookup = lookup - def process_request (self, request): + def process_request(self, request): global requestcontext requestcontext = RequestContext(request) requestcontext['is_secure'] = request.is_secure() diff --git a/lms/lib/mitxmako/shortcuts.py b/common/lib/mitxmako/shortcuts.py similarity index 84% rename from lms/lib/mitxmako/shortcuts.py rename to common/lib/mitxmako/shortcuts.py index 7286a4e259..9f6044b81e 100644 --- a/lms/lib/mitxmako/shortcuts.py +++ b/common/lib/mitxmako/shortcuts.py @@ -15,10 +15,9 @@ from django.template import Context from django.http import HttpResponse -import mitxmako.middleware as middleware +from . import middleware from django.conf import settings -import mitxmako.middleware def render_to_string(template_name, dictionary, context=None, namespace='main'): context_instance = Context(dictionary) @@ -28,15 +27,12 @@ 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 mitxmako.middleware.requestcontext: + for d in middleware.requestcontext: context_dictionary.update(d) for d in context_instance: context_dictionary.update(d) - if context: + if context: context_dictionary.update(context) - ## HACK - ## We should remove this, and possible set COURSE_TITLE in the middleware from the session. - if 'COURSE_TITLE' not in context_dictionary: context_dictionary['COURSE_TITLE'] = '' # fetch and render template template = middleware.lookup[namespace].get_template(template_name) return template.render(**context_dictionary) diff --git a/lms/lib/mitxmako/template.py b/common/lib/mitxmako/template.py similarity index 87% rename from lms/lib/mitxmako/template.py rename to common/lib/mitxmako/template.py index 9e5897ef25..911f5a5b28 100644 --- a/lms/lib/mitxmako/template.py +++ b/common/lib/mitxmako/template.py @@ -14,10 +14,11 @@ from mako.template import Template as MakoTemplate -import middleware +from . import middleware + +django_variables = ['lookup', 'template_dirs', 'output_encoding', + 'module_directory', 'encoding_errors'] -django_variables = ['lookup', 'template_dirs', 'output_encoding', - 'module_directory', 'encoding_errors',] class Template(MakoTemplate): def __init__(self, *args, **kwargs): @@ -25,4 +26,4 @@ class Template(MakoTemplate): if not kwargs.get('no_django', False): overrides = dict([(k, getattr(middleware, k, None),) for k in django_variables]) kwargs.update(overrides) - super(Template, self).__init__(*args, **kwargs) + super(Template, self).__init__(*args, **kwargs) From 5ff05f7a955ca80f563ea07721048f4c3a39a2b2 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 8 Jun 2012 14:01:41 -0400 Subject: [PATCH 008/123] Get the cms up to the point of rendering a template --- {lms/djangoapps/track => cms}/__init__.py | 0 .../templatetags => cms/envs}/__init__.py | 0 cms/envs/common.py | 151 ++++++++++++++++++ cms/envs/dev.py | 37 +++++ cms/manage.py | 14 ++ lms/lib/util/__init__.py => cms/models.py | 0 cms/templates/calendar.html | 3 + cms/urls.py | 9 ++ cms/views.py | 5 + common/djangoapps/track/__init__.py | 0 .../djangoapps/track/middleware.py | 0 {lms => common}/djangoapps/track/models.py | 0 {lms => common}/djangoapps/track/tests.py | 0 {lms => common}/djangoapps/track/views.py | 0 {lms => common}/lib/cache_toolbox/COPYING | 0 {lms => common}/lib/cache_toolbox/README.rst | 0 {lms => common}/lib/cache_toolbox/__init__.py | 0 .../lib/cache_toolbox/app_settings.py | 0 {lms => common}/lib/cache_toolbox/core.py | 0 .../lib/cache_toolbox/middleware.py | 0 {lms => common}/lib/cache_toolbox/model.py | 0 {lms => common}/lib/cache_toolbox/relation.py | 0 .../cache_toolbox/templatetags/__init__.py | 0 .../templatetags/cache_toolbox.py | 0 common/lib/monitoring/__init__.py | 0 common/lib/monitoring/exceptions.py | 10 ++ common/lib/util/__init__.py | 0 {lms => common}/lib/util/cache.py | 0 {lms => common}/lib/util/memcache.py | 0 {lms => common}/lib/util/middleware.py | 0 {lms => common}/lib/util/models.py | 0 {lms => common}/lib/util/tests.py | 0 {lms => common}/lib/util/views.py | 0 lms/envs/common.py | 1 + rakefile | 20 +-- 35 files changed, 240 insertions(+), 10 deletions(-) rename {lms/djangoapps/track => cms}/__init__.py (100%) rename {lms/lib/cache_toolbox/templatetags => cms/envs}/__init__.py (100%) create mode 100644 cms/envs/common.py create mode 100644 cms/envs/dev.py create mode 100644 cms/manage.py rename lms/lib/util/__init__.py => cms/models.py (100%) create mode 100644 cms/templates/calendar.html create mode 100644 cms/urls.py create mode 100644 cms/views.py create mode 100644 common/djangoapps/track/__init__.py rename {lms => common}/djangoapps/track/middleware.py (100%) rename {lms => common}/djangoapps/track/models.py (100%) rename {lms => common}/djangoapps/track/tests.py (100%) rename {lms => common}/djangoapps/track/views.py (100%) rename {lms => common}/lib/cache_toolbox/COPYING (100%) rename {lms => common}/lib/cache_toolbox/README.rst (100%) rename {lms => common}/lib/cache_toolbox/__init__.py (100%) rename {lms => common}/lib/cache_toolbox/app_settings.py (100%) rename {lms => common}/lib/cache_toolbox/core.py (100%) rename {lms => common}/lib/cache_toolbox/middleware.py (100%) rename {lms => common}/lib/cache_toolbox/model.py (100%) rename {lms => common}/lib/cache_toolbox/relation.py (100%) create mode 100644 common/lib/cache_toolbox/templatetags/__init__.py rename {lms => common}/lib/cache_toolbox/templatetags/cache_toolbox.py (100%) create mode 100644 common/lib/monitoring/__init__.py create mode 100644 common/lib/monitoring/exceptions.py create mode 100644 common/lib/util/__init__.py rename {lms => common}/lib/util/cache.py (100%) rename {lms => common}/lib/util/memcache.py (100%) rename {lms => common}/lib/util/middleware.py (100%) rename {lms => common}/lib/util/models.py (100%) rename {lms => common}/lib/util/tests.py (100%) rename {lms => common}/lib/util/views.py (100%) diff --git a/lms/djangoapps/track/__init__.py b/cms/__init__.py similarity index 100% rename from lms/djangoapps/track/__init__.py rename to cms/__init__.py diff --git a/lms/lib/cache_toolbox/templatetags/__init__.py b/cms/envs/__init__.py similarity index 100% rename from lms/lib/cache_toolbox/templatetags/__init__.py rename to cms/envs/__init__.py diff --git a/cms/envs/common.py b/cms/envs/common.py new file mode 100644 index 0000000000..9b349a06d0 --- /dev/null +++ b/cms/envs/common.py @@ -0,0 +1,151 @@ +""" +This is the common settings file, intended to set sane defaults. If you have a +piece of configuration that's dependent on a set of feature flags being set, +then create a function that returns the calculated value based on the value of +MITX_FEATURES[...]. Modules that extend this one can change the feature +configuration in an environment specific config file and re-calculate those +values. + +We should make a method that calls all these config methods so that you just +make one call at the end of your site-specific dev file to reset all the +dependent variables (like INSTALLED_APPS) for you. + +Longer TODO: +1. Right now our treatment of static content in general and in particular + course-specific static content is haphazard. +2. We should have a more disciplined approach to feature flagging, even if it + just means that we stick them in a dict called MITX_FEATURES. +3. We need to handle configuration for multiple courses. This could be as + multiple sites, but we do need a way to map their data assets. +""" + +import sys +import tempfile +from path import path + +############################# SET PATH INFORMATION ############################# +PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms +COMMON_ROOT = PROJECT_ROOT.dirname() / "common" +ENV_ROOT = PROJECT_ROOT.dirname().dirname() # virtualenv dir /mitx is in +ASKBOT_ROOT = ENV_ROOT / "askbot-devel" +COURSES_ROOT = ENV_ROOT / "data" + +# FIXME: To support multiple courses, we should walk the courses dir at startup +DATA_DIR = COURSES_ROOT + +sys.path.append(ENV_ROOT) +sys.path.append(ASKBOT_ROOT) +sys.path.append(ASKBOT_ROOT / "askbot" / "deps") +sys.path.append(PROJECT_ROOT / 'djangoapps') +sys.path.append(PROJECT_ROOT / 'lib') +sys.path.append(COMMON_ROOT / 'djangoapps') +sys.path.append(COMMON_ROOT / 'lib') + + +############################# WEB CONFIGURATION ############################# +# This is where we stick our compiled template files. +MAKO_MODULE_DIR = tempfile.mkdtemp('mako') +MAKO_TEMPLATES = {} +MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates'] + +MITX_ROOT_URL = '' + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.request', + 'django.core.context_processors.static', + 'django.contrib.messages.context_processors.messages', + 'django.core.context_processors.auth', # this is required for admin + 'django.core.context_processors.csrf', # necessary for csrf protection +) + +################################# Middleware ################################### +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +) + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.cache.UpdateCacheMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + + # Instead of AuthenticationMiddleware, we use a cached backed version + 'cache_toolbox.middleware.CacheBackedAuthenticationMiddleware', + + 'django.contrib.messages.middleware.MessageMiddleware', + 'track.middleware.TrackMiddleware', + 'mitxmako.middleware.MakoMiddleware', + + 'django.middleware.transaction.TransactionMiddleware', +) + +############################ SIGNAL HANDLERS ################################ +import monitoring.exceptions # noqa + +############################ DJANGO_BUILTINS ################################ +# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here +DEBUG = False +TEMPLATE_DEBUG = False + +# Site info +SITE_ID = 1 +SITE_NAME = "localhost:8000" +HTTPS = 'on' +ROOT_URLCONF = 'mitx.cms.urls' +IGNORABLE_404_ENDS = ('favicon.ico') + +# Email +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu' +DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu' +ADMINS = ( + ('MITx Admins', 'admin@mitx.mit.edu'), +) +MANAGERS = ADMINS + +# Static content +STATIC_URL = '/static/' +ADMIN_MEDIA_PREFIX = '/static/admin/' +STATIC_ROOT = ENV_ROOT / "staticfiles" + +# FIXME: We should iterate through the courses we have, adding the static +# contents for each of them. (Right now we just use symlinks.) +STATICFILES_DIRS = [ + PROJECT_ROOT / "static", + +# This is how you would use the textbook images locally +# ("book", ENV_ROOT / "book_images") +] + +# Locale/Internationalization +TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html +USE_I18N = True +USE_L10N = True + +# Messages +MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' + +############################ APPS ##################################### + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + # 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', +) diff --git a/cms/envs/dev.py b/cms/envs/dev.py new file mode 100644 index 0000000000..72a5e512c4 --- /dev/null +++ b/cms/envs/dev.py @@ -0,0 +1,37 @@ +""" +This config file runs the simplest dev environment""" + +from .common import * + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ENV_ROOT / "db" / "mitx.db", + } +} + +CACHES = { + # This is the cache used for most things. Askbot will not work without a + # functioning cache -- it relies on caching to load its settings in places. + # In staging/prod envs, the sessions also live here. + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'mitx_loc_mem_cache', + 'KEY_FUNCTION': 'util.memcache.safe_key', + }, + + # The general cache is what you get if you use our util.cache. It's used for + # things like caching the course.xml file for different A/B test groups. + # We set it to be a DummyCache to force reloading of course.xml in dev. + # In staging environments, we would grab VERSION from data uploaded by the + # push process. + 'general': { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + 'KEY_PREFIX': 'general', + 'VERSION': 4, + 'KEY_FUNCTION': 'util.cache.memcache_safe_key', + } +} diff --git a/cms/manage.py b/cms/manage.py new file mode 100644 index 0000000000..3e4eedc9ff --- /dev/null +++ b/cms/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +import imp +try: + imp.find_module('settings') # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) + sys.exit(1) + +import settings + +if __name__ == "__main__": + execute_manager(settings) diff --git a/lms/lib/util/__init__.py b/cms/models.py similarity index 100% rename from lms/lib/util/__init__.py rename to cms/models.py diff --git a/cms/templates/calendar.html b/cms/templates/calendar.html new file mode 100644 index 0000000000..05b2f88806 --- /dev/null +++ b/cms/templates/calendar.html @@ -0,0 +1,3 @@ +% for week in weeks: +${week} +% endfor diff --git a/cms/urls.py b/cms/urls.py new file mode 100644 index 0000000000..80e2b19e9d --- /dev/null +++ b/cms/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import patterns, url + +# Uncomment the next two lines to enable the admin: +# from django.contrib import admin +# admin.autodiscover() + +urlpatterns = patterns('cms.views', + url(r'^(?P[^/]+)/calendar/', 'calendar', name='calendar'), +) diff --git a/cms/views.py b/cms/views.py new file mode 100644 index 0000000000..d0d4f4871c --- /dev/null +++ b/cms/views.py @@ -0,0 +1,5 @@ +from mitxmako.shortcuts import render_to_response + + +def calendar(request, course): + return render_to_response('calendar.html', {}) diff --git a/common/djangoapps/track/__init__.py b/common/djangoapps/track/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/track/middleware.py b/common/djangoapps/track/middleware.py similarity index 100% rename from lms/djangoapps/track/middleware.py rename to common/djangoapps/track/middleware.py diff --git a/lms/djangoapps/track/models.py b/common/djangoapps/track/models.py similarity index 100% rename from lms/djangoapps/track/models.py rename to common/djangoapps/track/models.py diff --git a/lms/djangoapps/track/tests.py b/common/djangoapps/track/tests.py similarity index 100% rename from lms/djangoapps/track/tests.py rename to common/djangoapps/track/tests.py diff --git a/lms/djangoapps/track/views.py b/common/djangoapps/track/views.py similarity index 100% rename from lms/djangoapps/track/views.py rename to common/djangoapps/track/views.py diff --git a/lms/lib/cache_toolbox/COPYING b/common/lib/cache_toolbox/COPYING similarity index 100% rename from lms/lib/cache_toolbox/COPYING rename to common/lib/cache_toolbox/COPYING diff --git a/lms/lib/cache_toolbox/README.rst b/common/lib/cache_toolbox/README.rst similarity index 100% rename from lms/lib/cache_toolbox/README.rst rename to common/lib/cache_toolbox/README.rst diff --git a/lms/lib/cache_toolbox/__init__.py b/common/lib/cache_toolbox/__init__.py similarity index 100% rename from lms/lib/cache_toolbox/__init__.py rename to common/lib/cache_toolbox/__init__.py diff --git a/lms/lib/cache_toolbox/app_settings.py b/common/lib/cache_toolbox/app_settings.py similarity index 100% rename from lms/lib/cache_toolbox/app_settings.py rename to common/lib/cache_toolbox/app_settings.py diff --git a/lms/lib/cache_toolbox/core.py b/common/lib/cache_toolbox/core.py similarity index 100% rename from lms/lib/cache_toolbox/core.py rename to common/lib/cache_toolbox/core.py diff --git a/lms/lib/cache_toolbox/middleware.py b/common/lib/cache_toolbox/middleware.py similarity index 100% rename from lms/lib/cache_toolbox/middleware.py rename to common/lib/cache_toolbox/middleware.py diff --git a/lms/lib/cache_toolbox/model.py b/common/lib/cache_toolbox/model.py similarity index 100% rename from lms/lib/cache_toolbox/model.py rename to common/lib/cache_toolbox/model.py diff --git a/lms/lib/cache_toolbox/relation.py b/common/lib/cache_toolbox/relation.py similarity index 100% rename from lms/lib/cache_toolbox/relation.py rename to common/lib/cache_toolbox/relation.py diff --git a/common/lib/cache_toolbox/templatetags/__init__.py b/common/lib/cache_toolbox/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/lib/cache_toolbox/templatetags/cache_toolbox.py b/common/lib/cache_toolbox/templatetags/cache_toolbox.py similarity index 100% rename from lms/lib/cache_toolbox/templatetags/cache_toolbox.py rename to common/lib/cache_toolbox/templatetags/cache_toolbox.py diff --git a/common/lib/monitoring/__init__.py b/common/lib/monitoring/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/lib/monitoring/exceptions.py b/common/lib/monitoring/exceptions.py new file mode 100644 index 0000000000..6a34d9a38f --- /dev/null +++ b/common/lib/monitoring/exceptions.py @@ -0,0 +1,10 @@ +from django.core.signals import got_request_exception +from django.dispatch import receiver +import logging + + +@receiver(got_request_exception) +def record_request_exception(sender, **kwargs): + logging.exception("Uncaught exception from {sender}".format( + sender=sender + )) diff --git a/common/lib/util/__init__.py b/common/lib/util/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/lib/util/cache.py b/common/lib/util/cache.py similarity index 100% rename from lms/lib/util/cache.py rename to common/lib/util/cache.py diff --git a/lms/lib/util/memcache.py b/common/lib/util/memcache.py similarity index 100% rename from lms/lib/util/memcache.py rename to common/lib/util/memcache.py diff --git a/lms/lib/util/middleware.py b/common/lib/util/middleware.py similarity index 100% rename from lms/lib/util/middleware.py rename to common/lib/util/middleware.py diff --git a/lms/lib/util/models.py b/common/lib/util/models.py similarity index 100% rename from lms/lib/util/models.py rename to common/lib/util/models.py diff --git a/lms/lib/util/tests.py b/common/lib/util/tests.py similarity index 100% rename from lms/lib/util/tests.py rename to common/lib/util/tests.py diff --git a/lms/lib/util/views.py b/common/lib/util/views.py similarity index 100% rename from lms/lib/util/views.py rename to common/lib/util/views.py diff --git a/lms/envs/common.py b/lms/envs/common.py index ec288f8dd7..7d8b1ab3d0 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -60,6 +60,7 @@ sys.path.append(ASKBOT_ROOT) sys.path.append(ASKBOT_ROOT / "askbot" / "deps") sys.path.append(PROJECT_ROOT / 'djangoapps') sys.path.append(PROJECT_ROOT / 'lib') +sys.path.append(COMMON_ROOT / 'djangoapps') sys.path.append(COMMON_ROOT / 'lib') ################################## MITXWEB ##################################### diff --git a/rakefile b/rakefile index 29446f4287..a04d8ebdee 100644 --- a/rakefile +++ b/rakefile @@ -58,7 +58,7 @@ task :pylint => REPORT_DIR do end end -[:lms].each do |system| +[:lms, :cms].each do |system| task_name = "test_#{system}" report_dir = File.join(REPORT_DIR, task_name) directory report_dir @@ -70,6 +70,15 @@ end sh(django_admin(:lms, :test, 'test', *Dir['lms/djangoapps'].each)) end task :test => task_name + + desc <<-desc + Start the #{system} locally with the specified environment (defaults to dev). + 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 => '') + sh(django_admin(system, args.env, 'runserver', args.options)) + end end Dir["common/lib/*"].each do |lib| @@ -86,15 +95,6 @@ Dir["common/lib/*"].each do |lib| task :test => task_name end -desc <<-desc - Start the lms locally with the specified environment (defaults to dev). - Other useful environments are devplus (for dev testing with a real local database) - desc -task :lms, [:env] => [] do |t, args| - args.with_defaults(:env => 'dev') - sh(django_admin(:lms, args.env, 'runserver')) -end - task :runserver => :lms desc "Run django-admin against the specified system and environment" From 9089596fad4eb8ffcac0761cc1073ba601001bf8 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 8 Jun 2012 16:55:38 -0400 Subject: [PATCH 009/123] Allow login in the cms, and read a particular course from mongo --- .../djangoapps/instructor}/__init__.py | 0 cms/djangoapps/instructor/models.py | 61 ++++++++++++++ cms/djangoapps/instructor/tests.py | 16 ++++ cms/djangoapps/instructor/views.py | 49 ++++++++++++ cms/envs/dev.py | 6 ++ cms/lib/keystore/__init__.py | 80 +++++++++++++++++++ cms/lib/keystore/django.py | 12 +++ cms/lib/keystore/exceptions.py | 7 ++ cms/lib/keystore/mongo.py | 26 ++++++ cms/templates/login.html | 11 +++ cms/urls.py | 5 +- cms/views.py | 11 ++- common/lib/django_future/__init__.py | 0 {lms => common}/lib/django_future/csrf.py | 0 requirements.txt | 1 + 15 files changed, 281 insertions(+), 4 deletions(-) rename {lms/lib/django_future => cms/djangoapps/instructor}/__init__.py (100%) create mode 100644 cms/djangoapps/instructor/models.py create mode 100644 cms/djangoapps/instructor/tests.py create mode 100644 cms/djangoapps/instructor/views.py create mode 100644 cms/lib/keystore/__init__.py create mode 100644 cms/lib/keystore/django.py create mode 100644 cms/lib/keystore/exceptions.py create mode 100644 cms/lib/keystore/mongo.py create mode 100644 cms/templates/login.html create mode 100644 common/lib/django_future/__init__.py rename {lms => common}/lib/django_future/csrf.py (100%) diff --git a/lms/lib/django_future/__init__.py b/cms/djangoapps/instructor/__init__.py similarity index 100% rename from lms/lib/django_future/__init__.py rename to cms/djangoapps/instructor/__init__.py diff --git a/cms/djangoapps/instructor/models.py b/cms/djangoapps/instructor/models.py new file mode 100644 index 0000000000..906aeee2f1 --- /dev/null +++ b/cms/djangoapps/instructor/models.py @@ -0,0 +1,61 @@ +""" +WE'RE USING MIGRATIONS! + +If you make changes to this model, be sure to create an appropriate migration +file and check it in at the same time as your model changes. To do that, + +1. Go to the mitx dir +2. ./manage.py schemamigration user --auto description_of_your_change +3. Add the migration file created in mitx/courseware/migrations/ +""" +import uuid + +from django.db import models +from django.contrib.auth.models import User + + +class UserProfile(models.Model): + class Meta: + db_table = "auth_userprofile" + + ## CRITICAL TODO/SECURITY + # Sanitize all fields. + # This is not visible to other users, but could introduce holes later + user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile') + name = models.CharField(blank=True, max_length=255, db_index=True) + org = models.CharField(blank=True, max_length=255, db_index=True) + + +class Registration(models.Model): + ''' Allows us to wait for e-mail before user is registered. A + registration profile is created when the user creates an + account, but that account is inactive. Once the user clicks + on the activation key, it becomes active. ''' + class Meta: + db_table = "auth_registration" + + user = models.ForeignKey(User, unique=True) + activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True) + + def register(self, user): + # MINOR TODO: Switch to crypto-secure key + self.activation_key = uuid.uuid4().hex + self.user = user + self.save() + + def activate(self): + self.user.is_active = True + self.user.save() + #self.delete() + + +class PendingNameChange(models.Model): + user = models.OneToOneField(User, unique=True, db_index=True) + new_name = models.CharField(blank=True, max_length=255) + rationale = models.CharField(blank=True, max_length=1024) + + +class PendingEmailChange(models.Model): + user = models.OneToOneField(User, unique=True, db_index=True) + new_email = models.CharField(blank=True, max_length=255, db_index=True) + activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True) diff --git a/cms/djangoapps/instructor/tests.py b/cms/djangoapps/instructor/tests.py new file mode 100644 index 0000000000..501deb776c --- /dev/null +++ b/cms/djangoapps/instructor/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/cms/djangoapps/instructor/views.py b/cms/djangoapps/instructor/views.py new file mode 100644 index 0000000000..fbb341b468 --- /dev/null +++ b/cms/djangoapps/instructor/views.py @@ -0,0 +1,49 @@ +import logging + +from django.views.decorators.http import require_http_methods, require_POST, require_GET +from django.contrib.auth import logout, authenticate, login +from django.shortcuts import redirect +from mitxmako.shortcuts import render_to_response + +from django_future.csrf import ensure_csrf_cookie + +log = logging.getLogger("mitx.student") + + +@require_http_methods(['GET', 'POST']) +def do_login(request): + if request.method == 'POST': + return post_login(request) + elif request.method == 'GET': + return get_login(request) + + +@require_POST +@ensure_csrf_cookie +def post_login(request): + username = request.POST['username'] + password = request.POST['password'] + user = authenticate(username=username, password=password) + if user is not None: + if user.is_active: + login(request, user) + return redirect(request.POST.get('next', '/')) + else: + raise Exception("Can't log in, account disabled") + else: + raise Exception("Can't log in, invalid authentication") + + +@require_GET +@ensure_csrf_cookie +def get_login(request): + return render_to_response('login.html', { + 'next': request.GET.get('next') + }) + + +@ensure_csrf_cookie +def logout_user(request): + ''' HTTP request to log in the user. Redirects to marketing page''' + logout(request) + return redirect('/') diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 72a5e512c4..f7277b3d3f 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -6,6 +6,12 @@ from .common import * DEBUG = True TEMPLATE_DEBUG = DEBUG +KEYSTORE = { + 'host': 'localhost', + 'db': 'mongo_base', + 'collection': 'key_store', +} + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', diff --git a/cms/lib/keystore/__init__.py b/cms/lib/keystore/__init__.py new file mode 100644 index 0000000000..5e6374cf4a --- /dev/null +++ b/cms/lib/keystore/__init__.py @@ -0,0 +1,80 @@ +""" +This module provides an abstraction for working objects that conceptually have +the following attributes: + + location: An identifier for an item, of which there might be many revisions + children: A list of urls for other items required to fully define this object + data: A set of nested data needed to define this object + editor: The editor/owner of the object + parents: Url pointers for objects that this object was derived from + revision: What revision of the item this is +""" + + +class Location(object): + ''' Encodes a location. + Can be: + * String (url) + * Tuple + * Dictionary + ''' + def __init__(self, location): + self.update(location) + + def update(self, location): + if isinstance(location, basestring): + self.tag = location.split('/')[0][:-1] + (self.org, self.course, self.category, self.name) = location.split('/')[2:] + elif isinstance(location, list): + (self.tag, self.org, self.course, self.category, self.name) = location + elif isinstance(location, dict): + self.tag = location['tag'] + self.org = location['org'] + self.course = location['course'] + self.category = location['category'] + self.name = location['name'] + elif isinstance(location, Location): + self.update(location.list()) + + def url(self): + return "i4x://{org}/{course}/{category}/{name}".format(**self.dict()) + + def list(self): + return [self.tag, self.org, self.course, self.category, self.name] + + def dict(self): + return {'tag': self.tag, + 'org': self.org, + 'course': self.course, + 'category': self.category, + 'name': self.name} + + def to_json(self): + return self.dict() + + +class KeyStore(object): + def get_children_for_item(self, location): + """ + Returns the children for the most recent revision of the object + with the specified location. + + If no object is found at that location, raises keystore.exceptions.ItemNotFoundError + """ + raise NotImplementedError + + +class KeyStoreItem(object): + """ + An object from a KeyStore, which can be saved back to that keystore + """ + def __init__(self, location, children, data, editor, parents, revision): + self.location = location + self.children = children + self.data = data + self.editor = editor + self.parents = parents + self.revision = revision + + def save(self): + raise NotImplementedError diff --git a/cms/lib/keystore/django.py b/cms/lib/keystore/django.py new file mode 100644 index 0000000000..b6ffb83b5c --- /dev/null +++ b/cms/lib/keystore/django.py @@ -0,0 +1,12 @@ +""" +Module that provides a connection to the keystore specified in the django settings. + +Passes settings.KEYSTORE as kwargs to MongoKeyStore +""" + +from __future__ import absolute_import + +from django.conf import settings +from .mongo import MongoKeyStore + +keystore = MongoKeyStore(**settings.KEYSTORE) diff --git a/cms/lib/keystore/exceptions.py b/cms/lib/keystore/exceptions.py new file mode 100644 index 0000000000..b66470859f --- /dev/null +++ b/cms/lib/keystore/exceptions.py @@ -0,0 +1,7 @@ +""" +Exceptions thrown by KeyStore objects +""" + + +class ItemNotFoundError(Exception): + pass diff --git a/cms/lib/keystore/mongo.py b/cms/lib/keystore/mongo.py new file mode 100644 index 0000000000..fc190ee098 --- /dev/null +++ b/cms/lib/keystore/mongo.py @@ -0,0 +1,26 @@ +import pymongo +from . import KeyStore +from .exceptions import ItemNotFoundError + + +class MongoKeyStore(KeyStore): + """ + A Mongodb backed KeyStore + """ + def __init__(self, host, db, collection, port=27017): + self.collection = pymongo.connection.Connection( + host=host, + port=port + )[db][collection] + + def get_children_for_item(self, location): + item = self.collection.find_one( + {'location': location.dict()}, + fields={'children': True}, + sort=[('revision', pymongo.ASCENDING)], + ) + + if item is None: + raise ItemNotFoundError() + + return item['children'] diff --git a/cms/templates/login.html b/cms/templates/login.html new file mode 100644 index 0000000000..03ea5f967c --- /dev/null +++ b/cms/templates/login.html @@ -0,0 +1,11 @@ + + + + % if next is not None: + + % endif + + Username: + Possword: + + diff --git a/cms/urls.py b/cms/urls.py index 80e2b19e9d..55d7a1086e 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -4,6 +4,7 @@ from django.conf.urls.defaults import patterns, url # from django.contrib import admin # admin.autodiscover() -urlpatterns = patterns('cms.views', - url(r'^(?P[^/]+)/calendar/', 'calendar', name='calendar'), +urlpatterns = patterns('', + url(r'^(?P[^/]+)/(?P[^/]+)/calendar/', 'cms.views.calendar', name='calendar'), + url(r'^accounts/login/', 'instructor.views.do_login', name='login'), ) diff --git a/cms/views.py b/cms/views.py index d0d4f4871c..c6786b03c4 100644 --- a/cms/views.py +++ b/cms/views.py @@ -1,5 +1,12 @@ from mitxmako.shortcuts import render_to_response +from keystore import Location +from keystore.django import keystore +from django.contrib.auth.decorators import login_required -def calendar(request, course): - return render_to_response('calendar.html', {}) +@login_required +def calendar(request, org, course): + weeks = keystore.get_children_for_item( + Location(['i4x', org, course, 'Course', 'course']) + ) + return render_to_response('calendar.html', {'weeks': weeks}) diff --git a/common/lib/django_future/__init__.py b/common/lib/django_future/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/lib/django_future/csrf.py b/common/lib/django_future/csrf.py similarity index 100% rename from lms/lib/django_future/csrf.py rename to common/lib/django_future/csrf.py diff --git a/requirements.txt b/requirements.txt index 82b28b09cf..5e95e1bf9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ requests sympy newrelic glob2 +pymongo From 7766664f2f833b599fc6121bb5be2bc954f88a9c Mon Sep 17 00:00:00 2001 From: ichuang Date: Sat, 9 Jun 2012 18:26:49 -0400 Subject: [PATCH 010/123] add msg to textinput_dynamath --- common/lib/capa/templates/textinput_dynamath.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/capa/templates/textinput_dynamath.html b/common/lib/capa/templates/textinput_dynamath.html index e8b26c5fcc..41b9c5d172 100644 --- a/common/lib/capa/templates/textinput_dynamath.html +++ b/common/lib/capa/templates/textinput_dynamath.html @@ -30,4 +30,7 @@ + % if msg: + ${msg|n} + % endif From b8408a15290207a639a4fcc102b81a0bc9336b9e Mon Sep 17 00:00:00 2001 From: ichuang Date: Sat, 9 Jun 2012 18:27:09 -0400 Subject: [PATCH 011/123] first pass in capa cleanup: - responsetype used to be instantiated multiple times(!) in capa_problem now it is instantiated once, and stored in self.responders - responsetypes.GenericResponse restructured; each superclass show now provide setup_response (and not __init__), and may provide get_max_score(); general __init__ provided to clean up superclasses. --- common/lib/capa/capa_problem.py | 191 ++++++------- common/lib/capa/responsetypes.py | 275 ++++++++++++------- common/lib/capa/util.py | 18 ++ lms/djangoapps/courseware/module_render.py | 2 +- lms/static/coffee/src/modules/problem.coffee | 2 +- 5 files changed, 287 insertions(+), 201 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index b655270a9a..f790190215 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -1,6 +1,11 @@ # # File: capa/capa_problem.py # +# Nomenclature: +# +# A capa Problem is a collection of text and capa Response questions. Each Response may have one or more +# Input entry fields. The capa Problem may include a solution. +# ''' Main module which shows problems (of "capa" type). @@ -83,17 +88,32 @@ html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formul class LoncapaProblem(object): + ''' + Main class for capa Problems. + ''' + def __init__(self, fileobject, id, state=None, seed=None, system=None): + ''' + Initializes capa Problem. The problem itself is defined by the XML file + pointed to by fileobject. + + Arguments: + + - filesobject : an OSFS instance: see fs.osfs + - id : string used as the identifier for this problem; often a filename (no spaces) + - state : student state (represented as a dict) + - seed : random number generator seed (int) + - system : I4xSystem instance which provides OS, rendering, and user context + + ''' + ## Initialize class variables from state - self.seed = None self.student_answers = dict() self.correct_map = dict() self.done = False self.problem_id = id self.system = system - - if seed is not None: - self.seed = seed + self.seed = seed if state: if 'seed' in state: @@ -109,22 +129,21 @@ class LoncapaProblem(object): if not self.seed: self.seed = struct.unpack('i', os.urandom(4))[0] - ## Parse XML file - if getattr(system, 'DEBUG', False): + self.fileobject = fileobject # save problem file object, so we can use for debugging information later + if getattr(system, 'DEBUG', False): # get the problem XML string from the problem file log.info("[courseware.capa.capa_problem.lcp.init] fileobject = %s" % fileobject) file_text = fileobject.read() - self.fileobject = fileobject # save it, so we can use for debugging information later - # Convert startouttext and endouttext to proper - # TODO: Do with XML operations - file_text = re.sub("startouttext\s*/", "text", file_text) + file_text = re.sub("startouttext\s*/", "text", file_text) # Convert startouttext and endouttext to proper file_text = re.sub("endouttext\s*/", "/text", file_text) - self.tree = etree.XML(file_text) - self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers) + self.tree = etree.XML(file_text) # parse problem XML file into an element tree + + # construct script processor context (eg for customresponse problems) self.context = self.extract_context(self.tree, seed=self.seed) - for response in self.tree.xpath('//' + "|//".join(response_types)): - responder = response_types[response.tag](response, self.context, self.system) - responder.preprocess_response() + + # pre-parse the XML tree: modifies it to add ID's and perform some in-place transformations + # this also creates the list (self.responders) of Response instances for each question in the problem + self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers) def __unicode__(self): return u"LoncapaProblem ({0})".format(self.fileobject) @@ -140,12 +159,27 @@ class LoncapaProblem(object): def get_max_score(self): ''' - TODO: multiple points for programming problems. + Return maximum score for this problem. + We do this by counting the number of answers available for each question + in the problem. If the Response for a question has a get_max_score() method + then we call that and add its return value to the count. That can be + used to give complex problems (eg programming questions) multiple points. ''' - sum = 0 - for et in entry_types: - sum = sum + self.tree.xpath('count(//' + et + ')') - return int(sum) + maxscore = 0 + for responder in self.responders: + if hasattr(responder,'get_max_score'): + try: + maxscore += responder.get_max_score() + except Exception, err: + log.error('responder %s failed to properly return from get_max_score()' % responder) + raise + else: + try: + maxscore += len(responder.get_answers()) + except: + log.error('responder %s failed to properly return get_answers()' % responder) + raise + return maxscore def get_score(self): correct = 0 @@ -166,34 +200,35 @@ class LoncapaProblem(object): of each key removed (the string before the first "_"). Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123 + + Calles the Response for each question in this problem, to do the actual grading. ''' self.student_answers = answers self.correct_map = dict() - problems_simple = self.extract_problems(self.tree) - for response in problems_simple: - grader = response_types[response.tag](response, self.context, self.system) - results = grader.get_score(answers) # call the responsetype instance to do the actual grading + log.info('%s: in grade_answers, answers=%s' % (self,answers)) + for responder in self.responders: + results = responder.get_score(answers) # call the responsetype instance to do the actual grading self.correct_map.update(results) return self.correct_map def get_question_answers(self): - """Returns a dict of answer_ids to answer values. If we can't generate + """Returns a dict of answer_ids to answer values. If we cannot generate an answer (this sometimes happens in customresponses), that answer_id is not included. Called by "show answers" button JSON request (see capa_module) """ answer_map = dict() - problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries - for response in problems_simple: - responder = response_types[response.tag](response, self.context, self.system) # instance of numericalresponse, customresponse,... + for responder in self.responders: results = responder.get_answers() answer_map.update(results) # dict of (id,correct_answer) + # This should be handled in each responsetype, not here. # example for the following: - for entry in problems_simple.xpath("//" + "|//".join(response_properties + entry_types)): - answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline - if answer: - answer_map[entry.get('id')] = contextualize_text(answer, self.context) + for responder in self.responders: + for entry in responder.inputfields: + answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline + if answer: + answer_map[entry.get('id')] = contextualize_text(answer, self.context) # include solutions from ... stanzas # Tentative merge; we should figure out how we want to handle hints and solutions @@ -209,17 +244,16 @@ class LoncapaProblem(object): the dicts returned by grade_answers and get_question_answers. (Though get_question_answers may only return a subset of these.""" answer_ids = [] - problems_simple = self.extract_problems(self.tree) - for response in problems_simple: - responder = response_types[response.tag](response, self.context) - if hasattr(responder, "answer_id"): - answer_ids.append(responder.answer_id) - # customresponse types can have multiple answer_ids - elif hasattr(responder, "answer_ids"): - answer_ids.extend(responder.answer_ids) - + for responder in self.responders: + answer_ids.append(responder.get_answers().keys()) return answer_ids + def get_html(self): + ''' + Main method called externally to get the HTML to be rendered for this capa Problem. + ''' + return contextualize_text(etree.tostring(self.extract_html(self.tree)[0]), self.context) + # ======= Private ======== def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private ''' @@ -253,9 +287,6 @@ class LoncapaProblem(object): log.exception("Error while execing code: " + code) return context - def get_html(self): - return contextualize_text(etree.tostring(self.extract_html(self.tree)[0]), self.context) - def extract_html(self, problemtree): # private ''' Helper function for get_html. Recursively converts XML tree to HTML ''' @@ -335,76 +366,34 @@ class LoncapaProblem(object): Assign sub-IDs to all entries (textline, schematic, etc.) Annoted correctness and value In-place transformation + + Also create capa Response instances for each responsetype and save as self.responders ''' response_id = 1 + self.responders = [] for response in tree.xpath('//' + "|//".join(response_types)): response_id_str = self.problem_id + "_" + str(response_id) - response.attrib['id'] = response_id_str - if response_id not in correct_map: - correct = 'unsubmitted' - response.attrib['state'] = correct - response_id = response_id + 1 + response.attrib['id'] = response_id_str # create and save ID for this response + + # if response_id not in correct_map: correct = 'unsubmitted' # unused - to be removed + # response.attrib['state'] = correct + response_id += response_id + answer_id = 1 - for entry in tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x for x in (entry_types + solution_types)]), - id=response_id_str): - # assign one answer_id for each entry_type or solution_type + inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x for x in (entry_types + solution_types)]), + id=response_id_str) + for entry in inputfields: # assign one answer_id for each entry_type or solution_type entry.attrib['response_id'] = str(response_id) entry.attrib['answer_id'] = str(answer_id) entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id) answer_id = answer_id + 1 + responder = response_types[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response + self.responders.append(responder) # save in list in self + # ... may not be associated with any specific response; give IDs for those separately # TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i). solution_id = 1 for solution in tree.findall('.//solution'): solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id) solution_id += 1 - - def extract_problems(self, problem_tree): - ''' Remove layout from the problem, and give a purified XML tree of just the problems ''' - problem_tree = copy.deepcopy(problem_tree) - tree = Element('problem') - for response in problem_tree.xpath("//" + "|//".join(response_types)): - newresponse = copy.copy(response) - for e in newresponse: - newresponse.remove(e) - # copy.copy is needed to make xpath work right. Otherwise, it starts at the root - # of the tree. We should figure out if there's some work-around - for e in copy.copy(response).xpath("//" + "|//".join(response_properties + entry_types)): - newresponse.append(e) - - tree.append(newresponse) - return tree - -if __name__ == '__main__': - problem_id = 'simpleFormula' - filename = 'simpleFormula.xml' - - problem_id = 'resistor' - filename = 'resistor.xml' - - lcp = LoncapaProblem(filename, problem_id) - - context = lcp.extract_context(lcp.tree) - problem = lcp.extract_problems(lcp.tree) - print lcp.grade_problems({'resistor_2_1': '1.0', 'resistor_3_1': '2.0'}) - #print lcp.grade_problems({'simpleFormula_2_1':'3*x^3'}) -#numericalresponse(problem, context) - -#print etree.tostring((lcp.tree)) - print '============' - print -#print etree.tostring(lcp.extract_problems(lcp.tree)) - print lcp.get_html() -#print extract_context(tree) - - - - # def handle_fr(self, element): - # problem={"answer":self.contextualize_text(answer), - # "type":"formularesponse", - # "tolerance":evaluator({},{},self.contextualize_text(tolerance)), - # "sample_range":dict(zip(variables, sranges)), - # "samples_count": numsamples, - # "id":id, - # self.questions[self.lid]=problem diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index c5683bb0bf..c0ad98baa2 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -21,40 +21,123 @@ import abc # specific library imports from calc import evaluator, UndefinedVariable -from util import contextualize_text +from util import * from lxml import etree from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? -log = logging.getLogger(__name__) +#log = logging.getLogger(__name__) +log = logging.getLogger('mitx.common.lib.capa.responsetypes') -def compare_with_tolerance(v1, v2, tol): - ''' Compare v1 to v2 with maximum tolerance tol - tol is relative if it ends in %; otherwise, it is absolute +#----------------------------------------------------------------------------- +# Exceptions + +class LoncapaProblemError(Exception): ''' - relative = "%" in tol - if relative: - tolerance_rel = evaluator(dict(),dict(),tol[:-1]) * 0.01 - tolerance = tolerance_rel * max(abs(v1), abs(v2)) - else: - tolerance = evaluator(dict(),dict(),tol) - return abs(v1-v2) <= tolerance + Error in specification of a problem + ''' + pass + +class ResponseError(Exception): + ''' + Error for failure in processing a response + ''' + pass + +class StudentInputError(Exception): + pass + +#----------------------------------------------------------------------------- +# +# Main base class for CAPA responsetypes class GenericResponse(object): + ''' + Base class for CAPA responsetypes. Each response type (ie a capa question, + which is part of a capa problem) is represented as a superclass, + which should provide the following methods: + + - get_score : evaluate the given student answers, and return a CorrectMap + - get_answers : provide a dict of the expected answers for this problem + + In addition, these methods are optional: + + - get_max_score : if defined, this is called to obtain the maximum score possible for this question + - setup_response : find and note the answer input field IDs for the response; called by __init__ + + Each response type may also specify the following attributes: + + - max_inputfields : (int) maximum number of answer input fields (checked in __init__ if not None) + - allowed_inputfields : list of allowed input fields (each a string) for this Response + - required_attributes : list of required attributes (each a string) on the main response XML stanza + + ''' __metaclass__=abc.ABCMeta # abc = Abstract Base Class + max_inputfields = None + allowed_inputfields = [] + required_attributes = [] + + def __init__(self, xml, inputfields, context, system=None): + ''' + Init is passed the following arguments: + + - xml : ElementTree of this Response + - inputfields : list of ElementTrees for each input entry field in this Response + - context : script processor context + - system : I4xSystem instance which provides OS, rendering, and user context + - __unicode__ : unicode representation of this Response + + ''' + self.xml = xml + self.inputfields = inputfields + self.context = context + self.system = system + + for abox in inputfields: + if not abox.tag in self.allowed_inputfields: + msg = "%s: cannot have input field %s" % (unicode(self),abox.tag) + msg += "\nSee XML source line %s" % getattr(xml,'sourceline','') + raise LoncapaProblemError(msg) + + if self.max_inputfields and len(inputfields)>self.max_inputfields: + msg = "%s: cannot have more than %s input fields" % (unicode(self),self.max_inputfields) + msg += "\nSee XML source line %s" % getattr(xml,'sourceline','') + raise LoncapaProblemError(msg) + + for prop in self.required_attributes: + if not xml.get(prop): + msg = "Error in problem specification: %s missing required attribute %s" % (unicode(self),prop) + msg += "\nSee XML source line %s" % getattr(xml,'sourceline','') + raise LoncapaProblemError(msg) + + self.answer_ids = [x.get('id') for x in self.inputfields] + if self.max_inputfields==1: + self.answer_id = self.answer_ids[0] # for convenience + + if hasattr(self,'setup_response'): + self.setup_response() + @abc.abstractmethod def get_score(self, student_answers): + ''' + Return a CorrectMap for the answers expected vs given. This includes + (correctness, npoints, msg) for each answer_id. + ''' pass @abc.abstractmethod def get_answers(self): + ''' + Return a dict of (answer_id,answer_text) for each answer for this question. + ''' pass #not an abstract method because plenty of responses will not want to preprocess anything, and we should not require that they override this method. - def preprocess_response(self): + def setup_response(self): pass -#Every response type needs methods "get_score" and "get_answers" + def __unicode__(self): + return 'LoncapaProblem Response %s' % self.xml.tag #----------------------------------------------------------------------------- @@ -69,30 +152,19 @@ class MultipleChoiceResponse(GenericResponse): '''}] - def __init__(self, xml, context, system=None): - self.xml = xml - self.correct_choices = xml.xpath('//*[@id=$id]//choice[@correct="true"]', - id=xml.get('id')) - self.correct_choices = [choice.get('name') for choice in self.correct_choices] - self.context = context - self.answer_field = xml.find('choicegroup') # assumes only ONE choicegroup within this response - self.answer_id = xml.xpath('//*[@id=$id]//choicegroup/@id', - id=xml.get('id')) - if not len(self.answer_id) == 1: - raise Exception("should have exactly one choice group per multiplechoicceresponse") - self.answer_id=self.answer_id[0] + max_inputfields = 1 + allowed_inputfields = ['choicegroup'] - def get_score(self, student_answers): - if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices: - return {self.answer_id:'correct'} - else: - return {self.answer_id:'incorrect'} + def setup_response(self): + self.mc_setup_response() # call secondary setup for MultipleChoice questions, to set name attributes - def get_answers(self): - return {self.answer_id:self.correct_choices} + # define correct choices (after calling secondary setup) + xml = self.xml + cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]',id=xml.get('id')) + self.correct_choices = [choice.get('name') for choice in cxml] - def preprocess_response(self): + def mc_setup_response(self): ''' Initialize name attributes in stanzas in the in this response. ''' @@ -107,9 +179,22 @@ class MultipleChoiceResponse(GenericResponse): i+=1 else: choice.set("name", "choice_"+choice.get("name")) - + + def get_score(self, student_answers): + ''' + grade student response. + ''' + # log.debug('%s: student_answers=%s, correct_choices=%s' % (unicode(self),student_answers,self.correct_choices)) + if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices: + return {self.answer_id:'correct'} + else: + return {self.answer_id:'incorrect'} + + def get_answers(self): + return {self.answer_id:self.correct_choices} + class TrueFalseResponse(MultipleChoiceResponse): - def preprocess_response(self): + def mc_setup_response(self): i=0 for response in self.xml.xpath("choicegroup"): response.set("type", "TrueFalse") @@ -140,12 +225,13 @@ class OptionResponse(GenericResponse): The location of the earth '''}] - def __init__(self, xml, context, system=None): - self.xml = xml - self.answer_fields = xml.findall('optioninput') - self.context = context + allowed_inputfields = ['optioninput'] + + def setup_response(self): + self.answer_fields = self.inputfields def get_score(self, student_answers): + # log.debug('%s: student_answers=%s' % (unicode(self),student_answers)) cmap = {} amap = self.get_answers() for aid in amap: @@ -157,17 +243,20 @@ class OptionResponse(GenericResponse): def get_answers(self): amap = dict([(af.get('id'),af.get('correct')) for af in self.answer_fields]) + # log.debug('%s: expected answers=%s' % (unicode(self),amap)) return amap #----------------------------------------------------------------------------- class NumericalResponse(GenericResponse): - def __init__(self, xml, context, system=None): - self.xml = xml - if not xml.get('answer'): - msg = "Error in problem specification: numericalresponse missing required answer attribute\n" - msg += "See XML source line %s" % getattr(xml,'sourceline','') - raise Exception,msg + + allowed_inputfields = ['textline'] + required_attributes = ['answer'] + max_inputfields = 1 + + def setup_response(self): + xml = self.xml + context = self.context self.correct_answer = contextualize_text(xml.get('answer'), context) try: self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default', @@ -182,7 +271,7 @@ class NumericalResponse(GenericResponse): self.answer_id = None def get_score(self, student_answers): - ''' Display HTML for a numeric response ''' + '''Grade a numeric response ''' student_answer = student_answers[self.answer_id] try: correct = compare_with_tolerance (evaluator(dict(),dict(),student_answer), complex(self.correct_answer), self.tolerance) @@ -241,16 +330,11 @@ def sympy_check2(): '''}] - def __init__(self, xml, context, system=None): - self.xml = xml - self.system = system - ## CRITICAL TODO: Should cover all entrytypes - ## NOTE: xpath will look at root of XML tree, not just - ## what's in xml. @id=id keeps us in the right customresponse. - self.answer_ids = xml.xpath('//*[@id=$id]//textline/@id', - id=xml.get('id')) - self.answer_ids += [x.get('id') for x in xml.findall('textbox')] # also allow textbox inputs - self.context = context + allowed_inputfields = ['textline','textbox'] + + def setup_response(self): + xml = self.xml + context = self.context # if has an "expect" (or "answer") attribute then save that self.expect = xml.get('expect') or xml.get('answer') @@ -271,15 +355,17 @@ def sympy_check2(): cfn = xml.get('cfn') if cfn: log.debug("cfn = %s" % cfn) - if cfn in context: - self.code = context[cfn] + if cfn in self.context: + self.code = self.context[cfn] else: - print "can't find cfn in context = ",context + msg = "%s: can't find cfn in context = %s" % (unicode(self),self.context) + msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','') + raise LoncapaProblemError(msg) if not self.code: if answer is None: # raise Exception,"[courseware.capa.responsetypes.customresponse] missing code checking script! id=%s" % self.myid - print "[courseware.capa.responsetypes.customresponse] missing code checking script! id=%s" % self.myid + log.error("[courseware.capa.responsetypes.customresponse] missing code checking script! id=%s" % self.myid) self.code = '' else: answer_src = answer.get('src') @@ -294,6 +380,8 @@ def sympy_check2(): of each key removed (the string before the first "_"). ''' + log.debug('%s: student_answers=%s' % (unicode(self),student_answers)) + idset = sorted(self.answer_ids) # ordered list of answer id's try: submission = [student_answers[k] for k in idset] # ordered list of answers @@ -425,12 +513,12 @@ class SymbolicResponse(CustomResponse): Your input should be typed in as a list of lists, eg [[1,2],[3,4]]. '''}] - def __init__(self, xml, context, system=None): - xml.set('cfn','symmath_check') + + def setup_response(self): + self.xml.set('cfn','symmath_check') code = "from symmath import *" - exec code in context,context - CustomResponse.__init__(self,xml,context,system) - + exec code in self.context,self.context + CustomResponse.setup_response(self) #----------------------------------------------------------------------------- @@ -480,15 +568,13 @@ main() '''}] - def __init__(self, xml, context, system=None): - self.xml = xml - self.url = xml.get('url') or "http://eecs1.mit.edu:8889/pyloncapa" # FIXME - hardcoded URL - self.answer_ids = xml.xpath('//*[@id=$id]//textbox/@id|//*[@id=$id]//textline/@id', - id=xml.get('id')) - self.context = context - answer = xml.xpath('//*[@id=$id]//answer', - id=xml.get('id'))[0] + allowed_inputfields = ['textline','textbox'] + def setup_response(self): + xml = self.xml + self.url = xml.get('url') or "http://eecs1.mit.edu:8889/pyloncapa" # FIXME - hardcoded URL + + answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0] # FIXME - catch errors answer_src = answer.get('src') if answer_src is not None: self.code = self.system.filesystem.open('src/'+answer_src).read() @@ -590,8 +676,6 @@ main() raise Exception,'Short response from external server' return dict(zip(self.answer_ids,exans)) -class StudentInputError(Exception): - pass #----------------------------------------------------------------------------- @@ -617,8 +701,13 @@ class FormulaResponse(GenericResponse): '''}] - def __init__(self, xml, context, system=None): - self.xml = xml + allowed_inputfields = ['textline'] + required_attributes = ['answer'] + max_inputfields = 1 + + def setup_response(self): + xml = self.xml + context = self.context self.correct_answer = contextualize_text(xml.get('answer'), context) self.samples = contextualize_text(xml.get('samples'), context) try: @@ -628,14 +717,6 @@ class FormulaResponse(GenericResponse): except Exception: self.tolerance = 0 - try: - self.answer_id = xml.xpath('//*[@id=$id]//textline/@id', - id=xml.get('id'))[0] - except Exception: - self.answer_id = None - raise Exception, "[courseware.capa.responsetypes.FormulaResponse] Error: missing answer_id!!" - - self.context = context ts = xml.get('type') if ts is None: typeslist = [] @@ -648,7 +729,6 @@ class FormulaResponse(GenericResponse): else: # Default self.case_sensitive = False - def get_score(self, student_answers): variables=self.samples.split('@')[0].split(',') numsamples=int(self.samples.split('@')[1].split('#')[1]) @@ -697,13 +777,12 @@ class FormulaResponse(GenericResponse): #----------------------------------------------------------------------------- class SchematicResponse(GenericResponse): - def __init__(self, xml, context, system=None): - self.xml = xml - self.answer_ids = xml.xpath('//*[@id=$id]//schematic/@id', - id=xml.get('id')) - self.context = context - answer = xml.xpath('//*[@id=$id]//answer', - id=xml.get('id'))[0] + + allowed_inputfields = ['schematic'] + + def setup_response(self): + xml = self.xml + answer = xml.xpath('//*[@id=$id]//answer', id=xml.get('id'))[0] answer_src = answer.get('src') if answer_src is not None: self.code = self.system.filestore.open('src/'+answer_src).read() # Untested; never used @@ -740,10 +819,10 @@ class ImageResponse(GenericResponse): '''}] - def __init__(self, xml, context, system=None): - self.xml = xml - self.context = context - self.ielements = xml.findall('imageinput') + allowed_inputfields = ['imageinput'] + + def setup_response(self): + self.ielements = self.inputfields self.answer_ids = [ie.get('id') for ie in self.ielements] def get_score(self, student_answers): diff --git a/common/lib/capa/util.py b/common/lib/capa/util.py index d042aa21d3..996f6c8dac 100644 --- a/common/lib/capa/util.py +++ b/common/lib/capa/util.py @@ -1,3 +1,21 @@ +from calc import evaluator, UndefinedVariable + +#----------------------------------------------------------------------------- +# +# Utility functions used in CAPA responsetypes + +def compare_with_tolerance(v1, v2, tol): + ''' Compare v1 to v2 with maximum tolerance tol + tol is relative if it ends in %; otherwise, it is absolute + ''' + relative = "%" in tol + if relative: + tolerance_rel = evaluator(dict(),dict(),tol[:-1]) * 0.01 + tolerance = tolerance_rel * max(abs(v1), abs(v2)) + else: + tolerance = evaluator(dict(),dict(),tol) + return abs(v1-v2) <= tolerance + def contextualize_text(text, context): # private ''' Takes a string with variables. E.g. $a+$b. Does a substitution of those variables from the context ''' diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 97fd1b948c..0f82d9ba94 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -169,7 +169,7 @@ def render_x_module(user, request, xml_module, module_object_preload, position=N content = instance.get_html() # special extra information about each problem, only for users who are staff - if user.is_staff: + if False and user.is_staff: module_id = xml_module.get('id') histogram = grade_histogram(module_id) render_histogram = len(histogram) > 0 diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index c1a801d2b2..e1e062e949 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -48,7 +48,7 @@ class @Problem @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' else - @$("#answer_#{key}").text(value) + @$("#answer_#{key}").html(value) // needs to be html, not text, for complex solutions (eg coding) @$('.show').val 'Hide Answer' @element.addClass 'showed' else From 19f915aff175ab9a024357a4287f9a186d4a4109 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sat, 9 Jun 2012 18:35:42 -0400 Subject: [PATCH 012/123] responsetypes - fix comment --- common/lib/capa/responsetypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index c0ad98baa2..1c09493b03 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -63,6 +63,7 @@ class GenericResponse(object): - get_max_score : if defined, this is called to obtain the maximum score possible for this question - setup_response : find and note the answer input field IDs for the response; called by __init__ + - __unicode__ : unicode representation of this Response Each response type may also specify the following attributes: @@ -85,7 +86,6 @@ class GenericResponse(object): - inputfields : list of ElementTrees for each input entry field in this Response - context : script processor context - system : I4xSystem instance which provides OS, rendering, and user context - - __unicode__ : unicode representation of this Response ''' self.xml = xml @@ -137,7 +137,7 @@ class GenericResponse(object): pass def __unicode__(self): - return 'LoncapaProblem Response %s' % self.xml.tag + return u'LoncapaProblem Response %s' % self.xml.tag #----------------------------------------------------------------------------- From 8129f78611749c0ee54dbfe6300d8dcb8a0f5a8b Mon Sep 17 00:00:00 2001 From: ichuang Date: Sat, 9 Jun 2012 21:29:11 -0400 Subject: [PATCH 013/123] second pass in capa cleanup: - each response can now render its own xhtml - cleaned up LoncapaProblem.extract_html --- common/lib/capa/capa_problem.py | 119 ++++++++++++------------------- common/lib/capa/inputtypes.py | 71 ++++-------------- common/lib/capa/responsetypes.py | 40 ++++++++--- 3 files changed, 89 insertions(+), 141 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index f790190215..c63c13d420 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -23,7 +23,6 @@ import scipy import struct from lxml import etree -from lxml.etree import Element from xml.sax.saxutils import unescape from util import contextualize_text @@ -36,6 +35,7 @@ import eia log = logging.getLogger(__name__) +# dict of tagname, Response Class -- this should come from auto-registering response_types = {'numericalresponse': NumericalResponse, 'formularesponse': FormulaResponse, 'customresponse': CustomResponse, @@ -47,20 +47,13 @@ response_types = {'numericalresponse': NumericalResponse, 'optionresponse': OptionResponse, 'symbolicresponse': SymbolicResponse, } -entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput'] -solution_types = ['solution'] # extra things displayed after "show answers" is pressed -response_properties = ["responseparam", "answer"] # these get captured as student responses -# How to convert from original XML to HTML -# We should do this with xlst later +entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput'] +solution_types = ['solution'] # extra things displayed after "show answers" is pressed +response_properties = ["responseparam", "answer"] # these get captured as student responses + +# special problem tags which should be turned into innocuous HTML html_transforms = {'problem': {'tag': 'div'}, - "numericalresponse": {'tag': 'span'}, - "customresponse": {'tag': 'span'}, - "externalresponse": {'tag': 'span'}, - "schematicresponse": {'tag': 'span'}, - "formularesponse": {'tag': 'span'}, - "symbolicresponse": {'tag': 'span'}, - "multiplechoiceresponse": {'tag': 'span'}, "text": {'tag': 'span'}, "math": {'tag': 'span'}, } @@ -74,18 +67,6 @@ global_context = {'random': random, # These should be removed from HTML output, including all subelements html_problem_semantics = ["responseparam", "answer", "script"] -# These should be removed from HTML output, but keeping subelements -html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text", "externalresponse", 'symbolicresponse'] - -# removed in MC -## These should be transformed -#html_special_response = {"textline":inputtypes.textline.render, -# "schematic":inputtypes.schematic.render, -# "textbox":inputtypes.textbox.render, -# "formulainput":inputtypes.jstextline.render, -# "solution":inputtypes.solution.render, -# } - class LoncapaProblem(object): ''' @@ -142,7 +123,8 @@ class LoncapaProblem(object): self.context = self.extract_context(self.tree, seed=self.seed) # pre-parse the XML tree: modifies it to add ID's and perform some in-place transformations - # this also creates the list (self.responders) of Response instances for each question in the problem + # this also creates the dict (self.responders) of Response instances for each question in the problem. + # the dict has keys = xml subtree of Response, values = Response instance self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers) def __unicode__(self): @@ -166,7 +148,7 @@ class LoncapaProblem(object): used to give complex problems (eg programming questions) multiple points. ''' maxscore = 0 - for responder in self.responders: + for responder in self.responders.values(): if hasattr(responder,'get_max_score'): try: maxscore += responder.get_max_score() @@ -182,6 +164,10 @@ class LoncapaProblem(object): return maxscore def get_score(self): + ''' + Compute score for this problem. The score is the number of points awarded. + Returns an integer, from 0 to get_max_score(). + ''' correct = 0 for key in self.correct_map: if self.correct_map[key] == u'correct': @@ -206,7 +192,7 @@ class LoncapaProblem(object): self.student_answers = answers self.correct_map = dict() log.info('%s: in grade_answers, answers=%s' % (self,answers)) - for responder in self.responders: + for responder in self.responders.values(): results = responder.get_score(answers) # call the responsetype instance to do the actual grading self.correct_map.update(results) return self.correct_map @@ -218,24 +204,14 @@ class LoncapaProblem(object): (see capa_module) """ answer_map = dict() - for responder in self.responders: + for responder in self.responders.values(): results = responder.get_answers() answer_map.update(results) # dict of (id,correct_answer) - # This should be handled in each responsetype, not here. - # example for the following: - for responder in self.responders: - for entry in responder.inputfields: - answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline - if answer: - answer_map[entry.get('id')] = contextualize_text(answer, self.context) - # include solutions from ... stanzas - # Tentative merge; we should figure out how we want to handle hints and solutions for entry in self.tree.xpath("//" + "|//".join(solution_types)): answer = etree.tostring(entry) - if answer: - answer_map[entry.get('id')] = answer + if answer: answer_map[entry.get('id')] = answer return answer_map @@ -244,7 +220,7 @@ class LoncapaProblem(object): the dicts returned by grade_answers and get_question_answers. (Though get_question_answers may only return a subset of these.""" answer_ids = [] - for responder in self.responders: + for responder in self.responders.values(): answer_ids.append(responder.get_answers().keys()) return answer_ids @@ -252,7 +228,7 @@ class LoncapaProblem(object): ''' Main method called externally to get the HTML to be rendered for this capa Problem. ''' - return contextualize_text(etree.tostring(self.extract_html(self.tree)[0]), self.context) + return contextualize_text(etree.tostring(self.extract_html(self.tree)), self.context) # ======= Private ======== def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private @@ -264,12 +240,11 @@ class LoncapaProblem(object): Problem XML goes to Python execution context. Runs everything in script tags ''' random.seed(self.seed) - context = {'global_context': global_context} # save global context in here also - context.update(global_context) # initialize context to have stuff in global_context - context['__builtins__'] = globals()['__builtins__'] # put globals there also - context['the_lcp'] = self # pass instance of LoncapaProblem in + context = {'global_context': global_context} # save global context in here also + context.update(global_context) # initialize context to have stuff in global_context + context['__builtins__'] = globals()['__builtins__'] # put globals there also + context['the_lcp'] = self # pass instance of LoncapaProblem in - #for script in tree.xpath('/problem/script'): for script in tree.findall('.//script'): stype = script.get('type') if stype: @@ -288,16 +263,20 @@ class LoncapaProblem(object): return context def extract_html(self, problemtree): # private - ''' Helper function for get_html. Recursively converts XML tree to HTML + ''' + Main (private) function which converts Problem XML tree to HTML. + Calls itself recursively. + + Returns Element tree of XHTML representation of problemtree. + Calls render_html of Response instances to render responses into XHTML. + + Used by get_html. ''' if problemtree.tag in html_problem_semantics: return problemid = problemtree.get('id') # my ID - # used to be - # if problemtree.tag in html_special_response: - if problemtree.tag in inputtypes.get_input_xml_tags(): # status is currently the answer for the problem ID for the input element, # but it will turn into a dict containing both the answer and any associated message @@ -334,31 +313,25 @@ class LoncapaProblem(object): use='capa_input') return render_object.get_html() # function(problemtree, value, status, msg) # render the special response (textline, schematic,...) - tree = Element(problemtree.tag) + if problemtree in self.responders: # let each Response render itself + return self.responders[problemtree].render_html(self.extract_html) + + tree = etree.Element(problemtree.tag) for item in problemtree: - subitems = self.extract_html(item) - if subitems is not None: - for subitem in subitems: - tree.append(subitem) - for (key, value) in problemtree.items(): - tree.set(key, value) + item_xhtml = self.extract_html(item) # nothing special: recurse + if item_xhtml is not None: + tree.append(item_xhtml) + + if tree.tag in html_transforms: + tree.tag = html_transforms[problemtree.tag]['tag'] + else: + for (key, value) in problemtree.items(): # copy attributes over if not innocufying + tree.set(key, value) tree.text = problemtree.text tree.tail = problemtree.tail - if problemtree.tag in html_transforms: - tree.tag = html_transforms[problemtree.tag]['tag'] - # Reset attributes. Otherwise, we get metadata in HTML - # (e.g. answers) - # TODO: We should remove and not zero them. - # I'm not sure how to do that quickly with lxml - for k in tree.keys(): - tree.set(k, "") - - # TODO: Fix. This loses Element().tail - #if problemtree.tag in html_skip: - # return tree - return [tree] + return tree def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()): # private ''' @@ -370,7 +343,7 @@ class LoncapaProblem(object): Also create capa Response instances for each responsetype and save as self.responders ''' response_id = 1 - self.responders = [] + self.responders = {} for response in tree.xpath('//' + "|//".join(response_types)): response_id_str = self.problem_id + "_" + str(response_id) response.attrib['id'] = response_id_str # create and save ID for this response @@ -389,7 +362,7 @@ class LoncapaProblem(object): answer_id = answer_id + 1 responder = response_types[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response - self.responders.append(responder) # save in list in self + self.responders[response] = responder # save in list in self # ... may not be associated with any specific response; give IDs for those separately # TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i). diff --git a/common/lib/capa/inputtypes.py b/common/lib/capa/inputtypes.py index 3b25be3db7..10fbdb7f98 100644 --- a/common/lib/capa/inputtypes.py +++ b/common/lib/capa/inputtypes.py @@ -33,26 +33,17 @@ def get_input_xml_tags(): class SimpleInput():# XModule ''' Type for simple inputs -- plain HTML with a form element + State is a dictionary with optional keys: * Value * ID * Status (answered, unanswered, unsubmitted) * Feedback (dictionary containing keys for hints, errors, or other feedback from previous attempt) + ''' xml_tags = {} ## Maps tags to functions - - @classmethod - def get_xml_tags(c): - return c.xml_tags.keys() - - @classmethod - def get_uses(c): - return ['capa_input', 'capa_transform'] - - def get_html(self): - return self.xml_tags[self.tag](self.xml, self.value, self.status, self.system.render_template, self.msg) def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'): self.xml = xml @@ -83,49 +74,16 @@ class SimpleInput():# XModule if 'status' in state: self.status = state['status'] -## TODO -# class SimpleTransform(): -# ''' Type for simple XML to HTML transforms. Examples: -# * Math tags, which go from LON-CAPA-style m-tags to MathJAX -# ''' -# xml_tags = {} ## Maps tags to functions - -# @classmethod -# def get_xml_tags(c): -# return c.xml_tags.keys() + @classmethod + def get_xml_tags(c): + return c.xml_tags.keys() -# @classmethod -# def get_uses(c): -# return ['capa_transform'] - -# def get_html(self): -# return self.xml_tags[self.tag](self.xml, self.value, self.status, self.msg) - -# def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'): -# self.xml = xml -# self.tag = xml.tag -# if not state: -# state = {} -# if item_id: -# self.id = item_id -# if xml.get('id'): -# self.id = xml.get('id') -# if 'id' in state: -# self.id = state['id'] -# self.system = system - -# self.value = '' -# if 'value' in state: -# self.value = state['value'] - -# self.msg = '' -# if 'feedback' in state and 'message' in state['feedback']: -# self.msg = state['feedback']['message'] - -# self.status = 'unanswered' -# if 'status' in state: -# self.status = state['status'] + @classmethod + def get_uses(c): + return ['capa_input', 'capa_transform'] + def get_html(self): + return self.xml_tags[self.tag](self.xml, self.value, self.status, self.system.render_template, self.msg) def register_render_function(fn, names=None, cls=SimpleInput): if names is None: @@ -136,9 +94,6 @@ def register_render_function(fn, names=None, cls=SimpleInput): return fn return wrapped - - - #----------------------------------------------------------------------------- @register_render_function @@ -201,16 +156,16 @@ def choicegroup(element, value, status, render_template, msg=''): return etree.XML(html) @register_render_function -def textline(element, value, state, render_template, msg=""): +def textline(element, value, status, render_template, msg=""): ''' Simple text line input, with optional size specification. ''' if element.get('math') or element.get('dojs'): # 'dojs' flag is temporary, for backwards compatibility with 8.02x - return SimpleInput.xml_tags['textline_dynamath'](element,value,state,render_template,msg) + return SimpleInput.xml_tags['textline_dynamath'](element,value,status,render_template,msg) eid=element.get('id') count = int(eid.split('_')[-2])-1 # HACK size = element.get('size') - context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size, 'msg': msg} + context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg} html = render_template("textinput.html", context) return etree.XML(html) diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index 1c09493b03..bfd42814f7 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -63,7 +63,8 @@ class GenericResponse(object): - get_max_score : if defined, this is called to obtain the maximum score possible for this question - setup_response : find and note the answer input field IDs for the response; called by __init__ - - __unicode__ : unicode representation of this Response + - render_html : render this Response as HTML (must return XHTML compliant string) + - __unicode__ : unicode representation of this Response Each response type may also specify the following attributes: @@ -114,9 +115,30 @@ class GenericResponse(object): if self.max_inputfields==1: self.answer_id = self.answer_ids[0] # for convenience + self.default_answer_map = {} # dict for default answer map (provided in input elements) + for entry in self.inputfields: + answer = entry.get('correct_answer') + if answer: + self.default_answer_map[entry.get('id')] = contextualize_text(answer, self.context) + if hasattr(self,'setup_response'): self.setup_response() + def render_html(self,renderer): + ''' + Return XHTML Element tree representation of this Response. + + Arguments: + + - renderer : procedure which produces HTML given an ElementTree + ''' + tree = etree.Element('span') # render ourself as a + our content + for item in self.xml: + item_xhtml = renderer(item) # call provided procedure to do the rendering + if item_xhtml is not None: tree.append(item_xhtml) + tree.tail = self.xml.tail + return tree + @abc.abstractmethod def get_score(self, student_answers): ''' @@ -132,7 +154,6 @@ class GenericResponse(object): ''' pass - #not an abstract method because plenty of responses will not want to preprocess anything, and we should not require that they override this method. def setup_response(self): pass @@ -485,17 +506,17 @@ def sympy_check2(): ''' Give correct answer expected for this response. - capa_problem handles correct_answers from entry objects like textline, and that - is what should be used when this response has multiple entry objects. + use default_answer_map from entry elements (eg textline), + when this response has multiple entry objects. but for simplicity, if an "expect" attribute was given by the content author - ie then return it now. + ie then that. ''' if len(self.answer_ids)>1: - return {} + return self.default_answer_map if self.expect: return {self.answer_ids[0] : self.expect} - return {} + return self.default_answer_map #----------------------------------------------------------------------------- @@ -797,9 +818,8 @@ class SchematicResponse(GenericResponse): return zip(sorted(self.answer_ids), self.context['correct']) def get_answers(self): - # Since this is explicitly specified in the problem, this will - # be handled by capa_problem - return {} + # use answers provided in input elements + return self.default_answer_map #----------------------------------------------------------------------------- From 9891ef3f04bf182251eaabae5e4670483e52e6d2 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sat, 9 Jun 2012 23:29:08 -0400 Subject: [PATCH 014/123] third pass in capa cleanup: correct_map -> CorrectMap - added correctmap.py with CorrectMap class - messages subsumed into CorrectMap - response get_score called with old CorrectMap so hints based on history are possible --- common/lib/capa/capa_problem.py | 83 +++++++++++++++------------- common/lib/capa/correctmap.py | 80 +++++++++++++++++++++++++++ common/lib/capa/responsetypes.py | 92 +++++++++++++++++-------------- common/lib/xmodule/capa_module.py | 25 +++------ 4 files changed, 183 insertions(+), 97 deletions(-) create mode 100644 common/lib/capa/correctmap.py diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index c63c13d420..93d5620aae 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -25,15 +25,14 @@ import struct from lxml import etree from xml.sax.saxutils import unescape -from util import contextualize_text -import inputtypes - -from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse - import calc +from correctmap import CorrectMap import eia +import inputtypes +from util import contextualize_text -log = logging.getLogger(__name__) +# to be replaced with auto-registering +from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse # dict of tagname, Response Class -- this should come from auto-registering response_types = {'numericalresponse': NumericalResponse, @@ -68,6 +67,12 @@ global_context = {'random': random, # These should be removed from HTML output, including all subelements html_problem_semantics = ["responseparam", "answer", "script"] +#log = logging.getLogger(__name__) +log = logging.getLogger('mitx.common.lib.capa.capa_problem') + +#----------------------------------------------------------------------------- +# main class for this module + class LoncapaProblem(object): ''' Main class for capa Problems. @@ -89,9 +94,7 @@ class LoncapaProblem(object): ''' ## Initialize class variables from state - self.student_answers = dict() - self.correct_map = dict() - self.done = False + self.do_reset() self.problem_id = id self.system = system self.seed = seed @@ -102,7 +105,7 @@ class LoncapaProblem(object): if 'student_answers' in state: self.student_answers = state['student_answers'] if 'correct_map' in state: - self.correct_map = state['correct_map'] + self.correct_map.set_dict(state['correct_map']) if 'done' in state: self.done = state['done'] @@ -125,7 +128,15 @@ class LoncapaProblem(object): # pre-parse the XML tree: modifies it to add ID's and perform some in-place transformations # this also creates the dict (self.responders) of Response instances for each question in the problem. # the dict has keys = xml subtree of Response, values = Response instance - self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers) + self.preprocess_problem(self.tree, answer_map=self.student_answers) + + def do_reset(self): + ''' + Reset internal state to unfinished, with no answers + ''' + self.student_answers = dict() + self.correct_map = CorrectMap() + self.done = False def __unicode__(self): return u"LoncapaProblem ({0})".format(self.fileobject) @@ -134,9 +145,10 @@ class LoncapaProblem(object): ''' Stored per-user session data neeeded to: 1) Recreate the problem 2) Populate any student answers. ''' + return {'seed': self.seed, 'student_answers': self.student_answers, - 'correct_map': self.correct_map, + 'correct_map': self.correct_map.get_dict(), 'done': self.done} def get_max_score(self): @@ -170,8 +182,12 @@ class LoncapaProblem(object): ''' correct = 0 for key in self.correct_map: - if self.correct_map[key] == u'correct': - correct += 1 + try: + correct += self.correct_map.get_npoints(key) + except Exception,err: + log.error('key=%s, correct_map = %s' % (key,self.correct_map)) + raise + if (not self.student_answers) or len(self.student_answers) == 0: return {'score': 0, 'total': self.get_max_score()} @@ -190,12 +206,14 @@ class LoncapaProblem(object): Calles the Response for each question in this problem, to do the actual grading. ''' self.student_answers = answers - self.correct_map = dict() - log.info('%s: in grade_answers, answers=%s' % (self,answers)) + oldcmap = self.correct_map # old CorrectMap + newcmap = CorrectMap() # start new with empty CorrectMap for responder in self.responders.values(): - results = responder.get_score(answers) # call the responsetype instance to do the actual grading - self.correct_map.update(results) - return self.correct_map + results = responder.get_score(answers,oldcmap) # call the responsetype instance to do the actual grading + newcmap.update(results) + self.correct_map = newcmap + log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap)) + return newcmap def get_question_answers(self): """Returns a dict of answer_ids to answer values. If we cannot generate @@ -282,27 +300,17 @@ class LoncapaProblem(object): # but it will turn into a dict containing both the answer and any associated message # for the problem ID for the input element. status = "unsubmitted" + msg = '' if problemid in self.correct_map: - status = self.correct_map[problemtree.get('id')] + pid = problemtree.get('id') + status = self.correct_map.get_correctness(pid) + msg = self.correct_map.get_msg(pid) value = "" if self.student_answers and problemid in self.student_answers: value = self.student_answers[problemid] - #### This code is a hack. It was merged to help bring two branches - #### in sync, but should be replaced. msg should be passed in a - #### response_type - # prepare the response message, if it exists in correct_map - if 'msg' in self.correct_map: - msg = self.correct_map['msg'] - elif ('msg_%s' % problemid) in self.correct_map: - msg = self.correct_map['msg_%s' % problemid] - else: - msg = '' - # do the rendering - # This should be broken out into a helper function - # that handles all input objects render_object = inputtypes.SimpleInput(system=self.system, xml=problemtree, state={'value': value, @@ -333,7 +341,7 @@ class LoncapaProblem(object): return tree - def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()): # private + def preprocess_problem(self, tree, answer_map=dict()): # private ''' Assign IDs to all the responses Assign sub-IDs to all entries (textline, schematic, etc.) @@ -346,11 +354,8 @@ class LoncapaProblem(object): self.responders = {} for response in tree.xpath('//' + "|//".join(response_types)): response_id_str = self.problem_id + "_" + str(response_id) - response.attrib['id'] = response_id_str # create and save ID for this response - - # if response_id not in correct_map: correct = 'unsubmitted' # unused - to be removed - # response.attrib['state'] = correct - response_id += response_id + response.set('id',response_id_str) # create and save ID for this response + response_id += 1 answer_id = 1 inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x for x in (entry_types + solution_types)]), diff --git a/common/lib/capa/correctmap.py b/common/lib/capa/correctmap.py new file mode 100644 index 0000000000..3eac98cc3a --- /dev/null +++ b/common/lib/capa/correctmap.py @@ -0,0 +1,80 @@ +#----------------------------------------------------------------------------- +# class used to store graded responses to CAPA questions +# +# Used by responsetypes and capa_problem + +class CorrectMap(object): + ''' + Stores (correctness, npoints, msg) for each answer_id. + Behaves as a dict. + ''' + cmap = {} + + def __init__(self,*args,**kwargs): + self.set(*args,**kwargs) + + def set(self,answer_id=None,correctness=None,npoints=None,msg=''): + if answer_id is not None: + self.cmap[answer_id] = {'correctness': correctness, + 'npoints': npoints, + 'msg': msg } + + def __repr__(self): + return repr(self.cmap) + + def get_dict(self): + ''' + return dict version of self + ''' + return self.cmap + + def set_dict(self,correct_map): + ''' + set internal dict to provided correct_map dict + for graceful migration, if correct_map is a one-level dict, then convert it to the new + dict of dicts format. + ''' + if correct_map and not (type(correct_map[correct_map.keys()[0]])==dict): + for k in self.cmap.keys(): self.cmap.pop(k) # empty current dict + for k in correct_map: self.set(k,correct_map[k]) # create new dict entries + else: + self.cmap = correct_map + + def is_correct(self,answer_id): + if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] == 'correct' + return None + + def get_npoints(self,answer_id): + if self.is_correct(answer_id): + npoints = self.cmap[answer_id].get('npoints',1) # default to 1 point if correct + return npoints or 1 + return 0 # if not correct, return 0 + + def set_property(self,answer_id,property,value): + if answer_id in self.cmap: self.cmap[answer_id][property] = value + else: self.cmap[answer_id] = {property:value} + + def get_property(self,answer_id,property,default=None): + if answer_id in self.cmap: return self.cmap[answer_id].get(property,default) + return default + + def get_correctness(self,answer_id): + return self.get_property(answer_id,'correctness') + + def get_msg(self,answer_id): + return self.get_property(answer_id,'msg','') + + def update(self,other_cmap): + ''' + Update this CorrectMap with the contents of another CorrectMap + ''' + if not isinstance(other_cmap,CorrectMap): + raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap) + self.cmap.update(other_cmap.get_dict()) + + __getitem__ = cmap.__getitem__ + __iter__ = cmap.__iter__ + items = cmap.items + keys = cmap.keys + + diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index bfd42814f7..2de9e27893 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -21,6 +21,7 @@ import abc # specific library imports from calc import evaluator, UndefinedVariable +from correctmap import CorrectMap from util import * from lxml import etree from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? @@ -53,7 +54,7 @@ class StudentInputError(Exception): class GenericResponse(object): ''' Base class for CAPA responsetypes. Each response type (ie a capa question, - which is part of a capa problem) is represented as a superclass, + which is part of a capa problem) is represented as a subclass, which should provide the following methods: - get_score : evaluate the given student answers, and return a CorrectMap @@ -140,10 +141,16 @@ class GenericResponse(object): return tree @abc.abstractmethod - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): ''' Return a CorrectMap for the answers expected vs given. This includes (correctness, npoints, msg) for each answer_id. + + Arguments: + + - student_answers : dict of (answer_id,answer) where answer = student input (string) + - old_cmap : previous CorrectMap (may be empty); useful for analyzing or recording history of responses + ''' pass @@ -201,15 +208,15 @@ class MultipleChoiceResponse(GenericResponse): else: choice.set("name", "choice_"+choice.get("name")) - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): ''' grade student response. ''' # log.debug('%s: student_answers=%s, correct_choices=%s' % (unicode(self),student_answers,self.correct_choices)) if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices: - return {self.answer_id:'correct'} + return CorrectMap(self.answer_id,'correct') else: - return {self.answer_id:'incorrect'} + return CorrectMap(self.answer_id,'incorrect') def get_answers(self): return {self.answer_id:self.correct_choices} @@ -226,14 +233,14 @@ class TrueFalseResponse(MultipleChoiceResponse): else: choice.set("name", "choice_"+choice.get("name")) - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): correct = set(self.correct_choices) answers = set(student_answers.get(self.answer_id, [])) if correct == answers: - return { self.answer_id : 'correct'} + return CorrectMap( self.answer_id , 'correct') - return {self.answer_id : 'incorrect'} + return CorrectMap(self.answer_id ,'incorrect') #----------------------------------------------------------------------------- @@ -251,15 +258,15 @@ class OptionResponse(GenericResponse): def setup_response(self): self.answer_fields = self.inputfields - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): # log.debug('%s: student_answers=%s' % (unicode(self),student_answers)) - cmap = {} + cmap = CorrectMap() amap = self.get_answers() for aid in amap: if aid in student_answers and student_answers[aid]==amap[aid]: - cmap[aid] = 'correct' + cmap.set(aid,'correct') else: - cmap[aid] = 'incorrect' + cmap.set(aid,'incorrect') return cmap def get_answers(self): @@ -291,7 +298,7 @@ class NumericalResponse(GenericResponse): except Exception: self.answer_id = None - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): '''Grade a numeric response ''' student_answer = student_answers[self.answer_id] try: @@ -303,9 +310,9 @@ class NumericalResponse(GenericResponse): raise StudentInputError('Invalid input -- please use a number only') if correct: - return {self.answer_id:'correct'} + return CorrectMap(self.answer_id,'correct') else: - return {self.answer_id:'incorrect'} + return CorrectMap(self.answer_id,'incorrect') def get_answers(self): return {self.answer_id:self.correct_answer} @@ -395,7 +402,7 @@ def sympy_check2(): else: self.code = answer.text - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): ''' student_answers is a dict with everything from request.POST, but with the first part of each key removed (the string before the first "_"). @@ -495,12 +502,10 @@ def sympy_check2(): correct = ['correct']*len(idset) if ret else ['incorrect']*len(idset) # build map giving "correct"ness of the answer(s) - #correct_map = dict(zip(idset, self.context['correct'])) - correct_map = {} + correct_map = CorrectMap() for k in range(len(idset)): - correct_map[idset[k]] = correct[k] - correct_map['msg_%s' % idset[k]] = messages[k] - return correct_map + correct_map.set(idset[k], correct[k], msg=messages[k]) + return correct_map def get_answers(self): ''' @@ -642,9 +647,11 @@ main() return rxml - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): + idset = sorted(self.answer_ids) + cmap = CorrectMap() try: - submission = [student_answers[k] for k in sorted(self.answer_ids)] + submission = [student_answers[k] for k in idset] except Exception,err: log.error('Error %s: cannot get student answer for %s; student_answers=%s' % (err,self.answer_ids,student_answers)) raise Exception,err @@ -658,9 +665,9 @@ main() except Exception, err: log.error('Error %s' % err) if self.system.DEBUG: - correct_map = dict(zip(sorted(self.answer_ids), ['incorrect'] * len(self.answer_ids) )) - correct_map['msg_%s' % self.answer_ids[0]] = '%s' % str(err).replace('<','<') - return correct_map + cmap.set_dict(dict(zip(sorted(self.answer_ids), ['incorrect'] * len(idset) ))) + cmap.set_property(self.answer_ids[0],'msg','%s' % str(err).replace('<','<')) + return cmap ad = rxml.find('awarddetail').text admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses @@ -670,13 +677,13 @@ main() if ad in admap: self.context['correct'][0] = admap[ad] - # self.context['correct'] = ['correct','correct'] - correct_map = dict(zip(sorted(self.answer_ids), self.context['correct'])) - - # store message in correct_map - correct_map['msg_%s' % self.answer_ids[0]] = rxml.find('message').text.replace(' ',' ') + # create CorrectMap + for key in idset: + idx = idset.index(key) + msg = rxml.find('message').text.replace(' ',' ') if idx==0 else None + cmap.set(key, self.context['correct'][idx], msg=msg) - return correct_map + return cmap def get_answers(self): ''' @@ -750,7 +757,7 @@ class FormulaResponse(GenericResponse): else: # Default self.case_sensitive = False - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): variables=self.samples.split('@')[0].split(',') numsamples=int(self.samples.split('@')[1].split('#')[1]) sranges=zip(*map(lambda x:map(float, x.split(",")), @@ -776,11 +783,11 @@ class FormulaResponse(GenericResponse): #traceback.print_exc() raise StudentInputError("Error in formula") if numpy.isnan(student_result) or numpy.isinf(student_result): - return {self.answer_id:"incorrect"} + return CorrectMap(self.answer_id, "incorrect") if not compare_with_tolerance(student_result, instructor_result, self.tolerance): - return {self.answer_id:"incorrect"} + return CorrectMap(self.answer_id, "incorrect") - return {self.answer_id:"correct"} + return CorrectMap(self.answer_id, "correct") def strip_dict(self, d): ''' Takes a dict. Returns an identical dict, with all non-word @@ -810,12 +817,13 @@ class SchematicResponse(GenericResponse): else: self.code = answer.text - def get_score(self, student_answers): + def get_score(self, student_answers, old_cmap): from capa_problem import global_context submission = [json.loads(student_answers[k]) for k in sorted(self.answer_ids)] self.context.update({'submission':submission}) exec self.code in global_context, self.context - return zip(sorted(self.answer_ids), self.context['correct']) + cmap = CorrectMap() + return cmap.set_dict(zip(sorted(self.answer_ids), self.context['correct'])) def get_answers(self): # use answers provided in input elements @@ -845,8 +853,8 @@ class ImageResponse(GenericResponse): self.ielements = self.inputfields self.answer_ids = [ie.get('id') for ie in self.ielements] - def get_score(self, student_answers): - correct_map = {} + def get_score(self, student_answers, old_cmap): + correct_map = CorrectMap() expectedset = self.get_answers() for aid in self.answer_ids: # loop through IDs of fields in our stanza @@ -869,9 +877,9 @@ class ImageResponse(GenericResponse): # answer is correct if (x,y) is within the specified rectangle if (llx <= gx <= urx) and (lly <= gy <= ury): - correct_map[aid] = 'correct' + correct_map.set(aid, 'correct') else: - correct_map[aid] = 'incorrect' + correct_map.set(aid, 'incorrect') return correct_map def get_answers(self): diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 6bd7cbebdc..439982a2c1 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -13,6 +13,7 @@ from lxml import etree from x_module import XModule, XModuleDescriptor from capa.capa_problem import LoncapaProblem from capa.responsetypes import StudentInputError + log = logging.getLogger("mitx.courseware") #----------------------------------------------------------------------------- @@ -365,18 +366,17 @@ class Module(XModule): self.attempts = self.attempts + 1 self.lcp.done=True - success = 'correct' - for i in correct_map: - if correct_map[i]!='correct': + success = 'correct' # success = correct if ALL questions in this problem are correct + for answer_id in correct_map: + if not correct_map.is_correct(answer_id): success = 'incorrect' - event_info['correct_map']=correct_map + event_info['correct_map']=correct_map.get_dict() # log this in the tracker event_info['success']=success - self.tracker('save_problem_check', event_info) try: - html = self.get_problem_html(encapsulate=False) + html = self.get_problem_html(encapsulate=False) # render problem into HTML except Exception,err: log.error('failed to generate html') raise Exception,err @@ -430,17 +430,10 @@ class Module(XModule): self.tracker('reset_problem_fail', event_info) return "Refresh the page and make an attempt before resetting." - self.lcp.done=False - self.lcp.answers=dict() - self.lcp.correct_map=dict() - self.lcp.student_answers = dict() - - + self.lcp.do_reset() # call method in LoncapaProblem to reset itself if self.rerandomize == "always": - self.lcp.context=dict() - self.lcp.questions=dict() # Detailed info about questions in problem instance. TODO: Should be by id and not lid. - self.lcp.seed=None - + self.lcp.seed=None # reset random number generator seed (note the self.lcp.get_state() in next line) + self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, self.lcp.get_state(), system=self.system) event_info['new_state']=self.lcp.get_state() From 743e79453a80e8f812c72f6b0e5586cd4ae11810 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 17:17:57 -0400 Subject: [PATCH 015/123] fourth pass in capa cleanup: - Added hints + hintmethod - hintgroup compatible with loncapa spec - also does hintfn for custom hints (can do answer history) - GenericResponse -> LoncapaResponse - moved response type tags into responsetype classes - capa_problem should use __future__ division - hints stored in CorrectMap, copied to 'feedback' in SimpleInput for display --- common/lib/capa/capa_problem.py | 40 +++--- common/lib/capa/correctmap.py | 32 ++++- common/lib/capa/inputtypes.py | 55 +++++--- common/lib/capa/responsetypes.py | 226 +++++++++++++++++++++++++------ common/lib/capa/util.py | 5 + 5 files changed, 272 insertions(+), 86 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index 93d5620aae..b14001ef03 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -12,6 +12,8 @@ Main module which shows problems (of "capa" type). This is used by capa_module. ''' +from __future__ import division + import copy import logging import math @@ -32,20 +34,10 @@ import inputtypes from util import contextualize_text # to be replaced with auto-registering -from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse +import responsetypes # dict of tagname, Response Class -- this should come from auto-registering -response_types = {'numericalresponse': NumericalResponse, - 'formularesponse': FormulaResponse, - 'customresponse': CustomResponse, - 'schematicresponse': SchematicResponse, - 'externalresponse': ExternalResponse, - 'multiplechoiceresponse': MultipleChoiceResponse, - 'truefalseresponse': TrueFalseResponse, - 'imageresponse': ImageResponse, - 'optionresponse': OptionResponse, - 'symbolicresponse': SymbolicResponse, - } +response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__]) entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput'] solution_types = ['solution'] # extra things displayed after "show answers" is pressed @@ -65,7 +57,7 @@ global_context = {'random': random, 'eia': eia} # These should be removed from HTML output, including all subelements -html_problem_semantics = ["responseparam", "answer", "script"] +html_problem_semantics = ["responseparam", "answer", "script","hintgroup"] #log = logging.getLogger(__name__) log = logging.getLogger('mitx.common.lib.capa.capa_problem') @@ -209,7 +201,7 @@ class LoncapaProblem(object): oldcmap = self.correct_map # old CorrectMap newcmap = CorrectMap() # start new with empty CorrectMap for responder in self.responders.values(): - results = responder.get_score(answers,oldcmap) # call the responsetype instance to do the actual grading + results = responder.evaluate_answers(answers,oldcmap) # call the responsetype instance to do the actual grading newcmap.update(results) self.correct_map = newcmap log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap)) @@ -248,7 +240,8 @@ class LoncapaProblem(object): ''' return contextualize_text(etree.tostring(self.extract_html(self.tree)), self.context) - # ======= Private ======== + # ======= Private Methods Below ======== + def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private ''' Extract content of from the problem.xml file, and exec it in the @@ -296,15 +289,17 @@ class LoncapaProblem(object): problemid = problemtree.get('id') # my ID if problemtree.tag in inputtypes.get_input_xml_tags(): - # status is currently the answer for the problem ID for the input element, - # but it will turn into a dict containing both the answer and any associated message - # for the problem ID for the input element. + status = "unsubmitted" msg = '' + hint = '' + hintmode = None if problemid in self.correct_map: pid = problemtree.get('id') status = self.correct_map.get_correctness(pid) msg = self.correct_map.get_msg(pid) + hint = self.correct_map.get_hint(pid) + hintmode = self.correct_map.get_hintmode(pid) value = "" if self.student_answers and problemid in self.student_answers: @@ -316,7 +311,10 @@ class LoncapaProblem(object): state={'value': value, 'status': status, 'id': problemtree.get('id'), - 'feedback': {'message': msg} + 'feedback': {'message': msg, + 'hint' : hint, + 'hintmode' : hintmode, + } }, use='capa_input') return render_object.get_html() # function(problemtree, value, status, msg) # render the special response (textline, schematic,...) @@ -352,7 +350,7 @@ class LoncapaProblem(object): ''' response_id = 1 self.responders = {} - for response in tree.xpath('//' + "|//".join(response_types)): + for response in tree.xpath('//' + "|//".join(response_tag_dict)): response_id_str = self.problem_id + "_" + str(response_id) response.set('id',response_id_str) # create and save ID for this response response_id += 1 @@ -366,7 +364,7 @@ class LoncapaProblem(object): entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id) answer_id = answer_id + 1 - responder = response_types[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response + responder = response_tag_dict[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response self.responders[response] = responder # save in list in self # ... may not be associated with any specific response; give IDs for those separately diff --git a/common/lib/capa/correctmap.py b/common/lib/capa/correctmap.py index 3eac98cc3a..f694391cc6 100644 --- a/common/lib/capa/correctmap.py +++ b/common/lib/capa/correctmap.py @@ -5,7 +5,16 @@ class CorrectMap(object): ''' - Stores (correctness, npoints, msg) for each answer_id. + Stores map between answer_id and response evaluation result for each question + in a capa problem. The response evaluation result for each answer_id includes + (correctness, npoints, msg, hint, hintmode). + + - correctness : either 'correct' or 'incorrect' + - npoints : None, or integer specifying number of points awarded for this answer_id + - msg : string (may have HTML) giving extra message response (displayed below textline or textbox) + - hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg) + - hintmode : one of (None,'on_request','always') criteria for displaying hint + Behaves as a dict. ''' cmap = {} @@ -13,11 +22,14 @@ class CorrectMap(object): def __init__(self,*args,**kwargs): self.set(*args,**kwargs) - def set(self,answer_id=None,correctness=None,npoints=None,msg=''): + def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None): if answer_id is not None: self.cmap[answer_id] = {'correctness': correctness, 'npoints': npoints, - 'msg': msg } + 'msg': msg, + 'hint' : hint, + 'hintmode' : hintmode, + } def __repr__(self): return repr(self.cmap) @@ -64,6 +76,20 @@ class CorrectMap(object): def get_msg(self,answer_id): return self.get_property(answer_id,'msg','') + def get_hint(self,answer_id): + return self.get_property(answer_id,'hint','') + + def get_hintmode(self,answer_id): + return self.get_property(answer_id,'hintmode',None) + + def set_hint_and_mode(self,answer_id,hint,hintmode): + ''' + - hint : (string) HTML text for hint + - hintmode : (string) mode for hint display ('always' or 'on_request') + ''' + self.set_property(answer_id,'hint',hint) + self.set_property(answer_id,'hintmode',hintmode) + def update(self,other_cmap): ''' Update this CorrectMap with the contents of another CorrectMap diff --git a/common/lib/capa/inputtypes.py b/common/lib/capa/inputtypes.py index 10fbdb7f98..1fa51f2f84 100644 --- a/common/lib/capa/inputtypes.py +++ b/common/lib/capa/inputtypes.py @@ -32,44 +32,57 @@ def get_input_xml_tags(): return SimpleInput.get_xml_tags() class SimpleInput():# XModule - ''' Type for simple inputs -- plain HTML with a form element - - State is a dictionary with optional keys: - * Value - * ID - * Status (answered, unanswered, unsubmitted) - * Feedback (dictionary containing keys for hints, errors, or other - feedback from previous attempt) - + ''' + Type for simple inputs -- plain HTML with a form element ''' xml_tags = {} ## Maps tags to functions def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'): + ''' + Instantiate a SimpleInput class. Arguments: + + - system : I4xSystem instance which provides OS, rendering, and user context + - xml : Element tree of this Input element + - item_id : id for this input element (assigned by capa_problem.LoncapProblem) - string + - track_url : URL used for tracking - string + - state : a dictionary with optional keys: + * Value + * ID + * Status (answered, unanswered, unsubmitted) + * Feedback (dictionary containing keys for hints, errors, or other + feedback from previous attempt) + - use : + ''' + self.xml = xml self.tag = xml.tag - if not state: - state = {} + self.system = system + if not state: state = {} + ## ID should only come from one place. ## If it comes from multiple, we use state first, XML second, and parameter ## third. Since we don't make this guarantee, we can swap this around in ## the future if there's a more logical order. - if item_id: - self.id = item_id - if xml.get('id'): - self.id = xml.get('id') - if 'id' in state: - self.id = state['id'] - self.system = system + if item_id: self.id = item_id + if xml.get('id'): self.id = xml.get('id') + if 'id' in state: self.id = state['id'] self.value = '' if 'value' in state: self.value = state['value'] self.msg = '' - if 'feedback' in state and 'message' in state['feedback']: - self.msg = state['feedback']['message'] - + feedback = state.get('feedback') + if feedback is not None: + self.msg = feedback.get('message','') + self.hint = feedback.get('hint','') + self.hintmode = feedback.get('hintmode',None) + + # put hint above msg if to be displayed + if self.hintmode == 'always': + self.msg = self.hint + ('
    ' if self.msg else '') + self.msg + self.status = 'unanswered' if 'status' in state: self.status = state['status'] diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index 2de9e27893..5a5296d805 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -51,7 +51,7 @@ class StudentInputError(Exception): # # Main base class for CAPA responsetypes -class GenericResponse(object): +class LoncapaResponse(object): ''' Base class for CAPA responsetypes. Each response type (ie a capa question, which is part of a capa problem) is represented as a subclass, @@ -60,22 +60,31 @@ class GenericResponse(object): - get_score : evaluate the given student answers, and return a CorrectMap - get_answers : provide a dict of the expected answers for this problem + Each subclass must also define the following attributes: + + - response_tag : xhtml tag identifying this response (used in auto-registering) + In addition, these methods are optional: - - get_max_score : if defined, this is called to obtain the maximum score possible for this question - - setup_response : find and note the answer input field IDs for the response; called by __init__ - - render_html : render this Response as HTML (must return XHTML compliant string) - - __unicode__ : unicode representation of this Response + - get_max_score : if defined, this is called to obtain the maximum score possible for this question + - setup_response : find and note the answer input field IDs for the response; called by __init__ + - check_hint_condition : check to see if the student's answers satisfy a particular condition for a hint to be displayed + - render_html : render this Response as HTML (must return XHTML compliant string) + - __unicode__ : unicode representation of this Response Each response type may also specify the following attributes: - - max_inputfields : (int) maximum number of answer input fields (checked in __init__ if not None) - - allowed_inputfields : list of allowed input fields (each a string) for this Response - - required_attributes : list of required attributes (each a string) on the main response XML stanza + - max_inputfields : (int) maximum number of answer input fields (checked in __init__ if not None) + - allowed_inputfields : list of allowed input fields (each a string) for this Response + - required_attributes : list of required attributes (each a string) on the main response XML stanza + - hint_tag : xhtml tag identifying hint associated with this response inside hintgroup ''' __metaclass__=abc.ABCMeta # abc = Abstract Base Class + response_tag = None + hint_tag = None + max_inputfields = None allowed_inputfields = [] required_attributes = [] @@ -85,7 +94,7 @@ class GenericResponse(object): Init is passed the following arguments: - xml : ElementTree of this Response - - inputfields : list of ElementTrees for each input entry field in this Response + - inputfields : ordered list of ElementTrees for each input entry field in this Response - context : script processor context - system : I4xSystem instance which provides OS, rendering, and user context @@ -112,7 +121,7 @@ class GenericResponse(object): msg += "\nSee XML source line %s" % getattr(xml,'sourceline','') raise LoncapaProblemError(msg) - self.answer_ids = [x.get('id') for x in self.inputfields] + self.answer_ids = [x.get('id') for x in self.inputfields] # ordered list of answer_id values for this response if self.max_inputfields==1: self.answer_id = self.answer_ids[0] # for convenience @@ -140,8 +149,85 @@ class GenericResponse(object): tree.tail = self.xml.tail return tree + def evaluate_answers(self,student_answers,old_cmap): + ''' + Called by capa_problem.LoncapaProblem to evaluate student answers, and to + generate hints (if any). + + Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id. + ''' + new_cmap = self.get_score(student_answers) + self.get_hints(student_answers, new_cmap, old_cmap) + return new_cmap + + def get_hints(self, student_answers, new_cmap, old_cmap): + ''' + Generate adaptive hints for this problem based on student answers, the old CorrectMap, + and the new CorrectMap produced by get_score. + + Does not return anything. + + Modifies new_cmap, by adding hints to answer_id entries as appropriate. + ''' + hintgroup = self.xml.find('hintgroup') + if hintgroup is None: return + + # hint specified by function? + hintfn = hintgroup.get('hintfn') + if hintfn: + ''' + Hint is determined by a function defined in the @@ -358,6 +463,7 @@ def sympy_check2():
    '''}] + response_tag = 'customresponse' allowed_inputfields = ['textline','textbox'] def setup_response(self): @@ -402,7 +508,7 @@ def sympy_check2(): else: self.code = answer.text - def get_score(self, student_answers, old_cmap): + def get_score(self, student_answers): ''' student_answers is a dict with everything from request.POST, but with the first part of each key removed (the string before the first "_"). @@ -540,6 +646,8 @@ class SymbolicResponse(CustomResponse): '''}] + response_tag = 'symbolicresponse' + def setup_response(self): self.xml.set('cfn','symmath_check') code = "from symmath import *" @@ -548,7 +656,7 @@ class SymbolicResponse(CustomResponse): #----------------------------------------------------------------------------- -class ExternalResponse(GenericResponse): +class ExternalResponse(LoncapaResponse): ''' Grade the students input using an external server. @@ -594,6 +702,7 @@ main() '''}] + response_tag = 'externalresponse' allowed_inputfields = ['textline','textbox'] def setup_response(self): @@ -647,7 +756,7 @@ main() return rxml - def get_score(self, student_answers, old_cmap): + def get_score(self, student_answers): idset = sorted(self.answer_ids) cmap = CorrectMap() try: @@ -707,7 +816,7 @@ main() #----------------------------------------------------------------------------- -class FormulaResponse(GenericResponse): +class FormulaResponse(LoncapaResponse): ''' Checking of symbolic math response using numerical sampling. ''' @@ -729,6 +838,8 @@ class FormulaResponse(GenericResponse): '''}] + response_tag = 'formularesponse' + hint_tag = 'formulahint' allowed_inputfields = ['textline'] required_attributes = ['answer'] max_inputfields = 1 @@ -743,7 +854,7 @@ class FormulaResponse(GenericResponse): id=xml.get('id'))[0] self.tolerance = contextualize_text(self.tolerance_xml, context) except Exception: - self.tolerance = 0 + self.tolerance = '0.00001' ts = xml.get('type') if ts is None: @@ -757,11 +868,16 @@ class FormulaResponse(GenericResponse): else: # Default self.case_sensitive = False - def get_score(self, student_answers, old_cmap): - variables=self.samples.split('@')[0].split(',') - numsamples=int(self.samples.split('@')[1].split('#')[1]) + def get_score(self, student_answers): + given = student_answers[self.answer_id] + correctness = self.check_formula(self.correct_answer, given, self.samples) + return CorrectMap(self.answer_id, correctness) + + def check_formula(self,expected, given, samples): + variables=samples.split('@')[0].split(',') + numsamples=int(samples.split('@')[1].split('#')[1]) sranges=zip(*map(lambda x:map(float, x.split(",")), - self.samples.split('@')[1].split('#')[0].split(':'))) + samples.split('@')[1].split('#')[0].split(':'))) ranges=dict(zip(variables, sranges)) for i in range(numsamples): @@ -771,23 +887,26 @@ class FormulaResponse(GenericResponse): value = random.uniform(*ranges[var]) instructor_variables[str(var)] = value student_variables[str(var)] = value - instructor_result = evaluator(instructor_variables,dict(),self.correct_answer, cs = self.case_sensitive) + #log.debug('formula: instructor_vars=%s, expected=%s' % (instructor_variables,expected)) + instructor_result = evaluator(instructor_variables,dict(),expected, cs = self.case_sensitive) try: - #print student_variables,dict(),student_answers[self.answer_id] - student_result = evaluator(student_variables,dict(), - student_answers[self.answer_id], + #log.debug('formula: student_vars=%s, given=%s' % (student_variables,given)) + student_result = evaluator(student_variables, + dict(), + given, cs = self.case_sensitive) except UndefinedVariable as uv: + log.debbug('formularesponse: undefined variable in given=%s' % given) raise StudentInputError(uv.message+" not permitted in answer") - except: + except Exception, err: #traceback.print_exc() + log.debug('formularesponse: error %s in formula' % err) raise StudentInputError("Error in formula") if numpy.isnan(student_result) or numpy.isinf(student_result): - return CorrectMap(self.answer_id, "incorrect") + return "incorrect" if not compare_with_tolerance(student_result, instructor_result, self.tolerance): - return CorrectMap(self.answer_id, "incorrect") - - return CorrectMap(self.answer_id, "correct") + return "incorrect" + return "correct" def strip_dict(self, d): ''' Takes a dict. Returns an identical dict, with all non-word @@ -799,13 +918,30 @@ class FormulaResponse(GenericResponse): isinstance(d[k], numbers.Number)]) return d + def check_hint_condition(self,hxml_set,student_answers): + given = student_answers[self.answer_id] + hints_to_show = [] + for hxml in hxml_set: + samples = hxml.get('samples') + name = hxml.get('name') + correct_answer = contextualize_text(hxml.get('answer'),self.context) + try: + correctness = self.check_formula(correct_answer, given, samples) + except Exception,err: + correctness = 'incorrect' + if correctness=='correct': + hints_to_show.append(name) + log.debug('hints_to_show = %s' % hints_to_show) + return hints_to_show + def get_answers(self): return {self.answer_id:self.correct_answer} #----------------------------------------------------------------------------- -class SchematicResponse(GenericResponse): +class SchematicResponse(LoncapaResponse): + response_tag = 'schematicresponse' allowed_inputfields = ['schematic'] def setup_response(self): @@ -817,7 +953,7 @@ class SchematicResponse(GenericResponse): else: self.code = answer.text - def get_score(self, student_answers, old_cmap): + def get_score(self, student_answers): from capa_problem import global_context submission = [json.loads(student_answers[k]) for k in sorted(self.answer_ids)] self.context.update({'submission':submission}) @@ -831,7 +967,7 @@ class SchematicResponse(GenericResponse): #----------------------------------------------------------------------------- -class ImageResponse(GenericResponse): +class ImageResponse(LoncapaResponse): """ Handle student response for image input: the input is a click on an image, which produces an [x,y] coordinate pair. The click is correct if it falls @@ -847,13 +983,14 @@ class ImageResponse(GenericResponse): '''}] + response_tag = 'imageresponse' allowed_inputfields = ['imageinput'] def setup_response(self): self.ielements = self.inputfields self.answer_ids = [ie.get('id') for ie in self.ielements] - def get_score(self, student_answers, old_cmap): + def get_score(self, student_answers): correct_map = CorrectMap() expectedset = self.get_answers() @@ -884,3 +1021,10 @@ class ImageResponse(GenericResponse): def get_answers(self): return dict([(ie.get('id'),ie.get('rectangle')) for ie in self.ielements]) + +#----------------------------------------------------------------------------- +# TEMPORARY: List of all response subclasses +# FIXME: To be replaced by auto-registration + +__all__ = [ NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse ] + diff --git a/common/lib/capa/util.py b/common/lib/capa/util.py index 996f6c8dac..f1cc8f859e 100644 --- a/common/lib/capa/util.py +++ b/common/lib/capa/util.py @@ -7,6 +7,11 @@ from calc import evaluator, UndefinedVariable def compare_with_tolerance(v1, v2, tol): ''' Compare v1 to v2 with maximum tolerance tol tol is relative if it ends in %; otherwise, it is absolute + + - v1 : student result (number) + - v2 : instructor result (number) + - tol : tolerance (string or number) + ''' relative = "%" in tol if relative: From 2e97d8f6759e458e362276e217298b0db9fb1db7 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 18:41:54 -0400 Subject: [PATCH 016/123] bugfixes - correctmap should reinit self.cmap on init --- common/lib/capa/capa_problem.py | 1 + common/lib/capa/correctmap.py | 16 +++++++++------- common/lib/capa/responsetypes.py | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index b14001ef03..a341f954bc 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -200,6 +200,7 @@ class LoncapaProblem(object): self.student_answers = answers oldcmap = self.correct_map # old CorrectMap newcmap = CorrectMap() # start new with empty CorrectMap + log.debug('Responders: %s' % self.responders) for responder in self.responders.values(): results = responder.evaluate_answers(answers,oldcmap) # call the responsetype instance to do the actual grading newcmap.update(results) diff --git a/common/lib/capa/correctmap.py b/common/lib/capa/correctmap.py index f694391cc6..ec3fed54c2 100644 --- a/common/lib/capa/correctmap.py +++ b/common/lib/capa/correctmap.py @@ -17,11 +17,17 @@ class CorrectMap(object): Behaves as a dict. ''' - cmap = {} - def __init__(self,*args,**kwargs): + self.cmap = dict() # start with empty dict + self.__getitem__ = self.cmap.__getitem__ + self.__iter__ = self.cmap.__iter__ + self.items = self.cmap.items + self.keys = self.cmap.keys self.set(*args,**kwargs) + def __iter__(self): + return self.cmap.__iter__() + def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None): if answer_id is not None: self.cmap[answer_id] = {'correctness': correctness, @@ -47,7 +53,7 @@ class CorrectMap(object): dict of dicts format. ''' if correct_map and not (type(correct_map[correct_map.keys()[0]])==dict): - for k in self.cmap.keys(): self.cmap.pop(k) # empty current dict + self.__init__() # empty current dict for k in correct_map: self.set(k,correct_map[k]) # create new dict entries else: self.cmap = correct_map @@ -98,9 +104,5 @@ class CorrectMap(object): raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap) self.cmap.update(other_cmap.get_dict()) - __getitem__ = cmap.__getitem__ - __iter__ = cmap.__iter__ - items = cmap.items - keys = cmap.keys diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index 5a5296d805..a63cb991cf 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -158,6 +158,7 @@ class LoncapaResponse(object): ''' new_cmap = self.get_score(student_answers) self.get_hints(student_answers, new_cmap, old_cmap) + # log.debug('new_cmap = %s' % new_cmap) return new_cmap def get_hints(self, student_answers, new_cmap, old_cmap): @@ -492,7 +493,7 @@ def sympy_check2(): if cfn in self.context: self.code = self.context[cfn] else: - msg = "%s: can't find cfn in context = %s" % (unicode(self),self.context) + msg = "%s: can't find cfn %s in context" % (unicode(self),cfn) msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','') raise LoncapaProblemError(msg) @@ -896,7 +897,7 @@ class FormulaResponse(LoncapaResponse): given, cs = self.case_sensitive) except UndefinedVariable as uv: - log.debbug('formularesponse: undefined variable in given=%s' % given) + log.debug('formularesponse: undefined variable in given=%s' % given) raise StudentInputError(uv.message+" not permitted in answer") except Exception, err: #traceback.print_exc() From 21e2ebee8ba7b114d51f41975b96d19183eb9865 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 20:04:30 -0400 Subject: [PATCH 017/123] problem.coffee change: show answer -> also show solution_* & do mathjax typeset --- lms/static/coffee/src/modules/problem.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index e1e062e949..1f17d01405 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -48,11 +48,15 @@ class @Problem @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' else - @$("#answer_#{key}").html(value) // needs to be html, not text, for complex solutions (eg coding) + @$("#answer_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) + @$("#solution_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) + MathJax.Hub.Queue ["Typeset", MathJax.Hub] + MathJax.Hub.Queue ["Typeset", MathJax.Hub] @$('.show').val 'Hide Answer' @element.addClass 'showed' else @$('[id^=answer_]').text '' + @$('[id^=solution_]').text '' @$('[correct_answer]').attr correct_answer: null @element.removeClass 'showed' @$('.show').val 'Show Answer' From cd5a4314e2f5a4bbe49dd4737756cf626762ecc6 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 20:05:33 -0400 Subject: [PATCH 018/123] django pipeline working now (with new pip -e git+git...) --- common/lib/capa/capa_problem.py | 1 + lms/envs/dev_ike.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index a341f954bc..16ecb9186a 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -224,6 +224,7 @@ class LoncapaProblem(object): answer = etree.tostring(entry) if answer: answer_map[entry.get('id')] = answer + log.debug('answer_map = %s' % answer_map) return answer_map def get_answer_ids(self): diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index ee5b6e831b..0930ef4651 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -27,7 +27,7 @@ DEBUG = True ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome) QUICKEDIT = True -MITX_FEATURES['USE_DJANGO_PIPELINE'] = False +# MITX_FEATURES['USE_DJANGO_PIPELINE'] = False COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x', 'title' : 'Circuits and Electronics', From c5d0610da7a08b4bdc4c2f15f3e90e4ae4a08e33 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 20:52:10 -0400 Subject: [PATCH 019/123] fixes to schematicresponse to work with new CorrectMap --- common/lib/capa/responsetypes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index a63cb991cf..947a66b13c 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -960,7 +960,8 @@ class SchematicResponse(LoncapaResponse): self.context.update({'submission':submission}) exec self.code in global_context, self.context cmap = CorrectMap() - return cmap.set_dict(zip(sorted(self.answer_ids), self.context['correct'])) + cmap.set_dict(dict(zip(sorted(self.answer_ids), self.context['correct']))) + return cmap def get_answers(self): # use answers provided in input elements From d51604c78052cb723a3b38740d476a3413152506 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 21:05:21 -0400 Subject: [PATCH 020/123] fix xmodule/capa tests to use new CorrectMap --- common/lib/capa/capa_problem.py | 4 ++-- common/lib/xmodule/tests.py | 34 ++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index 16ecb9186a..80ec8806f3 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -200,12 +200,12 @@ class LoncapaProblem(object): self.student_answers = answers oldcmap = self.correct_map # old CorrectMap newcmap = CorrectMap() # start new with empty CorrectMap - log.debug('Responders: %s' % self.responders) + # log.debug('Responders: %s' % self.responders) for responder in self.responders.values(): results = responder.evaluate_answers(answers,oldcmap) # call the responsetype instance to do the actual grading newcmap.update(results) self.correct_map = newcmap - log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap)) + # log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap)) return newcmap def get_question_answers(self): diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 69e69aa1d9..f8187269e9 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -1,9 +1,9 @@ # -# unittests for courseware +# unittests for xmodule (and capa) # # Note: run this using a like like this: # -# django-admin.py test --settings=envs.test_ike --pythonpath=. courseware +# django-admin.py test --settings=lms.envs.test_ike --pythonpath=. common/lib/xmodule import unittest import os @@ -96,31 +96,31 @@ class MultiChoiceTest(unittest.TestCase): multichoice_file = os.path.dirname(__file__)+"/test_files/multichoice.xml" test_lcp = lcp.LoncapaProblem(open(multichoice_file), '1', system=i4xs) correct_answers = {'1_2_1':'choice_foil3'} - self.assertEquals(test_lcp.grade_answers(correct_answers)['1_2_1'], 'correct') + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') false_answers = {'1_2_1':'choice_foil2'} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') def test_MC_bare_grades(self): multichoice_file = os.path.dirname(__file__)+"/test_files/multi_bare.xml" test_lcp = lcp.LoncapaProblem(open(multichoice_file), '1', system=i4xs) correct_answers = {'1_2_1':'choice_2'} - self.assertEquals(test_lcp.grade_answers(correct_answers)['1_2_1'], 'correct') + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') false_answers = {'1_2_1':'choice_1'} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') def test_TF_grade(self): truefalse_file = os.path.dirname(__file__)+"/test_files/truefalse.xml" test_lcp = lcp.LoncapaProblem(open(truefalse_file), '1', system=i4xs) correct_answers = {'1_2_1':['choice_foil2', 'choice_foil1']} - self.assertEquals(test_lcp.grade_answers(correct_answers)['1_2_1'], 'correct') + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') false_answers = {'1_2_1':['choice_foil1']} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') false_answers = {'1_2_1':['choice_foil1', 'choice_foil3']} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') false_answers = {'1_2_1':['choice_foil3']} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') false_answers = {'1_2_1':['choice_foil1', 'choice_foil2', 'choice_foil3']} - self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect') class ImageResponseTest(unittest.TestCase): def test_ir_grade(self): @@ -131,8 +131,8 @@ class ImageResponseTest(unittest.TestCase): test_answers = {'1_2_1':'[500,20]', '1_2_2':'[250,300]', } - self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct') - self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect') class SymbolicResponseTest(unittest.TestCase): def test_sr_grade(self): @@ -220,8 +220,8 @@ class SymbolicResponseTest(unittest.TestCase): ''', } - self.assertEquals(test_lcp.grade_answers(correct_answers)['1_2_1'], 'correct') - self.assertEquals(test_lcp.grade_answers(wrong_answers)['1_2_1'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') + self.assertEquals(test_lcp.grade_answers(wrong_answers).get_correctness('1_2_1'), 'incorrect') class OptionResponseTest(unittest.TestCase): ''' @@ -237,8 +237,8 @@ class OptionResponseTest(unittest.TestCase): test_answers = {'1_2_1':'True', '1_2_2':'True', } - self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct') - self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect') #----------------------------------------------------------------------------- # Grading tests From 734ee6d8fe606645be31177c412c05631bb2b47f Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 21:11:04 -0400 Subject: [PATCH 021/123] fix i4xs in tests.py; move symbolicresponse.xml test back to where it should be --- .../xmodule/test_files/{test_files => }/symbolicresponse.xml | 0 common/lib/xmodule/tests.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename common/lib/xmodule/test_files/{test_files => }/symbolicresponse.xml (100%) diff --git a/common/lib/xmodule/test_files/test_files/symbolicresponse.xml b/common/lib/xmodule/test_files/symbolicresponse.xml similarity index 100% rename from common/lib/xmodule/test_files/test_files/symbolicresponse.xml rename to common/lib/xmodule/test_files/symbolicresponse.xml diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index f8187269e9..00711ce0da 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -28,12 +28,13 @@ class I4xSystem(object): self.track_function = lambda x: None self.render_function = lambda x: {} # Probably incorrect self.exception404 = Exception + self.DEBUG = True def __repr__(self): return repr(self.__dict__) def __str__(self): return str(self.__dict__) -i4xs = I4xSystem +i4xs = I4xSystem() class ModelsTest(unittest.TestCase): def setUp(self): @@ -136,7 +137,7 @@ class ImageResponseTest(unittest.TestCase): class SymbolicResponseTest(unittest.TestCase): def test_sr_grade(self): - raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test + # raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test symbolicresponse_file = os.path.dirname(__file__)+"/test_files/symbolicresponse.xml" test_lcp = lcp.LoncapaProblem(open(symbolicresponse_file), '1', system=i4xs) correct_answers = {'1_2_1':'cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]', From 37a554975ec415104e95b55a308126087c95062a Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 21:11:43 -0400 Subject: [PATCH 022/123] SymbolicResponseTest works if snuggletex war running ; back to skipping it for now --- common/lib/xmodule/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 00711ce0da..1ba423983c 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -137,7 +137,7 @@ class ImageResponseTest(unittest.TestCase): class SymbolicResponseTest(unittest.TestCase): def test_sr_grade(self): - # raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test + raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test symbolicresponse_file = os.path.dirname(__file__)+"/test_files/symbolicresponse.xml" test_lcp = lcp.LoncapaProblem(open(symbolicresponse_file), '1', system=i4xs) correct_answers = {'1_2_1':'cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]', From fe94985fd56a28ca9c4bb43ed635d756cdb44fda Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 21:27:11 -0400 Subject: [PATCH 023/123] add FormulaResponseWithHintTest test --- common/lib/xmodule/tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 1ba423983c..6f054b4bfb 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -241,6 +241,21 @@ class OptionResponseTest(unittest.TestCase): self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect') +class FormulaResponseWithHintTest(unittest.TestCase): + ''' + Test Formula response problem with a hint + This problem also uses calc. + ''' + def test_or_grade(self): + problem_file = os.path.dirname(__file__)+"/test_files/formularesponse_with_hint.xml" + test_lcp = lcp.LoncapaProblem(open(problem_file), '1', system=i4xs) + correct_answers = {'1_2_1':'2.5*x-5.0'} + test_answers = {'1_2_1':'0.4*x-5.0'} + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') + cmap = test_lcp.grade_answers(test_answers) + self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect') + self.assertTrue('You have inverted' in cmap.get_hint('1_2_1')) + #----------------------------------------------------------------------------- # Grading tests From ea22aa8670e30a975ec8aca6bf1d377882d0e8a1 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 21:59:29 -0400 Subject: [PATCH 024/123] added StringResponse (with hints) for hints, still to be done: numericalhint, optionhint no default hint processing done yet (ie hintmode = on_request) --- common/lib/capa/inputtypes.py | 4 ++++ common/lib/capa/responsetypes.py | 38 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/inputtypes.py b/common/lib/capa/inputtypes.py index 1fa51f2f84..75588e8aea 100644 --- a/common/lib/capa/inputtypes.py +++ b/common/lib/capa/inputtypes.py @@ -176,6 +176,10 @@ def textline(element, value, status, render_template, msg=""): if element.get('math') or element.get('dojs'): # 'dojs' flag is temporary, for backwards compatibility with 8.02x return SimpleInput.xml_tags['textline_dynamath'](element,value,status,render_template,msg) eid=element.get('id') + if eid is None: + msg = 'textline has no id: it probably appears outside of a known response type' + msg += "\nSee problem XML source line %s" % getattr(element,'sourceline','') + raise Exception(msg) count = int(eid.split('_')[-2])-1 # HACK size = element.get('size') context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg} diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index 947a66b13c..cf7310f92e 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -425,6 +425,42 @@ class NumericalResponse(LoncapaResponse): #----------------------------------------------------------------------------- +class StringResponse(LoncapaResponse): + + response_tag = 'stringresponse' + hint_tag = 'stringhint' + allowed_inputfields = ['textline'] + required_attributes = ['answer'] + max_inputfields = 1 + + def setup_response(self): + self.correct_answer = contextualize_text(self.xml.get('answer'), self.context).strip() + + def get_score(self, student_answers): + '''Grade a string response ''' + student_answer = student_answers[self.answer_id].strip() + correct = self.check_string(self.correct_answer,student_answer) + return CorrectMap(self.answer_id,'correct' if correct else 'incorrect') + + def check_string(self,expected,given): + if self.xml.get('type')=='ci': return given.lower() == expected.lower() + return given == expected + + def check_hint_condition(self,hxml_set,student_answers): + given = student_answers[self.answer_id].strip() + hints_to_show = [] + for hxml in hxml_set: + name = hxml.get('name') + correct_answer = contextualize_text(hxml.get('answer'),self.context).strip() + if self.check_string(correct_answer,given): hints_to_show.append(name) + log.debug('hints_to_show = %s' % hints_to_show) + return hints_to_show + + def get_answers(self): + return {self.answer_id:self.correct_answer} + +#----------------------------------------------------------------------------- + class CustomResponse(LoncapaResponse): ''' Custom response. The python code to be run should be in ... @@ -1028,5 +1064,5 @@ class ImageResponse(LoncapaResponse): # TEMPORARY: List of all response subclasses # FIXME: To be replaced by auto-registration -__all__ = [ NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse ] +__all__ = [ NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse ] From cd9e4672d1b8198ef1f6189e3484ffe6986295f8 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 22:06:13 -0400 Subject: [PATCH 025/123] added stringresponse_with_hint test --- .../test_files/formularesponse_with_hint.xml | 45 +++++++++++++++++++ .../test_files/stringresponse_with_hint.xml | 25 +++++++++++ common/lib/xmodule/tests.py | 14 ++++++ 3 files changed, 84 insertions(+) create mode 100644 common/lib/xmodule/test_files/formularesponse_with_hint.xml create mode 100644 common/lib/xmodule/test_files/stringresponse_with_hint.xml diff --git a/common/lib/xmodule/test_files/formularesponse_with_hint.xml b/common/lib/xmodule/test_files/formularesponse_with_hint.xml new file mode 100644 index 0000000000..e5b3e28708 --- /dev/null +++ b/common/lib/xmodule/test_files/formularesponse_with_hint.xml @@ -0,0 +1,45 @@ + + + + +

    Hints can be provided to students, based on the last response given, as well as the history of responses given. Here is an example of a hint produced by a Formula Response problem.

    + +

    +What is the equation of the line which passess through ($x1,$y1) and +($x2,$y2)?

    + +

    The correct answer is $answer. A common error is to invert the equation for the slope. Enter +$wrongans to see a hint.

    + +
    + + + + y = + + + + + You have inverted the slope in the question. + + + +
    + diff --git a/common/lib/xmodule/test_files/stringresponse_with_hint.xml b/common/lib/xmodule/test_files/stringresponse_with_hint.xml new file mode 100644 index 0000000000..86efdf0f18 --- /dev/null +++ b/common/lib/xmodule/test_files/stringresponse_with_hint.xml @@ -0,0 +1,25 @@ + +

    Example: String Response Problem

    +
    +
    + + Which US state has Lansing as its capital? + + + + + + + + + The state capital of Wisconsin is Madison. + + + The state capital of Minnesota is St. Paul. + + + The state you are looking for is also known as the 'Great Lakes State' + + + +
    diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 6f054b4bfb..370b3befe5 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -256,6 +256,20 @@ class FormulaResponseWithHintTest(unittest.TestCase): self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect') self.assertTrue('You have inverted' in cmap.get_hint('1_2_1')) +class StringResponseWithHintTest(unittest.TestCase): + ''' + Test String response problem with a hint + ''' + def test_or_grade(self): + problem_file = os.path.dirname(__file__)+"/test_files/stringresponse_with_hint.xml" + test_lcp = lcp.LoncapaProblem(open(problem_file), '1', system=i4xs) + correct_answers = {'1_2_1':'Michigan'} + test_answers = {'1_2_1':'Minnesota'} + self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct') + cmap = test_lcp.grade_answers(test_answers) + self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect') + self.assertTrue('St. Paul' in cmap.get_hint('1_2_1')) + #----------------------------------------------------------------------------- # Grading tests From 25860ca1fb518137cdf8ba0bacf6b251d9b8d037 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 22:20:56 -0400 Subject: [PATCH 026/123] fix capa_problems pep8 --- common/lib/capa/capa_problem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index 80ec8806f3..eed6953521 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -146,7 +146,7 @@ class LoncapaProblem(object): def get_max_score(self): ''' Return maximum score for this problem. - We do this by counting the number of answers available for each question + We do this by counting the number of answers available for each question in the problem. If the Response for a question has a get_max_score() method then we call that and add its return value to the count. That can be used to give complex problems (eg programming questions) multiple points. @@ -351,7 +351,7 @@ class LoncapaProblem(object): Also create capa Response instances for each responsetype and save as self.responders ''' response_id = 1 - self.responders = {} + self.responders = {} for response in tree.xpath('//' + "|//".join(response_tag_dict)): response_id_str = self.problem_id + "_" + str(response_id) response.set('id',response_id_str) # create and save ID for this response @@ -367,7 +367,7 @@ class LoncapaProblem(object): answer_id = answer_id + 1 responder = response_tag_dict[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response - self.responders[response] = responder # save in list in self + self.responders[response] = responder # save in list in self # ... may not be associated with any specific response; give IDs for those separately # TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i). From bf5e0402ecd8964c7da38f750a6534044bcca3e2 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 22:27:40 -0400 Subject: [PATCH 027/123] capa_problem and responsetypes pep8 and pyflakes --- common/lib/capa/capa_problem.py | 5 ++--- common/lib/capa/responsetypes.py | 23 +++++++++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index eed6953521..516c72494b 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -14,7 +14,6 @@ This is used by capa_module. from __future__ import division -import copy import logging import math import numpy @@ -156,7 +155,7 @@ class LoncapaProblem(object): if hasattr(responder,'get_max_score'): try: maxscore += responder.get_max_score() - except Exception, err: + except Exception: log.error('responder %s failed to properly return from get_max_score()' % responder) raise else: @@ -176,7 +175,7 @@ class LoncapaProblem(object): for key in self.correct_map: try: correct += self.correct_map.get_npoints(key) - except Exception,err: + except Exception: log.error('key=%s, correct_map = %s' % (key,self.correct_map)) raise diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py index cf7310f92e..32c1c6783c 100644 --- a/common/lib/capa/responsetypes.py +++ b/common/lib/capa/responsetypes.py @@ -505,7 +505,6 @@ def sympy_check2(): def setup_response(self): xml = self.xml - context = self.context # if has an "expect" (or "answer") attribute then save that self.expect = xml.get('expect') or xml.get('answer') @@ -560,7 +559,7 @@ def sympy_check2(): msg = '[courseware.capa.responsetypes.customresponse] error getting student answer from %s' % student_answers msg += '\n idset = %s, error = %s' % (idset,err) log.error(msg) - raise Exception,msg + raise Exception(msg) # global variable in context which holds the Presentation MathML from dynamic math input dynamath = [ student_answers.get(k+'_dynamath',None) for k in idset ] # ordered list of dynamath responses @@ -623,7 +622,7 @@ def sympy_check2(): log.error("oops in customresponse (cfn) error %s" % err) # print "context = ",self.context log.error(traceback.format_exc()) - raise Exception,"oops in customresponse (cfn) error %s" % err + raise Exception("oops in customresponse (cfn) error %s" % err) log.debug("[courseware.capa.responsetypes.customresponse.get_score] ret = %s" % ret) if type(ret)==dict: correct = ['correct']*len(idset) if ret['ok'] else ['incorrect']*len(idset) @@ -777,19 +776,19 @@ main() except Exception,err: msg = 'Error %s - cannot connect to external server url=%s' % (err,self.url) log.error(msg) - raise Exception, msg + raise Exception(msg) if self.system.DEBUG: log.info('response = %s' % r.text) if (not r.text ) or (not r.text.strip()): - raise Exception,'Error: no response from external server url=%s' % self.url + raise Exception('Error: no response from external server url=%s' % self.url) try: rxml = etree.fromstring(r.text) # response is XML; prase it except Exception,err: msg = 'Error %s - cannot parse response from external server r.text=%s' % (err,r.text) log.error(msg) - raise Exception, msg + raise Exception(msg) return rxml @@ -800,7 +799,7 @@ main() submission = [student_answers[k] for k in idset] except Exception,err: log.error('Error %s: cannot get student answer for %s; student_answers=%s' % (err,self.answer_ids,student_answers)) - raise Exception,err + raise Exception(err) self.context.update({'submission':submission}) @@ -817,7 +816,7 @@ main() ad = rxml.find('awarddetail').text admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses - 'WRONG_FORMAT': 'incorrect', + 'WRONG_FORMAT': 'incorrect', } self.context['correct'] = ['correct'] if ad in admap: @@ -847,7 +846,7 @@ main() if not (len(exans)==len(self.answer_ids)): log.error('Expected %d answers from external server, only got %d!' % (len(self.answer_ids),len(exans))) - raise Exception,'Short response from external server' + raise Exception('Short response from external server') return dict(zip(self.answer_ids,exans)) @@ -964,7 +963,7 @@ class FormulaResponse(LoncapaResponse): correct_answer = contextualize_text(hxml.get('answer'),self.context) try: correctness = self.check_formula(correct_answer, given, samples) - except Exception,err: + except Exception: correctness = 'incorrect' if correctness=='correct': hints_to_show.append(name) @@ -1041,13 +1040,13 @@ class ImageResponse(LoncapaResponse): if not m: msg = 'Error in problem specification! cannot parse rectangle in %s' % (etree.tostring(self.ielements[aid], pretty_print=True)) - raise Exception,'[capamodule.capa.responsetypes.imageinput] '+msg + raise Exception('[capamodule.capa.responsetypes.imageinput] '+msg) (llx,lly,urx,ury) = [int(x) for x in m.groups()] # parse given answer m = re.match('\[([0-9]+),([0-9]+)]',given.strip().replace(' ','')) if not m: - raise Exception,'[capamodule.capa.responsetypes.imageinput] error grading %s (input=%s)' % (aid,given) + raise Exception('[capamodule.capa.responsetypes.imageinput] error grading %s (input=%s)' % (aid,given)) (gx,gy) = [int(x) for x in m.groups()] # answer is correct if (x,y) is within the specified rectangle From bc881e73d993c13bb72f575e0999e361472588bd Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 22:34:30 -0400 Subject: [PATCH 028/123] add MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] flag to settings --- lms/djangoapps/courseware/module_render.py | 2 +- lms/envs/common.py | 1 + lms/envs/dev_ike.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 0f82d9ba94..fd419a68dc 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -169,7 +169,7 @@ def render_x_module(user, request, xml_module, module_object_preload, position=N content = instance.get_html() # special extra information about each problem, only for users who are staff - if False and user.is_staff: + if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF') and user.is_staff: module_id = xml_module.get('id') histogram = grade_histogram(module_id) render_histogram = len(histogram) > 0 diff --git a/lms/envs/common.py b/lms/envs/common.py index 7d8b1ab3d0..d3416f8127 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -37,6 +37,7 @@ PERFSTATS = False MITX_FEATURES = { 'SAMPLE' : False, 'USE_DJANGO_PIPELINE' : True, + 'DISPLAY_HISTOGRAMS_TO_STAFF' : True, } # Used for A/B testing diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index 0930ef4651..af51274433 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -28,6 +28,7 @@ ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see QUICKEDIT = True # MITX_FEATURES['USE_DJANGO_PIPELINE'] = False +MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] = False COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x', 'title' : 'Circuits and Electronics', From cc6c773d99d90d74642741cef486f6685e1ebdb3 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 10 Jun 2012 22:39:16 -0400 Subject: [PATCH 029/123] remove loncapa import in formularesponse_with_hint test (jenkins import path not consistent with dev machines?) --- common/lib/xmodule/test_files/formularesponse_with_hint.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/test_files/formularesponse_with_hint.xml b/common/lib/xmodule/test_files/formularesponse_with_hint.xml index e5b3e28708..90248dcf04 100644 --- a/common/lib/xmodule/test_files/formularesponse_with_hint.xml +++ b/common/lib/xmodule/test_files/formularesponse_with_hint.xml @@ -1,6 +1,6 @@ from the problem.xml file, and exec it in the context of this problem. Provides ability to randomize problems, and also set @@ -273,7 +273,7 @@ class LoncapaProblem(object): log.exception("Error while execing code: " + code) return context - def extract_html(self, problemtree): # private + def _extract_html(self, problemtree): # private ''' Main (private) function which converts Problem XML tree to HTML. Calls itself recursively. @@ -320,11 +320,11 @@ class LoncapaProblem(object): return render_object.get_html() # function(problemtree, value, status, msg) # render the special response (textline, schematic,...) if problemtree in self.responders: # let each Response render itself - return self.responders[problemtree].render_html(self.extract_html) + return self.responders[problemtree].render_html(self._extract_html) tree = etree.Element(problemtree.tag) for item in problemtree: - item_xhtml = self.extract_html(item) # nothing special: recurse + item_xhtml = self._extract_html(item) # nothing special: recurse if item_xhtml is not None: tree.append(item_xhtml) @@ -339,7 +339,7 @@ class LoncapaProblem(object): return tree - def preprocess_problem(self, tree): # private + def _preprocess_problem(self, tree): # private ''' Assign IDs to all the responses Assign sub-IDs to all entries (textline, schematic, etc.) From 7e3866866a78af253641c5f3dfd4b364e60ac1a9 Mon Sep 17 00:00:00 2001 From: ichuang Date: Tue, 12 Jun 2012 13:52:34 -0400 Subject: [PATCH 044/123] typo in correctmap --- common/lib/capa/correctmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/correctmap.py b/common/lib/capa/correctmap.py index 63d4fb33b2..786b2f5e2d 100644 --- a/common/lib/capa/correctmap.py +++ b/common/lib/capa/correctmap.py @@ -24,7 +24,7 @@ class CorrectMap(object): self.set(*args,**kwargs) def __getitem__(self, *args, **kwargs): - return self.cmap.__getitem(*args, **kwargs) + return self.cmap.__getitem__(*args, **kwargs) def __iter__(self): return self.cmap.__iter__() From e4cf0ac835804d55337ebfa7c1d8b313e5588658 Mon Sep 17 00:00:00 2001 From: ichuang Date: Tue, 12 Jun 2012 14:07:48 -0400 Subject: [PATCH 045/123] problem.coffee : queue mathjax typesetting just once --- lms/static/coffee/src/modules/problem.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index 1f17d01405..17587b9ade 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -51,7 +51,6 @@ class @Problem @$("#answer_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) @$("#solution_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) MathJax.Hub.Queue ["Typeset", MathJax.Hub] - MathJax.Hub.Queue ["Typeset", MathJax.Hub] @$('.show').val 'Hide Answer' @element.addClass 'showed' else From 59a13e861b427d4e34f38cf1b299995edf170561 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 12 Jun 2012 14:08:43 -0400 Subject: [PATCH 046/123] Cleanup show answer code --- lms/static/coffee/src/modules/problem.coffee | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index 17587b9ade..aad41f23d4 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -45,17 +45,14 @@ class @Problem $.each response, (key, value) => if $.isArray(value) for choice in value - @$("label[for='input_#{key}_#{choice}']").attr - correct_answer: 'true' + @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' else - @$("#answer_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) - @$("#solution_#{key}").html(value) # needs to be html, not text, for complex solutions (eg coding) + @$("#answer_#{key}, #solution_#{key}").html(value) MathJax.Hub.Queue ["Typeset", MathJax.Hub] @$('.show').val 'Hide Answer' @element.addClass 'showed' else - @$('[id^=answer_]').text '' - @$('[id^=solution_]').text '' + @$('[id^=answer_], [id^=solution_]').text '' @$('[correct_answer]').attr correct_answer: null @element.removeClass 'showed' @$('.show').val 'Show Answer' From 51b4d1ff6e4b544e95e3ff18d963e4ff083e6c7e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 11 Jun 2012 15:48:33 -0400 Subject: [PATCH 047/123] added TODO comment --- common/lib/xmodule/capa_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 439982a2c1..c7b52ae8ba 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -281,6 +281,7 @@ class Module(XModule): def answer_available(self): ''' Is the user allowed to see an answer? + TODO: simplify. ''' if self.show_answer == '': return False From 30ce55092e170def7c2fcab802502b11ee325be2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 11 Jun 2012 15:49:39 -0400 Subject: [PATCH 048/123] Add rough overview docs --- doc/README | 3 ++ doc/overview.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/testing.md | 12 ++++++ 3 files changed, 119 insertions(+) create mode 100644 doc/README create mode 100644 doc/overview.md create mode 100644 doc/testing.md diff --git a/doc/README b/doc/README new file mode 100644 index 0000000000..65f3cd2cb5 --- /dev/null +++ b/doc/README @@ -0,0 +1,3 @@ +This directory contains some high level documentation for the code. We should strive to keep it up-to-date, but don't take it as the absolute truth. + +A good place to start is 'overview' diff --git a/doc/overview.md b/doc/overview.md new file mode 100644 index 0000000000..a53e7371c2 --- /dev/null +++ b/doc/overview.md @@ -0,0 +1,104 @@ +# Documentation for edX code (mitx repo) + +This document explains the general structure of the edX platform, and defines some of the acronyms and terms you'll see flying around in the code. + +## Assumptions: + +You should be familiar with the following. If you're not, go read some docs... + + - python + - django + - javascript + - html, xml -- xpath, xslt + - css + - git + - mako templates -- we use these instead of django templates, because they support embedding real python. + +## Other relevant terms + + - CAPA -- lon-capa.org -- content management system that has defined a standard for online learning and assessment materials. Many of our materials follow this standard. + - TODO: add more details / link to relevant docs. lon-capa.org is not immediately intuitive. + - lcp = loncapa problem + + +## Parts of the system + + - LMS -- Learning Management System. The student-facing parts of the system. Handles student accounts, displaying videos, tutorials, exercies, problems, etc. + + - CMS -- Course Management System. The instructor-facing parts of the system. Allows instructors to see and modify their course, add lectures, problems, reorder things, etc. + + - Askbot -- the discussion forums. We have a custom fork of this project. We're also hoping to replace it with something better later. (e.g. need support for multiple classes, etc) + + - Data. In the data/ dir. There is currently a single `course.xml` file that describes an entire course. Speaking of which... + + - Courses. A course is broken up into Chapters ("week 1", "week 2", etc). A chapter is broken up into Sections ("Lecture 1", "Simple Circuits Exercises", "HW1", etc). A section can contain modules: Problems, Html, Videos, Verticals, or Sequences. + - Problems: specified in problem files. May have python scripts embedded to both generate random parameters and check answers. Also allows specifying things like tolerance or precision in answers + - Html: any html - often description, or links to outside resources + - Videos: links to youtube or elsewhere + - Verticals: a nesting tag: collect several videos, problems, html modules and display them vertically. + - Sequences: a sequence of modules, displayed with a horizontal navigation bar, displaying one component at a time. + - see `data/course.xml` for more examples + + +## High Level Entities in the code + +### Common libraries + +- x_modules -- generic learning modules. *x* can be sequence, video, template, html, vertical, capa, etc. These are the things that one puts inside sections in the course structure. Modules know how to render themselves to html, how to score themselves, and handle ajax calls from the front end. + - x_modules take a 'system' context parameter, which is a reference to an object that knows how to render things, track events, complain about 404s, etc. (TODO: figure out, document the necessary interface--different in `x_module.XModule.__init__` and in `x_module tests.py`) + - in `common/lib/xmodule` + +- capa modules -- defines `LoncapaProblem` and many related things. + - in `common/lib/capa` + +### LMS + +The LMS is a django site, with root in `lms/`. It runs in many different environments--the settings files are in `lms/envs`. + +- We use the Django Auth system, including the is_staff and is_superuser flags. User profiles and related code lives in `lms/djangoapps/student/`. There is support for groups of students (e.g. 'want emails about future courses', 'have unenrolled', etc) in `lms/djangoapps/student/models.py`. + +- `StudentModule` -- keeps track of where a particular student is in a module (problem, video, html)--what's their grade, have they started, are they done, etc. [This is only partly implemented so far.] + - `lms/djangoapps/courseware/models.py` + +- Core rendering path: + - `lms/urls.py` points to `courseware.views.index`, which gets module info from the course xml file, pulls list of `StudentModule` objects for this user (to avoid multiple db hits). + + - Calls `render_accordion` to render the "accordion"--the display of the course structure. + + - To render the current module, calls `module_render.py:render_x_module()`, which gets the `StudentModule` instance, and passes the `StudentModule` state and other system context to the module constructor the get an instance of the appropriate module class for this user. + + - calls the module's `.get_html()` method. If the module has nested submodules, render_x_module() will be called again for each. + + - ajax calls go to `module_render.py:modx_dispatch()`, which passes it to the module's `handle_ajax()` function, and then updates the grade and state if they changed. + +- Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`. + +### Other modules + +- Wiki -- in `lms/djangoapps/simplewiki`. Has some markdown extentions for embedding circuits, videos, etc. + +## Testing + +See `testing.md`. + + +## QUESTIONS: + +`common/lib/capa` : what is eia, `eia.py`? Random lists of numbers? + +what is `lms/lib/dogfood`? Looks like a way to test capa problems... Is it being used? + +is lms/envs/README.txt out of date? + +## TODO: + +- Only describes backend code so far. How does the front-end work? + +- What big pieces are missing? + +- Where should reader go next? + +--- +Note: this file uses markdown. To convert to html, run: + + markdown2 overview.md > overview.html diff --git a/doc/testing.md b/doc/testing.md new file mode 100644 index 0000000000..fa134ade66 --- /dev/null +++ b/doc/testing.md @@ -0,0 +1,12 @@ +# Testing + +Testing is good. Here is some useful info about how we set up tests-- + +### Backend code: + +- TODO + +### Frontend code: + +- TODO + From e013837ab15b327bc11f7008a3d7be265a202467 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 11 Jun 2012 15:34:52 -0400 Subject: [PATCH 049/123] add function docs to module_render.py --- lms/djangoapps/courseware/module_render.py | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index fd419a68dc..ec13dc11a5 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -89,6 +89,19 @@ def grade_histogram(module_id): return grades def get_module(user, request, xml_module, module_object_preload, position=None): + ''' Get the appropriate xmodule and StudentModule. + + Arguments: + - user : current django User + - request : current django HTTPrequest + - xml_module : lxml etree of xml subtree for the current module + - module_object_preload : list of StudentModule objects, one of which may match this module type and id + - position : extra information from URL for user-specified position within module + + Returns: + - a tuple (xmodule instance, student module, module type). + + ''' module_type=xml_module.tag module_class=xmodule.get_module_class(module_type) module_id=xml_module.get('id') #module_class.id_attribute) or "" @@ -184,7 +197,19 @@ def render_x_module(user, request, xml_module, module_object_preload, position=N return content def modx_dispatch(request, module=None, dispatch=None, id=None): - ''' Generic view for extensions. This is where AJAX calls go.''' + ''' Generic view for extensions. This is where AJAX calls go. + + Arguments: + + - request -- the django request. + - module -- the name of the module, as used in the course configuration xml. + - dispatch -- the command string to pass through to the module's handle_ajax call + (e.g. 'problem_reset'). If this string contains '?', only pass + through the part before the first '?'. + - id -- the module id. Used to look up the student module. + + TODO: why are id and module not the same? + ''' if not request.user.is_authenticated(): return redirect('/') @@ -200,6 +225,8 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): oldgrade = s.grade oldstate = s.state + # TODO: if dispatch is left at default value None, this will go boom. What's the correct + # behavior? dispatch=dispatch.split('?')[0] ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/' From 53078665ee20e9f576812f2878760b84c04e4808 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 12 Jun 2012 09:31:22 -0400 Subject: [PATCH 050/123] add README.md for dogfood --- lms/lib/dogfood/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 lms/lib/dogfood/README.md diff --git a/lms/lib/dogfood/README.md b/lms/lib/dogfood/README.md new file mode 100644 index 0000000000..c6a7113049 --- /dev/null +++ b/lms/lib/dogfood/README.md @@ -0,0 +1 @@ +This is a library for edx4edx, allowing users to practice writing problems. From 2a0680340fc44772cc7a34afb9116ab3abadd8d2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 12 Jun 2012 09:32:04 -0400 Subject: [PATCH 051/123] add comment/explanation to eia.py --- common/lib/capa/eia.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/capa/eia.py b/common/lib/capa/eia.py index bc962312ec..362dc33a2d 100644 --- a/common/lib/capa/eia.py +++ b/common/lib/capa/eia.py @@ -1,3 +1,6 @@ +""" Standard resistor codes. +http://en.wikipedia.org/wiki/Electronic_color_code +""" E6=[10,15,22,33,47,68] E12=[10,12,15,18,22,27,33,39,47,56,68,82] E24=[10,12,15,18,22,27,33,39,47,56,68,82,11,13,16,20,24,30,36,43,51,62,75,91] From 0a66d0eab9ef5e854898d5d71b75815a55d79f58 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 12 Jun 2012 09:32:41 -0400 Subject: [PATCH 052/123] clarify docstring for modx_dispatch --- lms/djangoapps/courseware/module_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index ec13dc11a5..fe6ebdd585 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -202,13 +202,13 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): Arguments: - request -- the django request. - - module -- the name of the module, as used in the course configuration xml. + - module -- the type of the module, as used in the course configuration xml. + e.g. 'problem', 'video', etc - dispatch -- the command string to pass through to the module's handle_ajax call (e.g. 'problem_reset'). If this string contains '?', only pass through the part before the first '?'. - id -- the module id. Used to look up the student module. - - TODO: why are id and module not the same? + e.g. filenamexformularesponse ''' if not request.user.is_authenticated(): return redirect('/') From 6140d5ac3d99e2262a590a79c2cdded057e83651 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 12 Jun 2012 09:33:41 -0400 Subject: [PATCH 053/123] Update overview to reflect suggestions --- doc/README | 2 +- doc/overview.md | 22 +++++++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/doc/README b/doc/README index 65f3cd2cb5..d40f5d988d 100644 --- a/doc/README +++ b/doc/README @@ -1,3 +1,3 @@ This directory contains some high level documentation for the code. We should strive to keep it up-to-date, but don't take it as the absolute truth. -A good place to start is 'overview' +A good place to start is 'overview.md' diff --git a/doc/overview.md b/doc/overview.md index a53e7371c2..304d5161b0 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -45,7 +45,8 @@ You should be familiar with the following. If you're not, go read some docs... ### Common libraries - x_modules -- generic learning modules. *x* can be sequence, video, template, html, vertical, capa, etc. These are the things that one puts inside sections in the course structure. Modules know how to render themselves to html, how to score themselves, and handle ajax calls from the front end. - - x_modules take a 'system' context parameter, which is a reference to an object that knows how to render things, track events, complain about 404s, etc. (TODO: figure out, document the necessary interface--different in `x_module.XModule.__init__` and in `x_module tests.py`) + - x_modules take a 'system context' parameter, which helps isolate xmodules from any particular application, so they can be used in many places. The modules should make no references to Django (though there are still a few left). The system context knows how to render things, track events, complain about 404s, etc. + - TODO: document the system context interface--it's different in `x_module.XModule.__init__` and in `x_module tests.py` (do this in the code, not here) - in `common/lib/xmodule` - capa modules -- defines `LoncapaProblem` and many related things. @@ -71,6 +72,10 @@ The LMS is a django site, with root in `lms/`. It runs in many different enviro - ajax calls go to `module_render.py:modx_dispatch()`, which passes it to the module's `handle_ajax()` function, and then updates the grade and state if they changed. + - [This diagram](https://github.com/MITx/mitx/wiki/MITx-Architecture) visually shows how the clients communicate with problems + modules. + +- See `lms/urls.py` for the wirings of urls to views. + - Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`. ### Other modules @@ -81,22 +86,13 @@ The LMS is a django site, with root in `lms/`. It runs in many different enviro See `testing.md`. - -## QUESTIONS: - -`common/lib/capa` : what is eia, `eia.py`? Random lists of numbers? - -what is `lms/lib/dogfood`? Looks like a way to test capa problems... Is it being used? - -is lms/envs/README.txt out of date? - ## TODO: -- Only describes backend code so far. How does the front-end work? +- update lms/envs/README.txt -- What big pieces are missing? +- describe our production environment -- Where should reader go next? +- describe the front-end architecture, tools, etc. Starting point: `lms/static` --- Note: this file uses markdown. To convert to html, run: From f809e5b010a5e7f3268b666ecf6966ab5646a4bd Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 13 Jun 2012 11:42:50 -0400 Subject: [PATCH 054/123] Add contentstore from cms proto as a baseline for importing and views for the cms --- cms/djangoapps/contentstore/__init__.py | 0 .../contentstore/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/ftpserve.py | 61 +++++++ .../management/commands/import.py | 158 ++++++++++++++++++ cms/djangoapps/contentstore/tests.py | 16 ++ cms/{ => djangoapps/contentstore}/views.py | 0 cms/envs/common.py | 6 +- cms/urls.py | 2 +- 9 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 cms/djangoapps/contentstore/__init__.py create mode 100644 cms/djangoapps/contentstore/management/__init__.py create mode 100644 cms/djangoapps/contentstore/management/commands/__init__.py create mode 100644 cms/djangoapps/contentstore/management/commands/ftpserve.py create mode 100644 cms/djangoapps/contentstore/management/commands/import.py create mode 100644 cms/djangoapps/contentstore/tests.py rename cms/{ => djangoapps/contentstore}/views.py (100%) diff --git a/cms/djangoapps/contentstore/__init__.py b/cms/djangoapps/contentstore/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/djangoapps/contentstore/management/__init__.py b/cms/djangoapps/contentstore/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/djangoapps/contentstore/management/commands/__init__.py b/cms/djangoapps/contentstore/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/djangoapps/contentstore/management/commands/ftpserve.py b/cms/djangoapps/contentstore/management/commands/ftpserve.py new file mode 100644 index 0000000000..f0a1c19dbf --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/ftpserve.py @@ -0,0 +1,61 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth.models import User +import contentstore.tasks + +from pyftpdlib import ftpserver +import os + +class DjangoAuthorizer(object): + def validate_authentication(self, username, password): + try: + u=User.objects.get(username=username) + except User.DoesNotExist: + return False + # TODO: Check security groups + return u.check_password(password) + def has_user(self, username): + print "????",username + return True + def has_perm(self, username, perm, path=None): + print "!!!!!",username, perm, path + return True + def get_home_dir(self, username): + d = "/tmp/ftp/"+username + try: + os.mkdir(d) + except OSError: + pass + return "/tmp/ftp/"+username + def get_perms(self, username): + return 'elradfmw' + def get_msg_login(self, username): + return 'Hello' + def get_msg_quit(self, username): + return 'Goodbye' + def __init__(self): + pass + def impersonate_user(self, username, password): + pass + def terminate_impersonation(self, username): + pass + +def on_upload(ftp_handler, filename): + source = ftp_handler.remote_ip + author = ftp_handler.username + print filename, author, source + # We pass on this for now: + # contentstore.tasks.on_upload + # It is a changing API, and it makes testing the FTP server slow. + +class Command(BaseCommand): + help = \ +''' Run FTP server.''' + def handle(self, *args, **options): + authorizer = DjangoAuthorizer() #ftpserver.DummyAuthorizer() + handler = ftpserver.FTPHandler + handler.on_file_received = on_upload + + handler.authorizer = authorizer + address = ("127.0.0.1", 2121) + ftpd = ftpserver.FTPServer(address, handler) + ftpd.serve_forever() diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py new file mode 100644 index 0000000000..78984f4119 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -0,0 +1,158 @@ +### +### One-off script for importing courseware form XML format +### + + +#import mitxmako.middleware +#from courseware import content_parser +#from django.contrib.auth.models import User +from mako.template import Template +from mako.lookup import TemplateLookup + +from django.core.management.base import BaseCommand +from contentstore.models import create_item, update_item, update_children + +from lxml import etree + +class Command(BaseCommand): + help = \ +''' Run FTP server.''' + def handle(self, *args, **options): + print args + data_dir = args[0] + course_file = 'course.xml' + + parser = etree.XMLParser(remove_comments = True) + + lookup = TemplateLookup(directories=[data_dir]) + template = lookup.get_template("course.xml") + course_string = template.render(groups=[]) + course = etree.XML(course_string, parser=parser) + + elements = course.xpath("//*") + + tag_to_category = {# Inside HTML ==> Skip these + # Custom tags + 'videodev': 'Custom', + 'slides': 'Custom', + 'book': 'Custom', + 'image': 'Custom', + 'discuss': 'Custom', + # Simple lists + 'chapter': 'Sequence', + 'course': 'Sequence', + 'sequential': 'Sequence', + 'vertical': 'Sequence', + 'section': 'Sequence', + # True types + 'video': 'VideoSegment', + 'html': 'HTML', + 'problem': 'Problem', + } + + + name_index=0 + for e in elements: + name = e.attrib.get('name', None) + for f in elements: + if f != e and f.attrib.get('name', None) == name: + name = None + if not name: + name = "{tag}_{index}".format(tag = e.tag,index = name_index) + name_index = name_index + 1 + if e.tag in tag_to_category: + category = tag_to_category[e.tag] + category = category.replace('/', '-') + name = name.replace('/', '-') + e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format(category = category, + name = name)) + + + def handle_skip(e): + print "Skipping ", e + + results = {} + + def handle_custom(e): + data = {'type':'i4x://mit.edu/6002xs12/tag/{tag}'.format(tag=e.tag), + 'attrib':dict(e.attrib)} + results[e.attrib['url']] = {'data':data} + + def handle_list(e): + if e.attrib.get("class", None) == "tutorials": + return + children = [{'url':le.attrib['url']} for le in e.getchildren()] + results[e.attrib['url']] = {'children':children} + + def handle_video(e): + url = e.attrib['url'] + clip_url = url.replace('VideoSegment', 'VideoClip') + # Take: 0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8 + # Make: [(0.75, 'izygArpw-Qo'), (1.0, 'p2Q6BrNhdh8'), (1.25, '1EeWXzPdhSA'), (1.5, 'rABDYkeK0x8')] + youtube_str = e.attrib['youtube'] + youtube_list = [(float(x), y) for x,y in map(lambda x:x.split(':'), youtube_str.split(','))] + clip_infos = [{ "status": "ready", + "format": "youtube", + "sane": True, + "location": "youtube", + "speed": speed, + "id": youtube_id, + "size": None} \ + for (speed, youtube_id) \ + in youtube_list] + results[clip_url] = {'data':{'clip_infos':clip_infos}} + results[url] = {'children' : [{'url':clip_url}]} + + def handle_html(e): + if 'src' in e.attrib: + text = open(data_dir+'html/'+e.attrib['src']).read() + else: + textlist=[e.text]+[etree.tostring(elem) for elem in e]+[e.tail] + textlist=[i for i in textlist if type(i)==str] + text = "".join(textlist) + + results[e.attrib['url']] = {'data':{'text':text}} + + def handle_problem(e): + data = open(data_dir+'problems/'+e.attrib['filename']+'.xml').read() + results[e.attrib['url']] = {'data':{'statement':data}} + + element_actions = {# Inside HTML ==> Skip these + 'a': handle_skip, + 'h1': handle_skip, + 'h2': handle_skip, + 'hr': handle_skip, + 'strong': handle_skip, + 'ul': handle_skip, + 'li': handle_skip, + 'p': handle_skip, + # Custom tags + 'videodev': handle_custom, + 'slides': handle_custom, + 'book': handle_custom, + 'image': handle_custom, + 'discuss': handle_custom, + # Simple lists + 'chapter': handle_list, + 'course': handle_list, + 'sequential': handle_list, + 'vertical': handle_list, + 'section': handle_list, + # True types + 'video': handle_video, + 'html': handle_html, + 'problem': handle_problem, + } + + for e in elements: + element_actions[e.tag](e) + + for k in results: + print k + create_item(k, 'Piotr Mitros') + if 'data' in results[k]: + update_item(k, results[k]['data']) + if 'children' in results[k]: + update_children(k, results[k]['children']) + + diff --git a/cms/djangoapps/contentstore/tests.py b/cms/djangoapps/contentstore/tests.py new file mode 100644 index 0000000000..501deb776c --- /dev/null +++ b/cms/djangoapps/contentstore/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/cms/views.py b/cms/djangoapps/contentstore/views.py similarity index 100% rename from cms/views.py rename to cms/djangoapps/contentstore/views.py diff --git a/cms/envs/common.py b/cms/envs/common.py index 9b349a06d0..8d402b6fa9 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -144,8 +144,6 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', + 'contentstore', + 'instructor', ) diff --git a/cms/urls.py b/cms/urls.py index 55d7a1086e..a7266066cc 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -5,6 +5,6 @@ from django.conf.urls.defaults import patterns, url # admin.autodiscover() urlpatterns = patterns('', - url(r'^(?P[^/]+)/(?P[^/]+)/calendar/', 'cms.views.calendar', name='calendar'), + url(r'^(?P[^/]+)/(?P[^/]+)/calendar/', 'contentstore.views.calendar', name='calendar'), url(r'^accounts/login/', 'instructor.views.do_login', name='login'), ) From 9c2397b96990244316d7949cb990078a7561a173 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 13 Jun 2012 11:54:17 -0400 Subject: [PATCH 055/123] Add templates directly from cms_proto/ui_prototype --- cms/djangoapps/contentstore/views.py | 4 + cms/static/css/base-style.css | 589 +++++++++++++++++ cms/static/css/ie.css | 196 ++++++ cms/static/css/style.css | 0 cms/static/img/indicator.gif | Bin 0 -> 1553 bytes cms/static/img/video.jpg | Bin 0 -> 7217 bytes cms/static/js/jquery.inlineedit.js | 251 ++++++++ cms/static/js/jquery.leanModal.min.js | 5 + cms/static/js/jquery.tablednd.js | 257 ++++++++ cms/static/js/main.js | 159 +++++ cms/static/js/markitup/jquery.markitup.js | 593 ++++++++++++++++++ .../js/markitup/sets/wiki/images/bold.png | Bin 0 -> 304 bytes .../js/markitup/sets/wiki/images/clean.png | Bin 0 -> 667 bytes .../js/markitup/sets/wiki/images/code.png | Bin 0 -> 859 bytes .../js/markitup/sets/wiki/images/h1.png | Bin 0 -> 276 bytes .../js/markitup/sets/wiki/images/h2.png | Bin 0 -> 304 bytes .../js/markitup/sets/wiki/images/h3.png | Bin 0 -> 306 bytes .../js/markitup/sets/wiki/images/h4.png | Bin 0 -> 293 bytes .../js/markitup/sets/wiki/images/h5.png | Bin 0 -> 304 bytes .../js/markitup/sets/wiki/images/image.png | Bin 0 -> 516 bytes .../js/markitup/sets/wiki/images/italic.png | Bin 0 -> 223 bytes .../js/markitup/sets/wiki/images/link.png | Bin 0 -> 343 bytes .../markitup/sets/wiki/images/list-bullet.png | Bin 0 -> 344 bytes .../sets/wiki/images/list-numeric.png | Bin 0 -> 357 bytes .../js/markitup/sets/wiki/images/picture.png | Bin 0 -> 606 bytes .../js/markitup/sets/wiki/images/preview.png | Bin 0 -> 537 bytes .../js/markitup/sets/wiki/images/quotes.png | Bin 0 -> 743 bytes .../js/markitup/sets/wiki/images/stroke.png | Bin 0 -> 269 bytes .../js/markitup/sets/wiki/images/url.png | Bin 0 -> 957 bytes cms/static/js/markitup/sets/wiki/set.js | 34 + cms/static/js/markitup/sets/wiki/style.css | 57 ++ .../markitup/skins/simple/images/handle.png | Bin 0 -> 258 bytes .../js/markitup/skins/simple/images/menu.png | Bin 0 -> 27151 bytes .../markitup/skins/simple/images/submenu.png | Bin 0 -> 240 bytes cms/static/js/markitup/skins/simple/style.css | 118 ++++ cms/static/sass/_base.scss | 51 ++ cms/static/sass/_calendar.scss | 206 ++++++ cms/static/sass/_problem.scss | 41 ++ cms/static/sass/_reset.scss | 229 +++++++ cms/static/sass/_video.scss | 40 ++ cms/static/sass/_week.scss | 152 +++++ cms/static/sass/base-style.scss | 18 + cms/static/sass/bourbon/_bourbon.scss | 35 ++ cms/static/sass/bourbon/addons/_button.scss | 267 ++++++++ cms/static/sass/bourbon/addons/_clearfix.scss | 29 + .../sass/bourbon/addons/_font-family.scss | 5 + .../bourbon/addons/_html5-input-types.scss | 36 ++ cms/static/sass/bourbon/addons/_position.scss | 30 + .../bourbon/addons/_timing-functions.scss | 32 + cms/static/sass/bourbon/css3/_animation.scss | 171 +++++ cms/static/sass/bourbon/css3/_appearance.scss | 7 + .../sass/bourbon/css3/_background-image.scss | 57 ++ .../sass/bourbon/css3/_background-size.scss | 15 + .../sass/bourbon/css3/_border-image.scss | 56 ++ .../sass/bourbon/css3/_border-radius.scss | 63 ++ cms/static/sass/bourbon/css3/_box-shadow.scss | 14 + cms/static/sass/bourbon/css3/_box-sizing.scss | 6 + cms/static/sass/bourbon/css3/_columns.scss | 67 ++ cms/static/sass/bourbon/css3/_flex-box.scss | 67 ++ .../sass/bourbon/css3/_inline-block.scss | 10 + .../sass/bourbon/css3/_linear-gradient.scss | 41 ++ .../sass/bourbon/css3/_radial-gradient.scss | 31 + cms/static/sass/bourbon/css3/_transform.scss | 19 + cms/static/sass/bourbon/css3/_transition.scss | 104 +++ .../sass/bourbon/css3/_user-select.scss | 6 + .../_deprecated-webkit-gradient.scss | 36 ++ .../sass/bourbon/functions/_flex-grid.scss | 35 ++ .../sass/bourbon/functions/_grid-width.scss | 13 + .../bourbon/functions/_linear-gradient.scss | 23 + .../bourbon/functions/_modular-scale.scss | 40 ++ .../bourbon/functions/_radial-gradient.scss | 15 + .../bourbon/functions/_render-gradients.scss | 14 + .../sass/bourbon/functions/_tint-shade.scss | 9 + cms/static/sass/bourbon/lib/bourbon.rb | 19 + .../bourbon/lib/bourbon/sass_extensions.rb | 6 + .../lib/bourbon/sass_extensions/functions.rb | 13 + .../sass_extensions/functions/compact.rb | 13 + cms/templates/base.html | 30 + cms/templates/index.html | 19 + cms/templates/widgets/captions.html | 242 +++++++ cms/templates/widgets/header.html | 5 + cms/templates/widgets/navigation.html | 120 ++++ cms/templates/widgets/new-module.html | 9 + cms/templates/widgets/problem-edit.html | 73 +++ cms/templates/widgets/problem-new.html | 52 ++ cms/templates/widgets/raw-videos.html | 3 + cms/templates/widgets/save-captions.html | 4 + cms/templates/widgets/speed-tooltip.html | 7 + cms/templates/widgets/video-box-unused.html | 38 ++ cms/templates/widgets/video-box.html | 35 ++ cms/templates/widgets/video-edit.html | 151 +++++ cms/templates/widgets/video-new.html | 48 ++ cms/templates/widgets/week-edit.html | 83 +++ cms/templates/widgets/week-new.html | 62 ++ cms/urls.py | 1 + 95 files changed, 5386 insertions(+) create mode 100644 cms/static/css/base-style.css create mode 100644 cms/static/css/ie.css create mode 100644 cms/static/css/style.css create mode 100755 cms/static/img/indicator.gif create mode 100644 cms/static/img/video.jpg create mode 100755 cms/static/js/jquery.inlineedit.js create mode 100644 cms/static/js/jquery.leanModal.min.js create mode 100644 cms/static/js/jquery.tablednd.js create mode 100644 cms/static/js/main.js create mode 100644 cms/static/js/markitup/jquery.markitup.js create mode 100644 cms/static/js/markitup/sets/wiki/images/bold.png create mode 100644 cms/static/js/markitup/sets/wiki/images/clean.png create mode 100644 cms/static/js/markitup/sets/wiki/images/code.png create mode 100644 cms/static/js/markitup/sets/wiki/images/h1.png create mode 100644 cms/static/js/markitup/sets/wiki/images/h2.png create mode 100644 cms/static/js/markitup/sets/wiki/images/h3.png create mode 100644 cms/static/js/markitup/sets/wiki/images/h4.png create mode 100644 cms/static/js/markitup/sets/wiki/images/h5.png create mode 100644 cms/static/js/markitup/sets/wiki/images/image.png create mode 100644 cms/static/js/markitup/sets/wiki/images/italic.png create mode 100644 cms/static/js/markitup/sets/wiki/images/link.png create mode 100644 cms/static/js/markitup/sets/wiki/images/list-bullet.png create mode 100644 cms/static/js/markitup/sets/wiki/images/list-numeric.png create mode 100644 cms/static/js/markitup/sets/wiki/images/picture.png create mode 100644 cms/static/js/markitup/sets/wiki/images/preview.png create mode 100644 cms/static/js/markitup/sets/wiki/images/quotes.png create mode 100644 cms/static/js/markitup/sets/wiki/images/stroke.png create mode 100644 cms/static/js/markitup/sets/wiki/images/url.png create mode 100644 cms/static/js/markitup/sets/wiki/set.js create mode 100644 cms/static/js/markitup/sets/wiki/style.css create mode 100644 cms/static/js/markitup/skins/simple/images/handle.png create mode 100644 cms/static/js/markitup/skins/simple/images/menu.png create mode 100644 cms/static/js/markitup/skins/simple/images/submenu.png create mode 100644 cms/static/js/markitup/skins/simple/style.css create mode 100644 cms/static/sass/_base.scss create mode 100644 cms/static/sass/_calendar.scss create mode 100644 cms/static/sass/_problem.scss create mode 100644 cms/static/sass/_reset.scss create mode 100644 cms/static/sass/_video.scss create mode 100644 cms/static/sass/_week.scss create mode 100644 cms/static/sass/base-style.scss create mode 100644 cms/static/sass/bourbon/_bourbon.scss create mode 100644 cms/static/sass/bourbon/addons/_button.scss create mode 100644 cms/static/sass/bourbon/addons/_clearfix.scss create mode 100644 cms/static/sass/bourbon/addons/_font-family.scss create mode 100644 cms/static/sass/bourbon/addons/_html5-input-types.scss create mode 100644 cms/static/sass/bourbon/addons/_position.scss create mode 100644 cms/static/sass/bourbon/addons/_timing-functions.scss create mode 100644 cms/static/sass/bourbon/css3/_animation.scss create mode 100644 cms/static/sass/bourbon/css3/_appearance.scss create mode 100644 cms/static/sass/bourbon/css3/_background-image.scss create mode 100644 cms/static/sass/bourbon/css3/_background-size.scss create mode 100644 cms/static/sass/bourbon/css3/_border-image.scss create mode 100644 cms/static/sass/bourbon/css3/_border-radius.scss create mode 100644 cms/static/sass/bourbon/css3/_box-shadow.scss create mode 100644 cms/static/sass/bourbon/css3/_box-sizing.scss create mode 100644 cms/static/sass/bourbon/css3/_columns.scss create mode 100644 cms/static/sass/bourbon/css3/_flex-box.scss create mode 100644 cms/static/sass/bourbon/css3/_inline-block.scss create mode 100644 cms/static/sass/bourbon/css3/_linear-gradient.scss create mode 100644 cms/static/sass/bourbon/css3/_radial-gradient.scss create mode 100644 cms/static/sass/bourbon/css3/_transform.scss create mode 100644 cms/static/sass/bourbon/css3/_transition.scss create mode 100644 cms/static/sass/bourbon/css3/_user-select.scss create mode 100644 cms/static/sass/bourbon/functions/_deprecated-webkit-gradient.scss create mode 100644 cms/static/sass/bourbon/functions/_flex-grid.scss create mode 100644 cms/static/sass/bourbon/functions/_grid-width.scss create mode 100644 cms/static/sass/bourbon/functions/_linear-gradient.scss create mode 100644 cms/static/sass/bourbon/functions/_modular-scale.scss create mode 100644 cms/static/sass/bourbon/functions/_radial-gradient.scss create mode 100644 cms/static/sass/bourbon/functions/_render-gradients.scss create mode 100644 cms/static/sass/bourbon/functions/_tint-shade.scss create mode 100644 cms/static/sass/bourbon/lib/bourbon.rb create mode 100644 cms/static/sass/bourbon/lib/bourbon/sass_extensions.rb create mode 100644 cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions.rb create mode 100644 cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions/compact.rb create mode 100644 cms/templates/base.html create mode 100644 cms/templates/index.html create mode 100644 cms/templates/widgets/captions.html create mode 100644 cms/templates/widgets/header.html create mode 100644 cms/templates/widgets/navigation.html create mode 100644 cms/templates/widgets/new-module.html create mode 100644 cms/templates/widgets/problem-edit.html create mode 100644 cms/templates/widgets/problem-new.html create mode 100644 cms/templates/widgets/raw-videos.html create mode 100644 cms/templates/widgets/save-captions.html create mode 100644 cms/templates/widgets/speed-tooltip.html create mode 100644 cms/templates/widgets/video-box-unused.html create mode 100644 cms/templates/widgets/video-box.html create mode 100644 cms/templates/widgets/video-edit.html create mode 100644 cms/templates/widgets/video-new.html create mode 100644 cms/templates/widgets/week-edit.html create mode 100644 cms/templates/widgets/week-new.html diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index c6786b03c4..38e9e8ad35 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -10,3 +10,7 @@ def calendar(request, org, course): Location(['i4x', org, course, 'Course', 'course']) ) return render_to_response('calendar.html', {'weeks': weeks}) + + +def index(request): + return render_to_response('index.html', {}) diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css new file mode 100644 index 0000000000..2cdeafe9e5 --- /dev/null +++ b/cms/static/css/base-style.css @@ -0,0 +1,589 @@ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: baseline; + background: transparent; } + +html, body { + font-size: 100%; } + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { + display: block; } + +audio, canvas, video { + display: inline-block; } + +audio:not([controls]) { + display: none; } + +[hidden] { + display: none; } + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +html, button, input, select, textarea { + font-family: sans-serif; } + +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } +a:hover, a:active { + outline: 0; } + +abbr[title] { + border-bottom: 1px dotted; } + +b, strong { + font-weight: bold; } + +blockquote { + margin: 1em 40px; } + +dfn { + font-style: italic; } + +mark { + background: #ff0; + color: #000; } + +pre, code, kbd, samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; } + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; } + +blockquote, q { + quotes: none; } + blockquote:before, blockquote:after, q:before, q:after { + content: ''; + content: none; } + +small { + font-size: 75%; } + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +nav ul, nav ol { + list-style: none; + list-style-image: none; } + +img { + border: 0; + height: auto; + max-width: 100%; + -ms-interpolation-mode: bicubic; } + +svg:not(:root) { + overflow: hidden; } + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +legend { + border: 0; + padding: 0; + white-space: normal; } + +button, input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; } + +button, input { + line-height: normal; } + +button, input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; } + +button[disabled], input[disabled] { + cursor: default; } + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; + padding: 0; } + +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; } + +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; } + +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; } + +textarea { + overflow: auto; + vertical-align: top; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +html { + height: 100%; } + +body { + zoom: 1; + height: 100%; + font: 14px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } + body:before, body:after { + content: ""; + display: table; } + body:after { + clear: both; } + body > section { + display: table; + width: 100%; } + body > header { + background: #000; + color: #fff; + display: block; + float: none; + padding: 6px 20px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + body > header nav { + zoom: 1; } + body > header nav:before, body > header nav:after { + content: ""; + display: table; } + body > header nav:after { + clear: both; } + body > header nav h2 { + font-size: 14px; + text-transform: uppercase; } + +a { + text-decoration: none; + color: #888; } + +input[type="submit"], .button, section.week-edit > header a, section.week-new > header a { + border: 1px solid #ccc; + background: #efefef; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + padding: 6px; } + +section.cal { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 25px; + zoom: 1; + overflow: scroll; } + section.cal:before, section.cal:after { + content: ""; + display: table; } + section.cal:after { + clear: both; } + section.cal > header { + zoom: 1; + margin-bottom: 10px; } + section.cal > header:before, section.cal > header:after { + content: ""; + display: table; } + section.cal > header:after { + clear: both; } + section.cal > header h1 { + float: left; + font-size: 18px; } + section.cal > header ul { + float: right; } + section.cal > header ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.cal > header ul li a { + padding: 6px; + border: 1px solid #ddd; + display: block; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + background: #efefef; } + section.cal > header ul li.dropdown { + position: relative; } + section.cal > header ul li.dropdown ul { + display: none; + position: absolute; + background: #fff; + border: 1px solid #ddd; } + section.cal > header ul li.dropdown ul li { + padding: 6px; + display: block; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; } + section.cal > header ul li.dropdown ul li:hover { + background-color: #efefef; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; } + section.cal > header ul li.dropdown:hover ul { + display: block; } + section.cal > header ul li.dropdown:hover a { + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + -ms-border-radius: 3px 3px 0 0; + -o-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; + border-bottom: 0; } + section.cal ol { + list-style: none; + zoom: 1; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-left: 1px solid #333; + border-top: 1px solid #333; + width: 100%; } + section.cal ol:before, section.cal ol:after { + content: ""; + display: table; } + section.cal ol:after { + clear: both; } + section.cal ol > li { + border-right: 1px solid #333; + border-bottom: 1px solid; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + float: left; + width: 25.0%; } + section.cal ol > li:last-child { + text-align: center; + background: #eee; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + section.cal ol > li:last-child p { + width: 100%; + height: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + section.cal ol > li:last-child p a { + display: block; + width: 100%; + height: 100%; } + section.cal ol > li:last-child section.new-week header { + background: #fff; + text-align: left; } + section.cal ol > li:last-child section.new-week form { + background: #fff; + width: 50%; + padding: 6px; + border: 1px solid #000; + margin: 0 auto; + -webkit-box-shadow: 0 0 2px #333333; + -moz-box-shadow: 0 0 2px #333333; + box-shadow: 0 0 2px #333333; + position: relative; } + section.cal ol > li:last-child section.new-week form:before { + background: #fff; + border-left: 1px solid #000; + border-top: 1px solid #000; + content: " "; + display: block; + height: 10px; + left: 50%; + position: absolute; + top: -6px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + width: 10px; + z-index: 0; } + section.cal ol > li:last-child section.new-week form select { + margin-bottom: 6px; + width: 100%; } + section.cal ol > li:last-child section.new-week form select option { + padding: 10px 0 !important; } + section.cal ol > li:last-child section.new-week form input[type="submit"] { + display: block; + margin-bottom: 6px; + width: 100%; } + section.cal ol > li:last-child section.new-week form a:first-child { + float: left; } + section.cal ol > li:last-child section.new-week form a:last-child { + float: right; } + section.cal ol > li header { + border-bottom: 1px solid #000; + -webkit-box-shadow: 0 1px 2px #cccccc; + -moz-box-shadow: 0 1px 2px #cccccc; + box-shadow: 0 1px 2px #cccccc; + display: block; + margin-bottom: 2px; + padding: 6px; } + section.cal ol > li header h1 { + font-size: 14px; } + section.cal ol > li ul { + list-style: none; + margin-bottom: 1px; } + section.cal ol > li ul li { + background: #efefef; + border-bottom: 1px solid #666; + padding: 6px; } + section.cal ol > li ul li.goal { + background: #fff; } + +body.content +section.cal { + width: 25.577%; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + body.content + section.cal > header ul { + display: none; } + body.content + section.cal ol li { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; } + +section.week-edit > header, section.week-new > header { + border-bottom: 1px solid #ccc; + zoom: 1; + padding: 6px; } + section.week-edit > header:before, section.week-edit > header:after, section.week-new > header:before, section.week-new > header:after { + content: ""; + display: table; } + section.week-edit > header:after, section.week-new > header:after { + clear: both; } + section.week-edit > header h1, section.week-new > header h1 { + font-size: 18px; + float: left; + margin-top: 8px 6px; } + section.week-edit > header a, section.week-new > header a { + float: right; + display: block; } +section.week-edit section header, section.week-new section header { + background: #666; + color: #fff; + padding: 6px; + border-bottom: 1px solid #333; + -webkit-font-smoothing: antialiased; } + section.week-edit section header h2, section.week-new section header h2 { + font-size: 14px; } +section.week-edit section.sidebar, section.week-new section.sidebar { + width: 34.368%; + float: left; + background: #ccc; + border-right: 1px solid #333; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + section.week-edit section.sidebar section, section.week-new section.sidebar section { + height: 50%; } + section.week-edit section.sidebar section > ul, section.week-new section.sidebar section > ul { + list-style: none; } + section.week-edit section.sidebar section > ul > li, section.week-new section.sidebar section > ul > li { + padding: 6px; + border-bottom: 1px solid #666; + background: #eee; } + section.week-edit section.sidebar section > ul > li.new-module, section.week-new section.sidebar section > ul > li.new-module { + position: relative; } + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown { + list-style: none; } + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li { + display: none; } + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child { + display: block; } + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li { + display: block; + padding: 6px 0; } + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child { + padding-top: 0; } + section.week-edit section.sidebar section p, section.week-new section.sidebar section p { + padding: 6px; + border-bottom: 1px solid #666; } +section.week-edit section.weeks-content, section.week-new section.weeks-content { + width: 65.632%; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + section.week-edit section.weeks-content header, section.week-new section.weeks-content header { + zoom: 1; } + section.week-edit section.weeks-content header:before, section.week-edit section.weeks-content header:after, section.week-new section.weeks-content header:before, section.week-new section.weeks-content header:after { + content: ""; + display: table; } + section.week-edit section.weeks-content header:after, section.week-new section.weeks-content header:after { + clear: both; } + section.week-edit section.weeks-content header h2, section.week-new section.weeks-content header h2 { + float: left; } + section.week-edit section.weeks-content header form, section.week-new section.weeks-content header form { + float: right; + margin: -2px 0; } + section.week-edit section.weeks-content header form input, section.week-new section.weeks-content header form input { + border: 1px solid #000; + background: #ddd; + padding: 2px 4px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; } + section.week-edit section.weeks-content section.filters, section.week-new section.weeks-content section.filters { + border-bottom: 1px solid #999; } + section.week-edit section.weeks-content section.filters ul, section.week-new section.weeks-content section.filters ul { + zoom: 1; + list-style: none; + padding: 6px; } + section.week-edit section.weeks-content section.filters ul:before, section.week-edit section.weeks-content section.filters ul:after, section.week-new section.weeks-content section.filters ul:before, section.week-new section.weeks-content section.filters ul:after { + content: ""; + display: table; } + section.week-edit section.weeks-content section.filters ul:after, section.week-new section.weeks-content section.filters ul:after { + clear: both; } + section.week-edit section.weeks-content section.filters ul li, section.week-new section.weeks-content section.filters ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.week-edit section.weeks-content section.filters ul li.advanced, section.week-new section.weeks-content section.filters ul li.advanced { + float: right; } + section.week-edit section.weeks-content section.modules ul, section.week-new section.weeks-content section.modules ul { + list-style: none; } + section.week-edit section.weeks-content section.modules ul li, section.week-new section.weeks-content section.modules ul li { + padding: 6px; + font-weight: bold; + font-size: 16px; + border-bottom: 1px solid #333; } + section.week-edit section.weeks-content section.modules ul li a, section.week-new section.weeks-content section.modules ul li a { + color: #000; } + +section.video-new, section.video-edit { + position: absolute; + top: 80px; + right: 0; + background: #fff; + width: 40.32%; + -webkit-box-shadow: 0 0 6px #666666; + -moz-box-shadow: 0 0 6px #666666; + box-shadow: 0 0 6px #666666; + border: 1px solid #333; + border-right: 0; + z-index: 4; } + section.video-new > header, section.video-edit > header { + background: #666; + zoom: 1; + color: #fff; + padding: 6px; + border-bottom: 1px solid #333; + -webkit-font-smoothing: antialiased; } + section.video-new > header:before, section.video-new > header:after, section.video-edit > header:before, section.video-edit > header:after { + content: ""; + display: table; } + section.video-new > header:after, section.video-edit > header:after { + clear: both; } + section.video-new > header h2, section.video-edit > header h2 { + float: left; + font-size: 14px; } + section.video-new > header a, section.video-edit > header a { + float: right; } + section.video-new section ul, section.video-edit section ul { + list-style: none; } + section.video-new section ul li, section.video-edit section ul li { + border-bottom: 1px solid #333; + padding: 10px 25px; } + +section.problem-new, section.problem-edit { + position: absolute; + top: 80px; + right: 0; + background: #fff; + width: 40.32%; + -webkit-box-shadow: 0 0 6px #666666; + -moz-box-shadow: 0 0 6px #666666; + box-shadow: 0 0 6px #666666; + border: 1px solid #333; + border-right: 0; + z-index: 4; } + section.problem-new > header, section.problem-edit > header { + background: #666; + zoom: 1; + color: #fff; + padding: 6px; + border-bottom: 1px solid #333; + -webkit-font-smoothing: antialiased; } + section.problem-new > header:before, section.problem-new > header:after, section.problem-edit > header:before, section.problem-edit > header:after { + content: ""; + display: table; } + section.problem-new > header:after, section.problem-edit > header:after { + clear: both; } + section.problem-new > header h2, section.problem-edit > header h2 { + float: left; + font-size: 14px; } + section.problem-new > header a, section.problem-edit > header a { + float: right; } + section.problem-new section ul, section.problem-edit section ul { + list-style: none; } + section.problem-new section ul li, section.problem-edit section ul li { + border-bottom: 1px solid #333; + padding: 10px 25px; } + +body.content section.main-content { + border-left: 2px solid #000; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 74.423%; + float: left; + -webkit-box-shadow: -2px 0 3px #dddddd; + -moz-box-shadow: -2px 0 3px #dddddd; + box-shadow: -2px 0 3px #dddddd; } diff --git a/cms/static/css/ie.css b/cms/static/css/ie.css new file mode 100644 index 0000000000..f497a329cf --- /dev/null +++ b/cms/static/css/ie.css @@ -0,0 +1,196 @@ +/* line 12, ../sass/_reset.scss */ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: baseline; + background: transparent; } + +/* line 21, ../sass/_reset.scss */ +html, body { + font-size: 100%; } + +/* line 26, ../sass/_reset.scss */ +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { + display: block; } + +/* line 31, ../sass/_reset.scss */ +audio, canvas, video { + display: inline-block; } + +/* line 36, ../sass/_reset.scss */ +audio:not([controls]) { + display: none; } + +/* line 41, ../sass/_reset.scss */ +[hidden] { + display: none; } + +/* line 47, ../sass/_reset.scss */ +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +/* line 54, ../sass/_reset.scss */ +html, button, input, select, textarea { + font-family: sans-serif; } + +/* line 60, ../sass/_reset.scss */ +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } +/* line 69, ../sass/_reset.scss */ +a:hover, a:active { + outline: 0; } + +/* line 75, ../sass/_reset.scss */ +abbr[title] { + border-bottom: 1px dotted; } + +/* line 80, ../sass/_reset.scss */ +b, strong { + font-weight: bold; } + +/* line 84, ../sass/_reset.scss */ +blockquote { + margin: 1em 40px; } + +/* line 89, ../sass/_reset.scss */ +dfn { + font-style: italic; } + +/* line 94, ../sass/_reset.scss */ +mark { + background: #ff0; + color: #000; } + +/* line 101, ../sass/_reset.scss */ +pre, code, kbd, samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; } + +/* line 108, ../sass/_reset.scss */ +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; } + +/* line 115, ../sass/_reset.scss */ +blockquote, q { + quotes: none; } + /* line 117, ../sass/_reset.scss */ + blockquote:before, blockquote:after, q:before, q:after { + content: ''; + content: none; } + +/* line 123, ../sass/_reset.scss */ +small { + font-size: 75%; } + +/* line 127, ../sass/_reset.scss */ +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +/* line 134, ../sass/_reset.scss */ +sup { + top: -0.5em; } + +/* line 138, ../sass/_reset.scss */ +sub { + bottom: -0.25em; } + +/* line 143, ../sass/_reset.scss */ +nav ul, nav ol { + list-style: none; + list-style-image: none; } + +/* line 150, ../sass/_reset.scss */ +img { + border: 0; + height: auto; + max-width: 100%; + -ms-interpolation-mode: bicubic; } + +/* line 158, ../sass/_reset.scss */ +svg:not(:root) { + overflow: hidden; } + +/* line 163, ../sass/_reset.scss */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +/* line 169, ../sass/_reset.scss */ +legend { + border: 0; + padding: 0; + white-space: normal; } + +/* line 175, ../sass/_reset.scss */ +button, input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; } + +/* line 182, ../sass/_reset.scss */ +button, input { + line-height: normal; } + +/* line 186, ../sass/_reset.scss */ +button, input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; } + +/* line 192, ../sass/_reset.scss */ +button[disabled], input[disabled] { + cursor: default; } + +/* line 196, ../sass/_reset.scss */ +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; + padding: 0; } + +/* line 201, ../sass/_reset.scss */ +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; } + +/* line 209, ../sass/_reset.scss */ +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; } + +/* line 215, ../sass/_reset.scss */ +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; } + +/* line 220, ../sass/_reset.scss */ +textarea { + overflow: auto; + vertical-align: top; } + +/* line 226, ../sass/_reset.scss */ +table { + border-collapse: collapse; + border-spacing: 0; } diff --git a/cms/static/css/style.css b/cms/static/css/style.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/static/img/indicator.gif b/cms/static/img/indicator.gif new file mode 100755 index 0000000000000000000000000000000000000000..085ccaecaf5fa5c34bc14cd2c2ed5cbbd8e25dcb GIT binary patch literal 1553 zcma)+TTl~c6vwlh>nb99Af5rT)t{mCEg5urg=A(g z{C|6SPb~9Xage|wB`SrZk2FOMYM!buln2sX?5Y+T78iB(Zu9cS7|LZyZ++}u$^oi1 z_j@S}bW9OzU2R+RMy&~OT>X-oZ98$jq#ogNfJ!BM-42wHGZk*6s2KD}U*IA%epmxb zm}|6BK9YoIF;*xSL!+z@<64lB7->LTW2Vi4ostCA(z&2XniwNIv}fFo-`MbG;)u4G z^p@F!)|9HhZprHd_vXjDoxs6WkK-6P0@lfxnGT>*p(QHoUV=u1FAqb@b%*W=a3{`LsH5k^AvQNL>6fPpy#oU(&MuH(*aEX4b35*} zn4n7)`I2U%=+Z=?BVZQ?vjQFW4gD@~XSOO6b{qu81`4&LFuU2(ilxW+1|ZkNMnWe79C$gs zWT?Ele|HR{JGPe)5BTW>0Ey?-Ls6S#GoV0tbt6ku7B&*0 z;i9QM$W1Rj*rRIdceL)rAOSl+sDe3LkB87<%){;ZdHp6|SNlopDXRx< zxBDF9-lTo&v`8$humFygUij@qgT=Qzhj8{ym2-{Xciwqq_Xwk%=O3B-MNAL_6e`3U zyxwmXex4`g0^1RYw~Dth3av3Dl^AAlpO3mG!nLr#&ZZ7c_wUboI+deC+&%TFjK2Lm z!Y&f1h|T_On%RCV&=4bx`!>(YezqGVhl&QpED?N6GV)HmzJ9&rh$x*i?*@o9#6QI< z5ZI_MRX;0+pY8$`j)eF#TlUyG(eE%E7S!rj;mj^M5vhUicPm zVWQ2z+imFyg}SRABmOBY_@osR!>7Ov!ioK`NB6_Rv}7Ud?35ed5Sb@?yND?kv~RCa wqs^a3Sh>&&L4)!LKI?D2&k@))k(LESaga|C278ChSzn3NWVkcuNoY&{0f?~U_5c6? literal 0 HcmV?d00001 diff --git a/cms/static/img/video.jpg b/cms/static/img/video.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e92271c3ad5e23185f1fac4bbf5954df735d5949 GIT binary patch literal 7217 zcmeHLYgm$Lw?@q@%^Y<;bF|daZH@;(Jd2o(nl(0BqlJo!DH0(83Mw8-YDUNHsF|Z= zO%l^ivL=yCNeRi)l+v^rJd0vx#GVn;fdxue-sfCT~OHaqgyH zu2(e)_B(9iMI@n396`1~l&urQ#K{2!ajgWu$g_!(d zriyB$=;P1;Z=XMEDNgRDe>6p*P(T!W5RrrdLtI^5!M1i_J3F8v0!U6K#78Cp31qYP z8NAVC6bTy}k0laJ)-y&%5fkFwO%+VvwSbTPkoKQy^_M_DzkeNy$FB?UHZ3_m0R1Q5 zf0~$lBsmrh4nUKM2_zI+5pK2)j)i)W(2?;((h(x@DD+p}?xuax$y)xpKd(aFmh0)gyrdinfyc5n%l-UnimR=LN7;V~hG`H5FAQR1~GPRkQ&f03qyu*7sV;srxVda88U zu!6%T@ZhXt_yMZ`rg^(taK}hqMpT6HST0x0d=fxg^f@}N`3vmWiUZd$q+U2TXj^-? zJrOA4Til52dME@*lOgH%;O8$?vRc$Tc@?GD0R5nA-Lo{T_^Mqoaj|$Qzh`j}6OUip z`^v2|&-WDpC{5Klof0qg#oL7Ic#Pd}D+N#G%(C;(W@lj@_4jYfq2`G@fg}ra_^KR` zgELqer6Rd8beyFmV;0OJCNA9OtU8AY%btyf z)-27_^9x8G&91TRnN3>B_{Hy%s~^=QFK0I*5h7&JiJFSy;i_qTJ4$x5orTs9sk|vc zW4lxKbI>-+M_JSZlyL*@jPqu)#W~&r{DqA2VwLir$y#3Q)fS9wmp%AkTe>wu=!_zQ zJ}H|xR2m$f8SGCVMzq&JZ)|w7(u!d){(kU|L+w)p3}z%Oqu9pUmL}?Ehtoda;`s>^ zZRvZIY3UotiqRZeg{4`O2Mh>3)AqgM%S9#Fkk=KAO4tFxV_pV5rDOEqT^S*#4C^U# zH#eAD&B*eZfVNMDrQdWj5f_|W9L}rBMo+A{yl5?8#nS7#K>G#Z0pCR4;VF{Jqg(b6UJYl&usRoI0rMn2)LyS&8Z5TYK?BQJ{)IHlDZ05qm!tY1zaKd(^9FvwEAIK*dPbFq z-{SF4u8oN(aafnWwZZNR_7LtTJ$!aZaB@vDoEbHjJx^>p%#C?z9^9tG#9G0tSWx9V zgO9U+8<7JTh6{2))fLCzyGbxtNp1j~Jl}aQjK2rg?@FWnJ~~6gBD%kR!ZKgBzQYcN z(<-2I%$wnw5gTmP&|=Mgjr|%nF|%C`a3ZyxB?eD5JmTF;tK?Rl;(1VX<78!7Vcct2 za&9wxhfOFg3oY@fvx4gl5#kobV;Vkt-xX8~m=UN=F@CIdegfP2G&QD6HVg_9l4kKk zKZ{GKj~bgZn~H$GRV@5qCDdo)a>d#$irHsPRfX8Yfz)xs`qgFWOiLSjD)2lwDB$UE z?tVESXuu=pE(>=#bx#MmitjUE-N&FBj9Zf+EUZQ${XVD6Y8zx@&FU>IfnHatJ!Bw| zu(iq(~#{96`1&NFAk4%f01dhvG9&)M>XV5;1fS;K@qoAW{O@(NwzorCfy!N=_7J8s` z?VCnKVWNdK22tH5S>=}d&LhULC6>>mLOKJCT*1Z8$7J%-UsgDY27jAyKuwTzOL@`(*sQl2d*etuoHQ%Z3{vT306rQ?p$ z{7S;3QsitUUBlfP;i=05J@1WaTy}Lj`EnR}&S#G|%5BGxmA`KK=ycAtFXot7L=ek* zZ?Zz}{aV#+nvK)WUEpF^AXVp5@1>PwHqMZuS0$b4x?AqCcJ#UrqoMpbJ%*lk=6c75 z)YFYy9R6KjR5W0bZjcy$FPxKmV^Y&on`MazkQE3JYqtn|IUs#%6rn6y&l~ugY}s$E zbE5KDZA7lwLb7h~lTm-hh$YdUO&nhM@*fX=ia>~``QI+hbtbme4{J(NFE1jJ^&oL8 zxB(AdmqW8~qB%4-S5LwX0{ioX6&q$&#;T3pfj+JdQ>V`Pgj7KgdD zN5rWOhLbVH_B`-0Wan+SfC@S;(}Qam1f5GKl?@M;9+FmWAMo0J2Cy@gazbCk#>!5! zFB6JxFYKmYK3f!Xgo(8dx!5kCS^LhjtiS5`G;BlSD5E`$fs(8W_s2;<)~j&lWmi#N z&~I0vv!kKBJZy|8r6Y9}4eJpyMpf^fQDnYRI<4ML<)r4#lR%J-R91B*{x;KUSmp33a6Ni@Hdl^DC&e=hTE z2(psd$40bE2!VCMT^00E0rLyplK_B9IrqEdsJLz&Qjy8X05fM-R<$D^iOo0si`4?PAsu5 zMo2{sE4~b{NZPnYjnjR}uhrN4L_7g*49;Oy@@(TA-Famp?B+J)_B5DX#bhYE%Nf{a z$E0R_Ik^`Az-2#?1I|&;tsPo+oYs2A09Nl4`}c%>$GCf^W<;=4s~_7J&-||mO*1ck zYQ)oO7xCkq>D%RP`ZaUu*2cMnFuJ+h5zX{@WOHwS9LGU3OTY{C>k(PR42RZ>480E2 zH?_yNy6Ln+J=>f>JAr*1g2SrW7Ga^9g+hr<(A4>9Gu@`a0CAKoW<;PB{_-@}{W}h1 zEkc9qn)+%yQL@;}CQ3OO1^LwK6rTZ{|E#LFg7gMVSvc6Vqix(6xJsHpn)E|Cc3f&h z2ZsL)c{fAdJ(n zr!LWl^)(BeaZOX&dYeP~vD&Y->{wS*ZU(7v6%>W-mJJq3eFY%n;0^=$d;Gu0c5Be= z(0CCkrD5h^^vua}^jMhdy=#8Fm!Q@rIY0+-uDg}L)qnMJ0L#p#Orzp^xJL^7Xa;`e zrbfY--s`D(IiNR;RdF|ScU;q}N8(f!Dz+x&e(S)+9ayT?H|^x{{T9ZiPluQ4W9in1 z*i*9Y$KJ8p2AD^U>;AABvv|2~CI4mie1axsbAOfiKYDa{2cl)CyNiw|JZzr`pq1Pt zmRVDFOFV{p(no|GrrF`ryq{b%&#!zdxw7xo8lpdlf-xsuub`I`{T`+=&GQ*IB52FB z9J5uY34vs)n&40psU84fn)x2y75(oi@*%44*3{I(Q2uj5MQKvLWzQo@ye7X$8aNE|-Fv8=o)61`j0e>pX2VnQo%Vi!d# z9*=L$%41YJ@1_@EoK-y3Kpn)M$AZU#!ifP6NvGthi*J?ULKE_pWM|eH=(k7)FYZ)& z-o05?_c<(o5boq*j6ZP-7{CXOLGNEB>CkfBzHThJ2`g@y%f%A&TX=xMJw3V1(qDKmNFG#W?JpIXKNRk(0ip|q5O19_x#$`VDSaj zCvZ+-S(mPWj3u$fje z9(7vULEEF7TV7|SnkYVq9AHrXrd5hW2W}|qn73e%}s3&pKvqi8KfPzXKdQwvR)qH zNU_D%`l>Rtfsn?;BCBrf4sFtUY}h9U{BSV^{#{k=^^RmP8tYJU@+4gj$jz;-xD~U@ zQsbM}&TH3kqvn=%N7aQ?4ck6Fw9%bO|7vp~ zowxS$BXRgj^{qyfl4%|E{43fk+hf5{K;ImEc=~T`0E<5_4pI#Y + * Licensed under the MIT (MIT-LICENSE.txt) license. + * + * http://github.com/caphun/jquery.inlineedit/ + * + * Inline (in-place) editing. + */ + +(function($) { + +// cached values +var namespace = '.inlineedit', + placeholderClass = 'inlineEdit-placeholder'; + +// define inlineEdit method +$.fn.inlineEdit = function( options ) { + var self = this; + + return this + + .each( function() { + $.inlineEdit.getInstance( this, options ).initValue(); + }) + + .live( ['click', 'mouseenter','mouseleave'].join(namespace+' '), function( event ) { + + var widget = $.inlineEdit.getInstance( this, options ), + editableElement = widget.element.find( widget.options.control ), + mutated = !!editableElement.length; + + widget.element.removeClass( widget.options.hover ); + + if ( event.target !== editableElement[0] ) { + switch ( event.type ) { + case 'click': + widget[ mutated ? 'mutate' : 'init' ](); + break; + + case 'mouseover': // jquery 1.4.x + case 'mouseout': // jquery 1.4.x + case 'mouseenter': + case 'mouseleave': + if ( !mutated ) { + widget.hoverClassChange( event ); + } + break; + } + } + + }); +} + +// plugin constructor +$.inlineEdit = function( elem, options ) { + + // deep extend + this.options = $.extend( true, {}, $.inlineEdit.defaults, options ); + + // the original element + this.element = $( elem ); + +} + +// plugin instance +$.inlineEdit.getInstance = function( elem, options ) { + return ( $.inlineEdit.initialised( elem ) ) + ? $( elem ).data( 'widget' + namespace ) + : new $.inlineEdit( elem, options ); +} + +// check if plugin initialised +$.inlineEdit.initialised = function( elem ) { + var init = $( elem ).data( 'init' + namespace ); + return init !== undefined && init !== null ? true : false; +} + +// plugin defaults +$.inlineEdit.defaults = { + hover: 'ui-state-hover', + value: '', + save: '', + buttons: ' ', + placeholder: 'Click to edit', + control: 'input', + cancelOnBlur: false, + saveOnBlur: false +}; + +// plugin prototypes +$.inlineEdit.prototype = { + + // initialisation + init: function() { + + // set initialise flag + this.element.data( 'init' + namespace, true ); + + // initialise value + this.initValue(); + + // mutate + this.mutate(); + + // save widget data + this.element.data( 'widget' + namespace, this ); + + }, + + initValue: function() { + this.value( $.trim( this.element.text() ) || this.options.value ); + + if ( !this.value() ) { + this.element.html( $( this.placeholderHtml() ) ); + } else if ( this.options.value ) { + this.element.html( this.options.value ); + } + }, + + mutate: function() { + var self = this; + + return self + .element + .html( self.mutatedHtml( self.value() ) ) + .find( 'button.save' ) + .bind( 'click', function( event ) { + self.save( self.element, event ); + self.change( self.element, event ); + return false; + }) + .end() + .find( 'button.cancel' ) + .bind( 'click', function( event ) { + self.change( self.element, event ); + return false; + }) + .end() + .find( self.options.control ) + .bind( 'blur', function( event ) { + if (self.options.cancelOnBlur === true) + self.change( self.element, event ); + else if (self.options.saveOnBlur == true){ + self.save( self.element, event ); + self.change( self.element, event ); + } + }) + .bind( 'keyup', function( event ) { + switch ( event.keyCode ) { + case 13: // save on ENTER + if (self.options.control !== 'textarea') { + self.save( self.element, event ); + self.change( self.element, event ); + } + break; + case 27: // cancel on ESC + self.change( self.element, event ); + break; + } + }) + .focus() + .end(); + }, + + value: function( newValue ) { + if ( arguments.length ) { + var value = newValue === this.options.placeholder ? '' : newValue; + this.element.data( 'value' + namespace, $( '.' + placeholderClass, this ).length ? '' : value && this.encodeHtml( value.replace( /\n/g,"
    " ) ) ); + } + return this.element.data( 'value' + namespace ); + }, + + mutatedHtml: function( value ) { + return this.controls[ this.options.control ].call( this, value ); + }, + + placeholderHtml: function() { + return ''+ this.options.placeholder +''; + }, + + buttonHtml: function( options ) { + var o = $.extend({}, { + before: ' ', + buttons: this.options.buttons, + after: '' + }, options); + + return o.before + o.buttons + o.after; + }, + + save: function( elem, event ) { + var $control = this.element.find( this.options.control ), + hash = { + value: this.encodeHtml( $control.val() ) + }; + + // save value back to control to avoid XSS + $control.val(hash.value); + + if ( ( $.isFunction( this.options.save ) && this.options.save.call( this.element[0], event, hash ) ) !== false || !this.options.save ) { + this.value( hash.value ); + } + }, + + change: function( elem, event ) { + var self = this; + + if ( this.timer ) { + window.clearTimeout( this.timer ); + } + + this.timer = window.setTimeout( function() { + self.element.html( self.value() || self.placeholderHtml() ); + self.element.removeClass( self.options.hover ); + }, 200 ); + + }, + + controls: { + textarea: function( value ) { + return '' + this.buttonHtml( { before: '
    ' } ); + }, + input: function( value ) { + return '' + this.buttonHtml(); + } + }, + + hoverClassChange: function( event ) { + $( event.target )[ /mouseover|mouseenter/.test( event.type ) ? 'addClass':'removeClass']( this.options.hover ); + }, + + encodeHtml: function( s ) { + var encoding = [ + {key: //g, value: '>'}, + {key: /"/g, value: '"'} + ], + value = s; + + $.each(encoding, function(i,n) { + value = value.replace(n.key, n.value); + }); + + return value; + } + +}; + +})(jQuery); diff --git a/cms/static/js/jquery.leanModal.min.js b/cms/static/js/jquery.leanModal.min.js new file mode 100644 index 0000000000..a5772dd8e2 --- /dev/null +++ b/cms/static/js/jquery.leanModal.min.js @@ -0,0 +1,5 @@ +// leanModal v1.1 by Ray Stone - http://finelysliced.com.au +// Dual licensed under the MIT and GPL + +(function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
    ");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); +$("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); diff --git a/cms/static/js/jquery.tablednd.js b/cms/static/js/jquery.tablednd.js new file mode 100644 index 0000000000..56413ccb08 --- /dev/null +++ b/cms/static/js/jquery.tablednd.js @@ -0,0 +1,257 @@ + +jQuery.tableDnD = { + /** Keep hold of the current table being dragged */ + currentTable : null, + /** Keep hold of the current drag object if any */ + dragObject: null, + /** The current mouse offset */ + mouseOffset: null, + /** Remember the old value of Y so that we don't do too much processing */ + oldY: 0, + + /** Actually build the structure */ + build: function(options) { + // Make sure options exists + options = options || {}; + // Set up the defaults if any + + this.each(function() { + // Remember the options + this.tableDnDConfig = { + onDragStyle: options.onDragStyle, + onDropStyle: options.onDropStyle, + // Add in the default class for whileDragging + onDragClass: options.onDragClass ? options.onDragClass : "dragged", + onDrop: options.onDrop, + onDragStart: options.onDragStart, + scrollAmount: options.scrollAmount ? options.scrollAmount : 5 + }; + // Now make the rows draggable + jQuery.tableDnD.makeDraggable(this); + }); + + // Now we need to capture the mouse up and mouse move event + // We can use bind so that we don't interfere with other event handlers + jQuery(document) + .bind('mousemove', jQuery.tableDnD.mousemove) + .bind('mouseup', jQuery.tableDnD.mouseup); + + // Don't break the chain + return this; + }, + + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ + makeDraggable: function(table) { + // Now initialise the rows + var rows = table.rows; //getElementsByTagName("tr") + var config = table.tableDnDConfig; + for (var i=0; i jQuery.tableDnD.oldY; + // update the old value + jQuery.tableDnD.oldY = y; + // update the style to show we're dragging + if (config.onDragClass) { + dragObj.addClass(config.onDragClass); + } else { + dragObj.css(config.onDragStyle); + } + // If we're over a row then move the dragged row to there so that the user sees the + // effect dynamically + var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); + if (currentRow) { + // TODO worry about what happens when there are multiple TBODIES + if (movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling); + } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) { + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow); + } + } + } + + return false; + }, + + /** We're only worried about the y position really, because we can only move rows up and down */ + findDropTargetRow: function(draggedRow, y) { + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i rowY - rowHeight) && (y < (rowY + rowHeight))) { + // that's the row we're over + // If it's the same as the current row, ignore it + if (row == draggedRow) {return null;} + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + if (config.onAllowDrop) { + if (config.onAllowDrop(draggedRow, row)) { + return row; + } else { + return null; + } + } else { + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) + var nodrop = $(row).hasClass("nodrop"); + if (! nodrop) { + return row; + } else { + return null; + } + } + return row; + } + } + return null; + }, + + mouseup: function(e) { + if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { + var droppedRow = jQuery.tableDnD.dragObject; + var config = jQuery.tableDnD.currentTable.tableDnDConfig; + // If we have a dragObject, then we need to release it, + // The row will already have been moved to the right place so we just reset stuff + if (config.onDragClass) { + jQuery(droppedRow).removeClass(config.onDragClass); + } else { + jQuery(droppedRow).css(config.onDropStyle); + } + jQuery.tableDnD.dragObject = null; + if (config.onDrop) { + // Call the onDrop method if there is one + config.onDrop(jQuery.tableDnD.currentTable, droppedRow); + } + jQuery.tableDnD.currentTable = null; // let go of the table too + } + }, + + serialize: function() { + if (jQuery.tableDnD.currentTable) { + var result = ""; + var tableId = jQuery.tableDnD.currentTable.id; + var rows = jQuery.tableDnD.currentTable.rows; + for (var i=0; i 0) result += "&"; + result += tableId + '[]=' + rows[i].id; + } + return result; + } else { + return "Error: No Table id set, you need to set an id on your table and every row"; + } + } +} + +jQuery.fn.extend( + { + tableDnD : jQuery.tableDnD.build + } + ); diff --git a/cms/static/js/main.js b/cms/static/js/main.js new file mode 100644 index 0000000000..45f94849f8 --- /dev/null +++ b/cms/static/js/main.js @@ -0,0 +1,159 @@ +$(document).ready(function(){ + $('section.main-content').children().hide(); + + $(function(){ + $('.editable').inlineEdit(); + $('.editable-textarea').inlineEdit({control: 'textarea'}); + }); + + // $("a[rel*=leanModal]").leanModal(); + + // $(".remove").click(function(){ + // $(this).parents('li').hide(); + // }); + + // $("#show-sidebar").click(function(){ + // $("#video-selector").toggleClass('hidden'); + // return false; + // }); + + // $('.use-video').click(function() { + // var used = $('#used'); + // if (used.is(':visible')) { + // used.hide().show('slow'); + // } + // used.show(); + // $('.no-video').hide(); + // }); + + // $('.remove-video').click(function() { + // $('#used').hide(); + // $('.no-video').show(); + // }); + + // $('#new-upload').click(function() { + // $('.selected-files').toggle(); + // return false; + // }); + + // /* $('.block').append('✕<\/a>'); */ + + // $('a.delete').click(function() { + // $(this).parents('.block').hide(); + // }); + + // $('.speed-list > li').hover(function(){ + // $(this).children('.tooltip').toggle(); + // }); + + // $('.delete-speed').click(function(){ + // $(this).parents('li.speed').hide(); + // return false; + // }); + + // $('.edit-captions').click(function(){ + // var parentVid = $(this).parents('div'); + // parentVid.siblings('div.caption-box').toggle(); + // return false; + // }); + + // $('.close-box').click(function(){ + // $(this).parents('.caption-box').hide(); + // return false; + // }); + + // $('ul.dropdown').hide(); + // $('li.questions').click(function() { + // $('ul.dropdown').toggle(); + // return false; + // }); + + // $('#mchoice').click(function(){ + // $('div.used').append($('
    ').load("/widgets/multi-choice.html")); + // return false; + // }); + + // $('#text').click(function(){ + // $('div.used').append($('
    ').load("/widgets/text.html")); + // return false; + // }); + + // $('#numerical').click(function(){ + // $('div.used').append($('
    ').load("/widgets/text-question.html")); + // return false; + // }); + + // $('#equation').click(function(){ + // $('div.used').append($('
    ').load("/widgets/latex-equation.html")); + // return false; + // }); + + // $('#script').click(function(){ + // $('div.used').append($('
    ').load("/widgets/script-widget.html")); + // return false; + // }); + + // $("#mark").markItUp(myWikiSettings); + + + var heighest = 0; + $('.cal ol > li').each(function(){ + heighest = ($(this).height() > heighest) ? $(this).height() : heighest; + + }); + + $('.cal ol > li').css('height',heighest + 'px'); + + $('.new-week').hide(); + $('.add-new-week').click(function() { + $(this).hide(); + $('.new-week').show(); + return false; + }); + + $('.new-week .close').click( function(){ + $(this).parents('.new-week').hide(); + $('p.add-new-week').show(); + return false; + }); + + var windowHeight = $(window).resize().height(); + + $('.sidebar').css('height', windowHeight); + + $('.edit-week').click( function() { + $('body').addClass('content'); + $('body.content .cal').css('height', windowHeight); + $('section.week-new').show(); + return false; + }); + + $('.cal ol li header h1 a').click( function() { + $('body').addClass('content'); + $('body.content .cal').css('height', windowHeight); + $('section.week-edit').show(); + return false; + }); + + + $('.video-new a').click(function(){ + $('section.video-new').show(); + return false; + }); + + $('.video-edit a').click(function(){ + $('section.video-edit').show(); + return false; + }); + + $('.problem-new a').click(function(){ + $('section.problem-new').show(); + return false; + }); + + $('.problem-edit a').click(function(){ + $('section.problem-edit').show(); + return false; + }); +}); + diff --git a/cms/static/js/markitup/jquery.markitup.js b/cms/static/js/markitup/jquery.markitup.js new file mode 100644 index 0000000000..10add9d27c --- /dev/null +++ b/cms/static/js/markitup/jquery.markitup.js @@ -0,0 +1,593 @@ +// ---------------------------------------------------------------------------- +// markItUp! Universal MarkUp Engine, JQuery plugin +// v 1.1.x +// Dual licensed under the MIT and GPL licenses. +// ---------------------------------------------------------------------------- +// Copyright (C) 2007-2011 Jay Salvat +// http://markitup.jaysalvat.com/ +// ---------------------------------------------------------------------------- +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// ---------------------------------------------------------------------------- +(function($) { + $.fn.markItUp = function(settings, extraSettings) { + var options, ctrlKey, shiftKey, altKey; + ctrlKey = shiftKey = altKey = false; + + options = { id: '', + nameSpace: '', + root: '', + previewInWindow: '', // 'width=800, height=600, resizable=yes, scrollbars=yes' + previewAutoRefresh: true, + previewPosition: 'after', + previewTemplatePath: '~/templates/preview.html', + previewParser: false, + previewParserPath: '', + previewParserVar: 'data', + resizeHandle: true, + beforeInsert: '', + afterInsert: '', + onEnter: {}, + onShiftEnter: {}, + onCtrlEnter: {}, + onTab: {}, + markupSet: [ { /* set */ } ] + }; + $.extend(options, settings, extraSettings); + + // compute markItUp! path + if (!options.root) { + $('script').each(function(a, tag) { + miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/); + if (miuScript !== null) { + options.root = miuScript[1]; + } + }); + } + + return this.each(function() { + var $$, textarea, levels, scrollPosition, caretPosition, caretOffset, + clicked, hash, header, footer, previewWindow, template, iFrame, abort; + $$ = $(this); + textarea = this; + levels = []; + abort = false; + scrollPosition = caretPosition = 0; + caretOffset = -1; + + options.previewParserPath = localize(options.previewParserPath); + options.previewTemplatePath = localize(options.previewTemplatePath); + + // apply the computed path to ~/ + function localize(data, inText) { + if (inText) { + return data.replace(/("|')~\//g, "$1"+options.root); + } + return data.replace(/^~\//, options.root); + } + + // init and build editor + function init() { + id = ''; nameSpace = ''; + if (options.id) { + id = 'id="'+options.id+'"'; + } else if ($$.attr("id")) { + id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"'; + + } + if (options.nameSpace) { + nameSpace = 'class="'+options.nameSpace+'"'; + } + $$.wrap('
    '); + $$.wrap('
    '); + $$.wrap('
    '); + $$.addClass("markItUpEditor"); + + // add the header before the textarea + header = $('
    ').insertBefore($$); + $(dropMenus(options.markupSet)).appendTo(header); + + // add the footer after the textarea + footer = $('
    ').insertAfter($$); + + // add the resize handle after textarea + if (options.resizeHandle === true && $.browser.safari !== true) { + resizeHandle = $('
    ') + .insertAfter($$) + .bind("mousedown", function(e) { + var h = $$.height(), y = e.clientY, mouseMove, mouseUp; + mouseMove = function(e) { + $$.css("height", Math.max(20, e.clientY+h-y)+"px"); + return false; + }; + mouseUp = function(e) { + $("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp); + return false; + }; + $("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp); + }); + footer.append(resizeHandle); + } + + // listen key events + $$.keydown(keyPressed).keyup(keyPressed); + + // bind an event to catch external calls + $$.bind("insertion", function(e, settings) { + if (settings.target !== false) { + get(); + } + if (textarea === $.markItUp.focused) { + markup(settings); + } + }); + + // remember the last focus + $$.focus(function() { + $.markItUp.focused = this; + }); + } + + // recursively build header with dropMenus from markupset + function dropMenus(markupSet) { + var ul = $('
      '), i = 0; + $('li:hover > ul', ul).css('display', 'block'); + $.each(markupSet, function() { + var button = this, t = '', title, li, j; + title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||''); + key = (button.key) ? 'accesskey="'+button.key+'"' : ''; + if (button.separator) { + li = $('
    • '+(button.separator||'')+'
    • ').appendTo(ul); + } else { + i++; + for (j = levels.length -1; j >= 0; j--) { + t += levels[j]+"-"; + } + li = $('
    • '+(button.name||'')+'
    • ') + .bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click + return false; + }).click(function() { + return false; + }).bind("focusin", function(){ + $$.focus(); + }).mouseup(function() { + if (button.call) { + eval(button.call)(); + } + setTimeout(function() { markup(button) },1); + return false; + }).hover(function() { + $('> ul', this).show(); + $(document).one('click', function() { // close dropmenu if click outside + $('ul ul', header).hide(); + } + ); + }, function() { + $('> ul', this).hide(); + } + ).appendTo(ul); + if (button.dropMenu) { + levels.push(i); + $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu)); + } + } + }); + levels.pop(); + return ul; + } + + // markItUp! markups + function magicMarkups(string) { + if (string) { + string = string.toString(); + string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g, + function(x, a) { + var b = a.split('|!|'); + if (altKey === true) { + return (b[1] !== undefined) ? b[1] : b[0]; + } else { + return (b[1] === undefined) ? "" : b[0]; + } + } + ); + // [![prompt]!], [![prompt:!:value]!] + string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g, + function(x, a) { + var b = a.split(':!:'); + if (abort === true) { + return false; + } + value = prompt(b[0], (b[1]) ? b[1] : ''); + if (value === null) { + abort = true; + } + return value; + } + ); + return string; + } + return ""; + } + + // prepare action + function prepare(action) { + if ($.isFunction(action)) { + action = action(hash); + } + return magicMarkups(action); + } + + // build block to insert + function build(string) { + var openWith = prepare(clicked.openWith); + var placeHolder = prepare(clicked.placeHolder); + var replaceWith = prepare(clicked.replaceWith); + var closeWith = prepare(clicked.closeWith); + var openBlockWith = prepare(clicked.openBlockWith); + var closeBlockWith = prepare(clicked.closeBlockWith); + var multiline = clicked.multiline; + + if (replaceWith !== "") { + block = openWith + replaceWith + closeWith; + } else if (selection === '' && placeHolder !== '') { + block = openWith + placeHolder + closeWith; + } else { + string = string || selection; + + var lines = selection.split(/\r?\n/), blocks = []; + + for (var l=0; l < lines.length; l++) { + line = lines[l]; + var trailingSpaces; + if (trailingSpaces = line.match(/ *$/)) { + blocks.push(openWith + line.replace(/ *$/g, '') + closeWith + trailingSpaces); + } else { + blocks.push(openWith + line + closeWith); + } + } + + block = blocks.join("\n"); + } + + block = openBlockWith + block + closeBlockWith; + + return { block:block, + openWith:openWith, + replaceWith:replaceWith, + placeHolder:placeHolder, + closeWith:closeWith + }; + } + + // define markup to insert + function markup(button) { + var len, j, n, i; + hash = clicked = button; + get(); + $.extend(hash, { line:"", + root:options.root, + textarea:textarea, + selection:(selection||''), + caretPosition:caretPosition, + ctrlKey:ctrlKey, + shiftKey:shiftKey, + altKey:altKey + } + ); + // callbacks before insertion + prepare(options.beforeInsert); + prepare(clicked.beforeInsert); + if ((ctrlKey === true && shiftKey === true) || button.multiline === true) { + prepare(clicked.beforeMultiInsert); + } + $.extend(hash, { line:1 }); + + if ((ctrlKey === true && shiftKey === true)) { + lines = selection.split(/\r?\n/); + for (j = 0, n = lines.length, i = 0; i < n; i++) { + if ($.trim(lines[i]) !== '') { + $.extend(hash, { line:++j, selection:lines[i] } ); + lines[i] = build(lines[i]).block; + } else { + lines[i] = ""; + } + } + string = { block:lines.join('\n')}; + start = caretPosition; + len = string.block.length + (($.browser.opera) ? n-1 : 0); + } else if (ctrlKey === true) { + string = build(selection); + start = caretPosition + string.openWith.length; + len = string.block.length - string.openWith.length - string.closeWith.length; + len = len - (string.block.match(/ $/) ? 1 : 0); + len -= fixIeBug(string.block); + } else if (shiftKey === true) { + string = build(selection); + start = caretPosition; + len = string.block.length; + len -= fixIeBug(string.block); + } else { + string = build(selection); + start = caretPosition + string.block.length ; + len = 0; + start -= fixIeBug(string.block); + } + if ((selection === '' && string.replaceWith === '')) { + caretOffset += fixOperaBug(string.block); + + start = caretPosition + string.openWith.length; + len = string.block.length - string.openWith.length - string.closeWith.length; + + caretOffset = $$.val().substring(caretPosition, $$.val().length).length; + caretOffset -= fixOperaBug($$.val().substring(0, caretPosition)); + } + $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } ); + + if (string.block !== selection && abort === false) { + insert(string.block); + set(start, len); + } else { + caretOffset = -1; + } + get(); + + $.extend(hash, { line:'', selection:selection }); + + // callbacks after insertion + if ((ctrlKey === true && shiftKey === true) || button.multiline === true) { + prepare(clicked.afterMultiInsert); + } + prepare(clicked.afterInsert); + prepare(options.afterInsert); + + // refresh preview if opened + if (previewWindow && options.previewAutoRefresh) { + refreshPreview(); + } + + // reinit keyevent + shiftKey = altKey = ctrlKey = abort = false; + } + + // Substract linefeed in Opera + function fixOperaBug(string) { + if ($.browser.opera) { + return string.length - string.replace(/\n*/g, '').length; + } + return 0; + } + // Substract linefeed in IE + function fixIeBug(string) { + if ($.browser.msie) { + return string.length - string.replace(/\r*/g, '').length; + } + return 0; + } + + // add markup + function insert(block) { + if (document.selection) { + var newSelection = document.selection.createRange(); + newSelection.text = block; + } else { + textarea.value = textarea.value.substring(0, caretPosition) + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length); + } + } + + // set a selection + function set(start, len) { + if (textarea.createTextRange){ + // quick fix to make it work on Opera 9.5 + if ($.browser.opera && $.browser.version >= 9.5 && len == 0) { + return false; + } + range = textarea.createTextRange(); + range.collapse(true); + range.moveStart('character', start); + range.moveEnd('character', len); + range.select(); + } else if (textarea.setSelectionRange ){ + textarea.setSelectionRange(start, start + len); + } + textarea.scrollTop = scrollPosition; + textarea.focus(); + } + + // get the selection + function get() { + textarea.focus(); + + scrollPosition = textarea.scrollTop; + if (document.selection) { + selection = document.selection.createRange().text; + if ($.browser.msie) { // ie + var range = document.selection.createRange(), rangeCopy = range.duplicate(); + rangeCopy.moveToElementText(textarea); + caretPosition = -1; + while(rangeCopy.inRange(range)) { + rangeCopy.moveStart('character'); + caretPosition ++; + } + } else { // opera + caretPosition = textarea.selectionStart; + } + } else { // gecko & webkit + caretPosition = textarea.selectionStart; + + selection = textarea.value.substring(caretPosition, textarea.selectionEnd); + } + return selection; + } + + // open preview window + function preview() { + if (!previewWindow || previewWindow.closed) { + if (options.previewInWindow) { + previewWindow = window.open('', 'preview', options.previewInWindow); + $(window).unload(function() { + previewWindow.close(); + }); + } else { + iFrame = $(''); + if (options.previewPosition == 'after') { + iFrame.insertAfter(footer); + } else { + iFrame.insertBefore(header); + } + previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1]; + } + } else if (altKey === true) { + if (iFrame) { + iFrame.remove(); + } else { + previewWindow.close(); + } + previewWindow = iFrame = false; + } + if (!options.previewAutoRefresh) { + refreshPreview(); + } + if (options.previewInWindow) { + previewWindow.focus(); + } + } + + // refresh Preview window + function refreshPreview() { + renderPreview(); + } + + function renderPreview() { + var phtml; + if (options.previewParser && typeof options.previewParser === 'function') { + var data = options.previewParser( $$.val() ); + writeInPreview( localize(data, 1) ); + } else if (options.previewParserPath !== '') { + $.ajax({ + type: 'POST', + dataType: 'text', + global: false, + url: options.previewParserPath, + data: options.previewParserVar+'='+encodeURIComponent($$.val()), + success: function(data) { + writeInPreview( localize(data, 1) ); + } + }); + } else { + if (!template) { + $.ajax({ + url: options.previewTemplatePath, + dataType: 'text', + global: false, + success: function(data) { + writeInPreview( localize(data, 1).replace(//g, $$.val()) ); + } + }); + } + } + return false; + } + + function writeInPreview(data) { + if (previewWindow.document) { + try { + sp = previewWindow.document.documentElement.scrollTop + } catch(e) { + sp = 0; + } + previewWindow.document.open(); + previewWindow.document.write(data); + previewWindow.document.close(); + previewWindow.document.documentElement.scrollTop = sp; + } + } + + // set keys pressed + function keyPressed(e) { + shiftKey = e.shiftKey; + altKey = e.altKey; + ctrlKey = (!(e.altKey && e.ctrlKey)) ? (e.ctrlKey || e.metaKey) : false; + + if (e.type === 'keydown') { + if (ctrlKey === true) { + li = $('a[accesskey="'+String.fromCharCode(e.keyCode)+'"]', header).parent('li'); + if (li.length !== 0) { + ctrlKey = false; + setTimeout(function() { + li.triggerHandler('mouseup'); + },1); + return false; + } + } + if (e.keyCode === 13 || e.keyCode === 10) { // Enter key + if (ctrlKey === true) { // Enter + Ctrl + ctrlKey = false; + markup(options.onCtrlEnter); + return options.onCtrlEnter.keepDefault; + } else if (shiftKey === true) { // Enter + Shift + shiftKey = false; + markup(options.onShiftEnter); + return options.onShiftEnter.keepDefault; + } else { // only Enter + markup(options.onEnter); + return options.onEnter.keepDefault; + } + } + if (e.keyCode === 9) { // Tab key + if (shiftKey == true || ctrlKey == true || altKey == true) { + return false; + } + if (caretOffset !== -1) { + get(); + caretOffset = $$.val().length - caretOffset; + set(caretOffset, 0); + caretOffset = -1; + return false; + } else { + markup(options.onTab); + return options.onTab.keepDefault; + } + } + } + } + + init(); + }); + }; + + $.fn.markItUpRemove = function() { + return this.each(function() { + var $$ = $(this).unbind().removeClass('markItUpEditor'); + $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$); + } + ); + }; + + $.markItUp = function(settings) { + var options = { target:false }; + $.extend(options, settings); + if (options.target) { + return $(options.target).each(function() { + $(this).focus(); + $(this).trigger('insertion', [options]); + }); + } else { + $('textarea').trigger('insertion', [options]); + } + }; +})(jQuery); diff --git a/cms/static/js/markitup/sets/wiki/images/bold.png b/cms/static/js/markitup/sets/wiki/images/bold.png new file mode 100644 index 0000000000000000000000000000000000000000..889ae80e37b6167cc15f2a89e05a183815ec18b2 GIT binary patch literal 304 zcmV-00nh%4P)b^}|6b=Y6y(;Y{!a!g z@UQp#@Aw}>L3(}s|7f5BUjeuKZvQRjV<2U7yvu*H{aAbvQ6K!@3oKzW z-{Qa8d3gae1^)HE{~f^!v<1}u>;4xnKvUpW540I-w9J3a{{r=B3T*2g{_BH1CtaZO zpZ`6V0*V5g1e5i;`_=Z#_e=H*@8|93RG@lX;D!K7TKswwko8{x0000LlS^n5Q5c2KWM*m}YV3eAws|yDY&wl?DsIf8f*>k% z<*Hc~7ec_5rHi-`yO3f)SBkFeMi8|t(GpA*#H3ciSWBp+c{r^&PVFO&3C&}i$+#MA zZCZ-`mgm3cyWAfxp=la+gJD~$f3lF*v;Jqx`Q8T$Z@WYD zo5ULg-eG}Z89L0f4Zd=*mecc6xt8;=w2x)zu;*%)sVnHZF8Gfgv50&V?aO?Vg=C`G zqt8;QwVsqD3w#sp>uk4(?#r`&eMV@ShR&fc4IOUt64yxY7gl5?KAAs}zQf1Y-^2&g z$Gu&9-`V$(R7R2uQ}2HsoE!1T1^`G5aq_1Rg+NBCRv6kqwQ}ZBXMdtuFS`d*&78wH z%FqTXD^)8WCsqSp-l}5wzGHH~3TMv4``ZJRQO3=(w6oCI`;E z`gMDg;9p*xrm`moLYyi48W1M{s};+X6Y)q)IQvrJsBPN-$Qt1?9(Dn}gMTvW8Vj;U zv~1YHR;Z*VmZrvRmZz6cE&o6XK(RnVCGj2D!Cx>lhwjfzzEPx#2?dhIYK}l!BvcK! z3)ER+Jz{5>jf6w4x#gTU%_MMNlkNp$oSbvBp&uHw9M;u0-4@=t5BI zP6Hx#-C_{5RMJ z0_P+Xkumexn8%)S+Y)#l(gR;YJP<6#1-=jjK0LONWPdJQIR8uK1HpvVIxBIQ2ztt+ zqoEx_X9S%QGMe=~(k#sebCL-an)%CR%a7YtUOQUgv+G>~?N~XSWhx=? z@$fx}0MB;$`JWcQ-Re{XV~5|{DvU(#*+NF*g)j^qk#b~G9_O!i*y&mZVZ=a3;Go(K z`DkskYn56Nhu+k@1Ke*uY|x zI&k6j$JfNe_a{GH%=n2rZOz$Z8R9V?Pe36hIk}jo+A-`;dt9vyvBu#Xm@veu&@v`| zzt%mwc_$nd0-sMVx2d)b0!MqGxmfCumx7yB#nIUWvA{!HOMfslMyW1iV&nY>zxwyj z8^JfLN|kT z4m^Q1mhO(_r4w@`V?H=YNkOf(i&bHT3Auc3bryK1_{hDSetLoLN{VLB^78ULiNFy^ zkUqqG$fjVkJj5tfWkOn|P5`HVEp5@-mGnc0wvJGHC=+39MC2TWT#i?t*~fNch*he_ zgtS^8dH$(KlW)EF1b4Fzv~?&0IQaNdg;W5&{t&Bmg9&N1-rBBr_;Rg8ekw^mn;@T# zlS{|Rq+-Nlg18i%UY;i|q1NnSwf>I@85#4U4002ovPDHLkV1mEDi4_0< literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/h1.png b/cms/static/js/markitup/sets/wiki/images/h1.png new file mode 100644 index 0000000000000000000000000000000000000000..9c122e91e358860733eaf08fd543e5fc585d4cfd GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$x=IP=XqH%ujg^j!|20W|*-XbO@ zAtBSd^khRzd{=pNv@vN)9uZDqGTXuVPf6#I=?;x2B`Y;1YQMI>>GxvE??vtn{c>{A z7MYxUVrui2JTF|YR&ldntL8M3{q7no52$4`vIX;r@S8@YTFOM5*~nV4Gk-UYI5L$} zDw(h9UDksmVjphbEsSQ?UdGUxU4Htk{EZoY&3@-Y5*_;Wwk`ZAkg@!FaC~ii{N>;; VD%(>GD}XL$@O1TaS?83{1OVXtVO9VD literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/h2.png b/cms/static/js/markitup/sets/wiki/images/h2.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd87657fbe001c0a78fb095284fffc32e739497 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$ROXGNhtQ`{C(%$wdeB3 zGTnLdz~IMJtNg?T>Z(s;oVU0)KW5x*9xvq)rPF;` zY|Fc4&#rLa>Txf#@y+aKf+ac0%`STzAI(*qdYo^XFH557y+_x*JpKO5^1S9?c^6}{ zP=+&OVHtDUrGNmdKI;Vst0KHFj AM*si- literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/h3.png b/cms/static/js/markitup/sets/wiki/images/h3.png new file mode 100644 index 0000000000000000000000000000000000000000..c7836cf09e4565cc76c13bd14c13971c9e093c40 GIT binary patch literal 306 zcmV-20nPr2P)wEzGB literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/h4.png b/cms/static/js/markitup/sets/wiki/images/h4.png new file mode 100644 index 0000000000000000000000000000000000000000..4e929eaf583f10cf50eb1666ff6530b9d4cc7915 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-&H?&;zfqH%ujg^j$;1_G=B-XbO( zon2btF4r#xDw?mmvtwaL3R_~+Yz66*rrQmiHySwec#9a-mn?F*9`VcnzTNDrbNY9( zxOA;CUb9S|sk-b7=Pc<<>lMA8+|AaBj(NoLhh2`-;+F zenR#-z6;(Syt?;Ub#DVE_OC literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/h5.png b/cms/static/js/markitup/sets/wiki/images/h5.png new file mode 100644 index 0000000000000000000000000000000000000000..30cabebf7445e168a0f31b0ed68c43d54eaf017d GIT binary patch literal 304 zcmV-00nh%4P)ZVg9Hj*!Zw zxkAM~zCH&l><=6QeDdgV4l9hop+%GWq_IPV?Z641X8iiHrWJUN^2}hSiGjhsfbOLp z?d`9_MC0P3jVAVsE0oSgT$J*kO*Aq9I~CW*s{G*(t$KS{OS+#aO%?udUme<*TTEO`Fr@r_QT zk=#}u-n~>Vm!+9S1PE{@3<)G~CPb<$Za;W?3+O}|+q)?*Pn355=}S(XIZmEANjZci zf5 zj<%@MX^bD1^BwlS^+AD|$dm-1wial0hwPI;CDM?Y9SXW#@w-UF0SQ8OgplRTleOB2 zUjkDS|0U9pI|lSN*EvXUa~*UIclJdZ#)Npbwh9>YT?Z;=B8|l&^t~P~om?<5Lre$+ z;%`P>SL7`djY#8Y9$wv9dv|3p)C#5QQ<|d}62BjvZR2H60wE-$B^mK6y(Kw&{<9vg>Q9!g~ne(gm zmj4swoA@7?D86%i^8WzK9JM17E&sp&Z#dpHfz$E-U9ks&4?Z9Gyg!%0k2Q{M-Tz#> z2OnD>vrPZ*#{EHKLq)>Jcx{H|Ovdb&|4aQZWSipI{El%e^Cxx{^9vSw28s;a3IDB= TS1%U=TF&6<>gTe~DWM4fm>N^1 literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/link.png b/cms/static/js/markitup/sets/wiki/images/link.png new file mode 100644 index 0000000000000000000000000000000000000000..25eacb7c2524142262d68bf729c5e2b61adfd6d4 GIT binary patch literal 343 zcmV-d0jU0oP)$`dXYaZs9=SbAto%g@>T~?_bH&lTUn@`uo|1bXE{eSR(AO)ESb=V4`uk}mK|39Px&03WLbv~pzk+s7D@lK^ zn+aB+sp)&Y_x-B3>;6ywU--WQNUr<8>TU0P-|L#1U&;A)67w(+> pDf@fM7q9#F25QXo3rUI;002ro52U44e~JJA002ovPDHLkV1l;_q@Mr) literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/list-bullet.png b/cms/static/js/markitup/sets/wiki/images/list-bullet.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8672bde48f806d3d4d37db192588a9aa3eac10 GIT binary patch literal 344 zcmV-e0jK_nP)PbXFR5;6H z`2YVu10|S&DhA}te_Swi*Xsu$nk)lAnzx?+_#Z@r_~qs0*+Bfiq@?73K|#U)?Ck9S zsi~>|6A}{sM@B~e4-O9gPhA%bd?2RGd{of6>E(lvp1b6Ep>6&12TPB<{a?EDDL4@0 z;^ML+A|n0=1_u83^78uc?CkvC#>VEqiHXU7U0vP(YHDhzff&$ntDt1@;|H#l*M@2! z+U8#>h@W=vfpy*`^1J}j+`sMRe-I7g8yOj8Yin!&S5Z;VicFNp}SURRVGD{CSNFe~ni^^#wyl5uzj4je z|23%2?k#{x(*%mqe9M%mih+W%ElRQ}7!$^91> z7ymCLB=nz$hvz>#JNtiTW@gkt1Zf0eyP_){bPq%T_kY#2Z7&xs00000NkvXXu0mjf DNYA0= literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/picture.png b/cms/static/js/markitup/sets/wiki/images/picture.png new file mode 100644 index 0000000000000000000000000000000000000000..4a158fef7e0da8fd19525f574f2c4966443866cf GIT binary patch literal 606 zcmV-k0-^nhP)Q2rnAt>LM%-F zK|rtwgcU)}7x~z1Hrcs5bH*ZO$!>xO8K#?==bZPQ_ecnV>#P`H`QzGaRhd62G_&rC zTLU$c7_x*nFP_dW#Q+*);mMHE?j)HexK784D4x9l_tfpz2$@1y}9rkF+ zI+J5NMWeZyObc!d+rUc=>D+uOdAOg#%+Ej6h+wn5^xPmVVH*Eu446Y0A_@ zo$rlds-+sL10DbHs{AQG2a)rMyf zFQK~pm1x3+7!nu%-M`k}``c>^00{o_1pjWJUTfl8mg=3qGEl8H@}^@w`VUx0_$uy4 z2FhRqKX}xI*?Tv1DJd8z#F#0c%*~rM30HE1@2o5m~}ZyoWhqv>ql{V z1ZGE0lgcoK^lx+eqc*rAX1Ky;Xx3U%u#zG!m-;eD1Qsn@kf3|F9qz~|95=&g3(7!X zB}JAT>RU;a%vaNOGnJ%e1=K6eAh43c(QN8RQ6~GP%O}Jju$~Ld*%`mO1p?P)vCi#|P&Xm-dkucwL z3)87{8iWe96huvPHfK`KOdC2Z({T6vJ9pwDx$D4>d(Pqff6w7Lmj{5i6;ZyPPpPN; zroaW=6d#@oL2Fa53F~$Su10(RG%K0p3VTuP3?Z=nBA8z$uq+XLUL^QrC74`bU|!e| zr>hK{)%Q!vdmIO5Z3JIvaOyjOX`X@c8-ua03`Q&)f&%p*{(A$q`ZTTjk%q_T7>v^J zu!R-a9fFLScYlKkNBP_Cob=9m9JLVoC-?c{)eOtMnh7qNN{ejy2sM{pS^mgFHJm@(buuM4>=<5Vr$&Kzw{B?uPr; z(1Yf=#g)zADkWnx=MR%ykl| z3Ui42k+O2{bCn)01-s5Sxp|z{G2di&KT(_M6;$EI zDL57JFf}cw4bP1P$pgTRKH$0@h|~aA>j`qZ2*kU5t2EVD5#~@VNhqx{vz8ethDD-=+1vnemftUBA zF;N!Q%PBB5B=KLB#QO(CHe?;R+-C8M?ppDW>R$5`cCPq@YpusFRTaH1i9Kv;l<>I( Ze*oTy+;kdDB`N>_002ovPDHLkV1l3CM+g7_ literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/images/stroke.png b/cms/static/js/markitup/sets/wiki/images/stroke.png new file mode 100644 index 0000000000000000000000000000000000000000..612058a78eba4e3ca259aa13417fd60cd6cf2fbd GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-%c@9E+gqH#X?$N2_%qkv$?&V++! z-fX$@sb%KG-5a$|R8J4(Tcmu7`G0cYgxS-++3#dN^zA^J(=P8r|6lx9G_LLaZ+v5S z!d9iC@)!RP{FnQmdw#}3Ee(C$|NPA#|L6YmR@fBO9w|C7oMMQ$vc`Tu(RETvC<`(>OPJ!ieM-fH~m>7>j(^b;|pVbz=yzjpaJj$ zhrpuriKefui_0DvN;1Ymq&%nwWg*IrK!Xz^eJWuq3u2H~0ra?EC@ge%+`A>6mV z9{TYo{=G6 zt@5m|4G+Q2zKv;Ch@O;`PfWArmB5n3gvMsxV&Iu>97{a!2kL74wd@!f_AP^O%_&ND zm}1c*+F;TcH^{p$P_|akvD5o7vmT>HCkP;z;;&+8tDBI;koi9eX`W!oH4`pYaHlFZwV;$>vvfQTw zM-`m&R_SPIBa^FUasC0GCCh%{h`$~db`z&-lFX#%(f>H6JD6Z(sIW`RKE+xOL+?+uQ%q z){?+F%=6pqEH{6=NzusC-*<`PZYiLCGyKD}Z8^V8ul-K=AV@SE1t4~D2*b1(9UUc= zN-;Dv#Ngl{rd7e$ZUPXC##BFmV>$26ZQi?6Po#@{4gllsPbku3Vq${Y+FAf~T}OJb zGWEz9{(zcvI&CUaN&p7GcqMG4&7ULx##68M4k(F4l7Q+Xm&>uSv4N&(w6?a=)YOC{ zoYLN-J?7@-9xGBx007$C+kK7w_2Z$(k&l}jo2#`dO;J#Ipsbc$pS#^Dy3Q&nSeE5x fGMT)t>sS8=`naU3reLNz00000NkvXXu0mjf)bGN+ literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/sets/wiki/set.js b/cms/static/js/markitup/sets/wiki/set.js new file mode 100644 index 0000000000..895625621d --- /dev/null +++ b/cms/static/js/markitup/sets/wiki/set.js @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------- +// markItUp! +// ---------------------------------------------------------------------------- +// Copyright (C) 2008 Jay Salvat +// http://markitup.jaysalvat.com/ +// ---------------------------------------------------------------------------- +myWikiSettings = { + nameSpace: "wiki", // Useful to prevent multi-instances CSS conflict + previewParserPath: "~/sets/wiki/preview.php", + onShiftEnter: {keepDefault:false, replaceWith:'\n\n'}, + markupSet: [ + {name:'Heading 1', key:'1', openWith:'== ', closeWith:' ==', placeHolder:'Your title here...' }, + {name:'Heading 2', key:'2', openWith:'=== ', closeWith:' ===', placeHolder:'Your title here...' }, + {name:'Heading 3', key:'3', openWith:'==== ', closeWith:' ====', placeHolder:'Your title here...' }, + {name:'Heading 4', key:'4', openWith:'===== ', closeWith:' =====', placeHolder:'Your title here...' }, + {name:'Heading 5', key:'5', openWith:'====== ', closeWith:' ======', placeHolder:'Your title here...' }, + {separator:'---------------' }, + {name:'Bold', key:'B', openWith:"'''", closeWith:"'''"}, + {name:'Italic', key:'I', openWith:"''", closeWith:"''"}, + {name:'Stroke through', key:'S', openWith:'', closeWith:''}, + {separator:'---------------' }, + {name:'Bulleted list', openWith:'(!(* |!|*)!)'}, + {name:'Numeric list', openWith:'(!(# |!|#)!)'}, + {separator:'---------------' }, + {name:'Picture', key:'P', replaceWith:'[[Image:[![Url:!:http://]!]|[![name]!]]]'}, + {name:'Link', key:'L', openWith:'[[![Link]!] ', closeWith:']', placeHolder:'Your text to link here...' }, + {name:'Url', openWith:'[[![Url:!:http://]!] ', closeWith:']', placeHolder:'Your text to link here...' }, + {separator:'---------------' }, + {name:'Quotes', openWith:'(!(> |!|>)!)'}, + {name:'Code', openWith:'(!(|!|
      )!)', closeWith:'(!(|!|
      )!)'}, + {separator:'---------------' }, + {name:'Preview', call:'preview', className:'preview'} + ] +} diff --git a/cms/static/js/markitup/sets/wiki/style.css b/cms/static/js/markitup/sets/wiki/style.css new file mode 100644 index 0000000000..0a26c698f5 --- /dev/null +++ b/cms/static/js/markitup/sets/wiki/style.css @@ -0,0 +1,57 @@ +/* ------------------------------------------------------------------- +// markItUp! +// By Jay Salvat - http://markitup.jaysalvat.com/ +// ------------------------------------------------------------------*/ +.wiki .markItUpButton1 a { + background-image:url(images/h1.png); +} +.wiki .markItUpButton2 a { + background-image:url(images/h2.png); +} +.wiki .markItUpButton3 a { + background-image:url(images/h3.png); +} +.wiki .markItUpButton4 a { + background-image:url(images/h4.png); +} +.wiki .markItUpButton5 a { + background-image:url(images/h5.png); +} + +.wiki .markItUpButton6 a { + background-image:url(images/bold.png); +} +.wiki .markItUpButton7 a { + background-image:url(images/italic.png); +} +.wiki .markItUpButton8 a { + background-image:url(images/stroke.png); +} + +.wiki .markItUpButton9 a { + background-image:url(images/list-bullet.png); +} +.wiki .markItUpButton10 a { + background-image:url(images/list-numeric.png); +} + +.wiki .markItUpButton11 a { + background-image:url(images/picture.png); +} +.wiki .markItUpButton12 a { + background-image:url(images/link.png); +} +.wiki .markItUpButton13 a { + background-image:url(images/url.png); +} + +.wiki .markItUpButton14 a { + background-image:url(images/quotes.png); +} +.wiki .markItUpButton15 a { + background-image:url(images/code.png); +} + +.wiki .preview a { + background-image:url(images/preview.png); +} diff --git a/cms/static/js/markitup/skins/simple/images/handle.png b/cms/static/js/markitup/skins/simple/images/handle.png new file mode 100644 index 0000000000000000000000000000000000000000..3993b20337e33a36c9125d139f1f53a279a4c128 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^qCm{X#0(?@t!)i}6mzkYX9x!e$L)vy4}e^r0G|-o z4LkP#|NsB{_wSo_?oZ!X{TC?CQWE4B{GZ|f|H~VSrU3bz1s;*b3=G^tAk28_ZrvZC zpje4(M2T}zYGO%dex5=|W^O8jfw{hsp}v86ds2l5P=!25MR0yvNqJ&XDuZuga#4P6 zYD#9Jf?H-$YI%N9cCmuR){ILPK&1wrE{-7_Gm{H=1WS1m6Eb=Pa(faIBBVrjnVf1= soMNglKFuA6ghUcX`bbD&-ZPgg&ebxsLQ0Hz~TmH+?% literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/skins/simple/images/menu.png b/cms/static/js/markitup/skins/simple/images/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..44a07afd30f499cdba30847094a1e92f13e1320e GIT binary patch literal 27151 zcmb@uby!=^voH>&K#@Wz#kHjZ#i7NeKyfI=U0STTJ0Vbtw8dQ$C`DS_-AZu@9z1yP z;DID0FQ0qw_ul(^|9Ic`xzBH(J!dvMJ9~C!&(6-AIVWG=zf-U&MM|6AsRp$5n7ToocSJk_Bn}YAzMBn}yzS-~r2X-X~Pg_4XPbV*SvHDMw{Bi(&-4O^6FCut;Y6b0D#;nkADCyhU-M0NFxfj4cv3?psgw0C*dj%d|)lI1h@ztu& zMbd4Jii?xH5^7}#dt1KyuNMDP$n{e=9}ka((#1(n_jZitrhbb`Nqsi!b4j-I-@Jr^ z#yXC3q+8EY$X)LaDr-M_)|>J-A%z#-OMI6ko7#A2C(Ymg3h0OHHP9D*lQ)5fAKf^Y z;QTteue|d&_IFDQqg04~C!45bP(iiGE+$xr(bTosz++@EzzM=qn%r3C?=|h-pCuwL zwWmElk7(KWCbR1eQF?DK8%ox6F6_`~g4_sX!X6x6y7mo4P8u?A@P5C}J zF(^2Vx$nrAucR9ix{!EE%D|z$Y){E+7RJ0uNgOgVdPP_7$yAf_}8@Rh8|OF>H8i3%)Vc8iqyV)?{~@s zsxp55an_BT7fRG-i;ijGv;leAP^37GXTVY@-}{x4Q&$Tzi1gfb*5eY1qyD3$InG!- zR3&&m5?GK)&YInAE$siB9P~20SYo#U7}k3JVwsPBu{hLZK9VJvgC$gwxl4MBv}Kv5 z>s~HJi}mpUdxrZ5d0qY&`3}WzbjRzSz9Q7Dg_k4<(Dv^iRD7NiVGA9>-aLh@)2X2p`N|irxPxg2^#m_a?>_EPu)0B7b%fktFm5jpgyng&B z`TZm`C9JNw;9UNjPB_YhP2)q2NICaA)RM=nLbsGcFRcGfIsMPcq0$C=A%#%OroCeE;fwSw0n;NO&Z76$e@5N>elO-UTt| zJPXs+GY+o9@WD5ZuPF*wv>2tnzqezQffMOg77}=6hc^?G`pI?wLWVT0C67C4bIJa> zO!Z7<&RbhH5Z000&vLXwG}@*% zde0v5W*WyN(|q{TR_}x@!X5jaVh*X7cN2{5@Eibgjcq&NApy@MiI=~7otK};ldsx% z6ueQZ$IJWCvgJ43#!^R657^5~|LRyYvt4R`#=^&^1nF3#Z^t)v=Tj-dabB3|PEtkYAI}!h)?46{X9+Tm9_)iJGG*iL zSwhd05|%Pd*In3aM)*%eBr+d|FjHjMhNV0Ajhx*5Sr;d ze-wlnpKQh$74HrNrUr4n;#B>bmOngRLvKQ>F#ORxlZxH-+&rJVAa4Q9d_R zDJ;*SANeshUxm*EPw^t3GmBiY=%Wt3$GZm})70)fX99Rl`aExSHdH)&Yn!%>?U*yL zBRRxH-0@Pq+_v#WQ7l_fo_4WRgQ7FgmZNGe!+js%5k{FP5>-WP^Wu|3#^F~(t@y{; zDL=xfd8K1!RPXda^L{?t%5b}%y8ntao6xFUN`pEnu~pvdl(l@|QSkEjfxoTsgL5oN zOxUp24Yq-s=CrZKwR!8t-p#bYmeUMh%QbUzff?pU+R zRhul|dnFnco@880?Yr{TZxdrI!0;LWk~~tB{c%KGh9z&rR8){dYNHwoHOkq6j)dC?rME_pTeglmw`TOAM-vNp)VTPA-{@w^W`*e zJo1&#Y<#OG@&4vu5L50{s7@)l(_?~H(tW!`pg?^_nICHt%v6_sxsh~|FN=wdjyxD@ zJYE!z_C1@m3m5!8yyxla)3daEz5JKq2@&z9o9g~1^kWIoW=0){_k)3z zy{&G6k8*6RT?Zt}1!jJ$RqISGvaT<38UVa}S4SH+K4 zL9Hq+_EhbQE3w3L%qOt36s;_Mq`u!qKGDJ8DJ^|CydLzNZ=9Xn7GJa3@2}u%n+ys4 zX7S3CYWrB?NGDn*<;020b?1v;RLFH#`2$YYzrQN4`9mk3YjGJ%4+q#+3mZM!rXo}- zVzBe<3QGF@;>hjvMzaAHl~m>v!L(00^`25kXIx=h%y$34jRaz||FB{F*EXx}u3b|j zvhbJhXE0YipNDIZS&FpO_hJ1+%wR@vjd18*68QzoeP5{;*dU5^C(k*Sp&uRj`T+Wc zzlCp~Wv%r}-E5MmIE=)NrWfZ@3)vCt<_Lh3_#fRb%*gD^U5aLsf>Epe$%ujle2UP1 z3ICkXsG7xcd|vuNS+?K}t4A2U8b^PQ?K4eJo2+}ks4m5O81Dl_MHd3~AGS!?ywj!4 z5h)f6`^K68yABfisPi4n2c8bs$kx0QH@)tpkm)m-MmnE{&t3y3hd{^)d^X_#55U#K>3@gz5oOl@nJktS8^la$#7rz+*o!YhQ%NBiF)mI zfGAMg{wEUjnXKt1p9&1__ivm3nn}>|yJyT4mvhIKqNOPzkTDhRYAbNiH%-H6#3d{{ zI8;;)!MyLIiRG2WJC;K58r#DgvCjtI85RaW=rc&!E9^<=`yF__#9Ak`uX*k^65PYe zY1coDh!e6_X?A2V5V#V{sIyQWp}-ry~k}x|`EG>z?_FfnBrcn`Y7bQNW$@ zd57`GxfG?6HofAs$#(9K`dOp-uX?Gnk5#&}YUi?ov z5+HP&PFg#9?B6CjuxuH=H%0i+jD|z;FoUeES5(eybtm_EG%vr=q=q~XdpTOVj?xeF6eI6yyxB0iHH&Y< z{E4?ubA@bj@zVCGOItHG)tFP5NQS#^W55T~^r(UiAY-B?q#ZNc%_0c9NYGwXgw)YYi$ zGo;VTSf-5(@HAX+@!t^0=9a6_$psQP!7&7O;ko?DrAFy2q?m}J)u&NF6n3xw)2Rb!o% z03A`&{y*u4BWd5%&5tX9W5!Pc!(5wl}b&-!fI+`R=1TEM%KWzZf%Wiht>A zs8dUXuK9&x^Y&7DQuRA$J}7HA^$mLdQl;{jZ!@j^$HIzrf{NOg32o!qZ0^}aYj#Y|6M(NW`V!%}+Y+TjJL=Jz zGM_ki6niu5siR{x?XF&d44+f#^#*wza~wu|ZyA*O+=(9TyYcU2$bd3G98pdgjN#b5 zbpeV+a{|vkDgec!%a`6=E-s@ztlijfZBNn`4b4pIV(pIZ5m`6`mZO0Rs@90t%ondk z!>p|(~oNw+>erc4p9Q-j{HJ_#v$U61H-Gg`2}FYxXYyUiuw1` zvc*4ABD^lwep}t&>BXQq@XLh^C3kxl7&a+x@k9-6oj*}|-Ibw?SVdglk zY_EN_#?!Fy{O6P|YSmua084i4*S!4&(W>dR4}^zv91w;NTr;_MFkZcWWY~3@&?Iq9 z>bh#!_D8qn520vW4V*HEH)pMJSd2jcceTC?wr1bsm{xUcoCdc13}~#*s@~v@+*G#ccP#VYjszCXRrYe=C{!?{ z5ZF^L@{opE}Xbc~JUM>gf@;%dn>H7B^ zYfN$b1B{Ej6wGd zp&^}F?)TDGce)PRj(@8jn|ZQNYi925(>*h2cH@##dVTVWtg?##7w=rF6+*GL(W1() zdxbE_{v$$jY^C!(KZdXZ&5F|Qo+UKp&nh4NTu#=&Hz#3e_OxsEpWfTuD?~^u_?lW) z9Z3w;y;0vsU{jTD9puKgcmBn+e=diHm4E*S zk~NIc(Ov&_r>l6$-k+=n!aT))Pmuz_`FjF7PB88W~BBrOQp5H@mN4;hF{!v!BRd*ep zPfH=}1Wr%=%R6$XzC6o#aa?CVqT|_K_OlVDN&{F!a|dC0!cMl#8e>$V<>mxL+Is#0 zN~O*IrP6a;Tgb1uIDK@XV#g_Xf`8^qhxt|E33)rg#Q~RWl12JX8*KaU3`-MjKs#96 zaQ`0x-wK9G!AQukz&9ltKf|K+j-f22YVF%YYgnWo75%Uap@Db$H+uPZ*}B_lf1fdZ zPAj_8`J5Rs&rHy8EU9pkDScR(lale?>SD#YzA zTY>i_REtS%63NmI7k&< zQyG5pD!C&}qWGQ63k-jW`C|*ckiW2K-?7=M4jqC6maFgBtptotaxjTjj{xl#)K=0; zwMDFQ!Sg4zRpp&TUoTWqhHda=4T|HSMaqSC^htmdWoOb3FZc5)Kw$I8<$B*!Pfg69q5umo zGSDRY<8Xill4~9;DIXkSm}`w2`U#U12<&RW5QtGq?@CTmw^7y{1fK&|_gg!BQCODT zwfXp`kN%)gVNRi%nL`7aje-VV#0l{h!lRk|1FY3l!bnn`+(trsv+-&fHqwPBY^LRE z#GO&C7Rn|l%896~WD$uD5l5%uSfH-S6;1xnc*8IJs739kkq>j19+QETEkVhkyOrSW zGJS_((e4X8@wyLqNxb1opUnQqj?=s1tz^>km!T{e90}r%h>k6vnVUq4NRiZ?M?Nv$ z55*Dfl!zV;AgI=^2LEyrJswpWXJj`@Q<1a$^+;sxEPCPlJTp)wa7ARbEBJZT(NLcX zA_M$r5&j1ryecgkJZq_H*h{q$in??el9VQ624ne{-p6wWf9YJms81

      +35?Hj$O) z2JCMj9zju?tEyh`;6@iN+FC>%gS>oOR0Ayr^JovjDPut@bP+CYawfiDCK-o) zx4QE%HM(2*$P4!!bCLbIk!*#}zjgKS_2%K|E^CP`#=m;gI1xYASMwV{@)g}jUC>+2 z44UqVT9%>0ya0zGcV^NU9H;L)6A{T1`m!#!SKt_twPgOOPcq#~R%6Wlzr&5nji;B4 z;^Y1rM?c)P{n|I-=NzcFjTL7jRt?@JXz+fb4D!hkQk z&OggEP90d;px;c1P7KzlB;uj*gagko~;y(+L z&Aj)FMPj{KI5o3wNCLHOvrIM8!FBYz!Q6ErZ)-jT5bNGedz@K$!+hs?`Yv3mT_Ic| z{UCWasCtEGL?WQtatfUpCTH;ZglsFcZJVj(v!)JQd4y}I0L5|7`mhSP_JOiIgstlX zXDkj#C(&2r9|E?%h&zh*O7p|=U>nDOuzk(n0G6k!0J1l?N7bav)@>7vQvHYTyydA; zI%#6QI>Zs+#H-lUM@Aey+E)AUuUSnUeWI-pWRnCwTA<%rZ3#d45@ z=&PB+9gM%`v1`Y7Y0;1svmC;BN5!apfiddQaN>mU`$JPggG77YH!mFs$Nr|Ma#uCB z{-h(Q$$BmruCB4lqsZMrVB*2jWo4Xo?6OnwLGyvVO~;)te_D=(E}dh5b3=*)rC3sN zzsTp#T%DYTkCX$NXs53jIc*4r!Cq5?4>wRHG9CmqQHs_HGAEza3vM5+vhGmxD%>uj zrZpIIgzauO87|J)$4WWS&f?nbN{2sDqw?AHjWIv~5DTbcIre4=98=G#1;=TreQSM- zI1q%$wH6(~)MeEVV+{TVynZGdB;W=nSMM{k9>fs4aev?Z*ol$sB`K;=8=5oH;0LGz zJYL58J^Ij+e=&b{d?&erf8%pa7a{CL4luh8^fZF+TTtyjNP#gTk#wZb)`P4;?FD0F zNtqr}JfFg>7T_~ZsQ9Bz?HZlRVLfY-m29Q8fq2>s0V zmGo!(R-HLVQLX2#o5d+bubagKRCd<)?wo&R{zm_6lLhzYHGnnZ^&30J*hZsmi9ltA z)9Uf>7HJw-RUYsC;f=U;e|i9`s`7h&m1F_v*`)GImhAQvP(Z)BAkuH%*fpM}!9Xa- z@TgsKEdjK(Cv3^YU_mH=6Xh{Z^s=jroBkqfS04AaQf!~`zK2Dr5!;~M{T4p zV$EL!OXO~ssFyQ?6uJsWpZ+Z~%i{jHp&g|h?-bVdwxLFcaNF!~Xvkfijx9cr6tcF= zFY)A$%zAGo`5Fs5)8Ajj0A%p3#PQ6$$Xc$9LJ7=3QX2iE4(ANE5o&^|CW*C+w z9kS{`?5O^}9F-#Gh^F0|rGM2L1Dv<#A+B>1r~3OTD4i;G;7M{GG^!RbVo{+cQeSuh z{pnko9Rnh}X5=BJLNv9==vgGa&Yiim9oTzj`=`2%Jy`2+CIX$)~cO;+0QjnpwF-ER(xcEeZxX z)`GjaP(=ShUwoH?J!vq>Xn>Dw$W)~D@4d~0^!6iucWD}txICKng1%2?9>hiJXI|}R z+rfOV=4IVr@!9>-&VMXQ!xdW0+t;JJW$U8_MAo*YB>-5CzeG;)tOQYO%GepK^ZLbu024rmb#rXiF9=Q-}mPF;HkP zOX3`-(;Bk6`DIToiUpxPA@)S&(qvBc^5k1kr8_QUpWb`_ste{3vhY*Xr0R3~@aOiy zKRE)n)-85I`lXSt%iY9xtS)`!EUwN__p(m2mYK31LchJRA?L@C0C~^PO-`pE9kG3G zwOmWv_c4dHrVhVCzb!d>jyo*HQ@(>xm4#0=>Upmve~`OQON}YwL4^r3TG3K4de?^c zahK`nV<^EC+M`UBCrZWUKXRvz__!u;1YA_>##d|7}M^2iD>xDoHMLp=PSZ7JiXfSkpneNS~OAM`hyJe zifAp3!pz~kAoO?J>`s%bQcwbIL!o`b&KJ2vZ=$Ihdgmp*KZ#;Pbbmw{;v3dX&!AlhnwDyVfUlqjX zxnkp}iE*4an{&f=ij4$L-LG<4C-{PoESsY5KQhF_Bdx#XMGrN08#)FWw){^+#{Zq} zHe{R|o46=OuB5BNsQQ@eY3$F0M@dRLDy|9HerHEyDuc$AEQ|iapqag>pZ6aAz)L_~ zd(lUJ_|NRG+5yC27ZLsOY&e$I7y5BQ>1*@X$7?-qot2iI*x$&74*MHJ@!#Y<=&&}( z`sHyNwz;(-ZpQ5eutM4JevnKEW^Q&y4SR%h2BOY)p)faC#|`FmYZ!?7aY&L%`~Qyy4Pp6FFigEZdkf4wTxArUEq zH)Jg>crZVRE5mAC`yt480yZsxrM&FWg$ACR5u-s^Al?o-739-1?xlIn0Zr03-2H$%5h;UwzNlc85^%~QKDS7&7lE2 z={0383+#cBf`fZm{~=3%Mf!eNvT_x)7 z4&XWGWOMdL>>LNagm7(m9cA*ReIv^PIZ_^!Nqc&9FrR5L1oJsB*$At^5%_#+U+r*_ zjRORk$BgrZ3;@P`Es;1i$$fvg+lal|<^H*`dD2SlKJZP$E&<;`DOcv2n+H%K4bzz{ z3xLVI=|>xZyT)J{r!ROhc$-s$IL>y%L|MosD!Zn-?fZG}A{`vHvP*RSb&;f{w}svp z+n3S_+27R48Ol7@!RA1(d$7I-rs}e$a)3fv-W$Km!>#&hmx-Cg8-SGZfc2L8m()%{ zKy+KN&(?|mwnpGe5i6@cwnu>cpZwn1^dK7}CDUndCVd&oVBN{q063}D?D$UBP6O=0 z^w`_mPwin=XGwM_`oF-Jy3Tp!E%wixEbV1SPV*|2WOE8?OWAR$%TQl|;NZEqai%$- zbbql*=li~+MSHl{LE{F0|0lMqx~{oXfAA#~87#8WuR)uI>>)_^eCzM^aicnjK3 zKZ6`Jp1t0Q-o?R<27!e*L*L``!eT8JCa%LL=xS?$G3H@kpY z>JFG`ajB2)DsYprbnxnqWq2;8pY94{ri!x<)t55ab zk7WJ;d?@Su7ipoekqV>obt*5LYv-s~y%mNnI^l>5S0^kbYyl-+WNK*s<8)kR<9uxm z?0(+psLq|8v*iYSVta~`;@cPkS!H+cS#Symj+{a+cYH{}-9^re+#;^OQA{snw0v!sYe2E1(?;Kh645iXcl=9)j)1RTE`F{|0&|P&HZj z!ZwAtfU=T?E`6RCuX8ahZkyJZY_(vlYT$t^sv{#lQR@g9=#PvEYJNV~{}>%%+Z@P; z{e@}0$;C|3P4_;TsGKK{ef(43;?IJg<;UG|z#eGw`GZjNtxuJf5S2mk77Bm_(KM1oOK#-xclv9C; zf@uLNVs!sBry>OO;ox60R+VMyQ`eczeJpyV z>+4b0gE-ipEx!yHi-41|w9Br1P6WmS=o|94au|)S;eLm`4T%fBBeJlqD%h`M6D*x1P!7jk+n+S2Xog>U_Uguy_GEjz60_@r?yh{TowKT<9z69>Ocvo4w z9P}v2Y0;|*HIlC$^xu=Jk%d2$a{UoMQR(^^_eVw=315~vZ4>DSN?vdDm!5lJ zkUQ9=|J<&ER{e)#GR=B{E8mvUd+sd$@CI1{==U}{4%}-HZ1pN|yMH1+Gu*n{IvGqBwsUt<>sI|l-=%U1~f|6KC_p%edIGYYiv{e4fH9zuDS4f}F-Oz@4e z**ds$b4h*Zc;x}iQ{4!cQST)LT(9PY6`sOd;?(vJ8fja^QsQD>$gC$By;)CdqZrdJ zLu=nKek@pB@HmglJ@xbdN_OxO$(7u>f7E0-q#m3apXfVA$G);82#=f5Y!i9lbk7!S z%PZ8MjX=G=28*YkvB;`h#3{D?4EZX43aQpcR96^RK$V)s()NK=l<#D>LL?=m)uhr9 zi!f|H?}0boNz31)fOm2$O#Qp|tyl$Y9EsT)`rXaC(UAIHx);}rT?=)by;;aQTnLaY zatN5N?QC2_a5b|oxteRWqCHnwESN@+wT@9Ckxv?rg4o=*o7b;}#Qj1=QkZA>$N8AS zfxNzl5@_z5eq6y>jb_k_VLIzgJX+1TxzYq#KD1Z=!|GXM%>Bd3pU&d(5p56QsfVD3 zOy-$N2!|F#KahK;TOTJYd2~a4?G*=KvdIcz^*vdA*O(pkHWx1XlFt}k%Eav}F%@7d zd444}3@8fO#VrR%G}dtUB(6lf!-@k7R+qZ~sX_96AP#S$%H0g8|53umOu1LQ(O{=j zWsGhao55i=hJOa$>Z2y@wnwgf(|IiQeWq381+JjxOrz{nf^ze%d5y@DW0_rU=Bd3# z#{S*FT>eV#xpvc_xJ(#Ji|6sO!ntOJG1Q|dWNpW^l{qLr;Cov^#p!4OBZ)QPK%iV_ z@ydDYdE$!X)R7N^^pkZeUTBRcA?BL8G>Vv;94lnOBihZ^7Spgm% z1@ylR*rc%kZixO*%l~e`GINnfutZaPuv7f~D)xYQ{Vnr=#qORAwAEhq4FrNUs&sU; zZ*_R^@ZNYhC_TN~N`Ri6DEwbwRThJ~*x!Ofz`)>j3FL41x>sQtp6wN~8>hW{{S7M| zjKkh+O%-eX8(Jv%`g98h;p+gPFC`IGqPBO>(aK8G=sHj;Nb92Wj>rVI;?nX$8|9l? zlngzDUFVwXotAChHD_$MC>*Y^KWqz zD>CMuiPZrVWqvBt4gA{GJG#mvTc{agf$FjSw&w+(0H9dZ2u&FVXka&s+Xd#fg^ghM z+)8u}{0^7{4FD5->*4(4G9@8lQU;#BEpKFo9?K$W^mA{n6ClGd1{^|=uBjKhBZcC) zINB`4rrrQp*1H2cihOweVEv6VGFdGhPPuh+_+86 zdU-F4S)5uCoA+%l4S`XJQ4}X&`6hl(p{zY1ymFcQB#7ENdI-d+rsj%+eAVPnZ67hJ$37rZu-J4#(VY((7Oow zW4-ma&zlVCXskKwag#`7oo_449 zSlke67B$*1C*5mysNH~h-t~I=;w9K%p6$r!nq%vcG;Ty`VCe?$xves$_*Dk$+R;^@ zm%69GDL`)U>@HVr zg;vIF35K`~Y=vL=RiwZZ?Un5_uhD6ezVV)Q5TtL2H^_r+qYM*-jy9|+xkM;NaD?j zODg*sTP`c@tyL;f!yq`rkwbef@>|eR`uT`lkuYfYTorgG7TN!)_SElm$Yx~H2kJLG zaNZQI&#a2vG=zJkgddBQAI(7Fss%E{x!mamCvUb6QYhUs&zZG*!E0v&%GOwu^xv4= zQ?tI`BYUX0w!cjJebi+Ku-wMG<|C8W{8ti(xSb)0unk;tuvEfcUVK0&H+JA05v*(& zlA3-5^yJ-_eX~C}u-pa0ovE%-#lv&~<2P}TNi3*$z4lbPGPm}N ze&);tOMtL6h@A24yrr=(y|nJIm-IS2k{xy zA=XhGda?y4+!gzvfJ;PBoA&h|o_HRMTn{A{exqKruUHG;>@R!jHK;J)aG+`Zj@J8Y z;6Xfji0iYBPx{0w%C9Jj;a8lN=m;;@U_*eO{d0c{>;3a&9;L&Zh=%@anyQ|O@+OMY z((`)mjc;U9-9tDIZUFy=zMq)1&ITUEA?(9(!kL*oVuljidQ} z(doHjtP#4u;3gUO;JOMtCRl4Y&HVsam}S*eKKQ= zU$5AE>u7mo)>RAz=qTUt=5zayb5g3CQKCPUy`Q3bQMmNdVlKa?)1vaA^2B37+U22i zK(PAR>6LG9sV(N~qS;M`=fJnzSNQedem3d2?2{nFy(ZMM;3OJ-H&;@6xpC{%pLt=Q z+8r(F_C^MkoidQIOQdHu9e2acs!*LwB>fVRIanH;(W!i_#-i1vFtkNrXnvhW;~yu; zSDT5b&!ZH#R0NM#|60G_eq(Us(T~Ny01j<%`A%}eOE&U`5&@8dPE+NryC>HyekD2J zqyCmVEQ{wGLA>kXZs+aHOS&vJ&{WG|5Mm4(K7_pz+L~sObkKVa_In8%^Fu{7W+qDz zO%t^g_xIaF%Nvq)v0C{X#9ku@D~|EYdo74(Cb6a=mcx}cyR#LIKTm47b8}`-&YMU4 z%;#~U|1rr|t$F+O=8z>P*7-x#7@vd=ZrfrF^U{ZivDwcEd^b6ZczD&3q_nb5eywZ{ z-mdRKWi{=rq!g0&Jm7O9yBlr%(|1RLOz^sBX$e^2`y+!58hnrsxIn6|pIz#CDW-Ez zSiLlWTH#j{JwM&`tF!`U&wefk>3Zx-{@}4Viv;sw?dEsmxq=>X%FXv;3;kqznuE4d z*T|%leqWrO#g26wV8Na>LhY{eE6#4Xx!Ael{@`kt2Dj{nJ1^EfJ|2`G{+RkceB_yH zWeYsmv$dNbvR8W^;Iu|YBB#jH;5LL4q?MrA#wG$9S6z~heVcCdtbR!OV8=${M*;Qfj8wIj@ zjVl1T$FvY{(W0H2kD}b_WhH(x&kR@G3@Y?f)Hfgb%?}a-D=^^UeHqy)bzjJqgj@sm z44^2{I51{e>dMR=G=cTO^++F7o^|dmdFIt-BRxiwJ4R~FWn^%}HWL7P?)EDTWQ#Fa zqb=fMef2`%64xq-!Tw{S9z{6cG=8%UZ9K2IjtHolv9VUH0& zD&+(M&#~WAgX*L*SH|QC-?dZD8qb=RKe(of*Kr7;U+vMll7>y+hj30YmJsy` zNXl8cm^?r6a(C{h>m5bNOdebMi|^ulezHJy$w_~SQ-7x87DB1CgWxv}l#AB2ex&!8*bMV z<4edk@Jjw7y(Rt*SkL22g)RmN#Sye(DPSqPQrSI-D`2_lbBHtsXorh44UWViDKC&- zRe?wE;am*LJ6k*R-M;}rCeVm6;nLbnSJ7TaES)>r@T_yp&u+{6649jC=?A%nnfA9s zCBu;s)i^6>*?(!xe{FLA_!Gy-HL9SAdGvW=cL+7E?6SSKe-h?EW#y)E8Sq8M$L&`S z%)!j#>w)@NFaUQCfj3Es76lFJ`ud2EDkBe1ejQYHTL04oel{3SpK|>EyIb;RiNBPr$ zZ2X1n4yUhx5LC=j^-OTRW>O^Lg&2c^9v!wKzs!xh#MBUk`aE(O(|#4HpSk%9fzZyp zxbb{(iEKYIg9FQ7z>~al^o*Mze&^pt0{Pya6-t*Z83s=chImL@=6B!){O-_fT{?l! z&*P8wBhxB3`{SLk_};~ckGj%A+vwoJ$!bVI-Ma-#fL7&^E%Z8NJF$H+{&Mx^*jr;Y zM^^;wVWogO9C2i}yQvdS;~3sus=9RoW(3J_yN4==l~{9RRxb74xmL zvLP%+OR_`g#hZ?UjW;3Q%1mPziI!5#Z+OtwvQO9$c25-FMesY#PNudw?7d+wn*p3j z2(4QYw-sm(DGUTpda>o+Bn~b@g~xl%ec`l;N57Yr{rx6E=gSfc=`sre-)?6;|AaF7 zspF4*zjoT&FZu%h%f!gmqNB$3&*=t=L?W^K`%Y>Ru<#nzbPe>wT}xek@UpAu*4ddC4uwK_ zg$(-?v+B?C>URJG0>tNTt)0s9a~v9t=2!D63zRLuIiMk{xJ(%tnUDoMFMOAR3ho%+ zZl8#m#4DPycH=Cc5wY4jRISfDhDq6w-Vgmg`ye>Zyk2&}vZTsG>3JskQUa8INfZSTDzzNu^b<0@cGWwLH$`xDOf)1D1WmLeX;|0u`A^Pjj})`Q*w`FA!{JT(=}i~*TsS(99va@m$ zSp)uqbZZM-<74FG2SE%zBlSad`bG0+jPs2+YPp<=n?#(lt&B1NE8B}>0A6AfeAjc@ z8RD*laf3$GA&%{+ob;eOSAlxP=W>l&7Le1SstFltWUZ`MZb1A{a7Dv05T?upeqSWT zFE}e0B9t}(f<}UM`2Wz0Ud7`vO?5o*azO&wygcJiGjP|yCuf`&=w)235V+JD3u$JN zJP%3o;k`KF0)%%)4@))ZUkSF_(>HA1NWeezxvzhO*S!wPy%I)p#;1k@olYLRyl@!; z%@5!TgurKf10klfTmWU*Sj)%w2_wZNjY>H5W**(0<^CsaFZIK8c=J!RDExFnlpcbF zD+OE#EWCkz*I=TSJp-jSBkR>aIkdl#?l|xKj|KT}tI}LklIs8Wlqk8$7L^|7I4>EO zgNKK&`(H|^Fyg-}lmBV?Rte3nIfPq1iqt0dpd$8oK&OJ#a>}6B~PE*{~mEAifXY3d{wd`{p}aET(RTy*dz< zI4C?IWI+~sfCJbr1|C5Xiw!rZ5hT3xrn3^o3PqM8-&D-_p>q^xz${Rw{~Q_$`OjD5 z|8ME)jV$SPS(DDf%YQ!ep7}5FHuU_zb?|>~bt~S(Jyk+eR` zw-1yB443XAGz(LBE8;oQ=pD;3MvRc^#-rC`M)ipPef&eo)NO0Dl|Ksof4;*14cp>P z5L3p8%hP@Blx2UW6) zD(Jc6Zr)4iga?ZV={ftzvKmJE0Kb8hX&m&l+iy8}zWPh|$!M?_=0w2_FEx8vj(f&K zxS_*Yi{{a)f9{(#&Rv1y56zX_vPhDLc}sdcKP_VkDn-MZyv%YJS0EV{^3H->7zL`c z)JaN8?rEn*&LgFOcTdDF(rn%gu61mCPF0{K#t0<=(f#oWC41_H&=v&d^Y9cD|l@k{#Ar$ z7S+_-)&1Fp0>pCHM=S3NQ6lbcb~SY)#m_{^FWZJ`YK;~iI_gF(8&?;$yVz#w#x{@=PtYgRdd0=>?eHntF5_Z` zWTMF^Qf6p~x#Z!n;&AWj1{Y1*v=k-JuE?d=tKPLP;Oy({z}1h(vOj;Qu^q2onlVW+ zXfbO()8gP1-4t1sD5jMr$^E@|GxeR=Sgrv1CtT%zL+QN)jXW=r>@fn(aHG9}Yx&zh z19;w_q1I0Aeb=^?Z|L$NrUXJF%t!BN%b5a^otBc#Yl5GFO7Et^nb+oFoq1b za6`goE~V!EC%@6aK0C`QSd~X=vnds@s&q!7?Pxyk_RRGChlv~fXptLwqldh zzhxhk7`uJK3snNqVFW}(*TTx#MAVc{(v?k*BMAL`xw0UlvOLV2Z~Wp&V~JcfSNn^W zsa-}0hn!?&FLnks790_jwKUS9%ajYx{K8G59Rii~{E5OIoPT*r6;|o={!%Pku`$8r z^oqn$PAl_d8j4y8Ed?>lgkX~*x0Y7tA`@GPcICxU_3I>&WV1i-i0PM?sxwn4^!U~} ze%z)4E6D6i?LCT}L2w+mBoTo4u2(s|(t`~aZBABo z)$$``kd*XyXdy1JDm@ zBP$0eEBiso{=|bf&LiSWe6xwTbicN)$YNj)0ekpwu*j(rHHC;@q}sXOPz`qNGpyq2i`yu`)J5%-%{_lzg3@n)ZLfy?_^*)jGZ&dD|Jzph}IVC8x5I#PL^+T&OB z`QcGyMgor2dKoz`d~VM4m@+LQ+CZP;jhK`KL|yv7BGF5g2BZ?V3i)rFPFI(O z3`!@h)5qyi9=m4p8>t z5J&IWfvrzR#d#xSxb7nO9H+&*4>`+6?Lz)K9_7~ zaM39xMw;b$cuX7^^l|MfWDx8iWs#{8a`0^be09O9foB?FF;J+)-^v^p(ZN)~4&3j6 zy?ce~Mn}E&8r?)tlB6E4M-7n$F>%^4x+rOJPFNB@GwxYc^#b}fm}h@+!czEYg%2mT zp+h3m>Y`b1LSWzDeCMF5+}}2N?evJAP8Phc}vI0FLK& zGtIMZQ6ZyH0PyW__@YnoE>gIMP>ChmsSWC=qr1?p^UTbAa5f0k*dBxhzb#@itFtrfa=~7tvBoWUlKoHqOGQY-T z`$YRZ)Cp^sRt2;qTTL+j*(WCsTI(cK&quCkq>;`rrJFcrMJk;ZJEJ?}aOj8A5+gr* z{16&SLG*tq{qe81Wrs&WHA(orfAbTiQH@{Dqp+Z&;lXrM`@OIBJXyXk!$v3s>$L}& za_~_qgwGrOCAm@a!$YAjeI8QV9j~p6q zUAFCtS(37i-ygr?l*2O1eK>lcc*P252(Gv8Ixbk^j=BfPH?qEV)pdb(d82~y1& z{%?2bbIae~bb zAnlWOr7LBeP=m7;y71Qy)2xV=17pcBtgxqH&|Z@XMZ|Sh1G_^}7(}W^o}Y=fuL!rp zgcWa3<++b$xXOcIEJm((gcB6!NNBlL6V^}h<|asElNJ?(JSjonF=`uD>tF)tQ)8G!;`{Wss9iE1#}y$StHp#h-U}{u@7u44eO5 znJ1wY?_YG1l36?r)!?XXwVttxw~JhPNB@q1Q?3g}{VuW}^NpFD$Sy#bY*v6vn9k zK#X4CaQJIk;eNTE&A+h=O=A6_(RO79P310lr=XG6`WssrU1sjv>Vr0pWT*!x9rveQ zZ5esdl%nz}>zQL6_}MzMp8jdIVQ}7}>2yi)mSCc0m91+z!RX}quUijB(XCn8d0%HR zv~t$dzNE@t5(i9}Kub2?SFRo4|G zRj$@;2a$|%b;T8kL%%ydEPEv7wtgbWR_Jjhe(nj>il8iYlwC8(uQs$cAoDAzh)lMA zTePLOgzgE-(_y8Gh{X0&w5)aDJzYhO^}|71@#?}tS3}N-C(;O)v%E{%d-lFJU%c&~ z;K{=YiO+Rhxrx4nvk=`cdi<3Zbd%dBm7A$^_<2qzqD%?iva~usZ5?#SO&*uf`cEHu z6<;e2PB=}x8(K#0(-gFm3*@N!zmTD?_!I#Yrg?TmAdO9AK5Isx%4dc?y9*uE;1IRg zANq6G@|hSKs%gC?uMp6J&s&(dML%H!h^H0tKiSq$;#0LiQ#PZWnpFl_ThD#ANvjLU zHC<76>y=^irR>hDfMgjDWdx|8IG+e}91Ug0`UsJE@>ZSh1gBg=I1*PgrdezVK4O4y z5b}U~-o&p!#QEcQ8zqMxPrPUj%DiG7Y4;y$khk{<85qUI{LZS+ zhrQe2GSpXMBYb#^M3y~VzbaAZR`Pf>B&>6ew!ybqtWl*SCu2fI2a(LZ{Yp^Y+1X}? zfRh4a+iFMnROi2sPdvDCzNj$}xSDPR|I^Q*&)}Odb#dcYo6p8%fx70ko$Ob`+N{O_Mzm{;hsG45~qJcLa-L1^4ntnW=|dsETclL#_x zom%TC)hNR^hvMwAjkk%)yJRzv0_CysKM^MLF6Ize7fE%qxuT!NWd_yC8aho|rxOh# zO4{o$Pqen1R?Su`akdE=G-R9}2;ldTdkr>>59i4TdB0ZYVobK0{{ zvHaiuv!Pn%fFOhKx9+3gsKM2gmn#M;n6NJ+o1#jP?J6ub3LLp^nfF>1Syvq$eC9Pw08SXRj8$ zDzJ%PN8}v)VDcb7q9m#@4Ef-XX`^cqKywMrEuqfpU`c_ddB#W^}Nin9+`ZZ@HPG@w{tSCYQwr`9LrA{2haOz4>N# zLsJ+AFSI#LnaTVde`yRTv%|Q;YtJs(Dr4L3%I7GF%HyMqe4Jy4$zdVH*cKASGpM!s zr8=JlHrVf(qwc27gia`AMNpp%^GWF%P@EgDvxko>EF})x;2#fK2@^x-j~74uvvoTg zaE=XY0jx1gu)J~i;|InT0V@49Fxj1jiOjO5(SlC44uI39#oxB&Ng8$R0YS;+s0E#8 zQMwsl_7H}ww5At$0Jt4#qb4eM3%rUnFa0c9>)~?J>vJ+ z<4KyuyfWObG2IwlS+B6E28m#x_kukykT!b!ZJEWE2}6&TPps{oK7h@%%3@b~O^VeQ zJC_tQY)*}^vZkSO360_?vguc+kbdH1kBI+QszIR@Mzoz+T~yvRlVAehSFD71CyJcE z5bcu*55U@cU1PKh92qenFQ5=hd>(4UtxB2uMV9p%3-{4e(~+3&4Gcr^<3(u4Fzz+x zVDVw?DRfoaPtwjtIID+3eW#|!OIQ2O`O(nxbS!0DlN;H@_4j7+ycPQ@PJ_RBOj#9l zHf;Z0HJ$N?3k|_VDs8m!%MVtAO-Ki?m-`*lB@IgppzXeVRL$KdMQ9cEfsEcmf*%yBlOs3D4o zsgG3RNr2xcVOfarH(CA^=;?6sY7HzG5~_`{)6F8)Vvx|mge{ArJO=4p^YbjQTz@rZ zr`bREY1rO{E#b2=`q@~cO#%UK`%0<^VH=0Edu`**Q2$J*+LE6$;Ln@1N4jY4`S+u0 zjB%tECO)`yr$Kpk90DgeO3?JDNi%sVj$Rm-lN~YRle~C@zi_$%7a4V<{Hz?DLgr^4 z(Kb``H^}aRJ@T<88kSd}GFv%8egW9LT&0umcIh8zN3?zUgnW3La;-ds zAtFm|XLGn$@n3H|lYTMh*ZVIaPGwo@qo(wtu9QR+?@&liX4**6^*K9iF70eC=&LRYzOcz@(3}`{b86*xXe2eJ+!bFV@=0>;8kYql~C4bLSr$)84!fsBnUN$LKw(Z z;Jo6DiNmzFCwks8usK!E44>;D2Y;I&x@=qvYP>>5qchkXa_OiQ@VS-C)ULwX%*=~V zpfaxXDHGgd%h^h=jsF|khoWhb&N+)Q+bgE=o%FSZQd!1miTc8G$@$JLqCn;Kms=&t z2A$g=22@v}gCAa#%KVpglQa6u79Z1))Vu)4q0{FD=` zC3F(YZvgORS#s^QIL*Co0`)H#M0eqSsy5eo#u8A)AhKv3JQ|7ZMzx*@$dj1YJO5fI zz2Kjm+C~y2BNbzd3>yqPxvdXpiZEIiX~F=0s{+t1GfjJDoD`^JL)$r+&JOa^mLCroN5ojd={?9H84Xbt& zXUC`u_fWHHH!)PJHdgM5$Iu>pnyrooX@+Mb=2q~sFVmdlz<v-pOJ@8L33Nv=sV><)^ZL^vc9~@4jHDT2eGpLSPttdSPhxs`TNd zQV~QM@<1aib-yn{??XRsOGN%yShCEDsF=r-`7=7-)vHM1SbF!@B`1PIjcZ@P?TQ}nCp6e6u_~zn18JH=1?B#xfKBm82AtcdjOIQm^)|aK3 zuo>`A_pWbNzLdrjTGI3VrbA~R=A}*2i%Uek5fJL*LO|iRUUl5S@U^0WF3^XmkW-U# z1!pGd_f2zwN}+BVBGF-Xl!!E_;~DF`9O?r*51G8q^FMuj&*LOr7(!*VloD;s_En33 zXv76x(FgWysOep0H+X%1FuvKhVa4S3;e)PvuhnW5VtzC&Zo2%}ppnx}@z2 z*5QFB`%fs^f{#MHzOcZ<*>R_TffpR5ot;kO_UesRpnm+7o=svn_xO~M`^JdXe!Zmq zF{}5cI;T@6ku0p(|Jm_4mP8lU$hUa!Cp-kpX2Z`K(o@}co7$X$ z3GwToCPl5wrll1ZpAA&ZzfnEm7oM=oCQXNWNC2E~8ZAcRclM(`$dw1Z$u54`?ivT$ zAV@-}`?d}y>~#xMu{)wn1p|KVV%$vA+bf{sE74H-jM;ZaiV;JTkScMj@MI^tI@x0h z8N>M4Y(t?0i?jVF#nN>bx^-B=aJY7~!Db_`gkAZ`iblWGDl+3Ev;R@(_(a;N8|o*I zL3_*I>twTRnE;uVqg^<8?%1Da-mNDpo*o%Lx?$D3l#xs-(tm9o?zWRy7mx`Hay1o6 z*WA}c1ch24>&pkorHPtZ3#g&!0Pk<^5urV>Vly!{mz|+@)}SI3!A&N^P>*mKSVwLrQgfS=@8`%C6Hr1##nT$B@u?4*}eCx>FC;!-v>fUy$!z* zG)a|l+TpVne8Ps%`3j4lACsttB@NpZ(aNmeckmn}(V#CCp)BKTYN03ILj>7e!}Qm( zNW8U^2=&RO040G+`7)NY8l2Z#cSu{kt7YMgsV+zHdq?2ERz|Jn8tmPX?H}QjS=V*v z%VbZFPZSFF4oZDxrjiLb6gGeh^1gCeC6ir`@P;`(NB!f9Dn&gv`m4_ejf`Zz>QpSF zVxuccjxDG#4>;Ggyl(Vc@UWn+BPn%uZ@~@P5AoH2rs)c1T}@zTARo)!$~^T+3znj* zt!*I@Wg$(tNYoD;ADNNCZHo7GI)tx@3K~eFgMUpCsw7SkWg;+AM7dkbJVMRbuS4`3 z>?y^LaNRaK@LTCc>H&g1J^x*2O9djo{R#OQk~ndhsUneRmjlP|Yr>0KDBdzI{5a>G zZ4k4MRX^*7A6%s*PtmeL-m}FPf$||bgHfTVwL#f&HbTba$A+O|Esp+O=fWH2DJWG; z^1u{u6)Etw5u>uwD`$?@#BV{kfSQ`Pm5}jjg{MLfj(J`!onjS`S<*C&-21DgU|Zub zwCE{zsy#$Y6V;7zI<@Mm(N$yM1-;7gU4MU5_g#=05MvH3w57{K?og&yrds6)H#TMf zRZS+}YUNC_a?rc!vpIdj^G`bLPch_VKHN`Dey|BkKK23k-u}HDF??;V%1!Zlv`kNF zx_vS6IUfY9r^Ty%0rVitV33MOT!u(4X)LU7VSruA=#r%|G0UNbPqiXne8xphl?DCwU?#8kwl%@N_i=2~X zoR(Y<-WATzR#KIvB$`y|XFeAKovnnt;KA;Scu`^10k!Aq*?q8M2xG=L02OZ5NYl!W z(J29*>l#}=kF+#7<830NdHm_TzmM1pKveMZUacrHC9Ub{fi8qKzvV($won~ zaxTXJ6{KkqGeTsnbdW6*H~PzJEl`i)X>?*+Vds$Y9N;wOvPK&#m`#6l+98o#_(|;e2h)=usQB>d;+j%EYQ;9T9n_nOt&+5qB zyrD_U$r=4qu%0P6U5BkmiVlL5u5#bf6^QzJrlwT7kn5ZVh#3ysGCkeO{#$ELYglX` z8fyN)Cv>^_89xYDUC4@~?fXT?C<^NjiejR0%v`yN*>i#0ZxZqdM~wQ@0;XH<$Bc;v zJY`?Oy)FWS-`MpgzhJ%1`&56l-GBBvM0;wC%9%jVzvf4J?@vB%2I8rk0r1>V$mdA4 z6ujFbAoG#pn>`3~onDIA_l|~WnX|wwWWrpxCs<4qa9zhQZPi5w?wbO^SOzVZF0L+uhI=17JkZMg+(3;T2Zh);;PSX&n{?uL+_5))1C01C?_e|osldP!1ftXrqoQ5 z*v{qg&raA2q9){_RO6YH5wIv#0@Mj~rmJy-uN(Truad*`Rtl{7vdOli9lsH{1Dm7cZlCijhSD z)W|}zcApBoxw!`pGfLK({aTI4@n^|tr`DU;jl3x|ZDx98;_)-b z>mBMjV{Y`2*O8M&B`MJlmN^*H7i#C2PuP3#e!^kXXO66EoZs^bp6uE9u55;B^T~|w z?pfj;V(`6rPsc96(GJm&YJ!+cOgpQXKy)+t$$yX~l*nS(ggk!a?yyFp_a}V46-}+; zGR88>OX$zb8kB=WKS*UA^Phr0q`i6)96T~A#&pSv3>$DRwPnS2tr2bPP?R64WDgX> z-P$K<%gYc|a#%i2@#ae|x#I-7VWzv9Dlz(aer^bHHWddE5TI4;z36@e;GY!qP?S&$V8!wP~CE`wN~ar66U|m1XvYhyyKfl!iltR zbYRMO`W!tug5Q4pWcfH=JpLtOe8`MKJ&_b#JeK(iVJibQ7sl+;r^DVhlEM+_v^KlJ z9w2)C2^e%~C z*0FtB#A8ZjaMbwVtCc*km`bwwx>uFZdb7TF^^-y@2UZ_y?;Yd_*ihnkUmtQH7 z{DT21@i6x7uQsnqE^B~dm9WU;cVI-beiIhB{YWgiT)0hP?YMZ&E_|O>$Pg z3`cTglRC6lAPN4Rr64|ebt4E> ze;`Z>4Pp8WHV;Hu(&rF(&j9Gd}8IL`hrmYo+{c!PG!T$pDLRUk~`j;W+4{qp0c$ zNazdGO(hmc2m4Dr#L0fZ$ltH-6N&mJnZPOj2hgDVT9DdalH1$<@G{;jEqw<;d;juKpS|rimIJgCy?Z~m9*|)EPhdRLVFlRh&@yck!>>YCP*H6s7?yL2-* z5i2WU4|zYS>hfxs3vHt_SZj94iwfD%|4&BA@G4uU4;B+LzbSgRg%9KVNZXLHBV)Lq zqG$MX%|{Yaex_uL=z<9`Ru zQxATnL`&I^LJ$J*DbtD5&Wq={HDKYyh!yx+)ugh??;-Fdk_+?B=(Ett`c{f#J|ni6^h+9saOK z3Me!bX#Qh3B9wk^&d+Wg4grT7BK8ehe_Hhz1sr%56u2D&0-YC020Q`|Jxg4;92g7@ eConKD@i1hIny&nuDpC)04TGnvpUXO@geCwEnM@7< literal 0 HcmV?d00001 diff --git a/cms/static/js/markitup/skins/simple/style.css b/cms/static/js/markitup/skins/simple/style.css new file mode 100644 index 0000000000..af8dc9cdb5 --- /dev/null +++ b/cms/static/js/markitup/skins/simple/style.css @@ -0,0 +1,118 @@ +/* ------------------------------------------------------------------- +// markItUp! Universal MarkUp Engine, JQuery plugin +// By Jay Salvat - http://markitup.jaysalvat.com/ +// ------------------------------------------------------------------*/ +.markItUp * { + margin:0px; padding:0px; + outline:none; +} +.markItUp a:link, +.markItUp a:visited { + color:#000; + text-decoration:none; +} +.markItUp { + /* width:700px; */ + margin:5px 0 10px 0; +} +.markItUpContainer { + font:11px Verdana, Arial, Helvetica, sans-serif; +} +.markItUpEditor { + font:12px 'Courier New', Courier, monospace; + padding:5px; + /* width:690px; */ + height:320px; + clear:both; + line-height:18px; + overflow:auto; +} +.markItUpPreviewFrame { + overflow:auto; + background-color:#FFF; + width:99.9%; + height:300px; + margin:5px 0; +} +.markItUpFooter { + width:100%; +} +.markItUpResizeHandle { + overflow:hidden; + width:22px; height:5px; + margin-left:auto; + margin-right:auto; + background-image:url(images/handle.png); + cursor:n-resize; +} +/***************************************************************************************/ +/* first row of buttons */ +.markItUpHeader ul li { + list-style:none; + float:left; + position:relative; +} +.markItUpHeader ul li:hover > ul{ + display:block; +} +.markItUpHeader ul .markItUpDropMenu { + background:transparent url(images/menu.png) no-repeat 115% 50%; + margin-right:5px; +} +.markItUpHeader ul .markItUpDropMenu li { + margin-right:0px; +} +/* next rows of buttons */ +.markItUpHeader ul ul { + display:none; + position:absolute; + top:18px; left:0px; + background:#FFF; + border:1px solid #000; +} +.markItUpHeader ul ul li { + float:none; + border-bottom:1px solid #000; +} +.markItUpHeader ul ul .markItUpDropMenu { + background:#FFF url(images/submenu.png) no-repeat 100% 50%; +} +.markItUpHeader ul .markItUpSeparator { + margin:0 10px; + width:1px; + height:16px; + overflow:hidden; + background-color:#CCC; +} +.markItUpHeader ul ul .markItUpSeparator { + width:auto; height:1px; + margin:0px; +} +/* next rows of buttons */ +.markItUpHeader ul ul ul { + position:absolute; + top:-1px; left:150px; +} +.markItUpHeader ul ul ul li { + float:none; +} +.markItUpHeader ul a { + display:block; + width:16px; height:16px; + text-indent:-10000px; + background-repeat:no-repeat; + padding:3px; + margin:0px; +} +.markItUpHeader ul ul a { + display:block; + padding-left:0px; + text-indent:0; + width:120px; + padding:5px 5px 5px 25px; + background-position:2px 50%; +} +.markItUpHeader ul ul a:hover { + color:#FFF; + background-color:#000; +} diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss new file mode 100644 index 0000000000..d9f3971d9e --- /dev/null +++ b/cms/static/sass/_base.scss @@ -0,0 +1,51 @@ + +$fg-column: 70px; +$fg-gutter: 26px; +$fg-max-columns: 12; + +html { + height: 100%; +} + +body { + @include clearfix(); + height: 100%; + font: 14px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + + > 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; + } + } + } +} + +a { + text-decoration: none; + color: #888; +} + +input[type="submit"], .button { + border: 1px solid #ccc; + background: #efefef; + @include border-radius(3px); + padding: 6px; +} + diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss new file mode 100644 index 0000000000..b149346ad6 --- /dev/null +++ b/cms/static/sass/_calendar.scss @@ -0,0 +1,206 @@ +section.cal { + @include box-sizing(border-box); + padding: 25px; + @include clearfix; + overflow: scroll; + + > header { + @include clearfix; + margin-bottom: 10px; + + h1 { + float: left; + font-size: 18px; + } + + ul { + float: right; + + li { + @include inline-block; + + a { + padding: 6px; + border: 1px solid #ddd; + display: block; + @include border-radius(3px); + background: #efefef; + } + + &.dropdown { + position: relative; + + ul { + display: none; + position: absolute; + background: #fff; + border: 1px solid #ddd; + + li { + padding: 6px; + display: block; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + + &:hover { + background-color: #efefef; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + } + } + } + + &:hover { + + ul { + display: block; + } + + a { + @include border-radius(3px 3px 0 0); + border-bottom: 0; + } + } + } + } + } + } + + ol { + list-style: none; + @include clearfix; + @include box-sizing(border-box); + border-left: 1px solid #333; + border-top: 1px solid #333; + width: 100%; + + > li { + border-right: 1px solid #333; + border-bottom: 1px solid; + @include box-sizing(border-box); + float: left; + width: flex-grid(3) + ((flex-gutter() * 3) / 4); + + &:last-child { + text-align: center; + background: #eee; + @include box-sizing(border-box); + + p { + width: 100%; + height: 100%; + @include box-sizing(border-box); + + a { + display: block; + width: 100%; + height: 100%; + } + } + + section.new-week { + header { + background: #fff; + text-align: left; + } + + form { + background: #fff; + width: 50%; + padding: 6px; + border: 1px solid #000; + margin: 0 auto; + @include box-shadow(0 0 2px #333); + position: relative; + + &:before { + background: #fff; + border-left: 1px solid #000; + border-top: 1px solid #000; + content: " "; + display: block; + height: 10px; + left: 50%; + position: absolute; + top: -6px; + @include transform(rotate(45deg)); + width: 10px; + z-index: 0; + } + + select { + margin-bottom: 6px; + width: 100%; + + option { + padding: 10px 0 !important; + } + } + + input[type="submit"] { + display: block; + margin-bottom: 6px; + width: 100%; + } + + a { + + &:first-child { + float: left; + } + + &:last-child { + float: right; + } + } + } + } + } + + header { + border-bottom: 1px solid #000; + @include box-shadow(0 1px 2px #ccc); + display: block; + margin-bottom: 2px; + padding: 6px; + + h1 { + font-size: 14px; + } + } + + ul { + list-style: none; + margin-bottom: 1px; + + li { + background: #efefef; + border-bottom: 1px solid #666; + padding: 6px; + + &.goal { + background: #fff; + } + } + } + } + } +} + +body.content + section.cal { + width: flex-grid(3) + flex-gutter(); + float: left; + @include box-sizing(border-box); + + > header ul { + display: none; + } + + ol { + li { + @include box-sizing(border-box); + width: 100%; + } + } + } diff --git a/cms/static/sass/_problem.scss b/cms/static/sass/_problem.scss new file mode 100644 index 0000000000..c513ce38d9 --- /dev/null +++ b/cms/static/sass/_problem.scss @@ -0,0 +1,41 @@ +section.problem-new, section.problem-edit { + position: absolute; + top: 80px; + right: 0; + background: #fff; + width: flex-grid(5); + @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 { + float: right; + } + } + + section { + ul { + list-style: none; + + li { + border-bottom: 1px solid #333; + padding: 10px 25px; + } + } + } +} + diff --git a/cms/static/sass/_reset.scss b/cms/static/sass/_reset.scss new file mode 100644 index 0000000000..bfe619c1b0 --- /dev/null +++ b/cms/static/sass/_reset.scss @@ -0,0 +1,229 @@ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin:0; + padding:0; + border:0; + outline:0; + vertical-align:baseline; + background:transparent; +} + +html,body { + font-size: 100%; +} + +// Corrects block display not defined in IE8/9 & FF3 +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { + display: block; +} + +// Corrects inline-block display not defined in IE8/9 & FF3 +audio, canvas, video { + display: inline-block; +} + +// Prevents modern browsers from displaying 'audio' without controls +audio:not([controls]) { + display: none; +} + +// Addresses styling for 'hidden' attribute not present in IE8/9, FF3, S4 +[hidden] { + display: none; +} + +// Prevents iOS text size adjust after orientation change, without disabling user zoom +// www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +// Addresses font-family inconsistency between 'textarea' and other form elements. +html, button, input, select, textarea { + font-family: sans-serif; +} + +a { + // Addresses outline displayed oddly in Chrome + &:focus { + outline: thin dotted; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; + } + + // Improves readability when focused and also mouse hovered in all browsers + // people.opera.com/patrickl/experiments/keyboard/test + &:hover, &:active { + outline: 0; + } +} + +// Addresses styling not present in IE8/9, S5, Chrome +abbr[title] { + border-bottom: 1px dotted; +} + +// Addresses style set to 'bolder' in FF3+, S4/5, Chrome +b, strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +// Addresses styling not present in S5, Chrome +dfn { + font-style: italic; +} + +// Addresses styling not present in IE8/9 +mark { + background: #ff0; + color: #000; +} + +// Corrects font family set oddly in S4/5, Chrome +// en.wikipedia.org/wiki/User:Davidgothberg/Test59 +pre, code, kbd, samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +// Improves readability of pre-formatted text in all browsers +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +// Addresses quote property not supported in S4 +blockquote, q { + quotes: none; + &:before, &:after { + content: ''; + content: none; + } +} + +small { + font-size: 75%; +} + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +nav { + ul, ol { + list-style: none; + list-style-image: none; + } +} + +// Removes border when inside 'a' element in IE8/9, FF3 +img { + border: 0; + height: auto; + max-width: 100%; + -ms-interpolation-mode: bicubic; +} + +// Corrects overflow displayed oddly in IE9 +svg:not(:root) { + overflow: hidden; +} + +// Define consistent border, margin, and padding +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +legend { + border: 0; // Corrects color not being inherited in IE8/9 + padding: 0; + white-space: normal; // Corrects text not wrapping in FF3 +} + +button, input, select, textarea { + font-size: 100%; // Corrects font size not being inherited in all browsers + margin: 0; // Addresses margins set differently in FF3+, S5, Chrome + vertical-align: baseline; // Improves appearance and consistency in all browsers +} + +// Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet +button, input { + line-height: normal; +} + +button, input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; // Improves usability and consistency of cursor style between image-type 'input' and others + -webkit-appearance: button; // Corrects inability to style clickable 'input' types in iOS +} + +// Re-set default cursor for disabled elements +button[disabled], input[disabled] { + cursor: default; +} + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; // Addresses box sizing set to content-box in IE8/9 + padding: 0; //Removes excess padding in IE8/9 +} + +input[type="search"] { + -webkit-appearance: textfield; // Addresses appearance set to searchfield in S5, Chrome + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; // Addresses box-sizing set to border-box in S5, Chrome (-moz to future-proof) + box-sizing: content-box; +} + +// Removes inner padding and search cancel button in S5, Chrome on OS X +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +// Removes inner padding and border in FF3+ +// www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; +} + +textarea { + overflow: auto; // Removes default vertical scrollbar in IE8/9 + vertical-align: top; // Improves readability and alignment in all browsers +} + +// Remove most spacing between table cells +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/cms/static/sass/_video.scss b/cms/static/sass/_video.scss new file mode 100644 index 0000000000..cb234108f4 --- /dev/null +++ b/cms/static/sass/_video.scss @@ -0,0 +1,40 @@ +section.video-new, section.video-edit { + position: absolute; + top: 80px; + right: 0; + background: #fff; + width: flex-grid(5); + @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 { + float: right; + } + } + + section { + ul { + list-style: none; + + li { + border-bottom: 1px solid #333; + padding: 10px 25px; + } + } + } +} diff --git a/cms/static/sass/_week.scss b/cms/static/sass/_week.scss new file mode 100644 index 0000000000..a628b0f164 --- /dev/null +++ b/cms/static/sass/_week.scss @@ -0,0 +1,152 @@ +section.week-edit, section.week-new { + + > header { + border-bottom: 1px solid #ccc; + @include clearfix(); + padding: 6px; + + h1 { + font-size: 18px; + float: left; + margin-top: 8px 6px; + } + + a { + float: right; + @extend .button; + display: block; + } + } + + section { + header { + background: #666; + color: #fff; + padding: 6px; + border-bottom: 1px solid #333; + -webkit-font-smoothing: antialiased; + + h2 { + font-size: 14px; + } + } + + &.sidebar { + width: flex-grid(3, 9) + flex-gutter(9); + float: left; + background: #ccc; + border-right: 1px solid #333; + @include box-sizing(border-box); + + section { + height: 50%; + + > ul { + list-style: none; + + > li { + padding: 6px; + border-bottom: 1px solid #666; + background: #eee; + + &.new-module { + position: relative; + + ul.new-dropdown { + list-style: none; + + li { + display: none; + + &:first-child { + display: block; + } + } + + &:hover { + li { + display: block; + padding: 6px 0; + + &:first-child { + padding-top: 0; + } + } + } + } + } + } + } + + p { + padding: 6px; + border-bottom: 1px solid #666; + } + } + } + + &.weeks-content { + width: flex-grid(6, 9); + float: left; + @include box-sizing(border-box); + + header { + @include clearfix; + + h2 { + float: left; + } + + form { + float: right; + margin: -2px 0; + + input { + border: 1px solid #000; + background: #ddd; + padding: 2px 4px; + @include border-radius(2px); + } + } + } + + + section { + &.filters { + border-bottom: 1px solid #999; + + ul { + @include clearfix(); + list-style: none; + padding: 6px; + + li { + @include inline-block(); + + &.advanced { + float: right; + } + } + } + } + + &.modules { + ul { + list-style: none; + + li { + padding: 6px; + font-weight: bold; + font-size: 16px; + border-bottom: 1px solid #333; + + a { + color: #000; + } + } + } + } + } + } + } +} diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss new file mode 100644 index 0000000000..6a2dfbd0d2 --- /dev/null +++ b/cms/static/sass/base-style.scss @@ -0,0 +1,18 @@ +@import 'bourbon/bourbon'; +@import 'reset'; + +@import 'base'; +@import 'calendar'; +@import 'week', 'video', 'problem'; + +body { + &.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 ); + } + } +} diff --git a/cms/static/sass/bourbon/_bourbon.scss b/cms/static/sass/bourbon/_bourbon.scss new file mode 100644 index 0000000000..27b056e303 --- /dev/null +++ b/cms/static/sass/bourbon/_bourbon.scss @@ -0,0 +1,35 @@ +// Custom Functions +@import "functions/deprecated-webkit-gradient"; +@import "functions/flex-grid"; +@import "functions/grid-width"; +@import "functions/linear-gradient"; +@import "functions/modular-scale"; +@import "functions/radial-gradient"; +@import "functions/render-gradients"; +@import "functions/tint-shade"; + +// CSS3 Mixins +@import "css3/animation"; +@import "css3/appearance"; +@import "css3/background-image"; +@import "css3/background-size"; +@import "css3/border-image"; +@import "css3/border-radius"; +@import "css3/box-shadow"; +@import "css3/box-sizing"; +@import "css3/columns"; +@import "css3/flex-box"; +@import "css3/inline-block"; +@import "css3/linear-gradient"; +@import "css3/radial-gradient"; +@import "css3/transform"; +@import "css3/transition"; +@import "css3/user-select"; + +// Addons & other mixins +@import "addons/button"; +@import "addons/clearfix"; +@import "addons/font-family"; +@import "addons/html5-input-types"; +@import "addons/position"; +@import "addons/timing-functions"; diff --git a/cms/static/sass/bourbon/addons/_button.scss b/cms/static/sass/bourbon/addons/_button.scss new file mode 100644 index 0000000000..1d32125140 --- /dev/null +++ b/cms/static/sass/bourbon/addons/_button.scss @@ -0,0 +1,267 @@ +@mixin button ($style: simple, $base-color: #4294f0) { + + @if type-of($style) == color { + $base-color: $style; + $style: simple; + } + + // Grayscale button + @if $base-color == grayscale($base-color) { + @if $style == simple { + @include simple($base-color, $grayscale: true); + } + + @else if $style == shiny { + @include shiny($base-color, $grayscale: true); + } + + @else if $style == pill { + @include pill($base-color, $grayscale: true); + } + } + + // Colored button + @else { + @if $style == simple { + @include simple($base-color); + } + + @else if $style == shiny { + @include shiny($base-color); + } + + @else if $style == pill { + @include pill($base-color); + } + } +} + + +// Simple Button +//************************************************************************// +@mixin simple($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); + $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); + $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border; + @include border-radius (3px); + @include box-shadow (inset 0 1px 0 0 $inset-shadow); + color: $color; + display: inline; + font-size: 11px; + font-weight: bold; + @include linear-gradient ($base-color, $stop-gradient); + padding: 6px 18px 7px; + text-shadow: 0 1px 0 $text-shadow; + -webkit-background-clip: padding-box; + + &:hover { + $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); + $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + } + + @include box-shadow (inset 0 1px 0 0 $inset-shadow-hover); + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + } + + &:active { + $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); + + @if $grayscale == true { + $border-active: grayscale($border-active); + $inset-shadow-active: grayscale($inset-shadow-active); + } + + border: 1px solid $border-active; + @include box-shadow (inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee); + } +} + + +// Shiny Button +//************************************************************************// +@mixin shiny($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); + $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); + $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); + $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); + $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); + $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); + $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $border-bottom: grayscale($border-bottom); + $fourth-stop: grayscale($fourth-stop); + $inset-shadow: grayscale($inset-shadow); + $second-stop: grayscale($second-stop); + $text-shadow: grayscale($text-shadow); + $third-stop: grayscale($third-stop); + } + + border: 1px solid $border; + border-bottom: 1px solid $border-bottom; + @include border-radius(5px); + @include box-shadow(inset 0 1px 0 0 $inset-shadow); + color: $color; + display: inline; + font-size: 14px; + font-weight: bold; + @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); + padding: 7px 20px 8px; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + + &:hover { + $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); + $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); + $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); + $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); + + @if $grayscale == true { + $first-stop-hover: grayscale($first-stop-hover); + $second-stop-hover: grayscale($second-stop-hover); + $third-stop-hover: grayscale($third-stop-hover); + $fourth-stop-hover: grayscale($fourth-stop-hover); + } + + cursor: pointer; + @include linear-gradient(top, $first-stop-hover 0%, + $second-stop-hover 50%, + $third-stop-hover 50%, + $fourth-stop-hover 100%); + } + + &:active { + $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); + + @if $grayscale == true { + $inset-shadow-active: grayscale($inset-shadow-active); + } + + @include box-shadow(inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff); + } +} + + +// Pill Button +//************************************************************************// +@mixin pill($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); + $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); + $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); + $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + @include border-radius(16px); + @include box-shadow(inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3); + color: $color; + display: inline; + font-size: 11px; + font-weight: normal; + line-height: 1; + @include linear-gradient ($base-color, $stop-gradient); + padding: 3px 16px 5px; + text-align: center; + text-shadow: 0 -1px 1px $text-shadow; + -webkit-background-clip: padding-box; + + &:hover { + $base-color-hover: adjust-color($base-color, $lightness: -4.5%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); + $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); + $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + $text-shadow-hover: grayscale($text-shadow-hover); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + @include box-shadow(inset 0 1px 0 0 $inset-shadow-hover); + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + text-shadow: 0 -1px 1px $text-shadow-hover; + -webkit-background-clip: padding-box; + } + + &:active { + $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); + $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); + $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); + $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); + $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); + + @if $grayscale == true { + $active-color: grayscale($active-color); + $border-active: grayscale($border-active); + $border-bottom-active: grayscale($border-bottom-active); + $inset-shadow-active: grayscale($inset-shadow-active); + $text-shadow-active: grayscale($text-shadow-active); + } + + background: $active-color; + border: 1px solid $border-active; + border-bottom: 1px solid $border-bottom-active; + @include box-shadow(inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff); + text-shadow: 0 -1px 1px $text-shadow-active; + } +} + diff --git a/cms/static/sass/bourbon/addons/_clearfix.scss b/cms/static/sass/bourbon/addons/_clearfix.scss new file mode 100644 index 0000000000..a9f6a795c5 --- /dev/null +++ b/cms/static/sass/bourbon/addons/_clearfix.scss @@ -0,0 +1,29 @@ +// Micro clearfix provides an easy way to contain floats without adding additional markup +// +// Example usage: +// +// // Contain all floats within .wrapper +// .wrapper { +// @include clearfix; +// .content, +// .sidebar { +// float : left; +// } +// } + +@mixin clearfix { + zoom: 1; + + &:before, + &:after { + content: ""; + display: table; + } + + &:after { + clear: both; + } +} + +// Acknowledgements +// Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/) diff --git a/cms/static/sass/bourbon/addons/_font-family.scss b/cms/static/sass/bourbon/addons/_font-family.scss new file mode 100644 index 0000000000..df8a80ddfc --- /dev/null +++ b/cms/static/sass/bourbon/addons/_font-family.scss @@ -0,0 +1,5 @@ +$georgia: Georgia, Cambria, "Times New Roman", Times, serif; +$helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif; +$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; +$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; +$verdana: Verdana, Geneva, sans-serif; diff --git a/cms/static/sass/bourbon/addons/_html5-input-types.scss b/cms/static/sass/bourbon/addons/_html5-input-types.scss new file mode 100644 index 0000000000..9d86fbb4d4 --- /dev/null +++ b/cms/static/sass/bourbon/addons/_html5-input-types.scss @@ -0,0 +1,36 @@ +//************************************************************************// +// Generate a variable ($all-text-inputs) with a list of all html5 +// input types that have a text-based input, excluding textarea. +// http://diveintohtml5.org/forms.html +//************************************************************************// +$inputs-list: 'input[type="email"]', + 'input[type="number"]', + 'input[type="password"]', + 'input[type="search"]', + 'input[type="tel"]', + 'input[type="text"]', + 'input[type="url"]', + + // Webkit & Gecko may change the display of these in the future + 'input[type="color"]', + 'input[type="date"]', + 'input[type="datetime"]', + 'input[type="datetime-local"]', + 'input[type="month"]', + 'input[type="time"]', + 'input[type="week"]'; + +$unquoted-inputs-list: (); + +@each $input-type in $inputs-list { + $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma); +} + +$all-text-inputs: $unquoted-inputs-list; + +// You must use interpolation on the variable: +// #{$all-text-inputs} +//************************************************************************// +// #{$all-text-inputs}, textarea { +// border: 1px solid red; +// } diff --git a/cms/static/sass/bourbon/addons/_position.scss b/cms/static/sass/bourbon/addons/_position.scss new file mode 100644 index 0000000000..6ad330f1df --- /dev/null +++ b/cms/static/sass/bourbon/addons/_position.scss @@ -0,0 +1,30 @@ +@mixin position ($position: relative, $coordinates: 0 0 0 0) { + + @if type-of($position) == list { + $coordinates: $position; + $position: relative; + } + + $top: nth($coordinates, 1); + $right: nth($coordinates, 2); + $bottom: nth($coordinates, 3); + $left: nth($coordinates, 4); + + position: $position; + + @if not(unitless($top)) { + top: $top; + } + + @if not(unitless($right)) { + right: $right; + } + + @if not(unitless($bottom)) { + bottom: $bottom; + } + + @if not(unitless($left)) { + left: $left; + } +} diff --git a/cms/static/sass/bourbon/addons/_timing-functions.scss b/cms/static/sass/bourbon/addons/_timing-functions.scss new file mode 100644 index 0000000000..51b2410914 --- /dev/null +++ b/cms/static/sass/bourbon/addons/_timing-functions.scss @@ -0,0 +1,32 @@ +// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) +// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html + +// EASE IN +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); + +// EASE OUT +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); + +// EASE IN OUT +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); diff --git a/cms/static/sass/bourbon/css3/_animation.scss b/cms/static/sass/bourbon/css3/_animation.scss new file mode 100644 index 0000000000..f99e06eb6f --- /dev/null +++ b/cms/static/sass/bourbon/css3/_animation.scss @@ -0,0 +1,171 @@ +// http://www.w3.org/TR/css3-animations/#the-animation-name-property- +// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. + +// Official animation shorthand property. +@mixin animation ($animation-1, + $animation-2: false, $animation-3: false, + $animation-4: false, $animation-5: false, + $animation-6: false, $animation-7: false, + $animation-8: false, $animation-9: false) + { + $full: compact($animation-1, $animation-2, $animation-3, $animation-4, + $animation-5, $animation-6, $animation-7, $animation-8, $animation-9); + + -webkit-animation: $full; + -moz-animation: $full; + animation: $full; +} + +// Individual Animation Properties +@mixin animation-name ($name-1, + $name-2: false, $name-3: false, + $name-4: false, $name-5: false, + $name-6: false, $name-7: false, + $name-8: false, $name-9: false) + { + $full: compact($name-1, $name-2, $name-3, $name-4, + $name-5, $name-6, $name-7, $name-8, $name-9); + + -webkit-animation-name: $full; + -moz-animation-name: $full; + animation-name: $full; +} + + +@mixin animation-duration ($time-1: 0, + $time-2: false, $time-3: false, + $time-4: false, $time-5: false, + $time-6: false, $time-7: false, + $time-8: false, $time-9: false) + { + $full: compact($time-1, $time-2, $time-3, $time-4, + $time-5, $time-6, $time-7, $time-8, $time-9); + + -webkit-animation-duration: $full; + -moz-animation-duration: $full; + animation-duration: $full; +} + + +@mixin animation-timing-function ($motion-1: ease, +// ease | linear | ease-in | ease-out | ease-in-out + $motion-2: false, $motion-3: false, + $motion-4: false, $motion-5: false, + $motion-6: false, $motion-7: false, + $motion-8: false, $motion-9: false) + { + $full: compact($motion-1, $motion-2, $motion-3, $motion-4, + $motion-5, $motion-6, $motion-7, $motion-8, $motion-9); + + -webkit-animation-timing-function: $full; + -moz-animation-timing-function: $full; + animation-timing-function: $full; +} + + +@mixin animation-iteration-count ($value-1: 1, +// infinite | + $value-2: false, $value-3: false, + $value-4: false, $value-5: false, + $value-6: false, $value-7: false, + $value-8: false, $value-9: false) + { + $full: compact($value-1, $value-2, $value-3, $value-4, + $value-5, $value-6, $value-7, $value-8, $value-9); + + -webkit-animation-iteration-count: $full; + -moz-animation-iteration-count: $full; + animation-iteration-count: $full; +} + + +@mixin animation-direction ($direction-1: normal, +// normal | alternate + $direction-2: false, $direction-3: false, + $direction-4: false, $direction-5: false, + $direction-6: false, $direction-7: false, + $direction-8: false, $direction-9: false) + { + $full: compact($direction-1, $direction-2, $direction-3, $direction-4, + $direction-5, $direction-6, $direction-7, $direction-8, $direction-9); + + -webkit-animation-direction: $full; + -moz-animation-direction: $full; + animation-direction: $full; +} + + +@mixin animation-play-state ($state-1: running, +// running | paused + $state-2: false, $state-3: false, + $state-4: false, $state-5: false, + $state-6: false, $state-7: false, + $state-8: false, $state-9: false) + { + $full: compact($state-1, $state-2, $state-3, $state-4, + $state-5, $state-6, $state-7, $state-8, $state-9); + + -webkit-animation-play-state: $full; + -moz-animation-play-state: $full; + animation-play-state: $full; +} + + +@mixin animation-delay ($time-1: 0, + $time-2: false, $time-3: false, + $time-4: false, $time-5: false, + $time-6: false, $time-7: false, + $time-8: false, $time-9: false) + { + $full: compact($time-1, $time-2, $time-3, $time-4, + $time-5, $time-6, $time-7, $time-8, $time-9); + + -webkit-animation-delay: $full; + -moz-animation-delay: $full; + animation-delay: $full; +} + + +@mixin animation-fill-mode ($mode-1: none, +// http://goo.gl/l6ckm +// none | forwards | backwards | both + $mode-2: false, $mode-3: false, + $mode-4: false, $mode-5: false, + $mode-6: false, $mode-7: false, + $mode-8: false, $mode-9: false) + { + $full: compact($mode-1, $mode-2, $mode-3, $mode-4, + $mode-5, $mode-6, $mode-7, $mode-8, $mode-9); + + -webkit-animation-fill-mode: $full; + -moz-animation-fill-mode: $full; + animation-fill-mode: $full; +} + + +// Deprecated +@mixin animation-basic ($name, $time: 0, $motion: ease) { + $length-of-name: length($name); + $length-of-time: length($time); + $length-of-motion: length($motion); + + @if $length-of-name > 1 { + @include animation-name(zip($name)); + } @else { + @include animation-name( $name); + } + + @if $length-of-time > 1 { + @include animation-duration(zip($time)); + } @else { + @include animation-duration( $time); + } + + @if $length-of-motion > 1 { + @include animation-timing-function(zip($motion)); + } @else { + @include animation-timing-function( $motion); + } + @warn "The animation-basic mixin is deprecated. Use the animation mixin instead."; +} + diff --git a/cms/static/sass/bourbon/css3/_appearance.scss b/cms/static/sass/bourbon/css3/_appearance.scss new file mode 100644 index 0000000000..548767e166 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_appearance.scss @@ -0,0 +1,7 @@ +@mixin appearance ($value) { + -webkit-appearance: $value; + -moz-appearance: $value; + -ms-appearance: $value; + -o-appearance: $value; + appearance: $value; +} diff --git a/cms/static/sass/bourbon/css3/_background-image.scss b/cms/static/sass/bourbon/css3/_background-image.scss new file mode 100644 index 0000000000..c23cef7c31 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_background-image.scss @@ -0,0 +1,57 @@ +//************************************************************************// +// Background-image property for adding multiple background images with +// gradients, or for stringing multiple gradients together. +//************************************************************************// + +@mixin background-image( + $image-1 , $image-2: false, + $image-3: false, $image-4: false, + $image-5: false, $image-6: false, + $image-7: false, $image-8: false, + $image-9: false, $image-10: false +) { + $images: compact($image-1, $image-2, + $image-3, $image-4, + $image-5, $image-6, + $image-7, $image-8, + $image-9, $image-10); + + background-image: add-prefix($images, webkit); + background-image: add-prefix($images, moz); + background-image: add-prefix($images, ms); + background-image: add-prefix($images, o); + background-image: add-prefix($images); +} + + +@function add-prefix($images, $vendor: false) { + $images-prefixed: (); + + @for $i from 1 through length($images) { + $type: type-of(nth($images, $i)); // Get type of variable - List or String + + // If variable is a list - Gradient + @if $type == list { + $gradient-type: nth(nth($images, $i), 1); // Get type of gradient (linear || radial) + $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue) + + $gradient: render-gradients($gradient-args, $gradient-type, $vendor); + $images-prefixed: append($images-prefixed, $gradient, comma); + } + + // If variable is a string - Image + @else if $type == string { + $images-prefixed: join($images-prefixed, nth($images, $i), comma); + } + } + @return $images-prefixed; +} + + + +//Examples: + //@include background-image(linear-gradient(top, orange, red)); + //@include background-image(radial-gradient(50% 50%, cover circle, orange, red)); + //@include background-image(url("/images/a.png"), linear-gradient(orange, red)); + //@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png")); + //@include background-image(linear-gradient(hsla(0, 100%, 100%, 0.25) 0%, hsla(0, 100%, 100%, 0.08) 50%, transparent 50%), linear-gradient(orange, red); diff --git a/cms/static/sass/bourbon/css3/_background-size.scss b/cms/static/sass/bourbon/css3/_background-size.scss new file mode 100644 index 0000000000..4bba11027d --- /dev/null +++ b/cms/static/sass/bourbon/css3/_background-size.scss @@ -0,0 +1,15 @@ +@mixin background-size ($length-1, + $length-2: false, $length-3: false, + $length-4: false, $length-5: false, + $length-6: false, $length-7: false, + $length-8: false, $length-9: false) + { + $full: compact($length-1, $length-2, $length-3, $length-4, + $length-5, $length-6, $length-7, $length-8, $length-9); + + -webkit-background-size: $full; + -moz-background-size: $full; + -ms-background-size: $full; + -o-background-size: $full; + background-size: $full; +} diff --git a/cms/static/sass/bourbon/css3/_border-image.scss b/cms/static/sass/bourbon/css3/_border-image.scss new file mode 100644 index 0000000000..da4f20ba49 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_border-image.scss @@ -0,0 +1,56 @@ +@mixin border-image($images) { + -webkit-border-image: border-add-prefix($images, webkit); + -moz-border-image: border-add-prefix($images, moz); + -o-border-image: border-add-prefix($images, o); + border-image: border-add-prefix($images); +} + +@function border-add-prefix($images, $vendor: false) { + $border-image: (); + $images-type: type-of(nth($images, 1)); + $first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial) + + // If input is a gradient + @if $images-type == string { + @if ($first-var == "linear") or ($first-var == "radial") { + @for $i from 2 through length($images) { + $gradient-type: nth($images, 1); // Get type of gradient (linear || radial) + $gradient-args: nth($images, $i); // Get actual gradient (red, blue) + $border-image: render-gradients($gradient-args, $gradient-type, $vendor); + } + } + + // If input is a URL + @else { + $border-image: $images; + } + } + + // If input is gradient or url + additional args + @else if $images-type == list { + @for $i from 1 through length($images) { + $type: type-of(nth($images, $i)); // Get type of variable - List or String + + // If variable is a list - Gradient + @if $type == list { + $gradient-type: nth(nth($images, $i), 1); // Get type of gradient (linear || radial) + $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue) + $border-image: render-gradients($gradient-args, $gradient-type, $vendor); + } + + // If variable is a string - Image or number + @else if ($type == string) or ($type == number) { + $border-image: append($border-image, nth($images, $i)); + } + } + } + @return $border-image; +} + +//Examples: +// @include border-image(url("image.png")); +// @include border-image(url("image.png") 20 stretch); +// @include border-image(linear-gradient(45deg, orange, yellow)); +// @include border-image(linear-gradient(45deg, orange, yellow) stretch); +// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); +// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); diff --git a/cms/static/sass/bourbon/css3/_border-radius.scss b/cms/static/sass/bourbon/css3/_border-radius.scss new file mode 100644 index 0000000000..f24389ebbe --- /dev/null +++ b/cms/static/sass/bourbon/css3/_border-radius.scss @@ -0,0 +1,63 @@ +@mixin border-radius ($radii) { + -webkit-border-radius: $radii; + -moz-border-radius: $radii; + -ms-border-radius: $radii; + -o-border-radius: $radii; + border-radius: $radii; +} + +@mixin border-top-left-radius($radii) { + -webkit-border-top-left-radius: $radii; + -moz-border-top-left-radius: $radii; + -moz-border-radius-topleft: $radii; + -ms-border-top-left-radius: $radii; + -o-border-top-left-radius: $radii; + border-top-left-radius: $radii; +} + +@mixin border-top-right-radius($radii) { + -webkit-border-top-right-radius: $radii; + -moz-border-top-right-radius: $radii; + -moz-border-radius-topright: $radii; + -ms-border-top-right-radius: $radii; + -o-border-top-right-radius: $radii; + border-top-right-radius: $radii; +} + +@mixin border-bottom-left-radius($radii) { + -webkit-border-bottom-left-radius: $radii; + -moz-border-bottom-left-radius: $radii; + -moz-border-radius-bottomleft: $radii; + -ms-border-bottom-left-radius: $radii; + -o-border-bottom-left-radius: $radii; + border-bottom-left-radius: $radii; +} + +@mixin border-bottom-right-radius($radii) { + -webkit-border-bottom-right-radius: $radii; + -moz-border-bottom-right-radius: $radii; + -moz-border-radius-bottomright: $radii; + -ms-border-bottom-right-radius: $radii; + -o-border-bottom-right-radius: $radii; + border-bottom-right-radius: $radii; +} + +@mixin border-top-radius($radii) { + @include border-top-left-radius($radii); + @include border-top-right-radius($radii); +} + +@mixin border-right-radius($radii) { + @include border-top-right-radius($radii); + @include border-bottom-right-radius($radii); +} + +@mixin border-bottom-radius($radii) { + @include border-bottom-left-radius($radii); + @include border-bottom-right-radius($radii); +} + +@mixin border-left-radius($radii) { + @include border-top-left-radius($radii); + @include border-bottom-left-radius($radii); +} diff --git a/cms/static/sass/bourbon/css3/_box-shadow.scss b/cms/static/sass/bourbon/css3/_box-shadow.scss new file mode 100644 index 0000000000..327b66d251 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_box-shadow.scss @@ -0,0 +1,14 @@ +// Box-Shadow Mixin Requires Sass v3.1.1+ +@mixin box-shadow ($shadow-1, + $shadow-2: false, $shadow-3: false, + $shadow-4: false, $shadow-5: false, + $shadow-6: false, $shadow-7: false, + $shadow-8: false, $shadow-9: false) + { + $full: compact($shadow-1, $shadow-2, $shadow-3, $shadow-4, + $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9); + + -webkit-box-shadow: $full; + -moz-box-shadow: $full; + box-shadow: $full; +} diff --git a/cms/static/sass/bourbon/css3/_box-sizing.scss b/cms/static/sass/bourbon/css3/_box-sizing.scss new file mode 100644 index 0000000000..3f3f7cca9a --- /dev/null +++ b/cms/static/sass/bourbon/css3/_box-sizing.scss @@ -0,0 +1,6 @@ +@mixin box-sizing ($box) { +// content-box | border-box | inherit + -webkit-box-sizing: $box; + -moz-box-sizing: $box; + box-sizing: $box; +} diff --git a/cms/static/sass/bourbon/css3/_columns.scss b/cms/static/sass/bourbon/css3/_columns.scss new file mode 100644 index 0000000000..2896c91d7f --- /dev/null +++ b/cms/static/sass/bourbon/css3/_columns.scss @@ -0,0 +1,67 @@ +@mixin columns($arg: auto) { +// || + -webkit-columns: $arg; + -moz-columns: $arg; + columns: $arg; +} + +@mixin column-count($int: auto) { +// auto || integer + -webkit-column-count: $int; + -moz-column-count: $int; + column-count: $int; +} + +@mixin column-gap($length: normal) { +// normal || length + -webkit-column-gap: $length; + -moz-column-gap: $length; + column-gap: $length; +} + +@mixin column-fill($arg: auto) { +// auto || length + -webkit-columns-fill: $arg; + -moz-columns-fill: $arg; + columns-fill: $arg; +} + +@mixin column-rule($arg) { +// || || + -webkit-column-rule: $arg; + -moz-column-rule: $arg; + column-rule: $arg; +} + +@mixin column-rule-color($color) { + -webkit-column-rule-color: $color; + -moz-column-rule-color: $color; + column-rule-color: $color; +} + +@mixin column-rule-style($style: none) { +// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid + -webkit-column-rule-style: $style; + -moz-column-rule-style: $style; + column-rule-style: $style; +} + +@mixin column-rule-width ($width: none) { + -webkit-column-rule-width: $width; + -moz-column-rule-width: $width; + column-rule-width: $width; +} + +@mixin column-span($arg: none) { +// none || all + -webkit-column-span: $arg; + -moz-column-span: $arg; + column-span: $arg; +} + +@mixin column-width($length: auto) { +// auto || length + -webkit-column-width: $length; + -moz-column-width: $length; + column-width: $length; +} diff --git a/cms/static/sass/bourbon/css3/_flex-box.scss b/cms/static/sass/bourbon/css3/_flex-box.scss new file mode 100644 index 0000000000..44c1dfd789 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_flex-box.scss @@ -0,0 +1,67 @@ +// CSS3 Flexible Box Model and property defaults + +// Custom shorthand notation for flexbox +@mixin box($orient: inline-axis, $pack: start, $align: stretch) { + @include display-box; + @include box-orient($orient); + @include box-pack($pack); + @include box-align($align); +} + +@mixin display-box { + display: -webkit-box; + display: -moz-box; + display: box; +} + +@mixin box-orient($orient: inline-axis) { +// horizontal|vertical|inline-axis|block-axis|inherit + -webkit-box-orient: $orient; + -moz-box-orient: $orient; + box-orient: $orient; +} + +@mixin box-pack($pack: start) { +// start|end|center|justify + -webkit-box-pack: $pack; + -moz-box-pack: $pack; + box-pack: $pack; +} + +@mixin box-align($align: stretch) { +// start|end|center|baseline|stretch + -webkit-box-align: $align; + -moz-box-align: $align; + box-align: $align; +} + +@mixin box-direction($direction: normal) { +// normal|reverse|inherit + -webkit-box-direction: $direction; + -moz-box-direction: $direction; + box-direction: $direction; +} +@mixin box-lines($lines: single) { +// single|multiple + -webkit-box-lines: $lines; + -moz-box-lines: $lines; + box-lines: $lines; +} + +@mixin box-ordinal-group($integer: 1) { + -webkit-box-ordinal-group: $integer; + -moz-box-ordinal-group: $integer; + box-ordinal-group: $integer; +} + +@mixin box-flex($value: 0.0) { + -webkit-box-flex: $value; + -moz-box-flex: $value; + box-flex: $value; +} + +@mixin box-flex-group($integer: 1) { + -webkit-box-flex-group: $integer; + -moz-box-flex-group: $integer; + box-flex-group: $integer; +} diff --git a/cms/static/sass/bourbon/css3/_inline-block.scss b/cms/static/sass/bourbon/css3/_inline-block.scss new file mode 100644 index 0000000000..d79a13c851 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_inline-block.scss @@ -0,0 +1,10 @@ +// Legacy support for inline-block in IE7 (maybe IE6) +@mixin inline-block { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; +} diff --git a/cms/static/sass/bourbon/css3/_linear-gradient.scss b/cms/static/sass/bourbon/css3/_linear-gradient.scss new file mode 100644 index 0000000000..e366a299a9 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_linear-gradient.scss @@ -0,0 +1,41 @@ +@mixin linear-gradient($pos, $G1, $G2: false, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false, + $fallback: false) { + // Detect what type of value exists in $pos + $pos-type: type-of(nth($pos, 1)); + + // If $pos is missing from mixin, reassign vars and add default position + @if ($pos-type == color) or (nth($pos, 1) == "transparent") { + $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5; + $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos; + $pos: top; // Default position + } + + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + + // Set $G1 as the default fallback color + $fallback-color: nth($G1, 1); + + // If $fallback is a color use that color as the fallback color + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + background-color: $fallback-color; + background-image: deprecated-webkit-gradient(linear, $full); // Safari <= 5.0 + background-image: -webkit-linear-gradient($pos, $full); // Safari 5.1+, Chrome + background-image: -moz-linear-gradient($pos, $full); + background-image: -ms-linear-gradient($pos, $full); + background-image: -o-linear-gradient($pos, $full); + background-image: unquote("linear-gradient(#{$pos}, #{$full})"); +} + + +// Usage: Gradient position is optional, default is top. Position can be a degree. Color stops are optional as well. +// @include linear-gradient(#1e5799, #2989d8); +// @include linear-gradient(#1e5799, #2989d8, $fallback:#2989d8); +// @include linear-gradient(top, #1e5799 0%, #2989d8 50%); +// @include linear-gradient(50deg, rgba(10, 10, 10, 0.5) 0%, #2989d8 50%, #207cca 51%, #7db9e8 100%); diff --git a/cms/static/sass/bourbon/css3/_radial-gradient.scss b/cms/static/sass/bourbon/css3/_radial-gradient.scss new file mode 100644 index 0000000000..e83cab5234 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_radial-gradient.scss @@ -0,0 +1,31 @@ +// Requires Sass 3.1+ +@mixin radial-gradient($pos, $shape-size, + $G1, $G2, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false, + $fallback: false) { + + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + + // Set $G1 as the default fallback color + $fallback-color: nth($G1, 1); + + // If $fallback is a color use that color as the fallback color + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + background-color: $fallback-color; + background-image: deprecated-webkit-gradient(radial, $full); // Safari <= 5.0 + background-image: -webkit-radial-gradient($pos, $shape-size, $full); + background-image: -moz-radial-gradient($pos, $shape-size, $full); + background-image: -ms-radial-gradient($pos, $shape-size, $full); + background-image: -o-radial-gradient($pos, $shape-size, $full); + background-image: unquote("radial-gradient(#{$pos}, #{$shape-size}, #{$full})"); +} + +// Usage: Gradient position and shape-size are required. Color stops are optional. +// @include radial-gradient(50% 50%, circle cover, #1e5799, #efefef); +// @include radial-gradient(50% 50%, circle cover, #eee 10%, #1e5799 30%, #efefef); diff --git a/cms/static/sass/bourbon/css3/_transform.scss b/cms/static/sass/bourbon/css3/_transform.scss new file mode 100644 index 0000000000..8d19e8b88d --- /dev/null +++ b/cms/static/sass/bourbon/css3/_transform.scss @@ -0,0 +1,19 @@ +@mixin transform($property: none) { +// none | + -webkit-transform: $property; + -moz-transform: $property; + -ms-transform: $property; + -o-transform: $property; + transform: $property; +} + +@mixin transform-origin($axes: 50%) { +// x-axis - left | center | right | length | % +// y-axis - top | center | bottom | length | % +// z-axis - length + -webkit-transform-origin: $axes; + -moz-transform-origin: $axes; + -ms-transform-origin: $axes; + -o-transform-origin: $axes; + transform-origin: $axes; +} diff --git a/cms/static/sass/bourbon/css3/_transition.scss b/cms/static/sass/bourbon/css3/_transition.scss new file mode 100644 index 0000000000..058dbe0e33 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_transition.scss @@ -0,0 +1,104 @@ +// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. +// Example: @include transition (all, 2.0s, ease-in-out); +// @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s)); +// @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s)); + +@mixin transition ($property: all, $duration: 0.15s, $timing-function: ease-out, $delay: 0) { + + // Detect # of args passed into each variable + $length-of-property: length($property); + $length-of-duration: length($duration); + $length-of-timing-function: length($timing-function); + $length-of-delay: length($delay); + + @if $length-of-property > 1 { + @include transition-property(zip($property)); } + @else { + @include transition-property( $property); + } + + @if $length-of-duration > 1 { + @include transition-duration(zip($duration)); } + @else { + @include transition-duration( $duration); + } + + @if $length-of-timing-function > 1 { + @include transition-timing-function(zip($timing-function)); } + @else { + @include transition-timing-function( $timing-function); + } + + @if $length-of-delay > 1 { + @include transition-delay(zip($delay)); } + @else { + @include transition-delay( $delay); + } +} + + +@mixin transition-property ($prop-1: all, + $prop-2: false, $prop-3: false, + $prop-4: false, $prop-5: false, + $prop-6: false, $prop-7: false, + $prop-8: false, $prop-9: false) + { + $full: compact($prop-1, $prop-2, $prop-3, $prop-4, $prop-5, + $prop-6, $prop-7, $prop-8, $prop-9); + + -webkit-transition-property: $full; + -moz-transition-property: $full; + -ms-transition-property: $full; + -o-transition-property: $full; + transition-property: $full; +} + +@mixin transition-duration ($time-1: 0, + $time-2: false, $time-3: false, + $time-4: false, $time-5: false, + $time-6: false, $time-7: false, + $time-8: false, $time-9: false) + { + $full: compact($time-1, $time-2, $time-3, $time-4, $time-5, + $time-6, $time-7, $time-8, $time-9); + + -webkit-transition-duration: $full; + -moz-transition-duration: $full; + -ms-transition-duration: $full; + -o-transition-duration: $full; + transition-duration: $full; +} + +@mixin transition-timing-function ($motion-1: ease, + $motion-2: false, $motion-3: false, + $motion-4: false, $motion-5: false, + $motion-6: false, $motion-7: false, + $motion-8: false, $motion-9: false) + { + $full: compact($motion-1, $motion-2, $motion-3, $motion-4, $motion-5, + $motion-6, $motion-7, $motion-8, $motion-9); + +// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() + -webkit-transition-timing-function: $full; + -moz-transition-timing-function: $full; + -ms-transition-timing-function: $full; + -o-transition-timing-function: $full; + transition-timing-function: $full; +} + +@mixin transition-delay ($time-1: 0, + $time-2: false, $time-3: false, + $time-4: false, $time-5: false, + $time-6: false, $time-7: false, + $time-8: false, $time-9: false) + { + $full: compact($time-1, $time-2, $time-3, $time-4, $time-5, + $time-6, $time-7, $time-8, $time-9); + + -webkit-transition-delay: $full; + -moz-transition-delay: $full; + -ms-transition-delay: $full; + -o-transition-delay: $full; + transition-delay: $full; +} + diff --git a/cms/static/sass/bourbon/css3/_user-select.scss b/cms/static/sass/bourbon/css3/_user-select.scss new file mode 100644 index 0000000000..d5f5749431 --- /dev/null +++ b/cms/static/sass/bourbon/css3/_user-select.scss @@ -0,0 +1,6 @@ +@mixin user-select($arg: none) { + -webkit-user-select: $arg; + -moz-user-select: $arg; + -ms-user-select: $arg; + user-select: $arg; +} diff --git a/cms/static/sass/bourbon/functions/_deprecated-webkit-gradient.scss b/cms/static/sass/bourbon/functions/_deprecated-webkit-gradient.scss new file mode 100644 index 0000000000..1322f6f60e --- /dev/null +++ b/cms/static/sass/bourbon/functions/_deprecated-webkit-gradient.scss @@ -0,0 +1,36 @@ +// Render Deprecated Webkit Gradient - Linear || Radial +//************************************************************************// +@function deprecated-webkit-gradient($type, $full) { + $gradient-list: (); + $gradient: false; + $full-length: length($full); + $percentage: false; + $gradient-type: $type; + + @for $i from 1 through $full-length { + $gradient: nth($full, $i); + + @if length($gradient) == 2 { + $color-stop: color-stop(nth($gradient, 2), nth($gradient, 1)); + $gradient-list: join($gradient-list, $color-stop, comma); + } + @else { + @if $i == $full-length { + $percentage: 100%; + } + @else { + $percentage: ($i - 1) * (100 / ($full-length - 1)) + "%"; + } + $color-stop: color-stop(unquote($percentage), $gradient); + $gradient-list: join($gradient-list, $color-stop, comma); + } + } + + @if $type == radial { + $gradient: -webkit-gradient(radial, center center, 0, center center, 460, $gradient-list); + } + @else if $type == linear { + $gradient: -webkit-gradient(linear, left top, left bottom, $gradient-list); + } + @return $gradient; +} diff --git a/cms/static/sass/bourbon/functions/_flex-grid.scss b/cms/static/sass/bourbon/functions/_flex-grid.scss new file mode 100644 index 0000000000..707f994e15 --- /dev/null +++ b/cms/static/sass/bourbon/functions/_flex-grid.scss @@ -0,0 +1,35 @@ +// Flexible grid +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); +} + +// Flexible gutter +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); +} + +// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function. +// This function takes the fluid grid equation (target / context = result) and uses columns to help define each. +// +// $fg-column: 60px; // Column Width +// $fg-gutter: 25px; // Gutter Width +// $fg-max-columns: 12; // Total Columns For Main Container +// +// div { +// width: flex-grid(4); // returns (315px / 1020px) = 30.882353%; +// margin-left: flex-gutter(); // returns (25px / 1020px) = 2.45098%; +// +// p { +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// float: left; +// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%; +// } +// +// blockquote { +// float: left; +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// } +// } diff --git a/cms/static/sass/bourbon/functions/_grid-width.scss b/cms/static/sass/bourbon/functions/_grid-width.scss new file mode 100644 index 0000000000..8e63d83d60 --- /dev/null +++ b/cms/static/sass/bourbon/functions/_grid-width.scss @@ -0,0 +1,13 @@ +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; +} + +// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function. +// +// $gw-column: 100px; // Column Width +// $gw-gutter: 40px; // Gutter Width +// +// div { +// width: grid-width(4); // returns 520px; +// margin-left: $gw-gutter; // returns 40px; +// } diff --git a/cms/static/sass/bourbon/functions/_linear-gradient.scss b/cms/static/sass/bourbon/functions/_linear-gradient.scss new file mode 100644 index 0000000000..3b10ca82a6 --- /dev/null +++ b/cms/static/sass/bourbon/functions/_linear-gradient.scss @@ -0,0 +1,23 @@ +@function linear-gradient($pos: top, $G1: false, $G2: false, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false) { + + // Detect what type of value exists in $pos + $pos-type: type-of(nth($pos, 1)); + + // If $pos is missing from mixin, reassign vars and add default position + @if ($pos-type == color) or (nth($pos, 1) == "transparent") { + $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5; + $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos; + $pos: top; // Default position + } + + $type: linear; + $gradient: compact($pos, $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + $type-gradient: append($type, $gradient, comma); + + @return $type-gradient; +} + diff --git a/cms/static/sass/bourbon/functions/_modular-scale.scss b/cms/static/sass/bourbon/functions/_modular-scale.scss new file mode 100644 index 0000000000..dddccb5224 --- /dev/null +++ b/cms/static/sass/bourbon/functions/_modular-scale.scss @@ -0,0 +1,40 @@ +@function modular-scale($value, $increment, $ratio) { + @if $increment > 0 { + @for $i from 1 through $increment { + $value: ($value * $ratio); + } + } + + @if $increment < 0 { + $increment: abs($increment); + @for $i from 1 through $increment { + $value: ($value / $ratio); + } + } + + @return $value; +} + +// div { +// Increment Up GR with positive value +// font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px +// +// Increment Down GR with negative value +// font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px +// +// Can be used with ceil(round up) or floor(round down) +// font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px +// font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px +// } +// +// modularscale.com + +@function golden-ratio($value, $increment) { + @return modular-scale($value, $increment, 1.618) +} + +// div { +// font-size: golden-ratio(14px, 1); // returns: 22.652px +// } +// +// goldenratiocalculator.com diff --git a/cms/static/sass/bourbon/functions/_radial-gradient.scss b/cms/static/sass/bourbon/functions/_radial-gradient.scss new file mode 100644 index 0000000000..3d5461ad6e --- /dev/null +++ b/cms/static/sass/bourbon/functions/_radial-gradient.scss @@ -0,0 +1,15 @@ +// This function is required and used by the background-image mixin. +@function radial-gradient($pos, $shape-size, + $G1, $G2, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false) { + + $type: radial; + $gradient: compact($pos, $shape-size, $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + $type-gradient: append($type, $gradient, comma); + + @return $type-gradient; +} + diff --git a/cms/static/sass/bourbon/functions/_render-gradients.scss b/cms/static/sass/bourbon/functions/_render-gradients.scss new file mode 100644 index 0000000000..fe7c799ebe --- /dev/null +++ b/cms/static/sass/bourbon/functions/_render-gradients.scss @@ -0,0 +1,14 @@ +// User for linear and radial gradients within background-image or border-image properties + +@function render-gradients($gradients, $gradient-type, $vendor: false) { + $vendor-gradients: false; + @if $vendor { + $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient($gradients); + } + + @else if $vendor == false { + $vendor-gradients: "#{$gradient-type}-gradient(#{$gradients})"; + $vendor-gradients: unquote($vendor-gradients); + } + @return $vendor-gradients; +} diff --git a/cms/static/sass/bourbon/functions/_tint-shade.scss b/cms/static/sass/bourbon/functions/_tint-shade.scss new file mode 100644 index 0000000000..f7172004ac --- /dev/null +++ b/cms/static/sass/bourbon/functions/_tint-shade.scss @@ -0,0 +1,9 @@ +// Add percentage of white to a color +@function tint($color, $percent){ + @return mix(white, $color, $percent); +} + +// Add percentage of black to a color +@function shade($color, $percent){ + @return mix(black, $color, $percent); +} diff --git a/cms/static/sass/bourbon/lib/bourbon.rb b/cms/static/sass/bourbon/lib/bourbon.rb new file mode 100644 index 0000000000..1635be836d --- /dev/null +++ b/cms/static/sass/bourbon/lib/bourbon.rb @@ -0,0 +1,19 @@ +require "bourbon/generator" + +module Bourbon + if defined?(Rails) + class Engine < ::Rails::Engine + require 'bourbon/engine' + end + + module Rails + class Railtie < ::Rails::Railtie + rake_tasks do + load "tasks/install.rake" + end + end + end + end +end + +require File.join(File.dirname(__FILE__), "/bourbon/sass_extensions") diff --git a/cms/static/sass/bourbon/lib/bourbon/sass_extensions.rb b/cms/static/sass/bourbon/lib/bourbon/sass_extensions.rb new file mode 100644 index 0000000000..ad567200e3 --- /dev/null +++ b/cms/static/sass/bourbon/lib/bourbon/sass_extensions.rb @@ -0,0 +1,6 @@ +module Bourbon::SassExtensions +end + +require "sass" + +require File.join(File.dirname(__FILE__), "/sass_extensions/functions") diff --git a/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions.rb b/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions.rb new file mode 100644 index 0000000000..daa877650e --- /dev/null +++ b/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions.rb @@ -0,0 +1,13 @@ +module Bourbon::SassExtensions::Functions +end + +require File.join(File.dirname(__FILE__), "/functions/compact") + +module Sass::Script::Functions + include Bourbon::SassExtensions::Functions::Compact +end + +# Wierd that this has to be re-included to pick up sub-modules. Ruby bug? +class Sass::Script::Functions::EvaluationContext + include Sass::Script::Functions +end diff --git a/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions/compact.rb b/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions/compact.rb new file mode 100644 index 0000000000..5192e921e7 --- /dev/null +++ b/cms/static/sass/bourbon/lib/bourbon/sass_extensions/functions/compact.rb @@ -0,0 +1,13 @@ +# Compact function pulled from compass +module Bourbon::SassExtensions::Functions::Compact + + def compact(*args) + sep = :comma + if args.size == 1 && args.first.is_a?(Sass::Script::List) + args = args.first.value + sep = args.first.separator + end + Sass::Script::List.new(args.reject{|a| !a.to_bool}, sep) + end + +end diff --git a/cms/templates/base.html b/cms/templates/base.html new file mode 100644 index 0000000000..497296ee66 --- /dev/null +++ b/cms/templates/base.html @@ -0,0 +1,30 @@ + + + + + + + + + + {% block title %}{% endblock %} + + + + + + {% include "widgets/header.html"%} + + {% block content %}{% endblock %} + + + + + + + + + + + + diff --git a/cms/templates/index.html b/cms/templates/index.html new file mode 100644 index 0000000000..82c3133a37 --- /dev/null +++ b/cms/templates/index.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block title %}Course Manager{% endblock %} + +{% block content %} +

      + + {% include "widgets/navigation.html"%} + +
      + {% include "widgets/week-edit.html"%} + {% include "widgets/week-new.html"%} + {% include "widgets/video-edit.html"%} + {% include "widgets/video-new.html"%} + {% include "widgets/problem-edit.html"%} + {% include "widgets/problem-new.html"%} +
      + +
      +{% endblock %} diff --git a/cms/templates/widgets/captions.html b/cms/templates/widgets/captions.html new file mode 100644 index 0000000000..088beb7a33 --- /dev/null +++ b/cms/templates/widgets/captions.html @@ -0,0 +1,242 @@ +
        +
      • English (main)
      • +
      • French
      • +
      • English v2
      • +
      • +
      • +
      + + diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html new file mode 100644 index 0000000000..86a2afb0f9 --- /dev/null +++ b/cms/templates/widgets/header.html @@ -0,0 +1,5 @@ +
      + +
      diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html new file mode 100644 index 0000000000..049e5da537 --- /dev/null +++ b/cms/templates/widgets/navigation.html @@ -0,0 +1,120 @@ +
      +
      +

      Circuts & Electronics

      + + +
      + +
        +
      1. +
        +

        Week 1

        +
        + +
          +
        • Goal 1
        • +
        • Goal 2
        • +
        • Lecture Sequence
        • +
        • Lecture Sequence
        • +
        • Lab
        • +
        • Homework
        • +
        • + Add new sequence
        • +
        +
      2. +
      3. +
        +

        Week 2

        +
        + +
          +
        • Goal 1
        • +
        • Lecture Sequence
        • +
        • Lecture Sequence
        • +
        • Lab
        • +
        • Homework
        • +
        • + Add new sequence
        • +
        +
      4. +
      5. +
        +

        Week 3

        +
        + +
          +
        • Goal 1
        • +
        • Lab
        • +
        • Lab
        • +
        • Homework
        • +
        • + Add new sequence
        • +
        +
      6. +
      7. +
        +

        Week 4

        +
        + +
          +
        • Goal 1
        • +
        • Lecture Sequence
        • +
        • Lab
        • +
        • Homework
        • +
        • Homework
        • +
        • + Add new sequence
        • +
        +
      8. + +
      9. +
        +

        Week 5

        +
        + +
          +
        • + Add new sequence
        • +
        +
      10. +
      11. +

        + + Add New +

        + +
        +
        +

        Week 6

        +
        + +
        + + +
        +
        +
      12. +
      +
      + diff --git a/cms/templates/widgets/new-module.html b/cms/templates/widgets/new-module.html new file mode 100644 index 0000000000..070b6462bb --- /dev/null +++ b/cms/templates/widgets/new-module.html @@ -0,0 +1,9 @@ +
    • + +
    • diff --git a/cms/templates/widgets/problem-edit.html b/cms/templates/widgets/problem-edit.html new file mode 100644 index 0000000000..b1d5796a9f --- /dev/null +++ b/cms/templates/widgets/problem-edit.html @@ -0,0 +1,73 @@ +
      +
      + cancel + Save & Update +
      + +
      +
      +

      Old Problem

      +
      + + +
      + Settings +
      + + +
      +
      +
      +

      Tags:

      +

      Click to edit

      +
      + +
      +

      Last modified:

      +

      mm/dd/yy

      +
      + +
      +

      By

      +

      Anant Agarwal

      +
      +
      +
      + +
      + +
      + 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. +
      +
      + +
      +
        +
      • +

        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.

        +

        Anant Agarwal

        +
      • +
      • +

        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.

        +

        Anant Agarwal

        +
      • +
      + +
      +

      Add notes

      + + +
      +
      + + Save & Update +
      +
      + diff --git a/cms/templates/widgets/problem-new.html b/cms/templates/widgets/problem-new.html new file mode 100644 index 0000000000..9f91dd1177 --- /dev/null +++ b/cms/templates/widgets/problem-new.html @@ -0,0 +1,52 @@ +
      +
      + cancel + Save & Update +
      + +
      +
      +

      New Problem

      +
      + + Settings + +
      +
      +
      +

      Tags:

      +

      Click to edit

      +
      + + + + + + + + + + +
      +
      + +
      + +
      +
      +
      + +
      +

      Add notes

      +
      + + Save & Update +
      +
      diff --git a/cms/templates/widgets/raw-videos.html b/cms/templates/widgets/raw-videos.html new file mode 100644 index 0000000000..f466fd59bc --- /dev/null +++ b/cms/templates/widgets/raw-videos.html @@ -0,0 +1,3 @@ +
    • +
      Video-file-name
      +
    • diff --git a/cms/templates/widgets/save-captions.html b/cms/templates/widgets/save-captions.html new file mode 100644 index 0000000000..87342f0cd0 --- /dev/null +++ b/cms/templates/widgets/save-captions.html @@ -0,0 +1,4 @@ +
      + Cancel + +
      diff --git a/cms/templates/widgets/speed-tooltip.html b/cms/templates/widgets/speed-tooltip.html new file mode 100644 index 0000000000..2a82e237e7 --- /dev/null +++ b/cms/templates/widgets/speed-tooltip.html @@ -0,0 +1,7 @@ +
      + +
      diff --git a/cms/templates/widgets/video-box-unused.html b/cms/templates/widgets/video-box-unused.html new file mode 100644 index 0000000000..8cde10f151 --- /dev/null +++ b/cms/templates/widgets/video-box-unused.html @@ -0,0 +1,38 @@ +
    • + +
      + +
      + video-name 236mb Uploaded 6 hours ago by Anant Agrawal +

      +

        + Speed +
      • + 0.75x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • Normal + {% include "widgets/speed-tooltip.html" %} +
      • +
      • 1.25x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • 1.5x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • +
      • +
      +

      +

      + Download All — + Delete All — + Edit Captions — + Use clip ⬆ +

      +
      +
      + {% include "widgets/captions.html" %} + {% include "widgets/save-captions.html" %} +
      +
    • + diff --git a/cms/templates/widgets/video-box.html b/cms/templates/widgets/video-box.html new file mode 100644 index 0000000000..9ab17030bd --- /dev/null +++ b/cms/templates/widgets/video-box.html @@ -0,0 +1,35 @@ +
    • +
      + +
      + video-name 236mb +

      Uploaded 6 hours ago by Anant Agrawal

      +

      +

        + Speed +
      • + 0.75x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • Normal + {% include "widgets/speed-tooltip.html" %} +
      • +
      • 1.25x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • 1.5x + {% include "widgets/speed-tooltip.html" %} +
      • +
      • +
      • +
      +

      +

      + Download all — +Remove ⬇ + +

      +
      +
      + {% include "widgets/captions.html" %} +
      +
    • diff --git a/cms/templates/widgets/video-edit.html b/cms/templates/widgets/video-edit.html new file mode 100644 index 0000000000..6b49dc27d7 --- /dev/null +++ b/cms/templates/widgets/video-edit.html @@ -0,0 +1,151 @@ +
      + +
      +
      + +
      +
      + Created 22/03/12 + by Piotr Mitros +
      + +
      + Last edited 22/03/12 + by David Ormsbee +
      +
      +
      +
      +

      Video title

      + +
      + Tag mitx, s4v1, circuits, anant +
      + +
      +
      + Keyword + S4V1 + + + Due Date + 21/03/12 + + Status + + + +
      + +
      + +
      +
      +
      + or + use an already uploaded one +
      +
      + +
      +
      +
        +
      • video-name-@0-75x.extension
      • +
      • video-name-@1x.extension
      • +
      • video-name-@1-25x.extension
      • +
      • video-name-@1-5x.extension
      • +
      +
      + + + Cancel +
      + +
      + +
      +
      +
        +

        Video clip in use

        + {% include "widgets/video-box.html" %} +
      +

      No video clip used. Select one from the list below, upload a new one or import an already uploaded video.

      +
      +
      + +
      +
      + + +
      +
      +
        + {% include "widgets/video-box-unused.html" %} +
      +
      +
      + +
      + +
      +

      Annotations

      + + +
      + +
      + +
      + +
      +

      Notes

      + + + +
      + +
      +
      +
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. +

      By Piotr Mitros 10 hours ago

      +
      +
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

      By Piotr Mitros 10 hours ago

      +
      +
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

      By Piotr Mitros 10 hours ago

      +
      +
      +
      + + + +
      + +
      + Select the source video or directly enter its ID +
      + +
      + +
      + +
      + + + Cancel +
      +
      diff --git a/cms/templates/widgets/video-new.html b/cms/templates/widgets/video-new.html new file mode 100644 index 0000000000..2555169c05 --- /dev/null +++ b/cms/templates/widgets/video-new.html @@ -0,0 +1,48 @@ +
      +
      + cancel + Save & Update +
      + +
      +
      +

      Untitled Video

      +
      + + Settings + +
      + +
      +
      +

      Tags:

      +

      Click to edit

      +
      + + + + + + + + + + +
      +
      + +
      + +
      +
      + Save & Update +
      +
      +
      diff --git a/cms/templates/widgets/week-edit.html b/cms/templates/widgets/week-edit.html new file mode 100644 index 0000000000..5e6ac199e9 --- /dev/null +++ b/cms/templates/widgets/week-edit.html @@ -0,0 +1,83 @@ +{% block content %} +
      +
      +

      Week 3

      + Done +
      + + + +
      +
      +

      Weeks Content

      +
      + +
      +
      + +
      +
        +
      • + + +
      • + +
      • + + +
      • +
      • + +
      • + +
      • + Advanced filters +
      • +
      +
      + +
      + +
      +
      +
      +{% endblock %} diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html new file mode 100644 index 0000000000..42f4f78e1e --- /dev/null +++ b/cms/templates/widgets/week-new.html @@ -0,0 +1,62 @@ +{% block content %} +
      +
      +

      Week 6

      + Done +
      + + + +
      +
      +

      Weeks Content

      +
      + +
      +
      + +
      +
        +
      • + + +
      • + +
      • + + +
      • +
      • + +
      • + +
      • + Advanced filters +
      • +
      +
      +
      +
      +{% endblock %} diff --git a/cms/urls.py b/cms/urls.py index a7266066cc..781c2c261f 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -7,4 +7,5 @@ from django.conf.urls.defaults import patterns, url urlpatterns = patterns('', url(r'^(?P[^/]+)/(?P[^/]+)/calendar/', 'contentstore.views.calendar', name='calendar'), url(r'^accounts/login/', 'instructor.views.do_login', name='login'), + url(r'^$', 'contentstore.views.index', name='index'), ) From 626f3f343b17b59e46ebea8aaaa6b139f9188f60 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 13 Jun 2012 13:22:38 -0400 Subject: [PATCH 056/123] Switch to mako templates --- cms/templates/base.html | 24 ++++++++++----------- cms/templates/index.html | 22 +++++++++---------- cms/templates/widgets/video-box-unused.html | 12 +++++------ cms/templates/widgets/video-box.html | 10 ++++----- cms/templates/widgets/video-edit.html | 8 +++---- cms/templates/widgets/week-edit.html | 6 +++--- cms/templates/widgets/week-new.html | 6 +++--- 7 files changed, 44 insertions(+), 44 deletions(-) diff --git a/cms/templates/base.html b/cms/templates/base.html index 497296ee66..a23a31d9a5 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -4,26 +4,26 @@ - - - - {% block title %}{% endblock %} + + + + <%block name="title"></%block> - {% include "widgets/header.html"%} + <%include file="widgets/header.html"/> - {% block content %}{% endblock %} + <%block name="content"> - - - - - - + + + + + + diff --git a/cms/templates/index.html b/cms/templates/index.html index 82c3133a37..efd9f9a242 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -1,19 +1,19 @@ -{% extends "base.html" %} -{% block title %}Course Manager{% endblock %} +<%inherit file="base.html" /> +<%block name="title">Course Manager -{% block content %} +<%block name="content">
      - {% include "widgets/navigation.html"%} + <%include file="widgets/navigation.html"/>
      - {% include "widgets/week-edit.html"%} - {% include "widgets/week-new.html"%} - {% include "widgets/video-edit.html"%} - {% include "widgets/video-new.html"%} - {% include "widgets/problem-edit.html"%} - {% include "widgets/problem-new.html"%} + <%include file="widgets/week-edit.html"/> + <%include file="widgets/week-new.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"/>
      -{% endblock %} + diff --git a/cms/templates/widgets/video-box-unused.html b/cms/templates/widgets/video-box-unused.html index 8cde10f151..3d643ff3c9 100644 --- a/cms/templates/widgets/video-box-unused.html +++ b/cms/templates/widgets/video-box-unused.html @@ -9,16 +9,16 @@ Speed
    • 0.75x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • Normal - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • 1.25x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • 1.5x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • +
    • @@ -31,8 +31,8 @@

      - {% include "widgets/captions.html" %} - {% include "widgets/save-captions.html" %} + <%include file="captions.html"/> + <%include file="save-captions.html"/>
      diff --git a/cms/templates/widgets/video-box.html b/cms/templates/widgets/video-box.html index 9ab17030bd..1f17e33511 100644 --- a/cms/templates/widgets/video-box.html +++ b/cms/templates/widgets/video-box.html @@ -9,16 +9,16 @@ Speed
    • 0.75x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • Normal - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • 1.25x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • 1.5x - {% include "widgets/speed-tooltip.html" %} + <%include file="speed-tooltip.html"/>
    • +
    • @@ -30,6 +30,6 @@

      - {% include "widgets/captions.html" %} + <%include file="captions.html"/>
      diff --git a/cms/templates/widgets/video-edit.html b/cms/templates/widgets/video-edit.html index 6b49dc27d7..ac4c921918 100644 --- a/cms/templates/widgets/video-edit.html +++ b/cms/templates/widgets/video-edit.html @@ -71,7 +71,7 @@

        Video clip in use

        - {% include "widgets/video-box.html" %} + <%include file="video-box.html"/>

      No video clip used. Select one from the list below, upload a new one or import an already uploaded video.

      @@ -84,7 +84,7 @@
        - {% include "widgets/video-box-unused.html" %} + <%include file="video-box-unused.html"/>
      @@ -129,7 +129,7 @@ @@ -140,7 +140,7 @@
      - +
      diff --git a/cms/templates/widgets/week-edit.html b/cms/templates/widgets/week-edit.html index 5e6ac199e9..ea6d29ef3b 100644 --- a/cms/templates/widgets/week-edit.html +++ b/cms/templates/widgets/week-edit.html @@ -1,4 +1,4 @@ -{% block content %} +<%block name="content">

      Week 3

      @@ -27,7 +27,7 @@
    • Problem title 13
    • Problem title 14
    • Video 3
    • - {% include "widgets/new-module.html"%} + <%include file="new-module.html"/>
      @@ -80,4 +80,4 @@ -{% endblock %} + diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html index 42f4f78e1e..986dc6e05e 100644 --- a/cms/templates/widgets/week-new.html +++ b/cms/templates/widgets/week-new.html @@ -1,4 +1,4 @@ -{% block content %} +<%block name="content">

      Week 6

      @@ -18,7 +18,7 @@

      Scratchpad

        - {% include "widgets/new-module.html"%} + <%include file="new-module.html"/>
      @@ -59,4 +59,4 @@ -{% endblock %} + From 062232e7368e596c49bc118dafad6d15050181d8 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 13 Jun 2012 14:43:01 -0400 Subject: [PATCH 057/123] Porting latest changes from ui_prototype --- cms/static/css/base-style.css | 684 ++++++++++++++------- cms/static/js/main.js | 165 ++--- cms/static/sass/_base.scss | 16 +- cms/static/sass/_calendar.scss | 286 +++++---- cms/static/sass/_module-header.scss | 128 ++++ cms/static/sass/_problem.scss | 47 +- cms/static/sass/_video.scss | 59 +- cms/static/sass/_week.scss | 4 +- cms/static/sass/base-style.scss | 14 +- cms/templates/index.html | 1 + cms/templates/widgets/header.html | 3 +- cms/templates/widgets/module-dropdown.html | 28 + cms/templates/widgets/navigation.html | 118 ++-- cms/templates/widgets/new-module.html | 16 +- cms/templates/widgets/problem-edit.html | 72 +-- cms/templates/widgets/problem-new.html | 33 +- cms/templates/widgets/sequnce-edit.html | 85 +++ cms/templates/widgets/video-edit.html | 184 ++---- cms/templates/widgets/video-new.html | 81 ++- cms/templates/widgets/week-edit.html | 94 +-- cms/templates/widgets/week-new.html | 114 ++-- 21 files changed, 1356 insertions(+), 876 deletions(-) create mode 100644 cms/static/sass/_module-header.scss create mode 100644 cms/templates/widgets/module-dropdown.html create mode 100644 cms/templates/widgets/sequnce-edit.html diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css index 2cdeafe9e5..518443a41c 100644 --- a/cms/static/css/base-style.css +++ b/cms/static/css/base-style.css @@ -191,13 +191,28 @@ body { clear: both; } body > header nav h2 { font-size: 14px; - text-transform: uppercase; } + text-transform: uppercase; + float: left; } + body > header nav a.new-module { + float: right; } + body.content section.main-content { + border-left: 2px solid #000; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 74.423%; + float: left; + -webkit-box-shadow: -2px 0 3px #dddddd; + -moz-box-shadow: -2px 0 3px #dddddd; + box-shadow: -2px 0 3px #dddddd; } a { text-decoration: none; color: #888; } -input[type="submit"], .button, section.week-edit > header a, section.week-new > header a { +input[type="submit"], .button, section.cal section.new-section > a, section.week-edit > header a, +section.week-new > header a, +section.sequence-edit > header a, section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button, section.video-new > section a.save-update, section.video-edit > section a.save-update, section.problem-new > section a.save, section.problem-edit > section a.save { border: 1px solid #ccc; background: #efefef; -webkit-border-radius: 3px; @@ -221,17 +236,36 @@ section.cal { clear: both; } section.cal > header { zoom: 1; - margin-bottom: 10px; } + margin-bottom: 10px; + background: #efefef; + border: 1px solid #ddd; } section.cal > header:before, section.cal > header:after { content: ""; display: table; } section.cal > header:after { clear: both; } - section.cal > header h1 { - float: left; - font-size: 18px; } + section.cal > header h2 { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 14px; + padding: 6px; + margin-left: 6px; + font-size: 12px; } section.cal > header ul { - float: right; } + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } section.cal > header ul li { display: -moz-inline-box; -moz-box-orient: vertical; @@ -239,42 +273,37 @@ section.cal { vertical-align: baseline; zoom: 1; *display: inline; - *vertical-align: auto; } + *vertical-align: auto; + margin-left: 6px; + padding-left: 6px; + border-left: 1px solid #ddd; + padding: 6px; } section.cal > header ul li a { - padding: 6px; - border: 1px solid #ddd; - display: block; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - -ms-border-radius: 3px; - -o-border-radius: 3px; - border-radius: 3px; - background: #efefef; } - section.cal > header ul li.dropdown { - position: relative; } - section.cal > header ul li.dropdown ul { - display: none; - position: absolute; - background: #fff; - border: 1px solid #ddd; } - section.cal > header ul li.dropdown ul li { - padding: 6px; - display: block; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; } - section.cal > header ul li.dropdown ul li:hover { - background-color: #efefef; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; } - section.cal > header ul li.dropdown:hover ul { - display: block; } - section.cal > header ul li.dropdown:hover a { - -webkit-border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - -ms-border-radius: 3px 3px 0 0; - -o-border-radius: 3px 3px 0 0; - border-radius: 3px 3px 0 0; - border-bottom: 0; } + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.cal > header ul li ul { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.cal > header ul li ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + padding: 0; + border-left: 0; } section.cal ol { list-style: none; zoom: 1; @@ -297,75 +326,26 @@ section.cal { box-sizing: border-box; float: left; width: 25.0%; } - section.cal ol > li:last-child { - text-align: center; - background: #eee; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - section.cal ol > li:last-child p { - width: 100%; - height: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - section.cal ol > li:last-child p a { - display: block; - width: 100%; - height: 100%; } - section.cal ol > li:last-child section.new-week header { - background: #fff; - text-align: left; } - section.cal ol > li:last-child section.new-week form { - background: #fff; - width: 50%; - padding: 6px; - border: 1px solid #000; - margin: 0 auto; - -webkit-box-shadow: 0 0 2px #333333; - -moz-box-shadow: 0 0 2px #333333; - box-shadow: 0 0 2px #333333; - position: relative; } - section.cal ol > li:last-child section.new-week form:before { - background: #fff; - border-left: 1px solid #000; - border-top: 1px solid #000; - content: " "; - display: block; - height: 10px; - left: 50%; - position: absolute; - top: -6px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - width: 10px; - z-index: 0; } - section.cal ol > li:last-child section.new-week form select { - margin-bottom: 6px; - width: 100%; } - section.cal ol > li:last-child section.new-week form select option { - padding: 10px 0 !important; } - section.cal ol > li:last-child section.new-week form input[type="submit"] { - display: block; - margin-bottom: 6px; - width: 100%; } - section.cal ol > li:last-child section.new-week form a:first-child { - float: left; } - section.cal ol > li:last-child section.new-week form a:last-child { - float: right; } section.cal ol > li header { border-bottom: 1px solid #000; - -webkit-box-shadow: 0 1px 2px #cccccc; - -moz-box-shadow: 0 1px 2px #cccccc; - box-shadow: 0 1px 2px #cccccc; + -webkit-box-shadow: 0 1px 2px #aaaaaa; + -moz-box-shadow: 0 1px 2px #aaaaaa; + box-shadow: 0 1px 2px #aaaaaa; display: block; - margin-bottom: 2px; - padding: 6px; } + margin-bottom: 2px; } section.cal ol > li header h1 { - font-size: 14px; } + font-size: 14px; + text-transform: uppercase; + border-bottom: 1px solid #ccc; + padding: 6px; } + section.cal ol > li header h1 a { + color: #000; + display: block; } + section.cal ol > li header ul li { + background: #fff; + color: #888; + border-bottom: 0; + font-size: 12px; } section.cal ol > li ul { list-style: none; margin-bottom: 1px; } @@ -373,8 +353,104 @@ section.cal { background: #efefef; border-bottom: 1px solid #666; padding: 6px; } - section.cal ol > li ul li.goal { - background: #fff; } + section.cal ol > li ul li.create-module { + position: relative; } + section.cal ol > li ul li.create-module > div { + display: none; + position: absolute; + top: 30px; + width: 90%; + background: rgba(0, 0, 0, 0.8); + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + z-index: 99; } + section.cal ol > li ul li.create-module > div ul li { + border-bottom: 0; + background: none; } + section.cal ol > li ul li.create-module > div ul li input { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-color: #000; + padding: 6px; } + section.cal ol > li ul li.create-module > div ul li select { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + section.cal ol > li ul li.create-module > div ul li select option { + font-size: 14px; } + section.cal ol > li ul li.create-module > div ul li a { + float: right; } + section.cal ol > li ul li.create-module > div ul li a:first-child { + float: left; } + section.cal ol > li ul li.create-module:hover div { + display: block; } + section.cal section.new-section { + margin-top: 10px; } + section.cal section.new-section > a { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.cal section.new-section section { + display: none; } + section.cal section.new-section section header { + background: #fff; + text-align: left; } + section.cal section.new-section section form { + background: #fff; + width: 50%; + padding: 6px; + border: 1px solid #000; + margin: 0 auto; + -webkit-box-shadow: 0 0 2px #333333; + -moz-box-shadow: 0 0 2px #333333; + box-shadow: 0 0 2px #333333; + position: relative; } + section.cal section.new-section section form:before { + background: #fff; + border-left: 1px solid #000; + border-top: 1px solid #000; + content: " "; + display: block; + height: 10px; + left: 50%; + position: absolute; + top: -6px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + width: 10px; + z-index: 0; } + section.cal section.new-section section form select { + margin-bottom: 6px; + width: 100%; } + section.cal section.new-section section form select option { + padding: 10px 0 !important; } + section.cal section.new-section section form input[type="submit"] { + display: block; + margin-bottom: 6px; + width: 100%; } + section.cal section.new-section section form a:first-child { + float: left; } + section.cal section.new-section section form a:last-child { + float: right; } + section.cal section.new-section:hover section { + display: block; } body.content section.cal { @@ -393,31 +469,49 @@ section.cal { box-sizing: border-box; width: 100%; } -section.week-edit > header, section.week-new > header { +section.week-edit > header, +section.week-new > header, +section.sequence-edit > header { border-bottom: 1px solid #ccc; zoom: 1; padding: 6px; } - section.week-edit > header:before, section.week-edit > header:after, section.week-new > header:before, section.week-new > header:after { + section.week-edit > header:before, section.week-edit > header:after, + section.week-new > header:before, + section.week-new > header:after, + section.sequence-edit > header:before, + section.sequence-edit > header:after { content: ""; display: table; } - section.week-edit > header:after, section.week-new > header:after { + section.week-edit > header:after, + section.week-new > header:after, + section.sequence-edit > header:after { clear: both; } - section.week-edit > header h1, section.week-new > header h1 { + section.week-edit > header h1, + section.week-new > header h1, + section.sequence-edit > header h1 { font-size: 18px; float: left; margin-top: 8px 6px; } - section.week-edit > header a, section.week-new > header a { + section.week-edit > header a, + section.week-new > header a, + section.sequence-edit > header a { float: right; display: block; } -section.week-edit section header, section.week-new section header { +section.week-edit section header, +section.week-new section header, +section.sequence-edit section header { background: #666; color: #fff; padding: 6px; border-bottom: 1px solid #333; -webkit-font-smoothing: antialiased; } - section.week-edit section header h2, section.week-new section header h2 { + section.week-edit section header h2, + section.week-new section header h2, + section.sequence-edit section header h2 { font-size: 14px; } -section.week-edit section.sidebar, section.week-new section.sidebar { +section.week-edit section.sidebar, +section.week-new section.sidebar, +section.sequence-edit section.sidebar { width: 34.368%; float: left; background: #ccc; @@ -425,49 +519,85 @@ section.week-edit section.sidebar, section.week-new section.sidebar { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } - section.week-edit section.sidebar section, section.week-new section.sidebar section { + section.week-edit section.sidebar section, + section.week-new section.sidebar section, + section.sequence-edit section.sidebar section { height: 50%; } - section.week-edit section.sidebar section > ul, section.week-new section.sidebar section > ul { + section.week-edit section.sidebar section > ul, + section.week-new section.sidebar section > ul, + section.sequence-edit section.sidebar section > ul { list-style: none; } - section.week-edit section.sidebar section > ul > li, section.week-new section.sidebar section > ul > li { + section.week-edit section.sidebar section > ul > li, + section.week-new section.sidebar section > ul > li, + section.sequence-edit section.sidebar section > ul > li { padding: 6px; border-bottom: 1px solid #666; background: #eee; } - section.week-edit section.sidebar section > ul > li.new-module, section.week-new section.sidebar section > ul > li.new-module { + section.week-edit section.sidebar section > ul > li.new-module, + section.week-new section.sidebar section > ul > li.new-module, + section.sequence-edit section.sidebar section > ul > li.new-module { position: relative; } - section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown { + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown, + section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown, + section.sequence-edit section.sidebar section > ul > li.new-module ul.new-dropdown { list-style: none; } - section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li { + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li, + section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li, + section.sequence-edit section.sidebar section > ul > li.new-module ul.new-dropdown li { display: none; } - section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child { + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child, + section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child, + section.sequence-edit section.sidebar section > ul > li.new-module ul.new-dropdown li:first-child { display: block; } - section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li { + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li, + section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li, + section.sequence-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li { display: block; padding: 6px 0; } - section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child, section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child { + section.week-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child, + section.week-new section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child, + section.sequence-edit section.sidebar section > ul > li.new-module ul.new-dropdown:hover li:first-child { padding-top: 0; } - section.week-edit section.sidebar section p, section.week-new section.sidebar section p { + section.week-edit section.sidebar section p, + section.week-new section.sidebar section p, + section.sequence-edit section.sidebar section p { padding: 6px; border-bottom: 1px solid #666; } -section.week-edit section.weeks-content, section.week-new section.weeks-content { +section.week-edit section.weeks-content, +section.week-new section.weeks-content, +section.sequence-edit section.weeks-content { width: 65.632%; float: left; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } - section.week-edit section.weeks-content header, section.week-new section.weeks-content header { + section.week-edit section.weeks-content header, + section.week-new section.weeks-content header, + section.sequence-edit section.weeks-content header { zoom: 1; } - section.week-edit section.weeks-content header:before, section.week-edit section.weeks-content header:after, section.week-new section.weeks-content header:before, section.week-new section.weeks-content header:after { + section.week-edit section.weeks-content header:before, section.week-edit section.weeks-content header:after, + section.week-new section.weeks-content header:before, + section.week-new section.weeks-content header:after, + section.sequence-edit section.weeks-content header:before, + section.sequence-edit section.weeks-content header:after { content: ""; display: table; } - section.week-edit section.weeks-content header:after, section.week-new section.weeks-content header:after { + section.week-edit section.weeks-content header:after, + section.week-new section.weeks-content header:after, + section.sequence-edit section.weeks-content header:after { clear: both; } - section.week-edit section.weeks-content header h2, section.week-new section.weeks-content header h2 { + section.week-edit section.weeks-content header h2, + section.week-new section.weeks-content header h2, + section.sequence-edit section.weeks-content header h2 { float: left; } - section.week-edit section.weeks-content header form, section.week-new section.weeks-content header form { + section.week-edit section.weeks-content header form, + section.week-new section.weeks-content header form, + section.sequence-edit section.weeks-content header form { float: right; margin: -2px 0; } - section.week-edit section.weeks-content header form input, section.week-new section.weeks-content header form input { + section.week-edit section.weeks-content header form input, + section.week-new section.weeks-content header form input, + section.sequence-edit section.weeks-content header form input { border: 1px solid #000; background: #ddd; padding: 2px 4px; @@ -476,18 +606,30 @@ section.week-edit section.weeks-content, section.week-new section.weeks-content -ms-border-radius: 2px; -o-border-radius: 2px; border-radius: 2px; } - section.week-edit section.weeks-content section.filters, section.week-new section.weeks-content section.filters { + section.week-edit section.weeks-content section.filters, + section.week-new section.weeks-content section.filters, + section.sequence-edit section.weeks-content section.filters { border-bottom: 1px solid #999; } - section.week-edit section.weeks-content section.filters ul, section.week-new section.weeks-content section.filters ul { + section.week-edit section.weeks-content section.filters ul, + section.week-new section.weeks-content section.filters ul, + section.sequence-edit section.weeks-content section.filters ul { zoom: 1; list-style: none; padding: 6px; } - section.week-edit section.weeks-content section.filters ul:before, section.week-edit section.weeks-content section.filters ul:after, section.week-new section.weeks-content section.filters ul:before, section.week-new section.weeks-content section.filters ul:after { + section.week-edit section.weeks-content section.filters ul:before, section.week-edit section.weeks-content section.filters ul:after, + section.week-new section.weeks-content section.filters ul:before, + section.week-new section.weeks-content section.filters ul:after, + section.sequence-edit section.weeks-content section.filters ul:before, + section.sequence-edit section.weeks-content section.filters ul:after { content: ""; display: table; } - section.week-edit section.weeks-content section.filters ul:after, section.week-new section.weeks-content section.filters ul:after { + section.week-edit section.weeks-content section.filters ul:after, + section.week-new section.weeks-content section.filters ul:after, + section.sequence-edit section.weeks-content section.filters ul:after { clear: both; } - section.week-edit section.weeks-content section.filters ul li, section.week-new section.weeks-content section.filters ul li { + section.week-edit section.weeks-content section.filters ul li, + section.week-new section.weeks-content section.filters ul li, + section.sequence-edit section.weeks-content section.filters ul li { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; @@ -495,95 +637,201 @@ section.week-edit section.weeks-content, section.week-new section.weeks-content zoom: 1; *display: inline; *vertical-align: auto; } - section.week-edit section.weeks-content section.filters ul li.advanced, section.week-new section.weeks-content section.filters ul li.advanced { + section.week-edit section.weeks-content section.filters ul li.advanced, + section.week-new section.weeks-content section.filters ul li.advanced, + section.sequence-edit section.weeks-content section.filters ul li.advanced { float: right; } - section.week-edit section.weeks-content section.modules ul, section.week-new section.weeks-content section.modules ul { + section.week-edit section.weeks-content section.modules ul, + section.week-new section.weeks-content section.modules ul, + section.sequence-edit section.weeks-content section.modules ul { list-style: none; } - section.week-edit section.weeks-content section.modules ul li, section.week-new section.weeks-content section.modules ul li { + section.week-edit section.weeks-content section.modules ul li, + section.week-new section.weeks-content section.modules ul li, + section.sequence-edit section.weeks-content section.modules ul li { padding: 6px; font-weight: bold; font-size: 16px; border-bottom: 1px solid #333; } - section.week-edit section.weeks-content section.modules ul li a, section.week-new section.weeks-content section.modules ul li a { + section.week-edit section.weeks-content section.modules ul li a, + section.week-new section.weeks-content section.modules ul li a, + section.sequence-edit section.weeks-content section.modules ul li a { color: #000; } -section.video-new, section.video-edit { - position: absolute; - top: 80px; - right: 0; - background: #fff; - width: 40.32%; - -webkit-box-shadow: 0 0 6px #666666; - -moz-box-shadow: 0 0 6px #666666; - box-shadow: 0 0 6px #666666; - border: 1px solid #333; - border-right: 0; - z-index: 4; } - section.video-new > header, section.video-edit > header { - background: #666; +section.video-new > section section.upload, section.video-edit > section section.upload { + padding: 6px; + margin-bottom: 10px; + border: 1px solid #ddd; } + section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; zoom: 1; - color: #fff; - padding: 6px; - border-bottom: 1px solid #333; - -webkit-font-smoothing: antialiased; } - section.video-new > header:before, section.video-new > header:after, section.video-edit > header:before, section.video-edit > header:after { - content: ""; - display: table; } - section.video-new > header:after, section.video-edit > header:after { - clear: both; } - section.video-new > header h2, section.video-edit > header h2 { - float: left; - font-size: 14px; } - section.video-new > header a, section.video-edit > header a { - float: right; } - section.video-new section ul, section.video-edit section ul { - list-style: none; } - section.video-new section ul li, section.video-edit section ul li { - border-bottom: 1px solid #333; - padding: 10px 25px; } + *display: inline; + *vertical-align: auto; } +section.video-new > section section.in-use h2, section.video-edit > section section.in-use h2 { + font-size: 14px; } +section.video-new > section section.in-use div, section.video-edit > section section.in-use div { + background: #eee; + text-align: center; + padding: 6px; } +section.video-new > section a.save-update, section.video-edit > section a.save-update { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-top: 20px; } -section.problem-new, section.problem-edit { - position: absolute; - top: 80px; - right: 0; - background: #fff; - width: 40.32%; - -webkit-box-shadow: 0 0 6px #666666; - -moz-box-shadow: 0 0 6px #666666; - box-shadow: 0 0 6px #666666; - border: 1px solid #333; - border-right: 0; - z-index: 4; } - section.problem-new > header, section.problem-edit > header { - background: #666; - zoom: 1; - color: #fff; - padding: 6px; - border-bottom: 1px solid #333; - -webkit-font-smoothing: antialiased; } - section.problem-new > header:before, section.problem-new > header:after, section.problem-edit > header:before, section.problem-edit > header:after { - content: ""; - display: table; } - section.problem-new > header:after, section.problem-edit > header:after { - clear: both; } - section.problem-new > header h2, section.problem-edit > header h2 { - float: left; - font-size: 14px; } - section.problem-new > header a, section.problem-edit > header a { - float: right; } - section.problem-new section ul, section.problem-edit section ul { - list-style: none; } - section.problem-new section ul li, section.problem-edit section ul li { - border-bottom: 1px solid #333; - padding: 10px 25px; } - -body.content section.main-content { - border-left: 2px solid #000; +section.problem-new > section textarea, section.problem-edit > section textarea { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - width: 74.423%; - float: left; - -webkit-box-shadow: -2px 0 3px #dddddd; - -moz-box-shadow: -2px 0 3px #dddddd; - box-shadow: -2px 0 3px #dddddd; } + display: block; + width: 100%; } +section.problem-new > section div.preview, section.problem-edit > section div.preview { + background: #eee; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + height: 40px; + padding: 10px; + width: 100%; } +section.problem-new > section a.save, section.problem-edit > section a.save { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-top: 20px; } + +section.video-new, section.video-edit, section.problem-new, section.problem-edit { + position: absolute; + top: 72px; + right: 0; + background: #fff; + width: 48.845%; + -webkit-box-shadow: 0 0 6px #666666; + -moz-box-shadow: 0 0 6px #666666; + box-shadow: 0 0 6px #666666; + border: 1px solid #333; + border-right: 0; + z-index: 4; } + section.video-new > header, section.video-edit > header, section.problem-new > header, section.problem-edit > header { + background: #666; + zoom: 1; + color: #fff; + padding: 6px; + border-bottom: 1px solid #333; + -webkit-font-smoothing: antialiased; } + section.video-new > header:before, section.video-new > header:after, section.video-edit > header:before, section.video-edit > header:after, section.problem-new > header:before, section.problem-new > header:after, section.problem-edit > header:before, section.problem-edit > header:after { + content: ""; + display: table; } + section.video-new > header:after, section.video-edit > header:after, section.problem-new > header:after, section.problem-edit > header:after { + clear: both; } + section.video-new > header h2, section.video-edit > header h2, section.problem-new > header h2, section.problem-edit > header h2 { + float: left; + font-size: 14px; } + section.video-new > header a, section.video-edit > header a, section.problem-new > header a, section.problem-edit > header a { + color: #fff; } + section.video-new > header a.save-update, section.video-edit > header a.save-update, section.problem-new > header a.save-update, section.problem-edit > header a.save-update { + float: right; } + section.video-new > header a.cancel, section.video-edit > header a.cancel, section.problem-new > header a.cancel, section.problem-edit > header a.cancel { + float: left; } + section.video-new > section, section.video-edit > section, section.problem-new > section, section.problem-edit > section { + padding: 20px; } + section.video-new > section > header h1, section.video-edit > section > header h1, section.problem-new > section > header h1, section.problem-edit > section > header h1 { + font-size: 24px; + margin: 12px 0; } + section.video-new > section > header section.status-settings ul, section.video-edit > section > header section.status-settings ul, section.problem-new > section > header section.status-settings ul, section.problem-edit > section > header section.status-settings ul { + list-style: none; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + border: 1px solid #999; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.video-new > section > header section.status-settings ul li, section.video-edit > section > header section.status-settings ul li, section.problem-new > section > header section.status-settings ul li, section.problem-edit > section > header section.status-settings ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + border-right: 1px solid #999; + padding: 6px; } + section.video-new > section > header section.status-settings ul li:last-child, section.video-edit > section > header section.status-settings ul li:last-child, section.problem-new > section > header section.status-settings ul li:last-child, section.problem-edit > section > header section.status-settings ul li:last-child { + border-right: 0; } + section.video-new > section > header section.status-settings ul li.current, section.video-edit > section > header section.status-settings ul li.current, section.problem-new > section > header section.status-settings ul li.current, section.problem-edit > section > header section.status-settings ul li.current { + background: #eee; } + section.video-new > section > header section.status-settings a.settings, section.video-edit > section > header section.status-settings a.settings, section.problem-new > section > header section.status-settings a.settings, section.problem-edit > section > header section.status-settings a.settings { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin: 0 20px; + border: 1px solid #999; + padding: 6px; } + section.video-new > section > header section.status-settings select, section.video-edit > section > header section.status-settings select, section.problem-new > section > header section.status-settings select, section.problem-edit > section > header section.status-settings select { + float: right; } + section.video-new > section > header section.meta, section.video-edit > section > header section.meta, section.problem-new > section > header section.meta, section.problem-edit > section > header section.meta { + background: #eee; + padding: 10px; + margin: 20px 0; + zoom: 1; } + section.video-new > section > header section.meta:before, section.video-new > section > header section.meta:after, section.video-edit > section > header section.meta:before, section.video-edit > section > header section.meta:after, section.problem-new > section > header section.meta:before, section.problem-new > section > header section.meta:after, section.problem-edit > section > header section.meta:before, section.problem-edit > section > header section.meta:after { + content: ""; + display: table; } + section.video-new > section > header section.meta:after, section.video-edit > section > header section.meta:after, section.problem-new > section > header section.meta:after, section.problem-edit > section > header section.meta:after { + clear: both; } + section.video-new > section > header section.meta div, section.video-edit > section > header section.meta div, section.problem-new > section > header section.meta div, section.problem-edit > section > header section.meta div { + float: left; + margin-right: 20px; } + section.video-new > section > header section.meta div h2, section.video-edit > section > header section.meta div h2, section.problem-new > section > header section.meta div h2, section.problem-edit > section > header section.meta div h2 { + font-size: 14px; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.video-new > section > header section.meta div p, section.video-edit > section > header section.meta div p, section.problem-new > section > header section.meta div p, section.problem-edit > section > header section.meta div p { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.video-new > section section.notes, section.video-edit > section section.notes, section.problem-new > section section.notes, section.problem-edit > section section.notes { + margin-top: 20px; + padding: 6px; + background: #eee; + border: 1px solid #ccc; } + section.video-new > section section.notes textarea, section.video-edit > section section.notes textarea, section.problem-new > section section.notes textarea, section.problem-edit > section section.notes textarea { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + width: 100%; } + section.video-new > section section.notes h2, section.video-edit > section section.notes h2, section.problem-new > section section.notes h2, section.problem-edit > section section.notes h2 { + font-size: 14px; + margin-bottom: 6px; } + section.video-new > section section.notes input[type="submit"], section.video-edit > section section.notes input[type="submit"], section.problem-new > section section.notes input[type="submit"], section.problem-edit > section section.notes input[type="submit"] { + margin-top: 10px; } diff --git a/cms/static/js/main.js b/cms/static/js/main.js index 45f94849f8..e7bdff0f14 100644 --- a/cms/static/js/main.js +++ b/cms/static/js/main.js @@ -6,96 +6,6 @@ $(document).ready(function(){ $('.editable-textarea').inlineEdit({control: 'textarea'}); }); - // $("a[rel*=leanModal]").leanModal(); - - // $(".remove").click(function(){ - // $(this).parents('li').hide(); - // }); - - // $("#show-sidebar").click(function(){ - // $("#video-selector").toggleClass('hidden'); - // return false; - // }); - - // $('.use-video').click(function() { - // var used = $('#used'); - // if (used.is(':visible')) { - // used.hide().show('slow'); - // } - // used.show(); - // $('.no-video').hide(); - // }); - - // $('.remove-video').click(function() { - // $('#used').hide(); - // $('.no-video').show(); - // }); - - // $('#new-upload').click(function() { - // $('.selected-files').toggle(); - // return false; - // }); - - // /* $('.block').append('✕<\/a>'); */ - - // $('a.delete').click(function() { - // $(this).parents('.block').hide(); - // }); - - // $('.speed-list > li').hover(function(){ - // $(this).children('.tooltip').toggle(); - // }); - - // $('.delete-speed').click(function(){ - // $(this).parents('li.speed').hide(); - // return false; - // }); - - // $('.edit-captions').click(function(){ - // var parentVid = $(this).parents('div'); - // parentVid.siblings('div.caption-box').toggle(); - // return false; - // }); - - // $('.close-box').click(function(){ - // $(this).parents('.caption-box').hide(); - // return false; - // }); - - // $('ul.dropdown').hide(); - // $('li.questions').click(function() { - // $('ul.dropdown').toggle(); - // return false; - // }); - - // $('#mchoice').click(function(){ - // $('div.used').append($('
      ').load("/widgets/multi-choice.html")); - // return false; - // }); - - // $('#text').click(function(){ - // $('div.used').append($('
      ').load("/widgets/text.html")); - // return false; - // }); - - // $('#numerical').click(function(){ - // $('div.used').append($('
      ').load("/widgets/text-question.html")); - // return false; - // }); - - // $('#equation').click(function(){ - // $('div.used').append($('
      ').load("/widgets/latex-equation.html")); - // return false; - // }); - - // $('#script').click(function(){ - // $('div.used').append($('
      ').load("/widgets/script-widget.html")); - // return false; - // }); - - // $("#mark").markItUp(myWikiSettings); - - var heighest = 0; $('.cal ol > li').each(function(){ heighest = ($(this).height() > heighest) ? $(this).height() : heighest; @@ -104,10 +14,7 @@ $(document).ready(function(){ $('.cal ol > li').css('height',heighest + 'px'); - $('.new-week').hide(); - $('.add-new-week').click(function() { - $(this).hide(); - $('.new-week').show(); + $('.add-new-section').click(function() { return false; }); @@ -117,43 +24,63 @@ $(document).ready(function(){ return false; }); - var windowHeight = $(window).resize().height(); - - $('.sidebar').css('height', windowHeight); - - $('.edit-week').click( function() { - $('body').addClass('content'); - $('body.content .cal').css('height', windowHeight); - $('section.week-new').show(); - return false; + $('.save-update').click(function(){ + $(this).parent().parent().hide(); + return false; }); - $('.cal ol li header h1 a').click( function() { - $('body').addClass('content'); - $('body.content .cal').css('height', windowHeight); - $('section.week-edit').show(); - return false; - }); + setHeight = function(){ + var windowHeight = $(this).height(); + var calHeight = windowHeight - 29; + var sidebarHeight = windowHeight - 73; + $('.sidebar').css('height', sidebarHeight); + $('body.content .cal').css('height', calHeight); + + $('.edit-week').click( function() { + $('body').addClass('content'); + $('body.content .cal').css('height', calHeight); + $('section.week-new').show(); + return false; + }); + + $('.cal ol li header h1 a').click( function() { + $('body').addClass('content'); + $('body.content .cal').css('height', calHeight); + $('section.week-edit').show(); + return false; + }); + + $('a.sequence-edit').click(function(){ + $('body').addClass('content'); + $('body.content .cal').css('height', calHeight); + $('section.sequence-edit').show(); + return false; + }); + } + + $(document).ready(setHeight); + $(window).bind('resize', setHeight); $('.video-new a').click(function(){ - $('section.video-new').show(); - return false; + $('section.video-new').show(); + return false; }); - $('.video-edit a').click(function(){ - $('section.video-edit').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; + $('section.problem-new').show(); + return false; }); - $('.problem-edit a').click(function(){ - $('section.problem-edit').show(); - return false; + $('a.problem-edit').click(function(){ + $('section.problem-edit').show(); + return false; }); + }); diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index d9f3971d9e..d06998c38a 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -32,7 +32,22 @@ body { h2 { font-size: 14px; text-transform: uppercase; + float: left; } + + a.new-module { + float: right; + } + } + } + + &.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 ); } } } @@ -48,4 +63,3 @@ input[type="submit"], .button { @include border-radius(3px); padding: 6px; } - diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss index b149346ad6..06ac62f549 100644 --- a/cms/static/sass/_calendar.scss +++ b/cms/static/sass/_calendar.scss @@ -7,59 +7,40 @@ section.cal { > header { @include clearfix; margin-bottom: 10px; + background: #efefef; + border: 1px solid #ddd; - h1 { - float: left; - font-size: 18px; + h2 { + @include inline-block(); + text-transform: uppercase; + letter-spacing: 1px; + font-size: 14px; + padding: 6px; + margin-left: 6px; + font-size: 12px; } ul { - float: right; + @include inline-block; li { @include inline-block; + margin-left: 6px; + padding-left: 6px; + border-left: 1px solid #ddd; + padding: 6px; a { - padding: 6px; - border: 1px solid #ddd; - display: block; - @include border-radius(3px); - background: #efefef; + @include inline-block(); } - &.dropdown { - position: relative; + ul { + @include inline-block(); - ul { - display: none; - position: absolute; - background: #fff; - border: 1px solid #ddd; - - li { - padding: 6px; - display: block; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - - &:hover { - background-color: #efefef; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - } - } - } - - &:hover { - - ul { - display: block; - } - - a { - @include border-radius(3px 3px 0 0); - border-bottom: 0; - } + li { + @include inline-block(); + padding: 0; + border-left: 0; } } } @@ -81,91 +62,31 @@ section.cal { float: left; width: flex-grid(3) + ((flex-gutter() * 3) / 4); - &:last-child { - text-align: center; - background: #eee; - @include box-sizing(border-box); - - p { - width: 100%; - height: 100%; - @include box-sizing(border-box); - - a { - display: block; - width: 100%; - height: 100%; - } - } - - section.new-week { - header { - background: #fff; - text-align: left; - } - - form { - background: #fff; - width: 50%; - padding: 6px; - border: 1px solid #000; - margin: 0 auto; - @include box-shadow(0 0 2px #333); - position: relative; - - &:before { - background: #fff; - border-left: 1px solid #000; - border-top: 1px solid #000; - content: " "; - display: block; - height: 10px; - left: 50%; - position: absolute; - top: -6px; - @include transform(rotate(45deg)); - width: 10px; - z-index: 0; - } - - select { - margin-bottom: 6px; - width: 100%; - - option { - padding: 10px 0 !important; - } - } - - input[type="submit"] { - display: block; - margin-bottom: 6px; - width: 100%; - } - - a { - - &:first-child { - float: left; - } - - &:last-child { - float: right; - } - } - } - } - } - header { border-bottom: 1px solid #000; - @include box-shadow(0 1px 2px #ccc); + @include box-shadow(0 1px 2px #aaa); display: block; margin-bottom: 2px; - padding: 6px; h1 { font-size: 14px; + text-transform: uppercase; + border-bottom: 1px solid #ccc; + padding: 6px; + + a { + color: #000; + display: block; + } + } + + ul { + li { + background: #fff; + color: #888; + border-bottom: 0; + font-size: 12px; + } } } @@ -178,13 +99,136 @@ section.cal { border-bottom: 1px solid #666; padding: 6px; - &.goal { - background: #fff; + &.create-module { + position: relative; + + > div { + display: none; + @include position(absolute, 30px 0 0 0); + width: 90%; + background: rgba(#000, .8); + padding: 10px; + @include box-sizing(border-box); + @include border-radius(3px); + z-index: 99; + + ul { + li { + border-bottom: 0; + background: none; + + input { + width: 100%; + @include box-sizing(border-box); + border-color: #000; + padding: 6px; + } + + select { + width: 100%; + @include box-sizing(border-box); + + option { + font-size: 14px; + } + } + + a { + float: right; + + &:first-child { + float: left; + } + } + } + } + } + + &:hover { + div { + display: block; + } + } } } } } } + + section.new-section { + margin-top: 10px; + + > a { + @extend .button; + @include inline-block(); + } + + section { + display: none; + + header { + background: #fff; + text-align: left; + } + + form { + background: #fff; + width: 50%; + padding: 6px; + border: 1px solid #000; + margin: 0 auto; + @include box-shadow(0 0 2px #333); + position: relative; + + &:before { + background: #fff; + border-left: 1px solid #000; + border-top: 1px solid #000; + content: " "; + display: block; + height: 10px; + left: 50%; + position: absolute; + top: -6px; + @include transform(rotate(45deg)); + width: 10px; + z-index: 0; + } + + select { + margin-bottom: 6px; + width: 100%; + + option { + padding: 10px 0 !important; + } + } + + input[type="submit"] { + display: block; + margin-bottom: 6px; + width: 100%; + } + + a { + + &:first-child { + float: left; + } + + &:last-child { + float: right; + } + } + } + } + + &:hover { + section { + display: block; + } + } + } } body.content diff --git a/cms/static/sass/_module-header.scss b/cms/static/sass/_module-header.scss new file mode 100644 index 0000000000..e2af263618 --- /dev/null +++ b/cms/static/sass/_module-header.scss @@ -0,0 +1,128 @@ +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; + } + } + } +} diff --git a/cms/static/sass/_problem.scss b/cms/static/sass/_problem.scss index c513ce38d9..66acacf65c 100644 --- a/cms/static/sass/_problem.scss +++ b/cms/static/sass/_problem.scss @@ -1,40 +1,23 @@ section.problem-new, section.problem-edit { - position: absolute; - top: 80px; - right: 0; - background: #fff; - width: flex-grid(5); - @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; + > section { + textarea { + @include box-sizing(border-box); + display: block; + width: 100%; } - a { - float: right; + div.preview { + background: #eee; + @include box-sizing(border-box); + height: 40px; + padding: 10px; + width: 100%; } - } - section { - ul { - list-style: none; - - li { - border-bottom: 1px solid #333; - padding: 10px 25px; - } + a.save { + @extend .button; + @include inline-block(); + margin-top: 20px; } } } diff --git a/cms/static/sass/_video.scss b/cms/static/sass/_video.scss index cb234108f4..b68176e2db 100644 --- a/cms/static/sass/_video.scss +++ b/cms/static/sass/_video.scss @@ -1,40 +1,33 @@ section.video-new, section.video-edit { - position: absolute; - top: 80px; - right: 0; - background: #fff; - width: flex-grid(5); - @include box-shadow(0 0 6px #666); - border: 1px solid #333; - border-right: 0; - z-index: 4; + > section { - > header { - background: #666; - @include clearfix; - color: #fff; - padding: 6px; - border-bottom: 1px solid #333; - -webkit-font-smoothing: antialiased; + section.upload { + padding: 6px; + margin-bottom: 10px; + border: 1px solid #ddd; - h2 { - float: left; - font-size: 14px; - } - - a { - float: right; - } - } - - section { - ul { - list-style: none; - - li { - border-bottom: 1px solid #333; - padding: 10px 25px; + 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; + } } } diff --git a/cms/static/sass/_week.scss b/cms/static/sass/_week.scss index a628b0f164..1a32557db5 100644 --- a/cms/static/sass/_week.scss +++ b/cms/static/sass/_week.scss @@ -1,4 +1,6 @@ -section.week-edit, section.week-new { +section.week-edit, +section.week-new, +section.sequence-edit { > header { border-bottom: 1px solid #ccc; diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index 6a2dfbd0d2..133e1bda1b 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -3,16 +3,4 @@ @import 'base'; @import 'calendar'; -@import 'week', 'video', 'problem'; - -body { - &.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 ); - } - } -} +@import 'week', 'video', 'problem', 'module-header'; diff --git a/cms/templates/index.html b/cms/templates/index.html index efd9f9a242..11c226ae3d 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -9,6 +9,7 @@
      <%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"/> diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 86a2afb0f9..ec550fec37 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -1,5 +1,6 @@
      diff --git a/cms/templates/widgets/module-dropdown.html b/cms/templates/widgets/module-dropdown.html new file mode 100644 index 0000000000..7c6e1e068c --- /dev/null +++ b/cms/templates/widgets/module-dropdown.html @@ -0,0 +1,28 @@ +
    • + + + Add new module + +
      +
      + +
      +
      +
    • diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index 049e5da537..2edef08a12 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -1,9 +1,8 @@
      -

      Circuts & Electronics

      - +

      Filter content:

        -
      • Sequences
          @@ -17,9 +16,20 @@
        • Deadlines + +
            +
          • Today
          • +
          • Tomorrow
          • +
          • This week
          • +
          • In 2 weeks
          • +
          • This month
          • +
        • Goals +
            +
          • Hide
          • +
      @@ -28,93 +38,131 @@
    • Week 1

      +
        +
      • Goal title: This is a goal that will be in the header of the week
      • +
      • Goal title two: This is another fgoal for this week so that students have two things to learn
      • +
        -
      • Goal 1
      • -
      • Goal 2
      • -
      • Lecture Sequence
      • -
      • Lecture Sequence
      • -
      • Lab
      • +
      • Lecture Sequence
      • +
      • Lecture Sequence
      • +
      • Lab
      • Homework
      • -
      • + Add new sequence
      • + <%include file="module-dropdown.html"/>
    • Week 2

      +
        +
      • Another title This is the goal for the week
      • +
        -
      • Goal 1
      • Lecture Sequence
      • Lecture Sequence
      • Lab
      • Homework
      • -
      • + Add new sequence
      • + <%include file="module-dropdown.html"/>
    • Week 3

      +
        +
      • Another title This is the goal for the week
      • +
        -
      • Goal 1
      • Lab
      • Lab
      • Homework
      • -
      • + Add new sequence
      • + <%include file="module-dropdown.html"/>
    • Week 4

      +
        +
      • Another title This is the goal for the week
      • +
      • Goal title two: This is another fgoal for this week so that students have two things to learn
      • +
        -
      • Goal 1
      • Lecture Sequence
      • Lab
      • Homework
      • Homework
      • -
      • + Add new sequence
      • + <%include file="module-dropdown.html"/>
    • Week 5

      +
        +
      • Please create a learning goal for this week
      • +
        -
      • + Add new sequence
      • + <%include file="module-dropdown.html"/>
    • -

      - + Add New -

      +
      +

      Week 6

      +
        +
      • Please create a learning goal for this week
      • +
      +
      -
      -
      -

      Week 6

      -
      +
        + <%include file="module-dropdown.html"/> +
      +
    • +
    • +
      +

      Week 7

      +
        +
      • Please create a learning goal for this week
      • +
      +
      -
      - -
      +
        + <%include file="module-dropdown.html"/> +
      +
    • + + +
      + + Add New Section + +
      + +
      - - - - + + + + + diff --git a/cms/templates/widgets/new-module.html b/cms/templates/widgets/new-module.html index 070b6462bb..6b7794944a 100644 --- a/cms/templates/widgets/new-module.html +++ b/cms/templates/widgets/new-module.html @@ -1,9 +1,7 @@ -
    • - -
    • + diff --git a/cms/templates/widgets/problem-edit.html b/cms/templates/widgets/problem-edit.html index b1d5796a9f..49f67e3e26 100644 --- a/cms/templates/widgets/problem-edit.html +++ b/cms/templates/widgets/problem-edit.html @@ -1,34 +1,13 @@ -
      +
      - cancel - Save & Update + Cancel + Save & Update
      -

      Old Problem

      -
      - - -
      - Settings -
      - - -
      -
      -
      -

      Tags:

      -

      Click to edit

      -
      - +

      New Problem

      +

      Last modified:

      mm/dd/yy

      @@ -39,6 +18,31 @@

      Anant Agarwal

      + +
      + + Settings + + +
      +
      +
      +

      Tags:

      +

      Click to edit

      +
      + +
      +

      Goal

      +

      Click to edit

      +
      +
      @@ -49,6 +53,10 @@
      +

      Add notes

      + + +
      • 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.

        @@ -58,16 +66,8 @@

        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.

        Anant Agarwal

      • -
      +
      -
      -

      Add notes

      - - -
      -
      - - Save & Update + Save & Update
      - diff --git a/cms/templates/widgets/problem-new.html b/cms/templates/widgets/problem-new.html index 9f91dd1177..d986f5a9ef 100644 --- a/cms/templates/widgets/problem-new.html +++ b/cms/templates/widgets/problem-new.html @@ -1,39 +1,36 @@
      - cancel - Save & Update + Cancel + Save & Update
      -

      New Problem

      -
      +

      New Problem

      + +
      - Settings + Settings +
      -
      +

      Tags:

      Click to edit

      - - - - - - - - - +
      +

      Goal

      +

      Click to edit

      +
      @@ -45,8 +42,10 @@

      Add notes

      + +
      - Save & Update + Save & Update
      diff --git a/cms/templates/widgets/sequnce-edit.html b/cms/templates/widgets/sequnce-edit.html new file mode 100644 index 0000000000..75d4504b68 --- /dev/null +++ b/cms/templates/widgets/sequnce-edit.html @@ -0,0 +1,85 @@ +<%block name="content"> +
      +
      + Done +

      Lecture Sequence name

      + Settings +
      + + + +
      +
      +

      Sequence Content

      +
      + +
      +
      + +
      +
        +
      • + + +
      • + +
      • + + +
      • +
      • + +
      • + +
      • + Advanced filters +
      • +
      +
      + +
      + +
      +
      +
      + + diff --git a/cms/templates/widgets/video-edit.html b/cms/templates/widgets/video-edit.html index ac4c921918..0c82e743fe 100644 --- a/cms/templates/widgets/video-edit.html +++ b/cms/templates/widgets/video-edit.html @@ -1,151 +1,59 @@ -
      +
      +
      + Cancel + Save & Update +
      -
      -
      +
      +
      +

      Untitled Video

      -
      -
      - Created 22/03/12 - by Piotr Mitros -
      +
      +
      -
      -
      -

      Video title

      + Settings -
      - Tag mitx, s4v1, circuits, anant -
      + +
      -
      -
      - Keyword - S4V1 - +
      +
      +

      Tags:

      +

      Click to edit

      +
      +
      +

      Goal

      +

      mitx, s4v1, circuits, anant

      +
      +
      + - Due Date - 21/03/12 - - Status - - - -
      - -
      - -
      -
      -
      - or - use an already uploaded one -
      -
      - -
      -
      -
        -
      • video-name-@0-75x.extension
      • -
      • video-name-@1x.extension
      • -
      • video-name-@1-25x.extension
      • -
      • video-name-@1-5x.extension
      • -
      -
      - - - Cancel -
      - -
      - -
      -
      -
        -

        Video clip in use

        - <%include file="video-box.html"/> -
      -

      No video clip used. Select one from the list below, upload a new one or import an already uploaded video.

      -
      -
      - -
      -
      - - -
      -
      -
        - <%include file="video-box-unused.html"/> -
      -
      -
      - -
      - -
      -

      Annotations

      - - -
      - +
      + Upload new video clip + Or + use an already uploaded one
      - -
      -
      -

      Notes

      - - - -
      - -
      -
      -
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. -

      By Piotr Mitros 10 hours ago

      -
      -
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

      By Piotr Mitros 10 hours ago

      -
      -
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

      By Piotr Mitros 10 hours ago

      +
      +

      Video in use

      +
      +

      No video clip used. Select one from the list below, upload a new one or import already existing video

      -
      - +
      +

      Add notes

      + + +
      -
      - -
      - Select the source video or directly enter its ID -
      - -
      - -
      - -
      - - - Cancel -
      + Save & Update
      +
      diff --git a/cms/templates/widgets/video-new.html b/cms/templates/widgets/video-new.html index 2555169c05..fecbaa423c 100644 --- a/cms/templates/widgets/video-new.html +++ b/cms/templates/widgets/video-new.html @@ -1,48 +1,85 @@
      - cancel - Save & Update + Cancel + Save & Update

      Untitled Video

      -
      -
      -
      - -
      -
      - Save & Update +
      + Upload new video clip + Or + use an already uploaded one + +
      +
      +
        +
      • video-name-@0-75x.extension
      • +
      • video-name-@1x.extension
      • +
      • video-name-@1-25x.extension
      • +
      • video-name-@1-5x.extension
      • +
      +
      + + + Cancel +
      + +
      +

      Video in use

      +
      +

      No video clip used. Select one from the list below, upload a new one or import already existing video

      +
      +
      + +
      +

      Add notes

      + + +
      + + Save & Update
      diff --git a/cms/templates/widgets/week-edit.html b/cms/templates/widgets/week-edit.html index ea6d29ef3b..348185be80 100644 --- a/cms/templates/widgets/week-edit.html +++ b/cms/templates/widgets/week-edit.html @@ -1,37 +1,20 @@ -<%block name="content">

      Week 3

      - Done +

      + new goal

      + +
      +

      Weeks goals:

      +
        +
      • +

        Goal title

        +

        This is the goal body

        +
      • +
      +
      -
      @@ -70,14 +53,55 @@
      + +
      +
      +

      Scratchpad

      +
      + +
        +
      • Problem title 11
      • +
      • Problem title 13
      • +
      • Problem title 14
      • +
      • Video 3
      • + <%include file="new-module.html"/> +
      +
      - diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html index 986dc6e05e..fe286a74b3 100644 --- a/cms/templates/widgets/week-new.html +++ b/cms/templates/widgets/week-new.html @@ -1,62 +1,86 @@ -<%block name="content">

      Week 6

      - Done -
      +

      + new goal

      -
      diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html index fe286a74b3..46aba9b778 100644 --- a/cms/templates/widgets/week-new.html +++ b/cms/templates/widgets/week-new.html @@ -1,10 +1,13 @@

      Week 6

      -

      + new goal

      -

      Weeks goals:

      +
      +

      Weeks goals:

      +

      +

      +
      +
      • Create new goal

        @@ -40,32 +43,32 @@
      • -
        - -
        +
        + +
      -
      -
      -
      -

      Scratch pad

      -
      +
      -
        -
      • - <%include file="new-module.html"/> -
      • -
      -
      +
      +
      +

      Weeks Content

      +
      +
      -
      -
      -

      Weeks Content

      -
      +
      +
      +

      Scratch pad

      +
      -
      +
        +
      • + <%include file="new-module.html"/> +
      • +
      +
      @@ -83,4 +86,5 @@ New Lab +
      From 8dc28549c210c5546551684c2dae65a36bf7d13f Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 14 Jun 2012 12:16:29 -0400 Subject: [PATCH 066/123] Use existential operator --- lms/static/coffee/src/modules/video/video_player.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/modules/video/video_player.coffee b/lms/static/coffee/src/modules/video/video_player.coffee index 0dd52a128b..20df378267 100644 --- a/lms/static/coffee/src/modules/video/video_player.coffee +++ b/lms/static/coffee/src/modules/video/video_player.coffee @@ -135,7 +135,7 @@ class @VideoPlayer @video.speed volume: (value) -> - if value != undefined + if value? @player.setVolume value else @player.getVolume() From 2d2a16dd708d4491c262b534f60b65af9b03079b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 14 Jun 2012 16:15:50 -0400 Subject: [PATCH 067/123] Successfully read course children out of mongodb --- .../management/commands/import.py | 29 +++++---- cms/djangoapps/contentstore/views.py | 5 +- cms/lib/keystore/__init__.py | 34 ++++++++++ cms/lib/keystore/exceptions.py | 4 ++ cms/lib/keystore/mongo.py | 63 ++++++++++++++++--- 5 files changed, 109 insertions(+), 26 deletions(-) diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 78984f4119..690e3dbea0 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -6,11 +6,13 @@ #import mitxmako.middleware #from courseware import content_parser #from django.contrib.auth.models import User +import os.path +from StringIO import StringIO from mako.template import Template from mako.lookup import TemplateLookup from django.core.management.base import BaseCommand -from contentstore.models import create_item, update_item, update_children +from keystore.django import keystore from lxml import etree @@ -20,16 +22,15 @@ class Command(BaseCommand): def handle(self, *args, **options): print args data_dir = args[0] - course_file = 'course.xml' parser = etree.XMLParser(remove_comments = True) lookup = TemplateLookup(directories=[data_dir]) template = lookup.get_template("course.xml") course_string = template.render(groups=[]) - course = etree.XML(course_string, parser=parser) + course = etree.parse(StringIO(course_string), parser=parser) - elements = course.xpath("//*") + elements = list(course.iter()) tag_to_category = {# Inside HTML ==> Skip these # Custom tags @@ -39,11 +40,11 @@ class Command(BaseCommand): 'image': 'Custom', 'discuss': 'Custom', # Simple lists - 'chapter': 'Sequence', - 'course': 'Sequence', - 'sequential': 'Sequence', - 'vertical': 'Sequence', - 'section': 'Sequence', + 'chapter': 'Chapter', + 'course': 'Course', + 'sequential': 'LectureSequence', + 'vertical': 'ProblemSet', + 'section': 'Section', # True types 'video': 'VideoSegment', 'html': 'HTML', @@ -114,7 +115,7 @@ class Command(BaseCommand): results[e.attrib['url']] = {'data':{'text':text}} def handle_problem(e): - data = open(data_dir+'problems/'+e.attrib['filename']+'.xml').read() + data = open(os.path.join(data_dir, 'problems', e.attrib['filename']+'.xml')).read() results[e.attrib['url']] = {'data':{'statement':data}} element_actions = {# Inside HTML ==> Skip these @@ -149,10 +150,8 @@ class Command(BaseCommand): for k in results: print k - create_item(k, 'Piotr Mitros') + keystore.create_item(k, 'Piotr Mitros') if 'data' in results[k]: - update_item(k, results[k]['data']) + keystore.update_item(k, results[k]['data']) if 'children' in results[k]: - update_children(k, results[k]['children']) - - + keystore.update_children(k, results[k]['children']) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 38e9e8ad35..429fb6c26b 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,14 +1,11 @@ from mitxmako.shortcuts import render_to_response -from keystore import Location from keystore.django import keystore from django.contrib.auth.decorators import login_required @login_required def calendar(request, org, course): - weeks = keystore.get_children_for_item( - Location(['i4x', org, course, 'Course', 'course']) - ) + weeks = keystore.get_children_for_item(['i4x', org, course, 'Course', None]) return render_to_response('calendar.html', {'weeks': weeks}) diff --git a/cms/lib/keystore/__init__.py b/cms/lib/keystore/__init__.py index 5e6374cf4a..d0a24be797 100644 --- a/cms/lib/keystore/__init__.py +++ b/cms/lib/keystore/__init__.py @@ -60,6 +60,40 @@ class KeyStore(object): with the specified location. If no object is found at that location, raises keystore.exceptions.ItemNotFoundError + + Searches for all matches of a partially specifed location, but raises an + keystore.exceptions.InsufficientSpecificationError if more + than a single object matches the query. + + location: Something that can be passed to Location + """ + raise NotImplementedError + + def create_item(self, location, editor): + """ + Create an empty item at the specified location with the supplied editor + + location: Something that can be passed to Location + """ + raise NotImplementedError + + def update_item(self, location, data): + """ + Set the data in the item specified by the location to + data + + location: Something that can be passed to Location + data: A nested dictionary of problem data + """ + raise NotImplementedError + + def update_children(self, location, children): + """ + Set the children for the item specified by the location to + data + + location: Something that can be passed to Location + children: A list of child item identifiers """ raise NotImplementedError diff --git a/cms/lib/keystore/exceptions.py b/cms/lib/keystore/exceptions.py index b66470859f..08fd9b11d0 100644 --- a/cms/lib/keystore/exceptions.py +++ b/cms/lib/keystore/exceptions.py @@ -5,3 +5,7 @@ Exceptions thrown by KeyStore objects class ItemNotFoundError(Exception): pass + + +class InsufficientSpecificationError(Exception): + pass diff --git a/cms/lib/keystore/mongo.py b/cms/lib/keystore/mongo.py index fc190ee098..d29afb4bd2 100644 --- a/cms/lib/keystore/mongo.py +++ b/cms/lib/keystore/mongo.py @@ -1,6 +1,6 @@ import pymongo -from . import KeyStore -from .exceptions import ItemNotFoundError +from . import KeyStore, Location +from .exceptions import ItemNotFoundError, InsufficientSpecificationError class MongoKeyStore(KeyStore): @@ -12,15 +12,64 @@ class MongoKeyStore(KeyStore): host=host, port=port )[db][collection] + + # Force mongo to report errors, at the expense of performance + self.collection.safe = True def get_children_for_item(self, location): - item = self.collection.find_one( - {'location': location.dict()}, + query = dict( + ('location.{key}'.format(key=key), val) + for (key, val) + in Location(location).dict().items() + if val is not None + ) + items = self.collection.find( + query, fields={'children': True}, sort=[('revision', pymongo.ASCENDING)], + limit=1, + ) + if items.count() > 1: + raise InsufficientSpecificationError(location) + + if items.count() == 0: + raise ItemNotFoundError(location) + + return items[0]['children'] + + def create_item(self, location, editor): + """ + Create an empty item at the specified location with the supplied editor + + location: Something that can be passed to Location + """ + self.collection.insert({ + 'location': Location(location).dict(), + 'editor': editor + }) + + def update_item(self, location, data): + """ + Set the data in the item specified by the location to + data + + location: Something that can be passed to Location + data: A nested dictionary of problem data + """ + self.collection.update( + {'location': Location(location).dict()}, + {'$set': {'data': data}} ) - if item is None: - raise ItemNotFoundError() + def update_children(self, location, children): + """ + Set the children for the item specified by the location to + data - return item['children'] + location: Something that can be passed to Location + children: A list of child item identifiers + """ + self.collection.update( + {'location': Location(location).dict()}, + {'$set': {'children': children}} + ) From 2b9d47bbea6cab59b8dd2646907848a14ed8443a Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Thu, 14 Jun 2012 17:15:23 -0400 Subject: [PATCH 068/123] Added more styels to the section view and default section view --- cms/static/css/base-style.css | 170 +++++++++++++++++++------- cms/static/img/drag-handle.png | Bin 0 -> 98 bytes cms/static/sass/_base.scss | 47 ++++++- cms/static/sass/_calendar.scss | 6 + cms/static/sass/_week.scss | 99 +++++++++------ cms/templates/widgets/header.html | 19 ++- cms/templates/widgets/new-module.html | 2 +- cms/templates/widgets/week-edit.html | 42 +++++-- cms/templates/widgets/week-new.html | 93 +++++++------- 9 files changed, 336 insertions(+), 142 deletions(-) create mode 100644 cms/static/img/drag-handle.png diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css index c84ab046ff..c4bfc6c415 100644 --- a/cms/static/css/base-style.css +++ b/cms/static/css/base-style.css @@ -193,8 +193,19 @@ body { font-size: 14px; text-transform: uppercase; float: left; } - body > header nav a.new-module { - float: right; } + body > header nav ul { + float: left; } + body > header nav ul.user-nav { + float: right; } + body > header nav ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-left: 15px; } body.content section.main-content { border-left: 2px solid #000; -webkit-box-sizing: border-box; @@ -213,7 +224,11 @@ a { input { font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } -input[type="submit"], .button, section.cal section.new-section > a, section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button, section.video-new > section a.save-update, section.video-edit > section a.save-update, section.problem-new > section a.save, section.problem-edit > section a.save { +input[type="submit"], .button, section.cal section.new-section > a, section.week-edit > section.content > div section.modules.empty a, +section.week-new > section.content > div section.modules.empty a, +section.sequence-edit > section.content > div section.modules.empty a, section.week-edit > section.content > div section.scratch-pad ol li ul li.empty a, +section.week-new > section.content > div section.scratch-pad ol li ul li.empty a, +section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty a, section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button, section.video-new > section a.save-update, section.video-edit > section a.save-update, section.problem-new > section a.save, section.problem-edit > section a.save { border: 1px solid #ccc; background: #efefef; -webkit-border-radius: 3px; @@ -223,6 +238,26 @@ input[type="submit"], .button, section.cal section.new-section > a, section.vide border-radius: 3px; padding: 6px; } +.new-module { + position: relative; } + .new-module a { + padding: 6px; + display: block; } + .new-module ul.new-dropdown { + list-style: none; + position: absolute; } + .new-module ul.new-dropdown li { + display: none; + padding: 6px; } + .new-module:hover ul.new-dropdown { + display: block; } + +.draggable { + width: 7px; + min-height: 14px; + background: url("../img/drag-handle.png") no-repeat center; + text-indent: -9999px; } + section.cal { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -484,11 +519,35 @@ section.cal { overflow: scroll; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; - box-sizing: border-box; } + box-sizing: border-box; + opacity: .4; + -webkit-transition-property: all; + -moz-transition-property: all; + -ms-transition-property: all; + -o-transition-property: all; + transition-property: all; + -webkit-transition-duration: 0.15s; + -moz-transition-duration: 0.15s; + -ms-transition-duration: 0.15s; + -o-transition-duration: 0.15s; + transition-duration: 0.15s; + -webkit-transition-timing-function: ease-out; + -moz-transition-timing-function: ease-out; + -ms-transition-timing-function: ease-out; + -o-transition-timing-function: ease-out; + transition-timing-function: ease-out; + -webkit-transition-delay: 0; + -moz-transition-delay: 0; + -ms-transition-delay: 0; + -o-transition-delay: 0; + transition-delay: 0; } body.content section.cal > header ul { display: none; } body.content + section.cal:hover { + opacity: 1; } + body.content section.cal ol li { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -637,6 +696,22 @@ section.sequence-edit > section.content { display: table-cell; width: 65.632%; border-right: 1px solid #333; } + section.week-edit > section.content > div section.modules.empty, + section.week-new > section.content > div section.modules.empty, + section.sequence-edit > section.content > div section.modules.empty { + text-align: center; + vertical-align: middle; } + section.week-edit > section.content > div section.modules.empty a, + section.week-new > section.content > div section.modules.empty a, + section.sequence-edit > section.content > div section.modules.empty a { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-top: 10px; } section.week-edit > section.content > div section.modules ol, section.week-new > section.content > div section.modules ol, section.sequence-edit > section.content > div section.modules ol { @@ -661,17 +736,27 @@ section.sequence-edit > section.content { section.week-edit > section.content > div section.modules ol li ol li, section.week-new > section.content > div section.modules ol li ol li, section.sequence-edit > section.content > div section.modules ol li ol li { - padding: 6px 0 6px 6px; } - section.week-edit > section.content > div section.modules ol li ol li h3, - section.week-new > section.content > div section.modules ol li ol li h3, - section.sequence-edit > section.content > div section.modules ol li ol li h3 { - text-transform: uppercase; - letter-spacing: 1px; - font-size: 12px; } - section.week-edit > section.content > div section.modules ol li ol li ol, - section.week-new > section.content > div section.modules ol li ol li ol, - section.sequence-edit > section.content > div section.modules ol li ol li ol { - border-left: 1px solid; } + padding: 6px; } + section.week-edit > section.content > div section.modules ol li ol li.group, + section.week-new > section.content > div section.modules ol li ol li.group, + section.sequence-edit > section.content > div section.modules ol li ol li.group { + padding: 0; + border-left: 4px solid #999; } + section.week-edit > section.content > div section.modules ol li ol li.group header, + section.week-new > section.content > div section.modules ol li ol li.group header, + section.sequence-edit > section.content > div section.modules ol li ol li.group header { + padding: 3px 6px; + background: none; } + section.week-edit > section.content > div section.modules ol li ol li.group header h3, + section.week-new > section.content > div section.modules ol li ol li.group header h3, + section.sequence-edit > section.content > div section.modules ol li ol li.group header h3 { + text-transform: uppercase; + letter-spacing: 1px; + font-size: 12px; } + section.week-edit > section.content > div section.modules ol li ol li.group ol li:last-child, + section.week-new > section.content > div section.modules ol li ol li.group ol li:last-child, + section.sequence-edit > section.content > div section.modules ol li ol li.group ol li:last-child { + border-bottom: 0; } section.week-edit > section.content > div section.scratch-pad, section.week-new > section.content > div section.scratch-pad, section.sequence-edit > section.content > div section.scratch-pad { @@ -679,7 +764,8 @@ section.sequence-edit > section.content { -moz-box-sizing: border-box; box-sizing: border-box; display: table-cell; - width: 34.368%; } + width: 34.368%; + vertical-align: top; } section.week-edit > section.content > div section.scratch-pad ol, section.week-new > section.content > div section.scratch-pad ol, section.sequence-edit > section.content > div section.scratch-pad ol { @@ -702,40 +788,32 @@ section.sequence-edit > section.content { section.week-new > section.content > div section.scratch-pad ol li ul li, section.sequence-edit > section.content > div section.scratch-pad ol li ul li { padding: 6px; } + section.week-edit > section.content > div section.scratch-pad ol li ul li:last-child, + section.week-new > section.content > div section.scratch-pad ol li ul li:last-child, + section.sequence-edit > section.content > div section.scratch-pad ol li ul li:last-child { + border-bottom: 0; } + section.week-edit > section.content > div section.scratch-pad ol li ul li:hover a.draggable, + section.week-new > section.content > div section.scratch-pad ol li ul li:hover a.draggable, + section.sequence-edit > section.content > div section.scratch-pad ol li ul li:hover a.draggable { + opacity: 1; } + section.week-edit > section.content > div section.scratch-pad ol li ul li.empty, + section.week-new > section.content > div section.scratch-pad ol li ul li.empty, + section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty { + padding: 12px; } + section.week-edit > section.content > div section.scratch-pad ol li ul li.empty a, + section.week-new > section.content > div section.scratch-pad ol li ul li.empty a, + section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty a { + display: block; + text-align: center; } + section.week-edit > section.content > div section.scratch-pad ol li ul li a.draggable, + section.week-new > section.content > div section.scratch-pad ol li ul li a.draggable, + section.sequence-edit > section.content > div section.scratch-pad ol li ul li a.draggable { + float: right; + opacity: .3; } section.week-edit > section.content > div section.scratch-pad ol li ul li a, section.week-new > section.content > div section.scratch-pad ol li ul li a, section.sequence-edit > section.content > div section.scratch-pad ol li ul li a { color: #000; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module, - section.week-new > section.content > div section.scratch-pad ol li.new-module, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module { - position: relative; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown, - section.week-new > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown { - list-style: none; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li, - section.week-new > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li { - display: none; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li:first-child, - section.week-new > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li:first-child, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown li:first-child { - display: block; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li, - section.week-new > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li { - display: block; - padding: 6px 0; } - section.week-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li:first-child, - section.week-new > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li:first-child, - section.sequence-edit > section.content > div section.scratch-pad ol li.new-module ul.new-dropdown:hover li:first-child { - padding-top: 0; } - section.week-edit > section.content > div section.scratch-pad p, - section.week-new > section.content > div section.scratch-pad p, - section.sequence-edit > section.content > div section.scratch-pad p { - padding: 6px; - border-bottom: 1px solid #666; } section.video-new > section section.upload, section.video-edit > section section.upload { padding: 6px; diff --git a/cms/static/img/drag-handle.png b/cms/static/img/drag-handle.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9c3c1ebcee37ec1fcfd82aa204228ecd629598 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)N!3HEB$FfNSDNRoo$B>F!Z+i`a$~c&!Yh(WZ v_v73tWm2xNh|g`ZvcZL`KR=$S&zsN6c8$yMo{i8-pe6=SS3j3^P6 header ul { display: none; } + &:hover { + opacity: 1; + } + ol { li { @include box-sizing(border-box); diff --git a/cms/static/sass/_week.scss b/cms/static/sass/_week.scss index 1c03ff7962..de7b1e6f9e 100644 --- a/cms/static/sass/_week.scss +++ b/cms/static/sass/_week.scss @@ -86,6 +86,17 @@ section.sequence-edit { 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; @@ -105,16 +116,30 @@ section.sequence-edit { list-style: none; li { - padding: 6px 0 6px 6px; + padding: 6px; - h3 { - text-transform: uppercase; - letter-spacing: 1px; - font-size: 12px; - } + &.group { + padding: 0; + border-left: 4px solid #999; - ol { - border-left: 1px solid; + header { + padding: 3px 6px; + background: none; + + h3 { + text-transform: uppercase; + letter-spacing: 1px; + font-size: 12px; + } + } + + ol { + li { + &:last-child { + border-bottom: 0; + } + } + } } } } @@ -126,6 +151,7 @@ section.sequence-edit { @include box-sizing(border-box); display: table-cell; width: flex-grid(3, 9) + flex-gutter(9); + vertical-align: top; ol { list-style: none; @@ -145,48 +171,41 @@ section.sequence-edit { 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; } } } - &.new-module { - position: relative; - - ul.new-dropdown { - list-style: none; - - li { - display: none; - - &:first-child { - display: block; - } - } - - &:hover { - li { - display: block; - padding: 6px 0; - - &:first-child { - padding-top: 0; - } - } - } - } - } } } - - p { - padding: 6px; - border-bottom: 1px solid #666; - } } } } } } - diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index ec550fec37..49965e4026 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -1,6 +1,21 @@
      diff --git a/cms/templates/widgets/new-module.html b/cms/templates/widgets/new-module.html index 6b7794944a..8b12e5763a 100644 --- a/cms/templates/widgets/new-module.html +++ b/cms/templates/widgets/new-module.html @@ -1,5 +1,5 @@ ++ add new
        -
      1. Problem title 11
      2. -
      3. Problem title 13
      4. -
      5. Problem title 14
      6. -
      7. Video 3
      8. +
      9. + Problem title 11 + handle +
      10. +
      11. + Problem Group + handle +
      12. +
      13. + Problem title 14 + handle +
      14. +
      15. + Video 3 + handle +
    • @@ -68,7 +80,7 @@
        -
      1. +
      2. Problem group @@ -97,10 +109,22 @@

          -
        1. Problem title 11
        2. -
        3. Problem title 13
        4. -
        5. Problem title 14
        6. -
        7. Video 3
        8. +
        9. + Problem title 11 + handle +
        10. +
        11. + Problem Group + handle +
        12. +
        13. + Problem title 14 + handle +
        14. +
        15. + Video 3 + handle +
      3. diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html index 46aba9b778..be08587755 100644 --- a/cms/templates/widgets/week-new.html +++ b/cms/templates/widgets/week-new.html @@ -1,23 +1,23 @@
        -

        Week 6

        +

        Week 3

        Weeks goals:

        -

        +

        +

        + new goal

        • -

          Create new goal

          -

          Goals are overarching themes for the week

          +

          Please add a goal for this section

        -
        +
        +
        • @@ -43,48 +43,59 @@
        • -
          - -
          +
        - -
        - -
        -
        -

        Weeks Content

        -
        +
        +
        +

        This are no groups or units in this section yet

        + Add a Group + Add a Unit
        -
        -
        -

        Scratch pad

        -
        +
        +
          +
        1. +
          +

          Section Scratch

          +
          + +
        2. +
        3. +
          +

          Course Scratch

          +
          + +
        4. -
            -
          • - <%include file="new-module.html"/> -
          • -
          -
        +
      4. + <%include file="new-module.html"/> +
      5. +
    • - -
      - +
      + From b48ac79ad3a0328e63c93cab286adc1e4f826600 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 15 Jun 2012 10:11:04 -0400 Subject: [PATCH 069/123] Added scratch on the calendar view and added handles on the calendar view --- cms/static/css/base-style.css | 52 +++-- cms/static/sass/_base.scss | 2 + cms/static/sass/_week.scss | 33 ++- cms/templates/widgets/navigation.html | 100 ++++++-- cms/templates/widgets/week-edit.html | 317 +++++++++++++++----------- 5 files changed, 329 insertions(+), 175 deletions(-) diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css index c4bfc6c415..abed82cc05 100644 --- a/cms/static/css/base-style.css +++ b/cms/static/css/base-style.css @@ -256,7 +256,9 @@ section.sequence-edit > section.content > div section.scratch-pad ol li ul li.em width: 7px; min-height: 14px; background: url("../img/drag-handle.png") no-repeat center; - text-indent: -9999px; } + text-indent: -9999px; + display: block; + float: right; } section.cal { -webkit-box-sizing: border-box; @@ -679,14 +681,27 @@ section.sequence-edit > section.content { section.week-new > section.content > div section header, section.sequence-edit > section.content > div section header { background: #eee; - padding: 10px; - border-bottom: 1px solid #ccc; } + padding: 6px; + border-bottom: 1px solid #ccc; + zoom: 1; } + section.week-edit > section.content > div section header:before, section.week-edit > section.content > div section header:after, + section.week-new > section.content > div section header:before, + section.week-new > section.content > div section header:after, + section.sequence-edit > section.content > div section header:before, + section.sequence-edit > section.content > div section header:after { + content: ""; + display: table; } + section.week-edit > section.content > div section header:after, + section.week-new > section.content > div section header:after, + section.sequence-edit > section.content > div section header:after { + clear: both; } section.week-edit > section.content > div section header h2, section.week-new > section.content > div section header h2, section.sequence-edit > section.content > div section header h2 { text-transform: uppercase; letter-spacing: 1px; - font-size: 12px; } + font-size: 12px; + float: left; } section.week-edit > section.content > div section.modules, section.week-new > section.content > div section.modules, section.sequence-edit > section.content > div section.modules { @@ -737,26 +752,37 @@ section.sequence-edit > section.content { section.week-new > section.content > div section.modules ol li ol li, section.sequence-edit > section.content > div section.modules ol li ol li { padding: 6px; } + section.week-edit > section.content > div section.modules ol li ol li:hover a.draggable, + section.week-new > section.content > div section.modules ol li ol li:hover a.draggable, + section.sequence-edit > section.content > div section.modules ol li ol li:hover a.draggable { + opacity: 1; } + section.week-edit > section.content > div section.modules ol li ol li a.draggable, + section.week-new > section.content > div section.modules ol li ol li a.draggable, + section.sequence-edit > section.content > div section.modules ol li ol li a.draggable { + float: right; + opacity: .5; } section.week-edit > section.content > div section.modules ol li ol li.group, section.week-new > section.content > div section.modules ol li ol li.group, section.sequence-edit > section.content > div section.modules ol li ol li.group { - padding: 0; - border-left: 4px solid #999; } + padding: 0; } section.week-edit > section.content > div section.modules ol li ol li.group header, section.week-new > section.content > div section.modules ol li ol li.group header, section.sequence-edit > section.content > div section.modules ol li ol li.group header { - padding: 3px 6px; + padding: 6px; background: none; } section.week-edit > section.content > div section.modules ol li ol li.group header h3, section.week-new > section.content > div section.modules ol li ol li.group header h3, section.sequence-edit > section.content > div section.modules ol li ol li.group header h3 { - text-transform: uppercase; - letter-spacing: 1px; - font-size: 12px; } - section.week-edit > section.content > div section.modules ol li ol li.group ol li:last-child, - section.week-new > section.content > div section.modules ol li ol li.group ol li:last-child, - section.sequence-edit > section.content > div section.modules ol li ol li.group ol li:last-child { + font-size: 14px; } + section.week-edit > section.content > div section.modules ol li ol li.group ol, + section.week-new > section.content > div section.modules ol li ol li.group ol, + section.sequence-edit > section.content > div section.modules ol li ol li.group ol { + border-left: 4px solid #999; border-bottom: 0; } + section.week-edit > section.content > div section.modules ol li ol li.group ol li:last-child, + section.week-new > section.content > div section.modules ol li ol li.group ol li:last-child, + section.sequence-edit > section.content > div section.modules ol li ol li.group ol li:last-child { + border-bottom: 0; } section.week-edit > section.content > div section.scratch-pad, section.week-new > section.content > div section.scratch-pad, section.sequence-edit > section.content > div section.scratch-pad { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 4bc24e662f..3a2ef86363 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -108,4 +108,6 @@ input[type="submit"], .button { min-height: 14px; background: url('../img/drag-handle.png') no-repeat center; text-indent: -9999px; + display: block; + float: right; } diff --git a/cms/static/sass/_week.scss b/cms/static/sass/_week.scss index de7b1e6f9e..e5283cbc55 100644 --- a/cms/static/sass/_week.scss +++ b/cms/static/sass/_week.scss @@ -67,16 +67,19 @@ section.sequence-edit { display: table; border: 1px solid; width: 100%; + section { header { background: #eee; - padding: 10px; + padding: 6px; border-bottom: 1px solid #ccc; + @include clearfix; h2 { text-transform: uppercase; letter-spacing: 1px; font-size: 12px; + float: left; } } @@ -118,22 +121,34 @@ section.sequence-edit { li { padding: 6px; + &:hover { + a.draggable { + opacity: 1; + } + } + + a.draggable { + float: right; + opacity: .5; + } + &.group { padding: 0; - border-left: 4px solid #999; header { - padding: 3px 6px; + padding: 6px; background: none; h3 { - text-transform: uppercase; - letter-spacing: 1px; - font-size: 12px; + font-size: 14px; } } + ol { + border-left: 4px solid #999; + border-bottom: 0; + li { &:last-child { border-bottom: 0; @@ -185,9 +200,9 @@ section.sequence-edit { padding: 12px; a { - @extend .button; - display: block; - text-align: center; + @extend .button; + display: block; + text-align: center; } } diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index 808c569e74..28b92979d9 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -45,10 +45,22 @@ @@ -61,10 +73,22 @@ @@ -77,9 +101,19 @@ @@ -93,10 +127,22 @@ @@ -137,6 +183,30 @@ <%include file="module-dropdown.html"/> +
    • +
      +

      Course Scratch Pad

      +
      + + +
    • diff --git a/cms/templates/widgets/week-edit.html b/cms/templates/widgets/week-edit.html index 6e59c44468..2e37520fde 100644 --- a/cms/templates/widgets/week-edit.html +++ b/cms/templates/widgets/week-edit.html @@ -10,160 +10,201 @@
      • -

        Goal title: This is the goal body and is wehre the goal will be further explained

        +

        Goal title: This is the goal body and is where the goal will be further explained

      -
      -
        -
      • - - -
      • - -
      • - - -
      • -
      • - -
      • - -
      • - Advanced filters -
      • - -
      • - -
      • -
      -
      -
      -
      -
        +
        +
      +
      -
      -
        -
      1. -
        -

        Section Scratch

        -
        -
          -
        • Problem title 11
        • -
        • Problem title 13
        • -
        • Problem title 14
        • -
        • Video 3
        • -
        -
      2. -
      3. -
        -

        Course Scratch

        -
        -
          -
        • Problem title 11
        • -
        • Problem title 13
        • -
        • Problem title 14
        • -
        • Video 3
        • -
        -
      4. +
        +
        +
          +
        1. +
          +

          Lecture Sequence

          + handle +
          -
        2. - <%include file="new-module.html"/> -
        3. -
        -
        +
          +
        1. + Problem title 11 + handle +
        2. +
        3. + Problem Group + handle +
        4. +
        5. + Problem title 14 + handle +
        6. +
        7. + Video 3 + handle +
        8. +
        + +
      5. +
        +

        Lecture Sequence

        + handle +
        + +
          +
        1. +
          +

          + Problem group + handle +

          +
          +
            +
          1. + Problem title 11 + handle +
          2. +
          3. + Problem title 11 + handle +
          4. +
          5. + Problem title 11 + handle +
          6. +
          +
        2. +
        3. + Problem title 13 + handle +
        4. +
        5. + Problem title 14 + handle +
        6. +
        7. + Video 3 + handle +
        8. +
        +
      6. +
      7. +
        +

        Homework 1a

        +
        + +
          +
        1. + Problem title 11 + handle +
        2. +
        3. + Problem Group + handle +
        4. +
        5. + Problem title 14 + handle +
        6. +
        7. + Video 3 + handle +
        8. +
        +
      8. + + + + +
      +
      + +
      +
        +
      1. +
        +

        Section Scratch

        +
        + +
      2. +
      3. +
        +

        Course Scratch

        +
        + + +
      4. + + + + +
      +
      From efe158378f7696eb852af11dbc31a3136bc9d9be Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 15 Jun 2012 13:27:24 -0400 Subject: [PATCH 070/123] Added more styles and markup for sequence --- cms/static/css/base-style.css | 112 +++++++++---- cms/static/sass/_calendar.scss | 2 +- cms/static/sass/_week.scss | 68 +++++--- cms/templates/widgets/sequnce-edit.html | 204 ++++++++++++++++++------ cms/templates/widgets/week-edit.html | 129 +++++++++++++-- cms/templates/widgets/week-new.html | 10 +- 6 files changed, 407 insertions(+), 118 deletions(-) diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css index abed82cc05..9ebc6bd839 100644 --- a/cms/static/css/base-style.css +++ b/cms/static/css/base-style.css @@ -460,7 +460,7 @@ section.cal { section.cal section.new-section section { display: none; position: absolute; - top: 32px; + top: 30px; background: rgba(0, 0, 0, 0.8); min-width: 300px; padding: 10px; @@ -575,46 +575,96 @@ section.sequence-edit > header { section.week-new > header:after, section.sequence-edit > header:after { clear: both; } - section.week-edit > header h1, - section.week-new > header h1, - section.sequence-edit > header h1 { - font-size: 18px; - margin: 8px 6px; - text-transform: uppercase; - letter-spacing: 1px; } + section.week-edit > header div, + section.week-new > header div, + section.sequence-edit > header div { + zoom: 1; + padding: 6px 20px; } + section.week-edit > header div:before, section.week-edit > header div:after, + section.week-new > header div:before, + section.week-new > header div:after, + section.sequence-edit > header div:before, + section.sequence-edit > header div:after { + content: ""; + display: table; } + section.week-edit > header div:after, + section.week-new > header div:after, + section.sequence-edit > header div:after { + clear: both; } + section.week-edit > header div h1, + section.week-new > header div h1, + section.sequence-edit > header div h1 { + font-size: 18px; + text-transform: uppercase; + letter-spacing: 1px; + float: left; } + section.week-edit > header div p, + section.week-new > header div p, + section.sequence-edit > header div p { + float: right; } + section.week-edit > header div.week, + section.week-new > header div.week, + section.sequence-edit > header div.week { + background: #eee; + font-size: 12px; + border-bottom: 1px solid #ccc; } + section.week-edit > header div.week h2, + section.week-new > header div.week h2, + section.sequence-edit > header div.week h2 { + font-size: 12px; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-right: 20px; } + section.week-edit > header div.week ul, + section.week-new > header div.week ul, + section.sequence-edit > header div.week ul { + list-style: none; + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; } + section.week-edit > header div.week ul li, + section.week-new > header div.week ul li, + section.sequence-edit > header div.week ul li { + display: -moz-inline-box; + -moz-box-orient: vertical; + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; + margin-right: 10px; } + section.week-edit > header div.week ul li p, + section.week-new > header div.week ul li p, + section.sequence-edit > header div.week ul li p { + float: none; } section.week-edit > header section.goals, section.week-new > header section.goals, section.sequence-edit > header section.goals { background: #eee; - padding: 6px; + padding: 6px 20px; border-top: 1px solid #ccc; } - section.week-edit > header section.goals header h2, - section.week-new > header section.goals header h2, - section.sequence-edit > header section.goals header h2 { - font-size: 14px; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.week-edit > header section.goals header p, - section.week-new > header section.goals header p, - section.sequence-edit > header section.goals header p { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } section.week-edit > header section.goals ul, section.week-new > header section.goals ul, section.sequence-edit > header section.goals ul { list-style: none; - margin-top: 6px; color: #999; } + section.week-edit > header section.goals ul li, + section.week-new > header section.goals ul li, + section.sequence-edit > header section.goals ul li { + margin-bottom: 6px; } + section.week-edit > header section.goals ul li:last-child, + section.week-new > header section.goals ul li:last-child, + section.sequence-edit > header section.goals ul li:last-child { + margin-bottom: 0; } section.week-edit > section.content, section.week-new > section.content, section.sequence-edit > section.content { diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss index ca91337366..fa10c65950 100644 --- a/cms/static/sass/_calendar.scss +++ b/cms/static/sass/_calendar.scss @@ -176,7 +176,7 @@ section.cal { section { display: none; - @include position(absolute, 32px 0 0 0); + @include position(absolute, 30px 0 0 0); background: rgba(#000, .8); min-width: 300px; padding: 10px; diff --git a/cms/static/sass/_week.scss b/cms/static/sass/_week.scss index e5283cbc55..b638a36f5c 100644 --- a/cms/static/sass/_week.scss +++ b/cms/static/sass/_week.scss @@ -6,34 +6,64 @@ section.sequence-edit { border-bottom: 2px solid #333; @include clearfix(); - h1 { - font-size: 18px; - margin: 8px 6px; - text-transform: uppercase; - letter-spacing: 1px; + 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; + padding: 6px 20px; border-top: 1px solid #ccc; - header { - h2 { - font-size: 14px; - @include inline-block(); - } - - p { - @include inline-block(); - } - - } - ul { list-style: none; - margin-top: 6px; color: #999; + + li { + margin-bottom: 6px; + + &:last-child { + margin-bottom: 0; + } + } } } } diff --git a/cms/templates/widgets/sequnce-edit.html b/cms/templates/widgets/sequnce-edit.html index 75d4504b68..b69b523bc4 100644 --- a/cms/templates/widgets/sequnce-edit.html +++ b/cms/templates/widgets/sequnce-edit.html @@ -1,47 +1,20 @@ -<%block name="content">
      - Done -

      Lecture Sequence name

      - Settings +
      +

      Week 1

      +
        +
      • +

        Goal title: This is the goal body and is where the goal will be further explained

        +
      • +
      +
      +
      +

      Lecture sequence

      +

      Group type: Ordered Sequence

      +
      - - -
      -
      -

      Sequence Content

      -
      - -
      -
      - +
      -
      - -
      +
      +
      +
        +
      1. +
          +
        1. + Problem title 11 + handle +
        2. +
        3. + Problem Group + handle +
        4. +
        5. + Problem title 14 + handle +
        6. +
        7. + Video 3 + handle +
        8. +
        9. +
          +

          + Problem group + handle +

          +
          +
            +
          1. + Problem title 11 + handle +
          2. +
          3. + Problem title 11 + handle +
          4. +
          5. + Problem title 11 + handle +
          6. +
          +
        10. +
        11. + Problem title 13 + handle +
        12. +
        13. + Problem title 14 + handle +
        14. +
        15. + Video 3 + handle +
        16. +
        17. + Problem title 11 + handle +
        18. +
        19. + Problem Group + handle +
        20. +
        21. + Problem title 14 + handle +
        22. +
        23. + Video 3 + handle +
        24. +
        +
      2. + + + + +
      +
      + +
      +
        +
      1. +
        +

        Section Scratch

        +
        + +
      2. +
      3. +
        +

        Course Scratch

        +
        + + +
      4. + + + + +
      +
      +
      - diff --git a/cms/templates/widgets/week-edit.html b/cms/templates/widgets/week-edit.html index 2e37520fde..95ca631be0 100644 --- a/cms/templates/widgets/week-edit.html +++ b/cms/templates/widgets/week-edit.html @@ -1,16 +1,14 @@
      -

      Week 3

      +
      +

      Week 3

      +

      + new goal

      +
      -
      -

      Weeks goals:

      -

      + new goal

      -
      -
      • -

        Goal title: This is the goal body and is where the goal will be further explained

        +

        Goal title: This is the goal body and is where the goal will be further explained

      @@ -62,7 +60,40 @@ Problem title 11 handle -
    • +
    • +
      +

      + Problem group + handle +

      +
      +
        +
      1. + Problem title 11 + handle +
      2. +
      3. + Problem title 11 + handle +
      4. +
      5. + Problem title 11 + handle +
      6. +
      7. + Problem title 13 + handle +
      8. +
      9. + Problem title 14 + handle +
      10. +
      11. + Video 3 + handle +
      12. +
      +
    • Problem Group handle
    • @@ -70,11 +101,77 @@ Problem title 14 handle -
    • +
    • +
      +

      + Problem group + handle +

      +
      +
        +
      1. + Problem title 11 + handle +
      2. +
      3. + Problem title 11 + handle +
      4. +
      5. + Problem title 11 + handle +
      6. +
      7. + Problem title 13 + handle +
      8. +
      9. + Problem title 14 + handle +
      10. +
      11. + Video 3 + handle +
      12. +
      +
    • Video 3 handle
    • - +
    • +
      +

      + Problem group + handle +

      +
      +
        +
      1. + Problem title 11 + handle +
      2. +
      3. + Problem title 11 + handle +
      4. +
      5. + Problem title 11 + handle +
      6. +
      7. + Problem title 13 + handle +
      8. +
      9. + Problem title 14 + handle +
      10. +
      11. + Video 3 + handle +
      12. +
      +
    • @@ -103,6 +200,18 @@ Problem title 11 handle
    • +
    • + Problem title 13 + handle +
    • +
    • + Problem title 14 + handle +
    • +
    • + Video 3 + handle +
    • diff --git a/cms/templates/widgets/week-new.html b/cms/templates/widgets/week-new.html index be08587755..c62730c1bc 100644 --- a/cms/templates/widgets/week-new.html +++ b/cms/templates/widgets/week-new.html @@ -1,13 +1,11 @@
      -

      Week 3

      +
      +

      Week 3

      +

      + new goal

      +
      -
      -

      Weeks goals:

      -

      + new goal

      -
      -
      • Please add a goal for this section

        From 30f6db8e204fddfd4f25724aa7715cdddbb4d10a Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 7 Jun 2012 17:16:56 -0400 Subject: [PATCH 071/123] fix typo in comment in student view --- lms/djangoapps/student/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/student/views.py b/lms/djangoapps/student/views.py index fa321963f5..af28654a59 100644 --- a/lms/djangoapps/student/views.py +++ b/lms/djangoapps/student/views.py @@ -94,7 +94,7 @@ def login_user(request, error=""): @ensure_csrf_cookie def logout_user(request): - ''' HTTP request to log in the user. Redirects to marketing page''' + ''' HTTP request to log out the user. Redirects to marketing page''' logout(request) return redirect('/') From cf6ea3d5937c972f146bf40b055a05b778a449a7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 14 Jun 2012 11:28:52 -0400 Subject: [PATCH 072/123] added rednose to requirements (adds color output to nose) --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index d6150ec49d..122276ea39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,4 @@ glob2 pymongo django_nose nosexcover +rednose From 7358b01cc4581b14cfd49f7b473bce8f99517050 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 14 Jun 2012 17:07:50 -0400 Subject: [PATCH 073/123] Clean and refactor courseware/views.py and module_render.py * Refactor index() so that it makes sense to me and hopefully others :) * Rename preloaded cache of student modules to student_module_cache * Fix line length and whitespace throughout * add docstrings and other comments * a few behavior-preserving tweaks to the code to make it clearer. * Separate codepaths for with-module and without-module in index view * Remove default chapter + section, since they don't exist anyway in course.xml --- lms/djangoapps/courseware/module_render.py | 227 +++++++++------- lms/djangoapps/courseware/views.py | 301 +++++++++++++-------- 2 files changed, 316 insertions(+), 212 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index fe6ebdd585..7c4de7d499 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -26,8 +26,29 @@ class I4xSystem(object): This is an abstraction such that x_modules can function independent of the courseware (e.g. import into other types of courseware, LMS, or if we want to have a sandbox server for user-contributed content) + + I4xSystem objects are passed to x_modules to provide access to system + functionality. ''' - def __init__(self, ajax_url, track_function, render_function, render_template, filestore=None): + def __init__(self, ajax_url, track_function, render_function, + render_template, filestore=None): + ''' + Create a closure around the system environment. + + ajax_url - the url where ajax calls to the encapsulating module go. + track_function - function of (event_type, event), intended for logging + or otherwise tracking the event. + TODO: Not used, and has inconsistent args in different + files. Update or remove. + render_function - function that takes (module_xml) and renders it, + returning a dictionary with a context for rendering the + module to html. Dictionary will contain keys 'content' + and 'type'. + render_template - a function that takes (template_file, context), and returns + rendered html. + filestore - A filestore ojbect. Defaults to an instance of OSFS based at + settings.DATA_DIR. + ''' self.ajax_url = ajax_url self.track_function = track_function if not filestore: @@ -35,37 +56,47 @@ class I4xSystem(object): else: self.filestore = filestore if settings.DEBUG: - log.info("[courseware.module_render.I4xSystem] filestore path = %s" % filestore) + log.info("[courseware.module_render.I4xSystem] filestore path = %s", + filestore) self.render_function = render_function self.render_template = render_template self.exception404 = Http404 self.DEBUG = settings.DEBUG - def get(self,attr): # uniform access to attributes (like etree) + def get(self, attr): + ''' provide uniform access to attributes (like etree).''' return self.__dict__.get(attr) - def set(self,attr,val): # uniform access to attributes (like etree) + + def set(self,attr,val): + '''provide uniform access to attributes (like etree)''' self.__dict__[attr] = val + def __repr__(self): return repr(self.__dict__) + def __str__(self): return str(self.__dict__) -def object_cache(cache, user, module_type, module_id): - # We don't look up on user -- all queries include user - # Additional lookup would require a DB hit the way Django - # is broken. +def smod_cache_lookup(cache, module_type, module_id): + ''' + Look for a student module with the given type and id in the cache. + + cache -- list of student modules + + returns first found object, or None + ''' for o in cache: - if o.module_type == module_type and \ - o.module_id == module_id: + if o.module_type == module_type and o.module_id == module_id: return o return None def make_track_function(request): ''' We want the capa problem (and other modules) to be able to track/log what happens inside them without adding dependencies on - Django or the rest of the codebase. We do this by passing a - tracking function to them. This generates a closure for each request - that gives a clean interface on both sides. + Django or the rest of the codebase. + + To do this in a clean way, we pass a tracking function to the module, + which calls it to log events. ''' import track.views @@ -75,85 +106,91 @@ def make_track_function(request): def grade_histogram(module_id): ''' Print out a histogram of grades on a given problem. - Part of staff member debug info. + Part of staff member debug info. ''' from django.db import connection cursor = connection.cursor() - cursor.execute("select courseware_studentmodule.grade,COUNT(courseware_studentmodule.student_id) from courseware_studentmodule where courseware_studentmodule.module_id=%s group by courseware_studentmodule.grade", [module_id]) + q = """SELECT courseware_studentmodule.grade, + COUNT(courseware_studentmodule.student_id) + FROM courseware_studentmodule + WHERE courseware_studentmodule.module_id=%s + GROUP BY courseware_studentmodule.grade""" + # Passing module_id this way prevents sql-injection. + cursor.execute(q, [module_id]) grades = list(cursor.fetchall()) - grades.sort(key=lambda x:x[0]) # Probably not necessary - if (len(grades) == 1 and grades[0][0] is None): + grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query? + if len(grades) == 1 and grades[0][0] is None: return [] return grades -def get_module(user, request, xml_module, module_object_preload, position=None): - ''' Get the appropriate xmodule and StudentModule. +def get_module(user, request, module_xml, student_module_cache, position=None): + ''' Get an instance of the xmodule class corresponding to module_xml, + setting the state based on an existing StudentModule, or creating one if none + exists. Arguments: - user : current django User - request : current django HTTPrequest - - xml_module : lxml etree of xml subtree for the current module - - module_object_preload : list of StudentModule objects, one of which may match this module type and id - - position : extra information from URL for user-specified position within module + - module_xml : lxml etree of xml subtree for the requested module + - student_module_cache : list of StudentModule objects, one of which may + match this module type and id + - position : extra information from URL for user-specified + position within module Returns: - a tuple (xmodule instance, student module, module type). - ''' - module_type=xml_module.tag - module_class=xmodule.get_module_class(module_type) - module_id=xml_module.get('id') #module_class.id_attribute) or "" + module_type = module_xml.tag + module_class = xmodule.get_module_class(module_type) + module_id = module_xml.get('id') - # Grab state from database - smod = object_cache(module_object_preload, - user, - module_type, - module_id) + # Grab xmodule state from StudentModule cache + smod = smod_cache_lookup(student_module_cache, module_type, module_id) + state = smod.state if smod else None - if not smod: # If nothing in the database... - state=None - else: - state = smod.state - - # get coursename if stored + # get coursename if present in request coursename = multicourse_settings.get_coursename_from_request(request) if coursename and settings.ENABLE_MULTICOURSE: - xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course + # path to XML for the course + xp = multicourse_settings.get_course_xmlpath(coursename) data_root = settings.DATA_DIR + xp else: data_root = settings.DATA_DIR - # Create a new instance - ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/' + # Setup system context for module instance + ajax_url = settings.MITX_ROOT_URL + '/modx/' + module_type + '/' + module_id + '/' + def render_function(module_xml): + return render_x_module(user, request, module_xml, student_module_cache, position) system = I4xSystem(track_function = make_track_function(request), - render_function = lambda x: render_x_module(user, request, x, module_object_preload, position), + render_function = render_function, render_template = render_to_string, ajax_url = ajax_url, filestore = OSFS(data_root), ) - system.set('position',position) # pass URL specified position along to module, through I4xSystem - instance=module_class(system, - etree.tostring(xml_module), - module_id, - state=state) + # pass position specified in URL to module through I4xSystem + system.set('position', position) + instance = module_class(system, + etree.tostring(module_xml), + module_id, + state=state) - # If instance wasn't already in the database, and this - # isn't a guest user, create it + # If StudentModule for this instance wasn't already in the database, + # and this isn't a guest user, create it. if not smod and user.is_authenticated(): - smod=StudentModule(student=user, - module_type = module_type, - module_id=module_id, - state=instance.get_state()) + smod = StudentModule(student=user, module_type = module_type, + module_id=module_id, state=instance.get_state()) smod.save() - module_object_preload.append(smod) + # Add to cache. The caller and the system context have references + # to it, so the change persists past the return + student_module_cache.append(smod) return (instance, smod, module_type) -def render_x_module(user, request, xml_module, module_object_preload, position=None): +def render_x_module(user, request, module_xml, student_module_cache, position=None): ''' Generic module for extensions. This renders to HTML. modules include sequential, vertical, problem, video, html @@ -164,37 +201,36 @@ def render_x_module(user, request, xml_module, module_object_preload, position=N - user : current django User - request : current django HTTPrequest - - xml_module : lxml etree of xml subtree for the current module - - module_object_preload : list of StudentModule objects, one of which may match this module type and id + - module_xml : lxml etree of xml subtree for the current module + - student_module_cache : list of StudentModule objects, one of which may match this module type and id - position : extra information from URL for user-specified position within module Returns: - - dict which is context for HTML rendering of the specified module - + - dict which is context for HTML rendering of the specified module. Will have + key 'content', and will have 'type' key if passed a valid module. ''' - if xml_module==None : - return {"content":""} + if module_xml is None : + return {"content": ""} - (instance, smod, module_type) = get_module(user, request, xml_module, module_object_preload, position) + (instance, smod, module_type) = get_module( + user, request, module_xml, student_module_cache, position) - # Grab content content = instance.get_html() # special extra information about each problem, only for users who are staff if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF') and user.is_staff: - module_id = xml_module.get('id') + module_id = module_xml.get('id') histogram = grade_histogram(module_id) render_histogram = len(histogram) > 0 - content=content+render_to_string("staff_problem_info.html", {'xml':etree.tostring(xml_module), - 'module_id' : module_id, - 'histogram': json.dumps(histogram), - 'render_histogram' : render_histogram}) + staff_context = {'xml': etree.tostring(module_xml), + 'module_id': module_id, + 'histogram': json.dumps(histogram), + 'render_histogram': render_histogram} + content += render_to_string("staff_problem_info.html", staff_context) - content = {'content':content, - 'type':module_type} - - return content + context = {'content': content, 'type': module_type} + return context def modx_dispatch(request, module=None, dispatch=None, id=None): ''' Generic view for extensions. This is where AJAX calls go. @@ -210,32 +246,38 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): - id -- the module id. Used to look up the student module. e.g. filenamexformularesponse ''' + # ''' (fix emacs broken parsing) if not request.user.is_authenticated(): return redirect('/') + # python concats adjacent strings + error_msg = ("We're sorry, this module is temporarily unavailable." + "Our staff is working to fix it as soon as possible") + + # Grab the student information for the module from the database s = StudentModule.objects.filter(student=request.user, module_id=id) - #s = StudentModule.get_with_caching(request.user, id) - if len(s) == 0 or s is None: - log.debug("Couldnt find module for user and id " + str(module) + " " + str(request.user) + " "+ str(id)) + + # s = StudentModule.get_with_caching(request.user, id) + if s is None or len(s) == 0: + log.debug("Couldn't find module '%s' for user '%s' and id '%s'", + module, request.user, id) raise Http404 s = s[0] oldgrade = s.grade oldstate = s.state - # TODO: if dispatch is left at default value None, this will go boom. What's the correct - # behavior? - dispatch=dispatch.split('?')[0] + # If there are arguments, get rid of them + if '?' in dispatch: + dispatch = dispatch.split('?')[0] - ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/' - - # get coursename if stored + ajax_url = '{root}/modx/{module}/{id}'.format(root = settings.MITX_ROOT_URL, + module=module, id=id) coursename = multicourse_settings.get_coursename_from_request(request) - if coursename and settings.ENABLE_MULTICOURSE: - xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course + xp = multicourse_settings.get_course_xmlpath(coursename) data_root = settings.DATA_DIR + xp else: data_root = settings.DATA_DIR @@ -244,11 +286,13 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): try: xml = content_parser.module_xml(request.user, module, 'id', id, coursename) except: - log.exception("Unable to load module during ajax call. module=%s, dispatch=%s, id=%s" % (module, dispatch, id)) + log.exception( + "Unable to load module during ajax call. module=%s, dispatch=%s, id=%s", + module, dispatch, id) if accepts(request, 'text/html'): return render_to_response("module-error.html", {}) else: - response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + response = HttpResponse(json.dumps({'success': error_msg})) return response # Create the module @@ -260,24 +304,23 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): ) try: - instance=xmodule.get_module_class(module)(system, - xml, - id, - state=oldstate) + module_class = xmodule.get_module_class(module) + instance = module_class(system, xml, id, state=oldstate) except: log.exception("Unable to load module instance during ajax call") if accepts(request, 'text/html'): return render_to_response("module-error.html", {}) else: - response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + response = HttpResponse(json.dumps({'success': error_msg})) return response # Let the module handle the AJAX - ajax_return=instance.handle_ajax(dispatch, request.POST) + ajax_return = instance.handle_ajax(dispatch, request.POST) + # Save the state back to the database - s.state=instance.get_state() + s.state = instance.get_state() if instance.get_score(): - s.grade=instance.get_score()['score'] + s.grade = instance.get_score()['score'] if s.grade != oldgrade or s.state != oldstate: s.save() # Return whatever the module wanted to return to the client/caller diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index fcd0104455..7bc8b323ce 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -41,66 +41,71 @@ def gradebook(request): coursename = multicourse_settings.get_coursename_from_request(request) student_objects = User.objects.all()[:100] - student_info = [{'username' :s.username, - 'id' : s.id, + student_info = [{'username': s.username, + 'id': s.id, 'email': s.email, - 'grade_info' : grades.grade_sheet(s,coursename), - 'realname' : UserProfile.objects.get(user = s).name + 'grade_info': grades.grade_sheet(s, coursename), + 'realname': UserProfile.objects.get(user = s).name } for s in student_objects] - return render_to_response('gradebook.html',{'students':student_info}) + return render_to_response('gradebook.html', {'students': student_info}) @login_required @cache_control(no_cache=True, no_store=True, must_revalidate=True) -def profile(request, student_id = None): +def profile(request, student_id=None): ''' User profile. Show username, location, etc, as well as grades . We need to allow the user to change some of these settings .''' if student_id is None: student = request.user - else: + else: if 'course_admin' not in content_parser.user_groups(request.user): raise Http404 student = User.objects.get( id = int(student_id)) - user_info = UserProfile.objects.get(user=student) # request.user.profile_cache # + user_info = UserProfile.objects.get(user=student) # request.user.profile_cache # coursename = multicourse_settings.get_coursename_from_request(request) - context={'name':user_info.name, - 'username':student.username, - 'location':user_info.location, - 'language':user_info.language, - 'email':student.email, - 'format_url_params' : content_parser.format_url_params, - 'csrf':csrf(request)['csrf_token'] - } - context.update(grades.grade_sheet(student,coursename)) + context = {'name': user_info.name, + 'username': student.username, + 'location': user_info.location, + 'language': user_info.language, + 'email': student.email, + 'format_url_params': content_parser.format_url_params, + 'csrf': csrf(request)['csrf_token'] + } + context.update(grades.grade_sheet(student, coursename)) return render_to_response('profile.html', context) -def render_accordion(request,course,chapter,section): + +def render_accordion(request, course, chapter, section): ''' Draws navigation bar. Takes current position in accordion as parameter. Returns (initialization_javascript, content)''' if not course: course = "6.002 Spring 2012" - toc=content_parser.toc_from_xml(content_parser.course_file(request.user,course), chapter, section) - active_chapter=1 + toc = content_parser.toc_from_xml( + content_parser.course_file(request.user, course), chapter, section) + + active_chapter = 1 for i in range(len(toc)): if toc[i]['active']: - active_chapter=i - context=dict([['active_chapter',active_chapter], - ['toc',toc], - ['course_name',course], - ['format_url_params',content_parser.format_url_params], - ['csrf',csrf(request)['csrf_token']]] + \ + active_chapter = i + + context=dict([('active_chapter', active_chapter), + ('toc', toc), + ('course_name', course), + ('format_url_params', content_parser.format_url_params), + ('csrf', csrf(request)['csrf_token'])] + template_imports.items()) - return render_to_string('accordion.html',context) + return render_to_string('accordion.html', context) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def render_section(request, section): - ''' TODO: Consolidate with index + ''' TODO: Consolidate with index ''' user = request.user if not settings.COURSEWARE_ENABLED: @@ -120,15 +125,15 @@ def render_section(request, section): } module_ids = dom.xpath("//@id") - + if user.is_authenticated(): - module_object_preload = list(StudentModule.objects.filter(student=user, + student_module_cache = list(StudentModule.objects.filter(student=user, module_id__in=module_ids)) else: - module_object_preload = [] - + student_module_cache = [] + try: - module = render_x_module(user, request, dom, module_object_preload) + module = render_x_module(user, request, dom, student_module_cache) except: log.exception("Unable to load module") context.update({ @@ -138,18 +143,67 @@ def render_section(request, section): return render_to_response('courseware.html', context) context.update({ - 'init':module.get('init_js', ''), - 'content':module['content'], + 'init': module.get('init_js', ''), + 'content': module['content'], }) result = render_to_response('courseware.html', context) return result +def get_course(request, course): + ''' Figure out what the correct course is. + + Needed to preserve backwards compatibility with non-multi-course version. + TODO: Can this go away once multicourse becomes standard? + ''' + + if course==None: + if not settings.ENABLE_MULTICOURSE: + course = "6.002 Spring 2012" + elif 'coursename' in request.session: + course = request.session['coursename'] + else: + course = settings.COURSE_DEFAULT + return course + +def get_module_xml(user, course, chapter, section): + ''' Look up the module xml for the given course/chapter/section path. + + Takes the user to look up the course file. + + Returns None if there was a problem, or the lxml etree for the module. + ''' + try: + # this is the course.xml etree + dom = content_parser.course_file(user, course) + except: + log.exception("Unable to parse courseware xml") + return None + + # this is the module's parent's etree + path = "//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]" + dom_module = dom.xpath(path, course=course, chapter=chapter, section=section) + + module_wrapper = dom_module[0] if len(dom_module) > 0 else None + if module_wrapper is None: + module = None + elif module_wrapper.get("src"): + module = content_parser.section_file( + user=user, section=module_wrapper.get("src"), coursename=course) + else: + # Copy the element out of the module's etree + module = etree.XML(etree.tostring(module_wrapper[0])) + return module + @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) -def index(request, course=None, chapter="Using the System", section="Hints",position=None): +def index(request, course=None, chapter=None, section=None, + position=None): ''' Displays courseware accordion, and any associated content. + If course, chapter, and section aren't all specified, just returns + the accordion. If they are specified, returns an error if they don't + point to a valid module. Arguments: @@ -162,110 +216,113 @@ def index(request, course=None, chapter="Using the System", section="Hints",posi Returns: - HTTPresponse + ''' + def clean(s): + ''' Fixes URLs -- we convert spaces to _ in URLs to prevent + funny encoding characters and keep the URLs readable. This undoes + that transformation. - ''' - user = request.user + TODO: Properly replace underscores. (Q: what is properly?) + ''' + return s.replace('_', ' ') + + def get_submodule_ids(module_xml): + ''' + Get a list with ids of the modules within this module. + ''' + return module_xml.xpath("//@id") + + def preload_student_modules(module_xml): + ''' + Find any StudentModule objects for this user that match + one of the given module_ids. Used as a cache to avoid having + each rendered module hit the db separately. + + Returns the list, or None on error. + ''' + if request.user.is_authenticated(): + module_ids = get_submodule_ids(module_xml) + return list(StudentModule.objects.filter(student=request.user, + module_id__in=module_ids)) + else: + return [] + + def get_module_context(): + ''' + Look up the module object and render it. If all goes well, returns + {'init': module-init-js, 'content': module-rendered-content} + + If there's an error, returns + {'content': module-error message} + ''' + # Can't modify variables of outer scope, so need new ones + chapter_ = clean(chapter) + section_ = clean(section) + + user = request.user + + module_xml = get_module_xml(user, course, chapter_, section_) + if module_xml is None: + log.exception("couldn't get module_xml: course/chapter/section: '%s/%s/%s'", + course, chapter_, section_) + return {'content' : render_to_string("module-error.html", {})} + + student_module_cache = preload_student_modules(module_xml) + + try: + module_context = render_x_module(user, request, module_xml, + student_module_cache, position) + except: + log.exception("Unable to load module") + return {'content' : render_to_string("module-error.html", {})} + + return {'init': module_context.get('init_js', ''), + 'content': module_context['content']} + if not settings.COURSEWARE_ENABLED: return redirect('/') - if course==None: - if not settings.ENABLE_MULTICOURSE: - course = "6.002 Spring 2012" - elif 'coursename' in request.session: - course = request.session['coursename'] - else: - course = settings.COURSE_DEFAULT - - # Fixes URLs -- we don't get funny encoding characters from spaces - # so they remain readable - ## TODO: Properly replace underscores - course=course.replace("_"," ") - chapter=chapter.replace("_"," ") - section=section.replace("_"," ") - - # use multicourse module to determine if "course" is valid - #if course!=settings.COURSE_NAME.replace('_',' '): + course = clean(get_course(request, course)) if not multicourse_settings.is_valid_course(course): return redirect('/') - request.session['coursename'] = course # keep track of current course being viewed in django's request.session - - try: - # this is the course.xml etree - dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path - except: - log.exception("Unable to parse courseware xml") - return render_to_response('courseware-error.html', {}) - - # this is the module's parent's etree - dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]", - course=course, chapter=chapter, section=section) - - #print "DM", dom_module - - if len(dom_module) == 0: - module_wrapper = None - else: - module_wrapper = dom_module[0] - - if module_wrapper is None: - module = None - elif module_wrapper.get("src"): - module = content_parser.section_file(user=user, section=module_wrapper.get("src"), coursename=course) - else: - # this is the module's etree - module = etree.XML(etree.tostring(module_wrapper[0])) # Copy the element out of the tree - - module_ids = [] - if module is not None: - module_ids = module.xpath("//@id", - course=course, chapter=chapter, section=section) - - if user.is_authenticated(): - module_object_preload = list(StudentModule.objects.filter(student=user, - module_id__in=module_ids)) - else: - module_object_preload = [] + # keep track of current course being viewed in django's request.session + request.session['coursename'] = course context = { 'csrf': csrf(request)['csrf_token'], 'accordion': render_accordion(request, course, chapter, section), - 'COURSE_TITLE':multicourse_settings.get_course_title(course), + 'COURSE_TITLE': multicourse_settings.get_course_title(course), + 'init': '', + 'content': '' } - try: - module_context = render_x_module(user, request, module, module_object_preload, position) - except: - log.exception("Unable to load module") - context.update({ - 'init': '', - 'content': render_to_string("module-error.html", {}), - }) - return render_to_response('courseware.html', context) - - context.update({ - 'init': module_context.get('init_js', ''), - 'content': module_context['content'], - }) + look_for_module = chapter is not None and section is not None + if look_for_module: + context.update(get_module_context()) result = render_to_response('courseware.html', context) return result def jump_to(request, probname=None): ''' - Jump to viewing a specific problem. The problem is specified by a problem name - currently the filename (minus .xml) - of the problem. Maybe this should change to a more generic tag, eg "name" given as an attribute in . + Jump to viewing a specific problem. The problem is specified by a + problem name - currently the filename (minus .xml) of the problem. + Maybe this should change to a more generic tag, eg "name" given as + an attribute in . - We do the jump by (1) reading course.xml to find the first instance of with the given filename, then - (2) finding the parent element of the problem, then (3) rendering that parent element with a specific computed position - value (if it is ). + We do the jump by (1) reading course.xml to find the first + instance of with the given filename, then (2) finding + the parent element of the problem, then (3) rendering that parent + element with a specific computed position value (if it is + ). ''' # get coursename if stored coursename = multicourse_settings.get_coursename_from_request(request) # begin by getting course.xml tree - xml = content_parser.course_file(request.user,coursename) + xml = content_parser.course_file(request.user, coursename) # look for problem of given name pxml = xml.xpath('//problem[@filename="%s"]' % probname) @@ -279,12 +336,16 @@ def jump_to(request, probname=None): section = None branch = parent for k in range(4): # max depth of recursion - if branch.tag=='section': section = branch.get('name') - if branch.tag=='chapter': chapter = branch.get('name') + if branch.tag == 'section': + section = branch.get('name') + if branch.tag == 'chapter': + chapter = branch.get('name') branch = branch.getparent() position = None - if parent.tag=='sequential': - position = parent.index(pxml)+1 # position in sequence - - return index(request,course=coursename,chapter=chapter,section=section,position=position) + if parent.tag == 'sequential': + position = parent.index(pxml) + 1 # position in sequence + + return index(request, + course=coursename, chapter=chapter, + section=section, position=position) From 54c8d3f6b9592c09a82202645039d09181933eb2 Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 15 Jun 2012 22:40:02 -0400 Subject: [PATCH 074/123] modify capa_problem to add functionality --- common/lib/capa/capa_problem.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index e70fa6ceff..4ee2a2113d 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -111,6 +111,7 @@ class LoncapaProblem(object): file_text = re.sub("endouttext\s*/", "/text", file_text) self.tree = etree.XML(file_text) # parse problem XML file into an element tree + self._process_includes() # handle any tags # construct script processor context (eg for customresponse problems) self.context = self._extract_context(self.tree, seed=self.seed) @@ -242,6 +243,36 @@ class LoncapaProblem(object): # ======= Private Methods Below ======== + def _process_includes(self): + ''' + Handle any tags by reading in the specified file and inserting it + into our XML tree. Fail gracefully if debugging. + ''' + includes = self.tree.findall('.//include') + for inc in includes: + file = inc.get('file') + if file is not None: + try: + ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore + except Exception,err: + log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True))) + log.error('Cannot find file %s in %s' % (file,self.system.filestore)) + if not self.system.get('DEBUG'): # if debugging, don't fail - just log error + raise + else: continue + try: + incxml = etree.XML(ifp.read()) # read in and convert to XML + except Exception,err: + log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True))) + log.error('Cannot parse XML in %s' % (file)) + if not self.system.get('DEBUG'): # if debugging, don't fail - just log error + raise + else: continue + parent = inc.getparent() # insert new XML into tree in place of inlcude + parent.insert(parent.index(inc),incxml) + parent.remove(inc) + log.debug('Included %s into %s' % (file,self.fileobject)) + def _extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private ''' Extract content of from the problem.xml file, and exec it in the From 8baf8c81b2a8a59603df601cc73d2c48bc6ec7a2 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 10:08:31 -0400 Subject: [PATCH 075/123] Read week headings from mongodb --- .../management/commands/import.py | 2 +- cms/djangoapps/contentstore/views.py | 13 +- cms/lib/keystore/__init__.py | 7 +- cms/lib/keystore/mongo.py | 19 ++- cms/templates/widgets/navigation.html | 123 +----------------- common/lib/xmodule/seq_module.py | 8 ++ common/lib/xmodule/setup.py | 14 ++ common/lib/xmodule/x_module.py | 75 +++++++++-- requirements.txt | 1 + 9 files changed, 115 insertions(+), 147 deletions(-) create mode 100644 common/lib/xmodule/setup.py diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 690e3dbea0..3e0ccdd5e8 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -82,7 +82,7 @@ class Command(BaseCommand): def handle_list(e): if e.attrib.get("class", None) == "tutorials": return - children = [{'url':le.attrib['url']} for le in e.getchildren()] + children = [le.attrib['url'] for le in e.getchildren()] results[e.attrib['url']] = {'children':children} def handle_video(e): diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 429fb6c26b..64bde14869 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -3,11 +3,10 @@ from keystore.django import keystore from django.contrib.auth.decorators import login_required -@login_required -def calendar(request, org, course): - weeks = keystore.get_children_for_item(['i4x', org, course, 'Course', None]) - return render_to_response('calendar.html', {'weeks': weeks}) - - def index(request): - return render_to_response('index.html', {}) + # FIXME (cpennington): These need to be read in from the active user + org = 'mit.edu' + course = '6002xs12' + course = keystore.get_item(['i4x', org, course, 'Course', None]) + weeks = course.get_children() + return render_to_response('index.html', {'weeks': weeks}) diff --git a/cms/lib/keystore/__init__.py b/cms/lib/keystore/__init__.py index d0a24be797..61c797241d 100644 --- a/cms/lib/keystore/__init__.py +++ b/cms/lib/keystore/__init__.py @@ -37,7 +37,7 @@ class Location(object): self.update(location.list()) def url(self): - return "i4x://{org}/{course}/{category}/{name}".format(**self.dict()) + return "{tag}://{org}/{course}/{category}/{name}".format(**self.dict()) def list(self): return [self.tag, self.org, self.course, self.category, self.name] @@ -54,10 +54,9 @@ class Location(object): class KeyStore(object): - def get_children_for_item(self, location): + def get_item(self, location): """ - Returns the children for the most recent revision of the object - with the specified location. + Returns an XModuleDescriptor instance for the item at location If no object is found at that location, raises keystore.exceptions.ItemNotFoundError diff --git a/cms/lib/keystore/mongo.py b/cms/lib/keystore/mongo.py index d29afb4bd2..d9760909c9 100644 --- a/cms/lib/keystore/mongo.py +++ b/cms/lib/keystore/mongo.py @@ -1,6 +1,7 @@ import pymongo from . import KeyStore, Location from .exceptions import ItemNotFoundError, InsufficientSpecificationError +from xmodule.x_module import XModuleDescriptor class MongoKeyStore(KeyStore): @@ -12,11 +13,22 @@ class MongoKeyStore(KeyStore): host=host, port=port )[db][collection] - + # Force mongo to report errors, at the expense of performance self.collection.safe = True - def get_children_for_item(self, location): + def get_item(self, location): + """ + Returns an XModuleDescriptor instance for the item at location + + If no object is found at that location, raises keystore.exceptions.ItemNotFoundError + + Searches for all matches of a partially specifed location, but raises an + keystore.exceptions.InsufficientSpecificationError if more + than a single object matches the query. + + location: Something that can be passed to Location + """ query = dict( ('location.{key}'.format(key=key), val) for (key, val) @@ -25,7 +37,6 @@ class MongoKeyStore(KeyStore): ) items = self.collection.find( query, - fields={'children': True}, sort=[('revision', pymongo.ASCENDING)], limit=1, ) @@ -35,7 +46,7 @@ class MongoKeyStore(KeyStore): if items.count() == 0: raise ItemNotFoundError(location) - return items[0]['children'] + return XModuleDescriptor.load_from_json(items[0], self.get_item) def create_item(self, location, editor): """ diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index 28b92979d9..75d581dd73 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -35,9 +35,10 @@
        + % for week in weeks:
      1. -

        Week 1

        +

        ${week.name}

        • Goal title: This is a goal that will be in the header of the week
        • Goal title two: This is another goal for this week so that students have two things to learn
        • @@ -64,125 +65,7 @@ <%include file="module-dropdown.html"/>
      2. -
      3. -
        -

        Week 2

        -
          -
        • Another title This is the goal for the week
        • -
        -
        - - -
      4. -
      5. -
        -

        Week 3

        -
          -
        • Another title This is the goal for the week
        • -
        -
        - - -
      6. -
      7. -
        -

        Week 4

        -
          -
        • Another title This is the goal for the week
        • -
        • Goal title two: This is another fgoal for this week so that students have two things to learn
        • -
        -
        - - -
      8. - -
      9. -
        -

        Week 5

        -
          -
        • Please create a learning goal for this week
        • -
        -
        - -
          - <%include file="module-dropdown.html"/> -
        -
      10. -
      11. -
        -

        Week 6

        -
          -
        • Please create a learning goal for this week
        • -
        -
        - -
          - <%include file="module-dropdown.html"/> -
        -
      12. -
      13. -
        -

        Week 7

        -
          -
        • Please create a learning goal for this week
        • -
        -
        - -
          - <%include file="module-dropdown.html"/> -
        -
      14. + %endfor
      15. Course Scratch Pad

        diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index f643eaab4a..4ae4fb3813 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -95,3 +95,11 @@ class Module(XModule): self.position = int(system.get('position')) self.rendered = False + + +class CourseModuleDescriptor(XModuleDescriptor): + pass + + +class ChapterModuleDescriptor(XModuleDescriptor): + pass diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py new file mode 100644 index 0000000000..6a659b6852 --- /dev/null +++ b/common/lib/xmodule/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_packages + +setup( + name="XModule", + version="0.1", + packages=find_packages(), + install_requires=['distribute'], + entry_points={ + 'xmodule.v1': [ + "Course = seq_module:CourseModuleDescriptor", + "Chapter = seq_module:ChapterModuleDescriptor", + ] + } +) diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index d783694fee..23025df50b 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -1,8 +1,34 @@ from lxml import etree +import pkg_resources +import logging +from keystore import Location + +log = logging.getLogger('mitx.' + __name__) def dummy_track(event_type, event): pass + +class ModuleMissingError(Exception): + pass + + +class Plugin(object): + @classmethod + def load_class(cls, identifier): + classes = list(pkg_resources.iter_entry_points(cls.entry_point, name=identifier)) + if len(classes) > 1: + log.warning("Found multiple classes for {entry_point} with identifier {id}: {classes}. Returning the first one.".format( + entry_point=cls.entry_point, + id=identifier, + classes=", ".join([class_.module_name for class_ in classes]))) + + if len(classes) == 0: + raise ModuleMissingError(identifier) + + return classes[0].load() + + class XModule(object): ''' Implements a generic learning module. Initialized on access with __init__, first time with state=None, and @@ -24,8 +50,8 @@ class XModule(object): or a CAPA input type ''' return ['xmodule'] - def get_name(): - name = self.__xmltree.get(name) + def get_name(self): + name = self.__xmltree.get('name') if name: return name else: @@ -98,15 +124,42 @@ class XModule(object): return "" -class XModuleDescriptor(object): - def __init__(self, xml = None, json = None): - if not xml and not json: - raise "XModuleDescriptor must be initalized with XML or JSON" - if not xml: - raise NotImplementedError("Code does not have support for JSON yet") - - self.xml = xml - self.json = json +class XModuleDescriptor(Plugin): + + entry_point = "xmodule.v1" + + @staticmethod + def load_from_json(json_data, load_item): + class_ = XModuleDescriptor.load_class(json_data['location']['category']) + return class_.from_json(json_data, load_item) + + @classmethod + def from_json(cls, json_data, load_item): + """ + Creates an instance of this descriptor from the supplied json_data. + + json_data: Json data specifying the data, children, and metadata for the descriptor + load_item: A function that takes an i4x url and returns a module descriptor + """ + return cls(load_item=load_item, **json_data) + + def __init__(self, + load_item, + data=None, + children=None, + **kwargs): + self.load_item = load_item + self.data = data if data is not None else {} + self.children = children if children is not None else [] + self.name = Location(kwargs.get('location')).name + self._child_instances = None + + def get_children(self): + """Returns a list of XModuleDescriptor instances for the children of this module""" + if self._child_instances is None: + self._child_instances = [self.load_item(child) for child in self.children] + return self._child_instances + def get_xml(self): ''' For conversions between JSON and legacy XML representations. diff --git a/requirements.txt b/requirements.txt index 122276ea39..37a30b6cef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,4 @@ pymongo django_nose nosexcover rednose +-e common/lib/xmodule From 896c7daf367de4d00e2cec90de870d2f7b478953 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 11:09:11 -0400 Subject: [PATCH 076/123] Add categories and XModuleDescriptors for all module types that are used at the top level of a course --- .../management/commands/import.py | 27 +++++++++++++------ cms/templates/widgets/navigation.html | 25 ++++++----------- common/lib/xmodule/seq_module.py | 19 ++++++++++--- common/lib/xmodule/setup.py | 11 ++++++-- common/lib/xmodule/video_module.py | 4 +++ common/lib/xmodule/x_module.py | 9 +++++-- 6 files changed, 63 insertions(+), 32 deletions(-) diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 3e0ccdd5e8..8b33f32b94 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -32,7 +32,7 @@ class Command(BaseCommand): elements = list(course.iter()) - tag_to_category = {# Inside HTML ==> Skip these + tag_to_category = { # Custom tags 'videodev': 'Custom', 'slides': 'Custom', @@ -40,33 +40,44 @@ class Command(BaseCommand): 'image': 'Custom', 'discuss': 'Custom', # Simple lists - 'chapter': 'Chapter', + 'chapter': 'Week', 'course': 'Course', 'sequential': 'LectureSequence', 'vertical': 'ProblemSet', - 'section': 'Section', + 'section': { + 'Lab': 'Lab', + 'Lecture Sequence': 'LectureSequence', + 'Homework': 'Homework', + 'Tutorial Index': 'TutorialIndex', + 'Video': 'VideoSegment', + 'Midterm': 'Exam', + 'Final': 'Exam', + None: 'Section', + }, # True types 'video': 'VideoSegment', 'html': 'HTML', 'problem': 'Problem', } - - name_index=0 + name_index = 0 for e in elements: name = e.attrib.get('name', None) for f in elements: if f != e and f.attrib.get('name', None) == name: name = None if not name: - name = "{tag}_{index}".format(tag = e.tag,index = name_index) + name = "{tag}_{index}".format(tag=e.tag, index=name_index) name_index = name_index + 1 if e.tag in tag_to_category: category = tag_to_category[e.tag] + if isinstance(category, dict): + category = category[e.get('format')] category = category.replace('/', '-') name = name.replace('/', '-') - e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format(category = category, - name = name)) + e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format( + category=category, + name=name)) def handle_skip(e): diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index 75d581dd73..1f75dab470 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -40,28 +40,19 @@

        ${week.name}

          -
        • Goal title: This is a goal that will be in the header of the week
        • -
        • Goal title two: This is another goal for this week so that students have two things to learn
        • + % for goal in week.get_goals(): +
        • ${goal.name}:${goal.data}
        • + % endfor
      16. diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index 4ae4fb3813..91ff6d2671 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -97,9 +97,22 @@ class Module(XModule): self.rendered = False -class CourseModuleDescriptor(XModuleDescriptor): - pass +class WeekDescriptor(XModuleDescriptor): + + def get_goals(self): + """ + Return a list of Goal XModuleDescriptors that are children + of this Week + """ + return [child for child in self.get_children() if child.type == 'Goal'] + + def get_non_goals(self): + """ + Return a list of non-Goal XModuleDescriptors that are children of + this Week + """ + return [child for child in self.get_children() if child.type != 'Goal'] -class ChapterModuleDescriptor(XModuleDescriptor): +class SectionDescriptor(XModuleDescriptor): pass diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 6a659b6852..1140037259 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -7,8 +7,15 @@ setup( install_requires=['distribute'], entry_points={ 'xmodule.v1': [ - "Course = seq_module:CourseModuleDescriptor", - "Chapter = seq_module:ChapterModuleDescriptor", + "Course = seq_module:SectionDescriptor", + "Week = seq_module:WeekDescriptor", + "Section = seq_module:SectionDescriptor", + "LectureSequence = seq_module:SectionDescriptor", + "Lab = seq_module:SectionDescriptor", + "Homework = seq_module:SectionDescriptor", + "TutorialIndex = seq_module:SectionDescriptor", + "Exam = seq_module:SectionDescriptor", + "VideoSegment = video_module:VideoSegmentDescriptor", ] } ) diff --git a/common/lib/xmodule/video_module.py b/common/lib/xmodule/video_module.py index 8f8a2c2ffd..d7c7f80291 100644 --- a/common/lib/xmodule/video_module.py +++ b/common/lib/xmodule/video_module.py @@ -57,3 +57,7 @@ class Module(XModule): self.annotations=[(e.get("name"),self.render_function(e)) \ for e in xmltree] + + +class VideoSegmentDescriptor(XModuleDescriptor): + pass diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index 23025df50b..3560eecbdc 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -152,13 +152,18 @@ class XModuleDescriptor(Plugin): self.data = data if data is not None else {} self.children = children if children is not None else [] self.name = Location(kwargs.get('location')).name + self.type = Location(kwargs.get('location')).category self._child_instances = None - def get_children(self): + def get_children(self, categories=None): """Returns a list of XModuleDescriptor instances for the children of this module""" if self._child_instances is None: self._child_instances = [self.load_item(child) for child in self.children] - return self._child_instances + + if categories is None: + return self._child_instances + else: + return [child for child in self._child_instances if child.type in categories] def get_xml(self): From d485b9be9e863ab04b0ebf933da3ff1b30d3e645 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 18 Jun 2012 12:04:10 -0400 Subject: [PATCH 077/123] Small cleanups in response to Calen + Piotr's comments --- lms/djangoapps/courseware/module_render.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 7c4de7d499..87892e8fec 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -29,6 +29,9 @@ class I4xSystem(object): I4xSystem objects are passed to x_modules to provide access to system functionality. + + Note that these functions can be closures over e.g. a django request + and user, or other environment-specific info. ''' def __init__(self, ajax_url, track_function, render_function, render_template, filestore=None): @@ -91,12 +94,9 @@ def smod_cache_lookup(cache, module_type, module_id): return None def make_track_function(request): - ''' We want the capa problem (and other modules) to be able to - track/log what happens inside them without adding dependencies on - Django or the rest of the codebase. - - To do this in a clean way, we pass a tracking function to the module, - which calls it to log events. + ''' + Make a tracking function that logs what happened. + For use in I4xSystem. ''' import track.views @@ -162,11 +162,11 @@ def get_module(user, request, module_xml, student_module_cache, position=None): # Setup system context for module instance ajax_url = settings.MITX_ROOT_URL + '/modx/' + module_type + '/' + module_id + '/' - def render_function(module_xml): + def render_x_module_wrapper(module_xml): return render_x_module(user, request, module_xml, student_module_cache, position) system = I4xSystem(track_function = make_track_function(request), - render_function = render_function, + render_function = render_x_module_wrapper, render_template = render_to_string, ajax_url = ajax_url, filestore = OSFS(data_root), @@ -251,7 +251,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): return redirect('/') # python concats adjacent strings - error_msg = ("We're sorry, this module is temporarily unavailable." + error_msg = ("We're sorry, this module is temporarily unavailable. " "Our staff is working to fix it as soon as possible") @@ -259,7 +259,6 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): s = StudentModule.objects.filter(student=request.user, module_id=id) - # s = StudentModule.get_with_caching(request.user, id) if s is None or len(s) == 0: log.debug("Couldn't find module '%s' for user '%s' and id '%s'", module, request.user, id) @@ -270,8 +269,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): oldstate = s.state # If there are arguments, get rid of them - if '?' in dispatch: - dispatch = dispatch.split('?')[0] + dispatch, _, _ = dispatch.partition('?') ajax_url = '{root}/modx/{module}/{id}'.format(root = settings.MITX_ROOT_URL, module=module, id=id) From 0a7542c28168bc111cff5ffaa88ea51416b14998 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 14 May 2012 18:43:21 -0400 Subject: [PATCH 078/123] Courseware can have same random seed in multiple problems for exam. Slight hack. --- common/lib/xmodule/capa_module.py | 2 ++ lms/djangoapps/courseware/module_render.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index c7b52ae8ba..55534f8a3e 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -222,6 +222,8 @@ class Module(XModule): self.weight=only_one(dom2.xpath('/problem/@weight')) if self.rerandomize == 'never': seed = 1 + elif self.rerandomize == "per_student" and hasattr(system, 'id'): + seed = system.id else: seed = None try: diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 87892e8fec..69d27e599e 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -34,7 +34,7 @@ class I4xSystem(object): and user, or other environment-specific info. ''' def __init__(self, ajax_url, track_function, render_function, - render_template, filestore=None): + render_template, request=None, filestore=None): ''' Create a closure around the system environment. @@ -49,6 +49,7 @@ class I4xSystem(object): and 'type'. render_template - a function that takes (template_file, context), and returns rendered html. + request - the request in progress filestore - A filestore ojbect. Defaults to an instance of OSFS based at settings.DATA_DIR. ''' @@ -65,6 +66,7 @@ class I4xSystem(object): self.render_template = render_template self.exception404 = Http404 self.DEBUG = settings.DEBUG + self.id = request.user.id if request is not None else 0 def get(self, attr): ''' provide uniform access to attributes (like etree).''' @@ -169,6 +171,7 @@ def get_module(user, request, module_xml, student_module_cache, position=None): render_function = render_x_module_wrapper, render_template = render_to_string, ajax_url = ajax_url, + request = request, filestore = OSFS(data_root), ) # pass position specified in URL to module through I4xSystem @@ -298,6 +301,7 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): render_function = None, render_template = render_to_string, ajax_url = ajax_url, + request = request, filestore = OSFS(data_root), ) From 2e6c8c52348dfef9d2e8825b076ca6fe03afe7d6 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 12:21:12 -0400 Subject: [PATCH 079/123] Ignore .egg-info directories --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ebf06998b1..ef28575da5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ log/ reports/ /src/ \#*\# +*.egg-info From 2aa6193a00fa118b466d1822ebb75ace5542612d Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 18 Jun 2012 13:12:47 -0400 Subject: [PATCH 080/123] put lambda function back in, as requested during code review --- lms/djangoapps/courseware/module_render.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 69d27e599e..af5fec6b85 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -164,11 +164,10 @@ def get_module(user, request, module_xml, student_module_cache, position=None): # Setup system context for module instance ajax_url = settings.MITX_ROOT_URL + '/modx/' + module_type + '/' + module_id + '/' - def render_x_module_wrapper(module_xml): - return render_x_module(user, request, module_xml, student_module_cache, position) - + system = I4xSystem(track_function = make_track_function(request), - render_function = render_x_module_wrapper, + render_function = lambda xml: render_x_module( + user, request, xml, student_module_cache, position), render_template = render_to_string, ajax_url = ajax_url, request = request, From d8d22ea57b13cf26e142b35d1296d9c8d1f65843 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 13:21:06 -0400 Subject: [PATCH 081/123] Make tests pass when running on cms --- .../management/commands/ftpserve.py | 61 ------------------- cms/envs/dev.py | 2 +- cms/envs/test.py | 54 ++++++++++++++++ .../{lib => djangoapps}/cache_toolbox/COPYING | 0 .../cache_toolbox/README.rst | 0 .../cache_toolbox/__init__.py | 0 .../cache_toolbox/app_settings.py | 0 .../{lib => djangoapps}/cache_toolbox/core.py | 0 .../cache_toolbox/middleware.py | 0 .../cache_toolbox/model.py | 0 .../cache_toolbox/relation.py | 0 .../cache_toolbox/templatetags/__init__.py | 0 .../templatetags/cache_toolbox.py | 0 common/{lib => djangoapps}/util/__init__.py | 0 common/{lib => djangoapps}/util/cache.py | 0 common/{lib => djangoapps}/util/memcache.py | 0 common/{lib => djangoapps}/util/middleware.py | 0 common/{lib => djangoapps}/util/models.py | 0 common/{lib => djangoapps}/util/tests.py | 0 common/{lib => djangoapps}/util/views.py | 0 {cms => common}/lib/keystore/__init__.py | 0 {cms => common}/lib/keystore/django.py | 0 {cms => common}/lib/keystore/exceptions.py | 0 {cms => common}/lib/keystore/mongo.py | 0 lms/envs/dev.py | 2 +- rakefile | 2 +- 26 files changed, 57 insertions(+), 64 deletions(-) delete mode 100644 cms/djangoapps/contentstore/management/commands/ftpserve.py create mode 100644 cms/envs/test.py rename common/{lib => djangoapps}/cache_toolbox/COPYING (100%) rename common/{lib => djangoapps}/cache_toolbox/README.rst (100%) rename common/{lib => djangoapps}/cache_toolbox/__init__.py (100%) rename common/{lib => djangoapps}/cache_toolbox/app_settings.py (100%) rename common/{lib => djangoapps}/cache_toolbox/core.py (100%) rename common/{lib => djangoapps}/cache_toolbox/middleware.py (100%) rename common/{lib => djangoapps}/cache_toolbox/model.py (100%) rename common/{lib => djangoapps}/cache_toolbox/relation.py (100%) rename common/{lib => djangoapps}/cache_toolbox/templatetags/__init__.py (100%) rename common/{lib => djangoapps}/cache_toolbox/templatetags/cache_toolbox.py (100%) rename common/{lib => djangoapps}/util/__init__.py (100%) rename common/{lib => djangoapps}/util/cache.py (100%) rename common/{lib => djangoapps}/util/memcache.py (100%) rename common/{lib => djangoapps}/util/middleware.py (100%) rename common/{lib => djangoapps}/util/models.py (100%) rename common/{lib => djangoapps}/util/tests.py (100%) rename common/{lib => djangoapps}/util/views.py (100%) rename {cms => common}/lib/keystore/__init__.py (100%) rename {cms => common}/lib/keystore/django.py (100%) rename {cms => common}/lib/keystore/exceptions.py (100%) rename {cms => common}/lib/keystore/mongo.py (100%) diff --git a/cms/djangoapps/contentstore/management/commands/ftpserve.py b/cms/djangoapps/contentstore/management/commands/ftpserve.py deleted file mode 100644 index f0a1c19dbf..0000000000 --- a/cms/djangoapps/contentstore/management/commands/ftpserve.py +++ /dev/null @@ -1,61 +0,0 @@ -from django.core.management.base import BaseCommand -from django.contrib.auth.models import User -import contentstore.tasks - -from pyftpdlib import ftpserver -import os - -class DjangoAuthorizer(object): - def validate_authentication(self, username, password): - try: - u=User.objects.get(username=username) - except User.DoesNotExist: - return False - # TODO: Check security groups - return u.check_password(password) - def has_user(self, username): - print "????",username - return True - def has_perm(self, username, perm, path=None): - print "!!!!!",username, perm, path - return True - def get_home_dir(self, username): - d = "/tmp/ftp/"+username - try: - os.mkdir(d) - except OSError: - pass - return "/tmp/ftp/"+username - def get_perms(self, username): - return 'elradfmw' - def get_msg_login(self, username): - return 'Hello' - def get_msg_quit(self, username): - return 'Goodbye' - def __init__(self): - pass - def impersonate_user(self, username, password): - pass - def terminate_impersonation(self, username): - pass - -def on_upload(ftp_handler, filename): - source = ftp_handler.remote_ip - author = ftp_handler.username - print filename, author, source - # We pass on this for now: - # contentstore.tasks.on_upload - # It is a changing API, and it makes testing the FTP server slow. - -class Command(BaseCommand): - help = \ -''' Run FTP server.''' - def handle(self, *args, **options): - authorizer = DjangoAuthorizer() #ftpserver.DummyAuthorizer() - handler = ftpserver.FTPHandler - handler.on_file_received = on_upload - - handler.authorizer = authorizer - address = ("127.0.0.1", 2121) - ftpd = ftpserver.FTPServer(address, handler) - ftpd.serve_forever() diff --git a/cms/envs/dev.py b/cms/envs/dev.py index f7277b3d3f..332f52f145 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -38,6 +38,6 @@ CACHES = { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 'KEY_PREFIX': 'general', 'VERSION': 4, - 'KEY_FUNCTION': 'util.cache.memcache_safe_key', + 'KEY_FUNCTION': 'util.memcache.safe_key', } } diff --git a/cms/envs/test.py b/cms/envs/test.py new file mode 100644 index 0000000000..1a20d9e6f8 --- /dev/null +++ b/cms/envs/test.py @@ -0,0 +1,54 @@ +""" +This config file runs the simplest dev environment using sqlite, and db-based +sessions. Assumes structure: + +/envroot/ + /db # This is where it'll write the database file + /mitx # The location of this repo + /log # Where we're going to write log files +""" +from .common import * +import os + +# Nose Test Runner +INSTALLED_APPS += ('django_nose',) +NOSE_ARGS = ['--cover-erase', '--with-xunit', '--with-xcoverage', '--cover-html', '--cover-inclusive'] +for app in os.listdir(PROJECT_ROOT / 'djangoapps'): + NOSE_ARGS += ['--cover-package', app] +TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' + +KEYSTORE = { + 'host': 'localhost', + 'db': 'mongo_base', + 'collection': 'key_store', +} + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ENV_ROOT / "db" / "mitx.db", + } +} + +CACHES = { + # This is the cache used for most things. Askbot will not work without a + # functioning cache -- it relies on caching to load its settings in places. + # In staging/prod envs, the sessions also live here. + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'mitx_loc_mem_cache', + 'KEY_FUNCTION': 'util.memcache.safe_key', + }, + + # The general cache is what you get if you use our util.cache. It's used for + # things like caching the course.xml file for different A/B test groups. + # We set it to be a DummyCache to force reloading of course.xml in dev. + # In staging environments, we would grab VERSION from data uploaded by the + # push process. + 'general': { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + 'KEY_PREFIX': 'general', + 'VERSION': 4, + 'KEY_FUNCTION': 'util.memcache.safe_key', + } +} diff --git a/common/lib/cache_toolbox/COPYING b/common/djangoapps/cache_toolbox/COPYING similarity index 100% rename from common/lib/cache_toolbox/COPYING rename to common/djangoapps/cache_toolbox/COPYING diff --git a/common/lib/cache_toolbox/README.rst b/common/djangoapps/cache_toolbox/README.rst similarity index 100% rename from common/lib/cache_toolbox/README.rst rename to common/djangoapps/cache_toolbox/README.rst diff --git a/common/lib/cache_toolbox/__init__.py b/common/djangoapps/cache_toolbox/__init__.py similarity index 100% rename from common/lib/cache_toolbox/__init__.py rename to common/djangoapps/cache_toolbox/__init__.py diff --git a/common/lib/cache_toolbox/app_settings.py b/common/djangoapps/cache_toolbox/app_settings.py similarity index 100% rename from common/lib/cache_toolbox/app_settings.py rename to common/djangoapps/cache_toolbox/app_settings.py diff --git a/common/lib/cache_toolbox/core.py b/common/djangoapps/cache_toolbox/core.py similarity index 100% rename from common/lib/cache_toolbox/core.py rename to common/djangoapps/cache_toolbox/core.py diff --git a/common/lib/cache_toolbox/middleware.py b/common/djangoapps/cache_toolbox/middleware.py similarity index 100% rename from common/lib/cache_toolbox/middleware.py rename to common/djangoapps/cache_toolbox/middleware.py diff --git a/common/lib/cache_toolbox/model.py b/common/djangoapps/cache_toolbox/model.py similarity index 100% rename from common/lib/cache_toolbox/model.py rename to common/djangoapps/cache_toolbox/model.py diff --git a/common/lib/cache_toolbox/relation.py b/common/djangoapps/cache_toolbox/relation.py similarity index 100% rename from common/lib/cache_toolbox/relation.py rename to common/djangoapps/cache_toolbox/relation.py diff --git a/common/lib/cache_toolbox/templatetags/__init__.py b/common/djangoapps/cache_toolbox/templatetags/__init__.py similarity index 100% rename from common/lib/cache_toolbox/templatetags/__init__.py rename to common/djangoapps/cache_toolbox/templatetags/__init__.py diff --git a/common/lib/cache_toolbox/templatetags/cache_toolbox.py b/common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py similarity index 100% rename from common/lib/cache_toolbox/templatetags/cache_toolbox.py rename to common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py diff --git a/common/lib/util/__init__.py b/common/djangoapps/util/__init__.py similarity index 100% rename from common/lib/util/__init__.py rename to common/djangoapps/util/__init__.py diff --git a/common/lib/util/cache.py b/common/djangoapps/util/cache.py similarity index 100% rename from common/lib/util/cache.py rename to common/djangoapps/util/cache.py diff --git a/common/lib/util/memcache.py b/common/djangoapps/util/memcache.py similarity index 100% rename from common/lib/util/memcache.py rename to common/djangoapps/util/memcache.py diff --git a/common/lib/util/middleware.py b/common/djangoapps/util/middleware.py similarity index 100% rename from common/lib/util/middleware.py rename to common/djangoapps/util/middleware.py diff --git a/common/lib/util/models.py b/common/djangoapps/util/models.py similarity index 100% rename from common/lib/util/models.py rename to common/djangoapps/util/models.py diff --git a/common/lib/util/tests.py b/common/djangoapps/util/tests.py similarity index 100% rename from common/lib/util/tests.py rename to common/djangoapps/util/tests.py diff --git a/common/lib/util/views.py b/common/djangoapps/util/views.py similarity index 100% rename from common/lib/util/views.py rename to common/djangoapps/util/views.py diff --git a/cms/lib/keystore/__init__.py b/common/lib/keystore/__init__.py similarity index 100% rename from cms/lib/keystore/__init__.py rename to common/lib/keystore/__init__.py diff --git a/cms/lib/keystore/django.py b/common/lib/keystore/django.py similarity index 100% rename from cms/lib/keystore/django.py rename to common/lib/keystore/django.py diff --git a/cms/lib/keystore/exceptions.py b/common/lib/keystore/exceptions.py similarity index 100% rename from cms/lib/keystore/exceptions.py rename to common/lib/keystore/exceptions.py diff --git a/cms/lib/keystore/mongo.py b/common/lib/keystore/mongo.py similarity index 100% rename from cms/lib/keystore/mongo.py rename to common/lib/keystore/mongo.py diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 17e1e96f45..decd92d136 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -44,7 +44,7 @@ CACHES = { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 'KEY_PREFIX': 'general', 'VERSION': 4, - 'KEY_FUNCTION': 'util.cache.memcache_safe_key', + 'KEY_FUNCTION': 'util.memcache.safe_key', } } diff --git a/rakefile b/rakefile index a04d8ebdee..a3d742cef1 100644 --- a/rakefile +++ b/rakefile @@ -67,7 +67,7 @@ end task task_name => report_dir do ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml") ENV['NOSE_COVER_HTML_DIR'] = File.join(report_dir, "cover") - sh(django_admin(:lms, :test, 'test', *Dir['lms/djangoapps'].each)) + sh(django_admin(system, :test, 'test', *Dir["#{system}/djangoapps/*"].each)) end task :test => task_name From 86205c21ff945c01c26e43698ab05fe99150facd Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 13:41:53 -0400 Subject: [PATCH 082/123] Delay initializition of the MongoKeyStore until required --- .../contentstore/management/commands/import.py | 7 +++---- cms/djangoapps/contentstore/views.py | 2 +- common/lib/keystore/django.py | 11 ++++++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 8b33f32b94..d6064e1e3d 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -160,9 +160,8 @@ class Command(BaseCommand): element_actions[e.tag](e) for k in results: - print k - keystore.create_item(k, 'Piotr Mitros') + keystore().create_item(k, 'Piotr Mitros') if 'data' in results[k]: - keystore.update_item(k, results[k]['data']) + keystore().update_item(k, results[k]['data']) if 'children' in results[k]: - keystore.update_children(k, results[k]['children']) + keystore().update_children(k, results[k]['children']) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 64bde14869..e29c41ea59 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -7,6 +7,6 @@ def index(request): # FIXME (cpennington): These need to be read in from the active user org = 'mit.edu' course = '6002xs12' - course = keystore.get_item(['i4x', org, course, 'Course', None]) + course = keystore().get_item(['i4x', org, course, 'Course', None]) weeks = course.get_children() return render_to_response('index.html', {'weeks': weeks}) diff --git a/common/lib/keystore/django.py b/common/lib/keystore/django.py index b6ffb83b5c..b88c74b8a3 100644 --- a/common/lib/keystore/django.py +++ b/common/lib/keystore/django.py @@ -9,4 +9,13 @@ from __future__ import absolute_import from django.conf import settings from .mongo import MongoKeyStore -keystore = MongoKeyStore(**settings.KEYSTORE) +_KEYSTORE = None + + +def keystore(): + global _KEYSTORE + + if _KEYSTORE is None: + _KEYSTORE = MongoKeyStore(**settings.KEYSTORE) + + return _KEYSTORE From 21ae3799f7a001a520bae2902855597483fbc6d2 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 13:48:26 -0400 Subject: [PATCH 083/123] Move libraries that can only be imported in the context of django into common/djangoapps --- common/{lib => djangoapps}/django_future/__init__.py | 0 common/{lib => djangoapps}/django_future/csrf.py | 0 common/{lib => djangoapps}/monitoring/__init__.py | 0 common/{lib => djangoapps}/monitoring/exceptions.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename common/{lib => djangoapps}/django_future/__init__.py (100%) rename common/{lib => djangoapps}/django_future/csrf.py (100%) rename common/{lib => djangoapps}/monitoring/__init__.py (100%) rename common/{lib => djangoapps}/monitoring/exceptions.py (100%) diff --git a/common/lib/django_future/__init__.py b/common/djangoapps/django_future/__init__.py similarity index 100% rename from common/lib/django_future/__init__.py rename to common/djangoapps/django_future/__init__.py diff --git a/common/lib/django_future/csrf.py b/common/djangoapps/django_future/csrf.py similarity index 100% rename from common/lib/django_future/csrf.py rename to common/djangoapps/django_future/csrf.py diff --git a/common/lib/monitoring/__init__.py b/common/djangoapps/monitoring/__init__.py similarity index 100% rename from common/lib/monitoring/__init__.py rename to common/djangoapps/monitoring/__init__.py diff --git a/common/lib/monitoring/exceptions.py b/common/djangoapps/monitoring/exceptions.py similarity index 100% rename from common/lib/monitoring/exceptions.py rename to common/djangoapps/monitoring/exceptions.py From 73b7a2e667ba5a99318e55f099856ae4e3714a2e Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 14:08:15 -0400 Subject: [PATCH 084/123] Remove copy/paste instructor module. It was a bad idea anyway. =) --- cms/djangoapps/instructor/__init__.py | 0 cms/djangoapps/instructor/models.py | 61 --------------------------- cms/djangoapps/instructor/tests.py | 16 ------- cms/djangoapps/instructor/views.py | 49 --------------------- 4 files changed, 126 deletions(-) delete mode 100644 cms/djangoapps/instructor/__init__.py delete mode 100644 cms/djangoapps/instructor/models.py delete mode 100644 cms/djangoapps/instructor/tests.py delete mode 100644 cms/djangoapps/instructor/views.py diff --git a/cms/djangoapps/instructor/__init__.py b/cms/djangoapps/instructor/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cms/djangoapps/instructor/models.py b/cms/djangoapps/instructor/models.py deleted file mode 100644 index 906aeee2f1..0000000000 --- a/cms/djangoapps/instructor/models.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -WE'RE USING MIGRATIONS! - -If you make changes to this model, be sure to create an appropriate migration -file and check it in at the same time as your model changes. To do that, - -1. Go to the mitx dir -2. ./manage.py schemamigration user --auto description_of_your_change -3. Add the migration file created in mitx/courseware/migrations/ -""" -import uuid - -from django.db import models -from django.contrib.auth.models import User - - -class UserProfile(models.Model): - class Meta: - db_table = "auth_userprofile" - - ## CRITICAL TODO/SECURITY - # Sanitize all fields. - # This is not visible to other users, but could introduce holes later - user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile') - name = models.CharField(blank=True, max_length=255, db_index=True) - org = models.CharField(blank=True, max_length=255, db_index=True) - - -class Registration(models.Model): - ''' Allows us to wait for e-mail before user is registered. A - registration profile is created when the user creates an - account, but that account is inactive. Once the user clicks - on the activation key, it becomes active. ''' - class Meta: - db_table = "auth_registration" - - user = models.ForeignKey(User, unique=True) - activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True) - - def register(self, user): - # MINOR TODO: Switch to crypto-secure key - self.activation_key = uuid.uuid4().hex - self.user = user - self.save() - - def activate(self): - self.user.is_active = True - self.user.save() - #self.delete() - - -class PendingNameChange(models.Model): - user = models.OneToOneField(User, unique=True, db_index=True) - new_name = models.CharField(blank=True, max_length=255) - rationale = models.CharField(blank=True, max_length=1024) - - -class PendingEmailChange(models.Model): - user = models.OneToOneField(User, unique=True, db_index=True) - new_email = models.CharField(blank=True, max_length=255, db_index=True) - activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True) diff --git a/cms/djangoapps/instructor/tests.py b/cms/djangoapps/instructor/tests.py deleted file mode 100644 index 501deb776c..0000000000 --- a/cms/djangoapps/instructor/tests.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" - -from django.test import TestCase - - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) diff --git a/cms/djangoapps/instructor/views.py b/cms/djangoapps/instructor/views.py deleted file mode 100644 index fbb341b468..0000000000 --- a/cms/djangoapps/instructor/views.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging - -from django.views.decorators.http import require_http_methods, require_POST, require_GET -from django.contrib.auth import logout, authenticate, login -from django.shortcuts import redirect -from mitxmako.shortcuts import render_to_response - -from django_future.csrf import ensure_csrf_cookie - -log = logging.getLogger("mitx.student") - - -@require_http_methods(['GET', 'POST']) -def do_login(request): - if request.method == 'POST': - return post_login(request) - elif request.method == 'GET': - return get_login(request) - - -@require_POST -@ensure_csrf_cookie -def post_login(request): - username = request.POST['username'] - password = request.POST['password'] - user = authenticate(username=username, password=password) - if user is not None: - if user.is_active: - login(request, user) - return redirect(request.POST.get('next', '/')) - else: - raise Exception("Can't log in, account disabled") - else: - raise Exception("Can't log in, invalid authentication") - - -@require_GET -@ensure_csrf_cookie -def get_login(request): - return render_to_response('login.html', { - 'next': request.GET.get('next') - }) - - -@ensure_csrf_cookie -def logout_user(request): - ''' HTTP request to log in the user. Redirects to marketing page''' - logout(request) - return redirect('/') From 3e887bc5c7fccf919caa157f0825919b295fc442 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 18 Jun 2012 14:24:45 -0400 Subject: [PATCH 085/123] Remove old template that is no longer relevant --- cms/templates/calendar.html | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 cms/templates/calendar.html diff --git a/cms/templates/calendar.html b/cms/templates/calendar.html deleted file mode 100644 index 05b2f88806..0000000000 --- a/cms/templates/calendar.html +++ /dev/null @@ -1,3 +0,0 @@ -% for week in weeks: -${week} -% endfor From c24b8cec7d1c838c45132a527cae11066dc6ee28 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 18 Jun 2012 17:06:17 -0400 Subject: [PATCH 086/123] Code standards --- doc/code_standards.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/code_standards.txt b/doc/code_standards.txt index 02953b3677..06be87fd6a 100644 --- a/doc/code_standards.txt +++ b/doc/code_standards.txt @@ -75,7 +75,21 @@ no hard standards. review it (this may change as the team grows). * Each contributor is responsible for finding a person to review their code. If it is not clear to the contributor who is appropriate, each - project has an owner + project has an owner who is the default go-to. + +2.1 Rapid pull + +Unmerged code can lead to merge conflicts, and slow down +development. We have an experimental procedure for handling rapid +pulls and merges. To qualify: + +* A piece of code must only have minor issues remaining (nothing which +we would be uncomfortable placing on a server). +* Either the requester or the puller takes ownership for guaranteeing +that those issues are resolved within a short timeframe. +* Both the requester and the puller must be comfortable with it. + +If code qualified, it can be merged, and repaired in master. 3. Documentation Standards From 3b9945e04ef409c2ca0f8a659037a45e864fcd54 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 18 Jun 2012 17:09:17 -0400 Subject: [PATCH 087/123] Rapid pull/history --- doc/code_standards.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/code_standards.txt b/doc/code_standards.txt index 06be87fd6a..33ec4cecab 100644 --- a/doc/code_standards.txt +++ b/doc/code_standards.txt @@ -88,6 +88,8 @@ we would be uncomfortable placing on a server). * Either the requester or the puller takes ownership for guaranteeing that those issues are resolved within a short timeframe. * Both the requester and the puller must be comfortable with it. +* Both the requester and the owner must have a history of/ability to +resolve remaining issues quickly. If code qualified, it can be merged, and repaired in master. From de90c8e2b0fef714cfc32582de13d747b1b003d7 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 18 Jun 2012 17:40:39 -0400 Subject: [PATCH 088/123] Documentation for rapid merge --- doc/code_standards.txt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/code_standards.txt b/doc/code_standards.txt index 33ec4cecab..a4833b8d36 100644 --- a/doc/code_standards.txt +++ b/doc/code_standards.txt @@ -84,14 +84,22 @@ development. We have an experimental procedure for handling rapid pulls and merges. To qualify: * A piece of code must only have minor issues remaining (nothing which -we would be uncomfortable placing on a server). + we would be uncomfortable placing on a server). * Either the requester or the puller takes ownership for guaranteeing -that those issues are resolved within a short timeframe. + that those issues are resolved within a short timeframe. * Both the requester and the puller must be comfortable with it. * Both the requester and the owner must have a history of/ability to -resolve remaining issues quickly. + resolve remaining issues quickly. -If code qualified, it can be merged, and repaired in master. +If code qualifies: +* It can be merged, and repaired in master. +* The pull message should specify '## pending fixes/OWNER' where ## is + the pull request number, and OWNER is the owner. +* All required fixes are documented in github in the (now closed) pull + request, and should be marked off there when applied (potentially, + directly to master). +* Once all fixes are applied, the final commit should specify + '## closed'. 3. Documentation Standards From 092d64aa527db0e04e662c327ed9d880a1f0607d Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:24:22 -0400 Subject: [PATCH 089/123] Conform to new TODO standards --- cms/djangoapps/contentstore/views.py | 2 +- doc/code_standards.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index e29c41ea59..ad846fb369 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -4,7 +4,7 @@ from django.contrib.auth.decorators import login_required def index(request): - # FIXME (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' course = '6002xs12' course = keystore().get_item(['i4x', org, course, 'Course', None]) diff --git a/doc/code_standards.txt b/doc/code_standards.txt index a4833b8d36..122dde228b 100644 --- a/doc/code_standards.txt +++ b/doc/code_standards.txt @@ -107,3 +107,6 @@ If code qualifies: * When impossible, it should live in the github repo. * Discussion should live on github, Basecamp or Pivotal, depending on context. +* Notes for later fixes should in general be put into Pivotal as stories. + If they are left in the code, they should be prefixed by + # TODO () From bde622d4f9878ac09806cc61c76c2bc299432e2a Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:25:29 -0400 Subject: [PATCH 090/123] Search for course specifically in navigation view --- cms/djangoapps/contentstore/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index ad846fb369..a87520ab13 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -7,6 +7,7 @@ def index(request): # TODO (cpennington): These need to be read in from the active user org = 'mit.edu' course = '6002xs12' - course = keystore().get_item(['i4x', org, course, 'Course', None]) + name = '6.002 Spring 2012' + course = keystore().get_item(['i4x', org, course, 'Course', name]) weeks = course.get_children() return render_to_response('index.html', {'weeks': weeks}) From 52621eb5aa8a2dd0928c93fd9c40c324eeeb6473 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:25:41 -0400 Subject: [PATCH 091/123] Get rid of references to askbot --- cms/envs/common.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 8d402b6fa9..80056af1d2 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -27,15 +27,12 @@ from path import path PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms COMMON_ROOT = PROJECT_ROOT.dirname() / "common" ENV_ROOT = PROJECT_ROOT.dirname().dirname() # virtualenv dir /mitx is in -ASKBOT_ROOT = ENV_ROOT / "askbot-devel" COURSES_ROOT = ENV_ROOT / "data" # FIXME: To support multiple courses, we should walk the courses dir at startup DATA_DIR = COURSES_ROOT sys.path.append(ENV_ROOT) -sys.path.append(ASKBOT_ROOT) -sys.path.append(ASKBOT_ROOT / "askbot" / "deps") sys.path.append(PROJECT_ROOT / 'djangoapps') sys.path.append(PROJECT_ROOT / 'lib') sys.path.append(COMMON_ROOT / 'djangoapps') From f2598426ceaa2f6db4d0fc565591841d32cd741f Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:27:29 -0400 Subject: [PATCH 092/123] Cleanup and test Location, and add the ability to specify a revision --- common/lib/keystore/__init__.py | 108 ++++++++++++++++++---- common/lib/keystore/exceptions.py | 3 + common/lib/keystore/test/test_location.py | 52 +++++++++++ 3 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 common/lib/keystore/test/test_location.py diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index 61c797241d..592cde7b4d 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -10,47 +10,117 @@ the following attributes: revision: What revision of the item this is """ +import re +from .exceptions import InvalidLocationError + +URL_RE = re.compile(""" + (?P[^:]+):// + (?P[^/]+)/ + (?P[^/]+)/ + (?P[^/]+)/ + (?P[^/]+) + (/(?P[^/]+))? + """, re.VERBOSE) + class Location(object): - ''' Encodes a location. - Can be: - * String (url) - * Tuple - * Dictionary + ''' + Encodes a location. + + Locations representations of URLs of the + form {tag}://{org}/{course}/{category}/{name}[/{revision}] + + However, they can also be represented a dictionaries (specifying each component), + tuples or list (specified in order), or as strings of the url ''' def __init__(self, location): + """ + Create a new location that is a clone of the specifed one. + + location - Can be any of the following types: + string: should be of the form {tag}://{org}/{course}/{category}/{name}[/{revision}] + list: should be of the form [tag, org, course, category, name, revision] + dict: should be of the form { + 'tag': tag, + 'org': org, + 'course': course, + 'category': category, + 'name': name, + 'revision': revision, + } + Location: another Location object + + None of the components of a location may contain the '/' character + """ self.update(location) def update(self, location): + """ + Update this instance with data from another Location object. + + location: can take the same forms as specified by `__init__` + """ + self.tag = self.org = self.course = self.category = self.name = self.revision = None + if isinstance(location, basestring): - self.tag = location.split('/')[0][:-1] - (self.org, self.course, self.category, self.name) = location.split('/')[2:] + match = URL_RE.match(location) + if match is None: + raise InvalidLocationError(location) + else: + self.update(match.groupdict()) elif isinstance(location, list): - (self.tag, self.org, self.course, self.category, self.name) = location + if len(location) not in (5, 6): + raise InvalidLocationError(location) + + (self.tag, self.org, self.course, self.category, self.name) = location[0:5] + self.revision = location[5] if len(location) == 6 else None elif isinstance(location, dict): - self.tag = location['tag'] - self.org = location['org'] - self.course = location['course'] - self.category = location['category'] - self.name = location['name'] + try: + self.tag = location['tag'] + self.org = location['org'] + self.course = location['course'] + self.category = location['category'] + self.name = location['name'] + except KeyError: + raise InvalidLocationError(location) + self.revision = location.get('revision') elif isinstance(location, Location): self.update(location.list()) + else: + raise InvalidLocationError(location) + + for val in self.list(): + if val is not None and '/' in val: + raise InvalidLocationError(location) + + def __str__(self): + return self.url() def url(self): - return "{tag}://{org}/{course}/{category}/{name}".format(**self.dict()) + """ + Return a string containing the URL for this location + """ + url = "{tag}://{org}/{course}/{category}/{name}".format(**self.dict()) + if self.revision: + url += "/" + self.revision + return url def list(self): - return [self.tag, self.org, self.course, self.category, self.name] + """ + Return a list representing this location + """ + return [self.tag, self.org, self.course, self.category, self.name, self.revision] def dict(self): + """ + Return a dictionary representing this location + """ return {'tag': self.tag, 'org': self.org, 'course': self.course, 'category': self.category, - 'name': self.name} - - def to_json(self): - return self.dict() + 'name': self.name, + 'revision': self.revision} class KeyStore(object): diff --git a/common/lib/keystore/exceptions.py b/common/lib/keystore/exceptions.py index 08fd9b11d0..4c8c55ffe9 100644 --- a/common/lib/keystore/exceptions.py +++ b/common/lib/keystore/exceptions.py @@ -9,3 +9,6 @@ class ItemNotFoundError(Exception): class InsufficientSpecificationError(Exception): pass + +class InvalidLocationError(Exception): + pass diff --git a/common/lib/keystore/test/test_location.py b/common/lib/keystore/test/test_location.py new file mode 100644 index 0000000000..f10f03c0b0 --- /dev/null +++ b/common/lib/keystore/test/test_location.py @@ -0,0 +1,52 @@ +from nose.tools import assert_equals, assert_raises +from keystore import Location +from keystore.exceptions import InvalidLocationError + + +def check_string_roundtrip(url): + assert_equals(url, Location(url).url()) + assert_equals(url, str(Location(url))) + + +def test_string_roundtrip(): + check_string_roundtrip("tag://org/course/category/name") + check_string_roundtrip("tag://org/course/category/name/revision") + check_string_roundtrip("tag://org/course/category/name with spaces/revision") + + +def test_dict(): + input_dict = { + 'tag': 'tag', + 'course': 'course', + 'category': 'category', + 'name': 'name', + 'org': 'org' + } + assert_equals("tag://org/course/category/name", Location(input_dict).url()) + assert_equals(dict(revision=None, **input_dict), Location(input_dict).dict()) + + input_dict['revision'] = 'revision' + assert_equals("tag://org/course/category/name/revision", Location(input_dict).url()) + assert_equals(input_dict, Location(input_dict).dict()) + + +def test_list(): + input_list = ['tag', 'org', 'course', 'category', 'name'] + assert_equals("tag://org/course/category/name", Location(input_list).url()) + assert_equals(input_list + [None], Location(input_list).list()) + + input_list.append('revision') + assert_equals("tag://org/course/category/name/revision", Location(input_list).url()) + assert_equals(input_list, Location(input_list).list()) + + +def test_location(): + input_list = ['tag', 'org', 'course', 'category', 'name'] + assert_equals("tag://org/course/category/name", Location(Location(input_list)).url()) + + +def test_invalid_locations(): + assert_raises(InvalidLocationError, Location, "foo") + assert_raises(InvalidLocationError, Location, ["foo", "bar"]) + assert_raises(InvalidLocationError, Location, ["foo", "bar", "baz", "blat", "foo/bar"]) + assert_raises(InvalidLocationError, Location, None) From 4dcca124835655ddbcf34b9d661b63f43eadf4a6 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:27:44 -0400 Subject: [PATCH 093/123] Fix string layout for readability --- cms/manage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cms/manage.py b/cms/manage.py index 3e4eedc9ff..f8773c0641 100644 --- a/cms/manage.py +++ b/cms/manage.py @@ -5,7 +5,9 @@ try: imp.find_module('settings') # Assumed to be in the same directory. except ImportError: import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. " + "It appears you've customized things.\nYou'll have to run django-admin.py, " + "passing it your settings module.\n" % __file__) sys.exit(1) import settings From 34fdce89025d8885824e15efa76afb1288cb336f Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:27:56 -0400 Subject: [PATCH 094/123] Remove unused urls --- cms/urls.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cms/urls.py b/cms/urls.py index 781c2c261f..d2e6415827 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -5,7 +5,5 @@ from django.conf.urls.defaults import patterns, url # admin.autodiscover() urlpatterns = patterns('', - url(r'^(?P[^/]+)/(?P[^/]+)/calendar/', 'contentstore.views.calendar', name='calendar'), - url(r'^accounts/login/', 'instructor.views.do_login', name='login'), url(r'^$', 'contentstore.views.index', name='index'), ) From 2aea7df602f3bc7d91127428db1d03540e59ccab Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:28:22 -0400 Subject: [PATCH 095/123] Remove unused code --- common/lib/keystore/__init__.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index 592cde7b4d..f5ca6f4164 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -165,19 +165,3 @@ class KeyStore(object): children: A list of child item identifiers """ raise NotImplementedError - - -class KeyStoreItem(object): - """ - An object from a KeyStore, which can be saved back to that keystore - """ - def __init__(self, location, children, data, editor, parents, revision): - self.location = location - self.children = children - self.data = data - self.editor = editor - self.parents = parents - self.revision = revision - - def save(self): - raise NotImplementedError From 05dcd76bf1707af6b637d4b768afafa805e9ee0f Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:29:48 -0400 Subject: [PATCH 096/123] Add documentation of mongo query syntax usage --- common/lib/keystore/mongo.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/lib/keystore/mongo.py b/common/lib/keystore/mongo.py index d9760909c9..9b6327c8e9 100644 --- a/common/lib/keystore/mongo.py +++ b/common/lib/keystore/mongo.py @@ -67,6 +67,9 @@ class MongoKeyStore(KeyStore): location: Something that can be passed to Location data: A nested dictionary of problem data """ + + # See http://www.mongodb.org/display/DOCS/Updating for + # atomic update syntax self.collection.update( {'location': Location(location).dict()}, {'$set': {'data': data}} @@ -80,6 +83,9 @@ class MongoKeyStore(KeyStore): location: Something that can be passed to Location children: A list of child item identifiers """ + + # See http://www.mongodb.org/display/DOCS/Updating for + # atomic update syntax self.collection.update( {'location': Location(location).dict()}, {'$set': {'children': children}} From b0b728c711e56550be8efd1e4943143e81925dbd Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:31:13 -0400 Subject: [PATCH 097/123] Cleanup intertwined descriptor and keystore code --- cms/envs/dev.py | 8 +-- cms/templates/widgets/navigation.html | 12 +++-- common/lib/keystore/__init__.py | 32 +++++------- common/lib/keystore/django.py | 16 +++--- common/lib/keystore/mongo.py | 47 +++++++++-------- common/lib/xmodule/seq_module.py | 17 ------- common/lib/xmodule/setup.py | 5 +- common/lib/xmodule/x_module.py | 72 +++++++++++++++++++++------ doc/overview.md | 34 +++++++++++-- 9 files changed, 147 insertions(+), 96 deletions(-) diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 332f52f145..16bed60729 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -7,9 +7,11 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG KEYSTORE = { - 'host': 'localhost', - 'db': 'mongo_base', - 'collection': 'key_store', + 'default': { + 'host': 'localhost', + 'db': 'mongo_base', + 'collection': 'key_store', + } } DATABASES = { diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html index 1f75dab470..2d5af9ead1 100644 --- a/cms/templates/widgets/navigation.html +++ b/cms/templates/widgets/navigation.html @@ -40,14 +40,18 @@

        ${week.name}

          - % for goal in week.get_goals(): -
        • ${goal.name}:${goal.data}
        • - % endfor + % if week.goals: + % for goal in week.goals: +
        • ${goal}
        • + % endfor + % else: +
        • Please create a learning goal for this week
        • + % endif
          - % for module in week.get_non_goals(): + % for module in week.get_children():
        • ${module.name} handle diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index f5ca6f4164..0e77a02a87 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -1,13 +1,6 @@ """ -This module provides an abstraction for working objects that conceptually have -the following attributes: - - location: An identifier for an item, of which there might be many revisions - children: A list of urls for other items required to fully define this object - data: A set of nested data needed to define this object - editor: The editor/owner of the object - parents: Url pointers for objects that this object was derived from - revision: What revision of the item this is +This module provides an abstraction for working with XModuleDescriptors +that are stored in a database an accessible using their Location as an identifier """ import re @@ -123,27 +116,26 @@ class Location(object): 'revision': self.revision} -class KeyStore(object): +class ModuleStore(object): + """ + An abstract interface for a database backend that stores XModuleDescriptor instances + """ def get_item(self, location): """ - Returns an XModuleDescriptor instance for the item at location + Returns an XModuleDescriptor instance for the item at location. + If location.revision is None, returns the most item with the most + recent revision + If any segment of the location is None except revision, raises + keystore.exceptions.InsufficientSpecificationError If no object is found at that location, raises keystore.exceptions.ItemNotFoundError - Searches for all matches of a partially specifed location, but raises an - keystore.exceptions.InsufficientSpecificationError if more - than a single object matches the query. - location: Something that can be passed to Location """ raise NotImplementedError + # TODO (cpennington): Replace with clone_item def create_item(self, location, editor): - """ - Create an empty item at the specified location with the supplied editor - - location: Something that can be passed to Location - """ raise NotImplementedError def update_item(self, location, data): diff --git a/common/lib/keystore/django.py b/common/lib/keystore/django.py index b88c74b8a3..2ba3f0756e 100644 --- a/common/lib/keystore/django.py +++ b/common/lib/keystore/django.py @@ -1,21 +1,21 @@ """ Module that provides a connection to the keystore specified in the django settings. -Passes settings.KEYSTORE as kwargs to MongoKeyStore +Passes settings.KEYSTORE as kwargs to MongoModuleStore """ from __future__ import absolute_import from django.conf import settings -from .mongo import MongoKeyStore +from .mongo import MongoModuleStore -_KEYSTORE = None +_KEYSTORES = {} -def keystore(): - global _KEYSTORE +def keystore(name='default'): + global _KEYSTORES - if _KEYSTORE is None: - _KEYSTORE = MongoKeyStore(**settings.KEYSTORE) + if name not in _KEYSTORES: + _KEYSTORES[name] = MongoModuleStore(**settings.KEYSTORE[name]) - return _KEYSTORE + return _KEYSTORES[name] diff --git a/common/lib/keystore/mongo.py b/common/lib/keystore/mongo.py index 9b6327c8e9..1bef298fde 100644 --- a/common/lib/keystore/mongo.py +++ b/common/lib/keystore/mongo.py @@ -1,12 +1,12 @@ import pymongo -from . import KeyStore, Location +from . import ModuleStore, Location from .exceptions import ItemNotFoundError, InsufficientSpecificationError -from xmodule.x_module import XModuleDescriptor +from xmodule.x_module import XModuleDescriptor, XModuleSystem -class MongoKeyStore(KeyStore): +class MongoModuleStore(ModuleStore): """ - A Mongodb backed KeyStore + A Mongodb backed ModuleStore """ def __init__(self, host, db, collection, port=27017): self.collection = pymongo.connection.Connection( @@ -19,34 +19,33 @@ class MongoKeyStore(KeyStore): def get_item(self, location): """ - Returns an XModuleDescriptor instance for the item at location + Returns an XModuleDescriptor instance for the item at location. + If location.revision is None, returns the most item with the most + recent revision + If any segment of the location is None except revision, raises + keystore.exceptions.InsufficientSpecificationError If no object is found at that location, raises keystore.exceptions.ItemNotFoundError - Searches for all matches of a partially specifed location, but raises an - keystore.exceptions.InsufficientSpecificationError if more - than a single object matches the query. - location: Something that can be passed to Location """ - query = dict( - ('location.{key}'.format(key=key), val) - for (key, val) - in Location(location).dict().items() - if val is not None - ) - items = self.collection.find( + + query = {} + for key, val in Location(location).dict().iteritems(): + if key != 'revision' and val is None: + raise InsufficientSpecificationError(location) + + if val is not None: + query['location.{key}'.format(key=key)] = val + + item = self.collection.find_one( query, sort=[('revision', pymongo.ASCENDING)], - limit=1, ) - if items.count() > 1: - raise InsufficientSpecificationError(location) - - if items.count() == 0: + if item is None: raise ItemNotFoundError(location) - return XModuleDescriptor.load_from_json(items[0], self.get_item) + return XModuleDescriptor.load_from_json(item, XModuleSystem(self.get_item)) def create_item(self, location, editor): """ @@ -72,7 +71,7 @@ class MongoKeyStore(KeyStore): # atomic update syntax self.collection.update( {'location': Location(location).dict()}, - {'$set': {'data': data}} + {'$set': {'definition.data': data}} ) def update_children(self, location, children): @@ -88,5 +87,5 @@ class MongoKeyStore(KeyStore): # atomic update syntax self.collection.update( {'location': Location(location).dict()}, - {'$set': {'children': children}} + {'$set': {'definition.children': children}} ) diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index 91ff6d2671..b394227aa7 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -97,22 +97,5 @@ class Module(XModule): self.rendered = False -class WeekDescriptor(XModuleDescriptor): - - def get_goals(self): - """ - Return a list of Goal XModuleDescriptors that are children - of this Week - """ - return [child for child in self.get_children() if child.type == 'Goal'] - - def get_non_goals(self): - """ - Return a list of non-Goal XModuleDescriptors that are children of - this Week - """ - return [child for child in self.get_children() if child.type != 'Goal'] - - class SectionDescriptor(XModuleDescriptor): pass diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 1140037259..7f3370ed37 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -5,10 +5,13 @@ setup( version="0.1", packages=find_packages(), install_requires=['distribute'], + + # See http://guide.python-distribute.org/creation.html#entry-points + # for a description of entry_points entry_points={ 'xmodule.v1': [ "Course = seq_module:SectionDescriptor", - "Week = seq_module:WeekDescriptor", + "Week = seq_module:SectionDescriptor", "Section = seq_module:SectionDescriptor", "LectureSequence = seq_module:SectionDescriptor", "Lab = seq_module:SectionDescriptor", diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index 3560eecbdc..9f960843d9 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -21,7 +21,7 @@ class Plugin(object): log.warning("Found multiple classes for {entry_point} with identifier {id}: {classes}. Returning the first one.".format( entry_point=cls.entry_point, id=identifier, - classes=", ".join([class_.module_name for class_ in classes]))) + classes=", ".join(class_.module_name for class_ in classes))) if len(classes) == 0: raise ModuleMissingError(identifier) @@ -125,47 +125,82 @@ class XModule(object): class XModuleDescriptor(Plugin): + """ + An XModuleDescriptor is a specification for an element of a course. This could + be a problem, an organizational element (a group of content), or a segment of video, + for example. + XModuleDescriptors are independent and agnostic to the current student state on a + problem. They handle the editing interface used by instructors to create a problem, + and can generate XModules (which do know about student state). + """ entry_point = "xmodule.v1" @staticmethod - def load_from_json(json_data, load_item): + def load_from_json(json_data, system): + """ + This method instantiates the correct subclass of XModuleDescriptor based + on the contents of json_data. + + json_data must contain a 'location' element, and must be suitable to be + passed into the subclasses `from_json` method. + """ class_ = XModuleDescriptor.load_class(json_data['location']['category']) - return class_.from_json(json_data, load_item) + return class_.from_json(json_data, system) @classmethod - def from_json(cls, json_data, load_item): + def from_json(cls, json_data, system): """ Creates an instance of this descriptor from the supplied json_data. + This may be overridden by subclasses json_data: Json data specifying the data, children, and metadata for the descriptor - load_item: A function that takes an i4x url and returns a module descriptor + system: An XModuleSystem for interacting with external resources """ - return cls(load_item=load_item, **json_data) + return cls(system=system, **json_data) def __init__(self, - load_item, - data=None, - children=None, + system, + definition=None, **kwargs): - self.load_item = load_item - self.data = data if data is not None else {} - self.children = children if children is not None else [] + """ + Construct a new XModuleDescriptor. The only required arguments are the + system, used for interaction with external resources, and the definition, + which specifies all the data needed to edit and display the problem (but none + of the associated metadata that handles recordkeeping around the problem). + + This allows for maximal flexibility to add to the interface while preserving + backwards compatibility. + + system: An XModuleSystem for interacting with external resources + definition: A dict containing `data` and `children` representing the problem definition + + Current arguments passed in kwargs: + location: A keystore.Location object indicating the name and ownership of this problem + goals: A list of strings of learning goals associated with this module + """ + self.system = system + self.definition = definition if definition is not None else {} self.name = Location(kwargs.get('location')).name self.type = Location(kwargs.get('location')).category + + # 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 + # to find the best teaching method + self.goals = kwargs.get('goals', []) + self._child_instances = None def get_children(self, categories=None): """Returns a list of XModuleDescriptor instances for the children of this module""" if self._child_instances is None: - self._child_instances = [self.load_item(child) for child in self.children] + self._child_instances = [self.system.load_item(child) for child in self.definition['children']] if categories is None: return self._child_instances else: return [child for child in self._child_instances if child.type in categories] - def get_xml(self): ''' For conversions between JSON and legacy XML representations. ''' @@ -192,3 +227,12 @@ class XModuleDescriptor(Plugin): # Full ==> what we edit # ''' # raise NotImplementedError + + +class DescriptorSystem(object): + def __init__(self, load_item): + """ + load_item: Takes a Location and returns and XModuleDescriptor + """ + + self.load_item = load_item diff --git a/doc/overview.md b/doc/overview.md index 304d5161b0..6d187dca91 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -44,10 +44,27 @@ You should be familiar with the following. If you're not, go read some docs... ### Common libraries -- x_modules -- generic learning modules. *x* can be sequence, video, template, html, vertical, capa, etc. These are the things that one puts inside sections in the course structure. Modules know how to render themselves to html, how to score themselves, and handle ajax calls from the front end. - - x_modules take a 'system context' parameter, which helps isolate xmodules from any particular application, so they can be used in many places. The modules should make no references to Django (though there are still a few left). The system context knows how to render things, track events, complain about 404s, etc. - - TODO: document the system context interface--it's different in `x_module.XModule.__init__` and in `x_module tests.py` (do this in the code, not here) - - in `common/lib/xmodule` +- xmodule: generic learning modules. *x* can be sequence, video, template, html, + vertical, capa, etc. These are the things that one puts inside sections + in the course structure. + + - XModuleDescriptor: This defines the problem and all data and UI needed to edit + that problem. It is unaware of any student data, but can be used to retrieve + an XModule, which is aware of that student state. + + - XModule: The XModule is a problem instance that is particular to a student. It knows + how to render itself to html to display the problem, how to score itself, + and how to handle ajax calls from the front end. + + - Both XModule and XModuleDescriptor take system context parameters. These are named + ModuleSystem and DescriptorSystem respectively. These help isolate the XModules + from any interactions with external resources that they require. + + For instance, the DescriptorSystem has a function to load an XModuleDescriptor + from a Location object, and the ModuleSystem knows how to render things, + track events, and complain about 404s + - TODO: document the system context interface--it's different in `x_module.XModule.__init__` and in `x_module tests.py` (do this in the code, not here) + - in `common/lib/xmodule` - capa modules -- defines `LoncapaProblem` and many related things. - in `common/lib/capa` @@ -76,7 +93,14 @@ The LMS is a django site, with root in `lms/`. It runs in many different enviro - See `lms/urls.py` for the wirings of urls to views. -- Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`. +- Tracking: there is support for basic tracking of client-side events in `lms/djangoapps/track`. + +### CMS + +The CMS is a django site, with root in `cms`. It can run in a number of different +environments, defined in `cms/envs`. + +- Core rendering path: Still TBD ### Other modules From 09d53f1018cde6173ab37e48e4277901888e5812 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:36:22 -0400 Subject: [PATCH 098/123] Change name of XModuleSystem to DescriptorSystem at usage sites --- common/lib/keystore/mongo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/keystore/mongo.py b/common/lib/keystore/mongo.py index 1bef298fde..29115a33a7 100644 --- a/common/lib/keystore/mongo.py +++ b/common/lib/keystore/mongo.py @@ -1,7 +1,7 @@ import pymongo from . import ModuleStore, Location from .exceptions import ItemNotFoundError, InsufficientSpecificationError -from xmodule.x_module import XModuleDescriptor, XModuleSystem +from xmodule.x_module import XModuleDescriptor, DescriptorSystem class MongoModuleStore(ModuleStore): @@ -45,7 +45,7 @@ class MongoModuleStore(ModuleStore): if item is None: raise ItemNotFoundError(location) - return XModuleDescriptor.load_from_json(item, XModuleSystem(self.get_item)) + return XModuleDescriptor.load_from_json(item, DescriptorSystem(self.get_item)) def create_item(self, location, editor): """ From 568cf10f7e1f72067e3a0f81a1a32f6a563dd46d Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 11:44:54 -0400 Subject: [PATCH 099/123] Remove reference to instructor module --- cms/envs/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 80056af1d2..c3c8ee85a8 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -142,5 +142,4 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'contentstore', - 'instructor', ) From 591175b6803b84ca7b72231d71422272176fbba3 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 14:17:05 -0400 Subject: [PATCH 100/123] Remove extra word in doc string --- common/lib/keystore/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index 0e77a02a87..fc06a4d780 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -123,7 +123,7 @@ class ModuleStore(object): def get_item(self, location): """ Returns an XModuleDescriptor instance for the item at location. - If location.revision is None, returns the most item with the most + If location.revision is None, returns the item with the most recent revision If any segment of the location is None except revision, raises From 189869154450a8a6b2b4f3e9aaf802804526328a Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 14:24:14 -0400 Subject: [PATCH 101/123] Add comment about None in Locations --- common/lib/keystore/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index fc06a4d780..2605424517 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -44,6 +44,9 @@ class Location(object): Location: another Location object None of the components of a location may contain the '/' character + + Components may be set to None, which may be interpreted by some contexts to mean + wildcard selection """ self.update(location) From d147638d07a90219042bf39c762113479fac53ba Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 14:12:03 -0400 Subject: [PATCH 102/123] Switch the cms over to using django-pipeline --- cms/envs/common.py | 63 +++++++++++++++++-- cms/models.py | 0 cms/static/sass/.gitignore | 1 + cms/templates/base.html | 6 ++ .../djangoapps/pipeline_mako/__init__.py | 6 +- .../pipeline_mako/templates/mako}/css.html | 0 .../templates/mako}/inline_js.html | 0 .../pipeline_mako/templates/mako}/js.html | 0 .../templates/static_content.html | 0 .../djangoapps}/static_replace.py | 0 lms/envs/common.py | 2 + requirements.txt | 2 +- 12 files changed, 72 insertions(+), 8 deletions(-) delete mode 100644 cms/models.py create mode 100644 cms/static/sass/.gitignore rename lms/lib/pipeline_mako.py => common/djangoapps/pipeline_mako/__init__.py (91%) rename {lms/templates/pipeline_mako => common/djangoapps/pipeline_mako/templates/mako}/css.html (100%) rename {lms/templates/pipeline_mako => common/djangoapps/pipeline_mako/templates/mako}/inline_js.html (100%) rename {lms/templates/pipeline_mako => common/djangoapps/pipeline_mako/templates/mako}/js.html (100%) rename {lms => common/djangoapps/pipeline_mako}/templates/static_content.html (100%) rename {lms/lib => common/djangoapps}/static_replace.py (100%) diff --git a/cms/envs/common.py b/cms/envs/common.py index c3c8ee85a8..20d49b7ac5 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -23,6 +23,12 @@ import sys import tempfile from path import path +############################ FEATURE CONFIGURATION ############################# + +MITX_FEATURES = { + 'USE_DJANGO_PIPELINE': True, +} + ############################# SET PATH INFORMATION ############################# PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms COMMON_ROOT = PROJECT_ROOT.dirname() / "common" @@ -43,7 +49,10 @@ sys.path.append(COMMON_ROOT / 'lib') # This is where we stick our compiled template files. MAKO_MODULE_DIR = tempfile.mkdtemp('mako') MAKO_TEMPLATES = {} -MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates'] +MAKO_TEMPLATES['main'] = [ + PROJECT_ROOT / 'templates', + COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates' +] MITX_ROOT_URL = '' @@ -59,8 +68,8 @@ TEMPLATE_CONTEXT_PROCESSORS = ( # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'staticfiles.finders.FileSystemFinder', + 'staticfiles.finders.AppDirectoriesFinder', ) # List of callables that know how to import templates from various sources. @@ -132,14 +141,60 @@ USE_L10N = True # Messages MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' +############################### Pipeline ####################################### + +STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' + +PIPELINE_CSS = { + 'base-style': { + 'source_filenames': ['sass/base-style.scss'], + 'output_filename': 'css/base-style.css', + }, +} + +PIPELINE_ALWAYS_RECOMPILE = ['sass/base-style.scss'] + +PIPELINE_JS = { +} + +PIPELINE_COMPILERS = [ + 'pipeline.compilers.sass.SASSCompiler', + 'pipeline.compilers.coffee.CoffeeScriptCompiler', +] + +PIPELINE_SASS_ARGUMENTS = '-t compressed -r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT) + +PIPELINE_CSS_COMPRESSOR = None +PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.yui.YUICompressor' + +STATICFILES_IGNORE_PATTERNS = ( + "sass/*", + "coffee/*", + "*.py", + "*.pyc" +) + +PIPELINE_YUI_BINARY = 'yui-compressor' +PIPELINE_SASS_BINARY = 'sass' +PIPELINE_COFFEE_SCRIPT_BINARY = 'coffee' + +# Setting that will only affect the MITx version of django-pipeline until our changes are merged upstream +PIPELINE_COMPILE_INPLACE = True + ############################ APPS ##################################### INSTALLED_APPS = ( + # Standard apps 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - 'django.contrib.staticfiles', + + # For CMS 'contentstore', + + # For asset pipelining + 'pipeline', + 'staticfiles', ) diff --git a/cms/models.py b/cms/models.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cms/static/sass/.gitignore b/cms/static/sass/.gitignore new file mode 100644 index 0000000000..b3a5267117 --- /dev/null +++ b/cms/static/sass/.gitignore @@ -0,0 +1 @@ +*.css diff --git a/cms/templates/base.html b/cms/templates/base.html index a23a31d9a5..271f73614d 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -1,10 +1,16 @@ +<%namespace name='static' file='static_content.html'/> + + % if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']: + <%static:css group='base-style'/> + % else: + % endif <%block name="title"></%block> diff --git a/lms/lib/pipeline_mako.py b/common/djangoapps/pipeline_mako/__init__.py similarity index 91% rename from lms/lib/pipeline_mako.py rename to common/djangoapps/pipeline_mako/__init__.py index 34e65a63ac..f100d95916 100644 --- a/lms/lib/pipeline_mako.py +++ b/common/djangoapps/pipeline_mako/__init__.py @@ -21,7 +21,7 @@ def compressed_css(package_name): return render_individual_css(package, paths) def render_css(package, path): - template_name = package.template_name or "pipeline_mako/css.html" + template_name = package.template_name or "mako/css.html" context = package.extra_context context.update({ 'type': guess_type(path, 'text/css'), @@ -50,7 +50,7 @@ def compressed_js(package_name): return render_individual_js(package, paths, templates) def render_js(package, path): - template_name = package.template_name or "pipeline_mako/js.html" + template_name = package.template_name or "mako/js.html" context = package.extra_context context.update({ 'type': guess_type(path, 'text/javascript'), @@ -63,7 +63,7 @@ def render_inline_js(package, js): context.update({ 'source': js }) - return render_to_string("pipeline_mako/inline_js.html", context) + return render_to_string("mako/inline_js.html", context) def render_individual_js(package, paths, templates=None): tags = [render_js(package, js) for js in paths] diff --git a/lms/templates/pipeline_mako/css.html b/common/djangoapps/pipeline_mako/templates/mako/css.html similarity index 100% rename from lms/templates/pipeline_mako/css.html rename to common/djangoapps/pipeline_mako/templates/mako/css.html diff --git a/lms/templates/pipeline_mako/inline_js.html b/common/djangoapps/pipeline_mako/templates/mako/inline_js.html similarity index 100% rename from lms/templates/pipeline_mako/inline_js.html rename to common/djangoapps/pipeline_mako/templates/mako/inline_js.html diff --git a/lms/templates/pipeline_mako/js.html b/common/djangoapps/pipeline_mako/templates/mako/js.html similarity index 100% rename from lms/templates/pipeline_mako/js.html rename to common/djangoapps/pipeline_mako/templates/mako/js.html diff --git a/lms/templates/static_content.html b/common/djangoapps/pipeline_mako/templates/static_content.html similarity index 100% rename from lms/templates/static_content.html rename to common/djangoapps/pipeline_mako/templates/static_content.html diff --git a/lms/lib/static_replace.py b/common/djangoapps/static_replace.py similarity index 100% rename from lms/lib/static_replace.py rename to common/djangoapps/static_replace.py diff --git a/lms/envs/common.py b/lms/envs/common.py index d3416f8127..ec2e1f81de 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -73,7 +73,9 @@ MAKO_TEMPLATES['course'] = [DATA_DIR] MAKO_TEMPLATES['sections'] = [DATA_DIR / 'sections'] MAKO_TEMPLATES['custom_tags'] = [DATA_DIR / 'custom_tags'] MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates', + COMMON_ROOT / 'templates', COMMON_ROOT / 'lib' / 'capa' / 'templates', + COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates', DATA_DIR / 'info', DATA_DIR / 'problems'] diff --git a/requirements.txt b/requirements.txt index 37a30b6cef..2618336630 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ python-memcached django-celery path.py django_debug_toolbar --e git+git://github.com/MITx/django-pipeline.git@incremental_compile#egg=django-pipeline +-e git+git://github.com/MITx/django-pipeline.git#egg=django-pipeline django-staticfiles>=1.2.1 django-masquerade fs From 592f124070e307743109290c79d2516e491c02f2 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 14:20:10 -0400 Subject: [PATCH 103/123] Remove old pre-compiled css --- cms/static/css/base-style.css | 1071 --------------------------------- cms/static/css/style.css | 0 2 files changed, 1071 deletions(-) delete mode 100644 cms/static/css/base-style.css delete mode 100644 cms/static/css/style.css diff --git a/cms/static/css/base-style.css b/cms/static/css/base-style.css deleted file mode 100644 index 9ebc6bd839..0000000000 --- a/cms/static/css/base-style.css +++ /dev/null @@ -1,1071 +0,0 @@ -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, -del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, -b, i, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - outline: 0; - vertical-align: baseline; - background: transparent; } - -html, body { - font-size: 100%; } - -article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { - display: block; } - -audio, canvas, video { - display: inline-block; } - -audio:not([controls]) { - display: none; } - -[hidden] { - display: none; } - -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; } - -html, button, input, select, textarea { - font-family: sans-serif; } - -a:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; } -a:hover, a:active { - outline: 0; } - -abbr[title] { - border-bottom: 1px dotted; } - -b, strong { - font-weight: bold; } - -blockquote { - margin: 1em 40px; } - -dfn { - font-style: italic; } - -mark { - background: #ff0; - color: #000; } - -pre, code, kbd, samp { - font-family: monospace, serif; - _font-family: 'courier new', monospace; - font-size: 1em; } - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; } - -blockquote, q { - quotes: none; } - blockquote:before, blockquote:after, q:before, q:after { - content: ''; - content: none; } - -small { - font-size: 75%; } - -sub, sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; } - -sup { - top: -0.5em; } - -sub { - bottom: -0.25em; } - -nav ul, nav ol { - list-style: none; - list-style-image: none; } - -img { - border: 0; - height: auto; - max-width: 100%; - -ms-interpolation-mode: bicubic; } - -svg:not(:root) { - overflow: hidden; } - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; } - -legend { - border: 0; - padding: 0; - white-space: normal; } - -button, input, select, textarea { - font-size: 100%; - margin: 0; - vertical-align: baseline; } - -button, input { - line-height: normal; } - -button, input[type="button"], input[type="reset"], input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; } - -button[disabled], input[disabled] { - cursor: default; } - -input[type="checkbox"], input[type="radio"] { - box-sizing: border-box; - padding: 0; } - -input[type="search"] { - -webkit-appearance: textfield; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - box-sizing: content-box; } - -input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; } - -button::-moz-focus-inner, input::-moz-focus-inner { - border: 0; - padding: 0; } - -textarea { - overflow: auto; - vertical-align: top; } - -table { - border-collapse: collapse; - border-spacing: 0; } - -html { - height: 100%; } - -body { - zoom: 1; - height: 100%; - font: 14px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } - body:before, body:after { - content: ""; - display: table; } - body:after { - clear: both; } - body > section { - display: table; - width: 100%; } - body > header { - background: #000; - color: #fff; - display: block; - float: none; - padding: 6px 20px; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - body > header nav { - zoom: 1; } - body > header nav:before, body > header nav:after { - content: ""; - display: table; } - body > header nav:after { - clear: both; } - body > header nav h2 { - font-size: 14px; - text-transform: uppercase; - float: left; } - body > header nav ul { - float: left; } - body > header nav ul.user-nav { - float: right; } - body > header nav ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-left: 15px; } - body.content section.main-content { - border-left: 2px solid #000; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 74.423%; - float: left; - -webkit-box-shadow: -2px 0 3px #dddddd; - -moz-box-shadow: -2px 0 3px #dddddd; - box-shadow: -2px 0 3px #dddddd; } - -a { - text-decoration: none; - color: #888; } - -input { - font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; } - -input[type="submit"], .button, section.cal section.new-section > a, section.week-edit > section.content > div section.modules.empty a, -section.week-new > section.content > div section.modules.empty a, -section.sequence-edit > section.content > div section.modules.empty a, section.week-edit > section.content > div section.scratch-pad ol li ul li.empty a, -section.week-new > section.content > div section.scratch-pad ol li ul li.empty a, -section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty a, section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button, section.video-new > section a.save-update, section.video-edit > section a.save-update, section.problem-new > section a.save, section.problem-edit > section a.save { - border: 1px solid #ccc; - background: #efefef; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - -ms-border-radius: 3px; - -o-border-radius: 3px; - border-radius: 3px; - padding: 6px; } - -.new-module { - position: relative; } - .new-module a { - padding: 6px; - display: block; } - .new-module ul.new-dropdown { - list-style: none; - position: absolute; } - .new-module ul.new-dropdown li { - display: none; - padding: 6px; } - .new-module:hover ul.new-dropdown { - display: block; } - -.draggable { - width: 7px; - min-height: 14px; - background: url("../img/drag-handle.png") no-repeat center; - text-indent: -9999px; - display: block; - float: right; } - -section.cal { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 25px; - zoom: 1; } - section.cal:before, section.cal:after { - content: ""; - display: table; } - section.cal:after { - clear: both; } - section.cal > header { - zoom: 1; - margin-bottom: 10px; - background: #efefef; - border: 1px solid #ddd; } - section.cal > header:before, section.cal > header:after { - content: ""; - display: table; } - section.cal > header:after { - clear: both; } - section.cal > header h2 { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - text-transform: uppercase; - letter-spacing: 1px; - font-size: 14px; - padding: 6px; - margin-left: 6px; - font-size: 12px; } - section.cal > header ul { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.cal > header ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-left: 6px; - padding-left: 6px; - border-left: 1px solid #ddd; - padding: 6px; } - section.cal > header ul li a { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.cal > header ul li ul { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.cal > header ul li ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - padding: 0; - border-left: 0; } - section.cal ol { - list-style: none; - zoom: 1; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - border-left: 1px solid #333; - border-top: 1px solid #333; - width: 100%; } - section.cal ol:before, section.cal ol:after { - content: ""; - display: table; } - section.cal ol:after { - clear: both; } - section.cal ol > li { - border-right: 1px solid #333; - border-bottom: 1px solid; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - float: left; - width: 25.0%; } - section.cal ol > li header { - border-bottom: 1px solid #000; - -webkit-box-shadow: 0 1px 2px #aaaaaa; - -moz-box-shadow: 0 1px 2px #aaaaaa; - box-shadow: 0 1px 2px #aaaaaa; - display: block; - margin-bottom: 2px; } - section.cal ol > li header h1 { - font-size: 14px; - text-transform: uppercase; - border-bottom: 1px solid #ccc; - padding: 6px; } - section.cal ol > li header h1 a { - color: #000; - display: block; } - section.cal ol > li header ul li { - background: #fff; - color: #888; - border-bottom: 0; - font-size: 12px; } - section.cal ol > li ul { - list-style: none; - margin-bottom: 1px; } - section.cal ol > li ul li { - background: #efefef; - border-bottom: 1px solid #666; - padding: 6px; } - section.cal ol > li ul li.create-module { - position: relative; } - section.cal ol > li ul li.create-module > div { - display: none; - position: absolute; - top: 30px; - width: 90%; - background: rgba(0, 0, 0, 0.9); - padding: 10px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - -ms-border-radius: 3px; - -o-border-radius: 3px; - border-radius: 3px; - z-index: 99; } - section.cal ol > li ul li.create-module > div:before { - content: " "; - display: block; - background: rgba(0, 0, 0, 0.8); - width: 10px; - height: 10px; - position: absolute; - top: -5px; - left: 50%; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); } - section.cal ol > li ul li.create-module > div ul li { - border-bottom: 0; - background: none; } - section.cal ol > li ul li.create-module > div ul li input { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - border-color: #000; - padding: 6px; } - section.cal ol > li ul li.create-module > div ul li select { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - section.cal ol > li ul li.create-module > div ul li select option { - font-size: 14px; } - section.cal ol > li ul li.create-module > div ul li a { - float: right; } - section.cal ol > li ul li.create-module > div ul li a:first-child { - float: left; } - section.cal ol > li ul li.create-module:hover div { - display: block; } - section.cal section.new-section { - margin-top: 10px; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - position: relative; } - section.cal section.new-section > a { - display: block; } - section.cal section.new-section section { - display: none; - position: absolute; - top: 30px; - background: rgba(0, 0, 0, 0.8); - min-width: 300px; - padding: 10px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - -ms-border-radius: 3px; - -o-border-radius: 3px; - border-radius: 3px; - z-index: 99; } - section.cal section.new-section section:before { - content: " "; - display: block; - background: rgba(0, 0, 0, 0.8); - width: 10px; - height: 10px; - position: absolute; - top: -5px; - left: 20%; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); } - section.cal section.new-section section form ul { - list-style: none; } - section.cal section.new-section section form ul li { - border-bottom: 0; - background: none; - margin-bottom: 6px; } - section.cal section.new-section section form ul li input { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - border-color: #000; - padding: 6px; } - section.cal section.new-section section form ul li select { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - section.cal section.new-section section form ul li select option { - font-size: 14px; } - section.cal section.new-section section form ul li a { - float: right; } - section.cal section.new-section section form ul li a:first-child { - float: left; } - section.cal section.new-section:hover section { - display: block; } - -body.content -section.cal { - width: 25.577%; - float: left; - overflow: scroll; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - opacity: .4; - -webkit-transition-property: all; - -moz-transition-property: all; - -ms-transition-property: all; - -o-transition-property: all; - transition-property: all; - -webkit-transition-duration: 0.15s; - -moz-transition-duration: 0.15s; - -ms-transition-duration: 0.15s; - -o-transition-duration: 0.15s; - transition-duration: 0.15s; - -webkit-transition-timing-function: ease-out; - -moz-transition-timing-function: ease-out; - -ms-transition-timing-function: ease-out; - -o-transition-timing-function: ease-out; - transition-timing-function: ease-out; - -webkit-transition-delay: 0; - -moz-transition-delay: 0; - -ms-transition-delay: 0; - -o-transition-delay: 0; - transition-delay: 0; } - body.content - section.cal > header ul { - display: none; } - body.content - section.cal:hover { - opacity: 1; } - body.content - section.cal ol li { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; } - body.content - section.cal ol li.create-module { - display: none; } - -section.week-edit > header, -section.week-new > header, -section.sequence-edit > header { - border-bottom: 2px solid #333; - zoom: 1; } - section.week-edit > header:before, section.week-edit > header:after, - section.week-new > header:before, - section.week-new > header:after, - section.sequence-edit > header:before, - section.sequence-edit > header:after { - content: ""; - display: table; } - section.week-edit > header:after, - section.week-new > header:after, - section.sequence-edit > header:after { - clear: both; } - section.week-edit > header div, - section.week-new > header div, - section.sequence-edit > header div { - zoom: 1; - padding: 6px 20px; } - section.week-edit > header div:before, section.week-edit > header div:after, - section.week-new > header div:before, - section.week-new > header div:after, - section.sequence-edit > header div:before, - section.sequence-edit > header div:after { - content: ""; - display: table; } - section.week-edit > header div:after, - section.week-new > header div:after, - section.sequence-edit > header div:after { - clear: both; } - section.week-edit > header div h1, - section.week-new > header div h1, - section.sequence-edit > header div h1 { - font-size: 18px; - text-transform: uppercase; - letter-spacing: 1px; - float: left; } - section.week-edit > header div p, - section.week-new > header div p, - section.sequence-edit > header div p { - float: right; } - section.week-edit > header div.week, - section.week-new > header div.week, - section.sequence-edit > header div.week { - background: #eee; - font-size: 12px; - border-bottom: 1px solid #ccc; } - section.week-edit > header div.week h2, - section.week-new > header div.week h2, - section.sequence-edit > header div.week h2 { - font-size: 12px; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-right: 20px; } - section.week-edit > header div.week ul, - section.week-new > header div.week ul, - section.sequence-edit > header div.week ul { - list-style: none; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.week-edit > header div.week ul li, - section.week-new > header div.week ul li, - section.sequence-edit > header div.week ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-right: 10px; } - section.week-edit > header div.week ul li p, - section.week-new > header div.week ul li p, - section.sequence-edit > header div.week ul li p { - float: none; } - section.week-edit > header section.goals, - section.week-new > header section.goals, - section.sequence-edit > header section.goals { - background: #eee; - padding: 6px 20px; - border-top: 1px solid #ccc; } - section.week-edit > header section.goals ul, - section.week-new > header section.goals ul, - section.sequence-edit > header section.goals ul { - list-style: none; - color: #999; } - section.week-edit > header section.goals ul li, - section.week-new > header section.goals ul li, - section.sequence-edit > header section.goals ul li { - margin-bottom: 6px; } - section.week-edit > header section.goals ul li:last-child, - section.week-new > header section.goals ul li:last-child, - section.sequence-edit > header section.goals ul li:last-child { - margin-bottom: 0; } -section.week-edit > section.content, -section.week-new > section.content, -section.sequence-edit > section.content { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 20px; } - section.week-edit > section.content section.filters, - section.week-new > section.content section.filters, - section.sequence-edit > section.content section.filters { - zoom: 1; - margin-bottom: 10px; - background: #efefef; - border: 1px solid #ddd; } - section.week-edit > section.content section.filters:before, section.week-edit > section.content section.filters:after, - section.week-new > section.content section.filters:before, - section.week-new > section.content section.filters:after, - section.sequence-edit > section.content section.filters:before, - section.sequence-edit > section.content section.filters:after { - content: ""; - display: table; } - section.week-edit > section.content section.filters:after, - section.week-new > section.content section.filters:after, - section.sequence-edit > section.content section.filters:after { - clear: both; } - section.week-edit > section.content section.filters ul, - section.week-new > section.content section.filters ul, - section.sequence-edit > section.content section.filters ul { - zoom: 1; - list-style: none; - padding: 6px; } - section.week-edit > section.content section.filters ul:before, section.week-edit > section.content section.filters ul:after, - section.week-new > section.content section.filters ul:before, - section.week-new > section.content section.filters ul:after, - section.sequence-edit > section.content section.filters ul:before, - section.sequence-edit > section.content section.filters ul:after { - content: ""; - display: table; } - section.week-edit > section.content section.filters ul:after, - section.week-new > section.content section.filters ul:after, - section.sequence-edit > section.content section.filters ul:after { - clear: both; } - section.week-edit > section.content section.filters ul li, - section.week-new > section.content section.filters ul li, - section.sequence-edit > section.content section.filters ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.week-edit > section.content section.filters ul li.advanced, - section.week-new > section.content section.filters ul li.advanced, - section.sequence-edit > section.content section.filters ul li.advanced { - float: right; } - section.week-edit > section.content > div, - section.week-new > section.content > div, - section.sequence-edit > section.content > div { - display: table; - border: 1px solid; - width: 100%; } - section.week-edit > section.content > div section header, - section.week-new > section.content > div section header, - section.sequence-edit > section.content > div section header { - background: #eee; - padding: 6px; - border-bottom: 1px solid #ccc; - zoom: 1; } - section.week-edit > section.content > div section header:before, section.week-edit > section.content > div section header:after, - section.week-new > section.content > div section header:before, - section.week-new > section.content > div section header:after, - section.sequence-edit > section.content > div section header:before, - section.sequence-edit > section.content > div section header:after { - content: ""; - display: table; } - section.week-edit > section.content > div section header:after, - section.week-new > section.content > div section header:after, - section.sequence-edit > section.content > div section header:after { - clear: both; } - section.week-edit > section.content > div section header h2, - section.week-new > section.content > div section header h2, - section.sequence-edit > section.content > div section header h2 { - text-transform: uppercase; - letter-spacing: 1px; - font-size: 12px; - float: left; } - section.week-edit > section.content > div section.modules, - section.week-new > section.content > div section.modules, - section.sequence-edit > section.content > div section.modules { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - display: table-cell; - width: 65.632%; - border-right: 1px solid #333; } - section.week-edit > section.content > div section.modules.empty, - section.week-new > section.content > div section.modules.empty, - section.sequence-edit > section.content > div section.modules.empty { - text-align: center; - vertical-align: middle; } - section.week-edit > section.content > div section.modules.empty a, - section.week-new > section.content > div section.modules.empty a, - section.sequence-edit > section.content > div section.modules.empty a { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-top: 10px; } - section.week-edit > section.content > div section.modules ol, - section.week-new > section.content > div section.modules ol, - section.sequence-edit > section.content > div section.modules ol { - list-style: none; - border-bottom: 1px solid #333; } - section.week-edit > section.content > div section.modules ol li, - section.week-new > section.content > div section.modules ol li, - section.sequence-edit > section.content > div section.modules ol li { - border-bottom: 1px solid #333; } - section.week-edit > section.content > div section.modules ol li:last-child, - section.week-new > section.content > div section.modules ol li:last-child, - section.sequence-edit > section.content > div section.modules ol li:last-child { - border-bottom: 0; } - section.week-edit > section.content > div section.modules ol li a, - section.week-new > section.content > div section.modules ol li a, - section.sequence-edit > section.content > div section.modules ol li a { - color: #000; } - section.week-edit > section.content > div section.modules ol li ol, - section.week-new > section.content > div section.modules ol li ol, - section.sequence-edit > section.content > div section.modules ol li ol { - list-style: none; } - section.week-edit > section.content > div section.modules ol li ol li, - section.week-new > section.content > div section.modules ol li ol li, - section.sequence-edit > section.content > div section.modules ol li ol li { - padding: 6px; } - section.week-edit > section.content > div section.modules ol li ol li:hover a.draggable, - section.week-new > section.content > div section.modules ol li ol li:hover a.draggable, - section.sequence-edit > section.content > div section.modules ol li ol li:hover a.draggable { - opacity: 1; } - section.week-edit > section.content > div section.modules ol li ol li a.draggable, - section.week-new > section.content > div section.modules ol li ol li a.draggable, - section.sequence-edit > section.content > div section.modules ol li ol li a.draggable { - float: right; - opacity: .5; } - section.week-edit > section.content > div section.modules ol li ol li.group, - section.week-new > section.content > div section.modules ol li ol li.group, - section.sequence-edit > section.content > div section.modules ol li ol li.group { - padding: 0; } - section.week-edit > section.content > div section.modules ol li ol li.group header, - section.week-new > section.content > div section.modules ol li ol li.group header, - section.sequence-edit > section.content > div section.modules ol li ol li.group header { - padding: 6px; - background: none; } - section.week-edit > section.content > div section.modules ol li ol li.group header h3, - section.week-new > section.content > div section.modules ol li ol li.group header h3, - section.sequence-edit > section.content > div section.modules ol li ol li.group header h3 { - font-size: 14px; } - section.week-edit > section.content > div section.modules ol li ol li.group ol, - section.week-new > section.content > div section.modules ol li ol li.group ol, - section.sequence-edit > section.content > div section.modules ol li ol li.group ol { - border-left: 4px solid #999; - border-bottom: 0; } - section.week-edit > section.content > div section.modules ol li ol li.group ol li:last-child, - section.week-new > section.content > div section.modules ol li ol li.group ol li:last-child, - section.sequence-edit > section.content > div section.modules ol li ol li.group ol li:last-child { - border-bottom: 0; } - section.week-edit > section.content > div section.scratch-pad, - section.week-new > section.content > div section.scratch-pad, - section.sequence-edit > section.content > div section.scratch-pad { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - display: table-cell; - width: 34.368%; - vertical-align: top; } - section.week-edit > section.content > div section.scratch-pad ol, - section.week-new > section.content > div section.scratch-pad ol, - section.sequence-edit > section.content > div section.scratch-pad ol { - list-style: none; - border-bottom: 1px solid #999; } - section.week-edit > section.content > div section.scratch-pad ol li, - section.week-new > section.content > div section.scratch-pad ol li, - section.sequence-edit > section.content > div section.scratch-pad ol li { - border-bottom: 1px solid #999; - background: #f9f9f9; } - section.week-edit > section.content > div section.scratch-pad ol li:last-child, - section.week-new > section.content > div section.scratch-pad ol li:last-child, - section.sequence-edit > section.content > div section.scratch-pad ol li:last-child { - border-bottom: 0; } - section.week-edit > section.content > div section.scratch-pad ol li ul, - section.week-new > section.content > div section.scratch-pad ol li ul, - section.sequence-edit > section.content > div section.scratch-pad ol li ul { - list-style: none; } - section.week-edit > section.content > div section.scratch-pad ol li ul li, - section.week-new > section.content > div section.scratch-pad ol li ul li, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li { - padding: 6px; } - section.week-edit > section.content > div section.scratch-pad ol li ul li:last-child, - section.week-new > section.content > div section.scratch-pad ol li ul li:last-child, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li:last-child { - border-bottom: 0; } - section.week-edit > section.content > div section.scratch-pad ol li ul li:hover a.draggable, - section.week-new > section.content > div section.scratch-pad ol li ul li:hover a.draggable, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li:hover a.draggable { - opacity: 1; } - section.week-edit > section.content > div section.scratch-pad ol li ul li.empty, - section.week-new > section.content > div section.scratch-pad ol li ul li.empty, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty { - padding: 12px; } - section.week-edit > section.content > div section.scratch-pad ol li ul li.empty a, - section.week-new > section.content > div section.scratch-pad ol li ul li.empty a, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li.empty a { - display: block; - text-align: center; } - section.week-edit > section.content > div section.scratch-pad ol li ul li a.draggable, - section.week-new > section.content > div section.scratch-pad ol li ul li a.draggable, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li a.draggable { - float: right; - opacity: .3; } - section.week-edit > section.content > div section.scratch-pad ol li ul li a, - section.week-new > section.content > div section.scratch-pad ol li ul li a, - section.sequence-edit > section.content > div section.scratch-pad ol li ul li a { - color: #000; } - -section.video-new > section section.upload, section.video-edit > section section.upload { - padding: 6px; - margin-bottom: 10px; - border: 1px solid #ddd; } - section.video-new > section section.upload a.upload-button, section.video-edit > section section.upload a.upload-button { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } -section.video-new > section section.in-use h2, section.video-edit > section section.in-use h2 { - font-size: 14px; } -section.video-new > section section.in-use div, section.video-edit > section section.in-use div { - background: #eee; - text-align: center; - padding: 6px; } -section.video-new > section a.save-update, section.video-edit > section a.save-update { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-top: 20px; } - -section.problem-new > section textarea, section.problem-edit > section textarea { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - display: block; - width: 100%; } -section.problem-new > section div.preview, section.problem-edit > section div.preview { - background: #eee; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - height: 40px; - padding: 10px; - width: 100%; } -section.problem-new > section a.save, section.problem-edit > section a.save { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin-top: 20px; } - -section.video-new, section.video-edit, section.problem-new, section.problem-edit { - position: absolute; - top: 72px; - right: 0; - background: #fff; - width: 48.845%; - -webkit-box-shadow: 0 0 6px #666666; - -moz-box-shadow: 0 0 6px #666666; - box-shadow: 0 0 6px #666666; - border: 1px solid #333; - border-right: 0; - z-index: 4; } - section.video-new > header, section.video-edit > header, section.problem-new > header, section.problem-edit > header { - background: #666; - zoom: 1; - color: #fff; - padding: 6px; - border-bottom: 1px solid #333; - -webkit-font-smoothing: antialiased; } - section.video-new > header:before, section.video-new > header:after, section.video-edit > header:before, section.video-edit > header:after, section.problem-new > header:before, section.problem-new > header:after, section.problem-edit > header:before, section.problem-edit > header:after { - content: ""; - display: table; } - section.video-new > header:after, section.video-edit > header:after, section.problem-new > header:after, section.problem-edit > header:after { - clear: both; } - section.video-new > header h2, section.video-edit > header h2, section.problem-new > header h2, section.problem-edit > header h2 { - float: left; - font-size: 14px; } - section.video-new > header a, section.video-edit > header a, section.problem-new > header a, section.problem-edit > header a { - color: #fff; } - section.video-new > header a.save-update, section.video-edit > header a.save-update, section.problem-new > header a.save-update, section.problem-edit > header a.save-update { - float: right; } - section.video-new > header a.cancel, section.video-edit > header a.cancel, section.problem-new > header a.cancel, section.problem-edit > header a.cancel { - float: left; } - section.video-new > section, section.video-edit > section, section.problem-new > section, section.problem-edit > section { - padding: 20px; } - section.video-new > section > header h1, section.video-edit > section > header h1, section.problem-new > section > header h1, section.problem-edit > section > header h1 { - font-size: 24px; - margin: 12px 0; } - section.video-new > section > header section.status-settings ul, section.video-edit > section > header section.status-settings ul, section.problem-new > section > header section.status-settings ul, section.problem-edit > section > header section.status-settings ul { - list-style: none; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - -ms-border-radius: 2px; - -o-border-radius: 2px; - border-radius: 2px; - border: 1px solid #999; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.video-new > section > header section.status-settings ul li, section.video-edit > section > header section.status-settings ul li, section.problem-new > section > header section.status-settings ul li, section.problem-edit > section > header section.status-settings ul li { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - border-right: 1px solid #999; - padding: 6px; } - section.video-new > section > header section.status-settings ul li:last-child, section.video-edit > section > header section.status-settings ul li:last-child, section.problem-new > section > header section.status-settings ul li:last-child, section.problem-edit > section > header section.status-settings ul li:last-child { - border-right: 0; } - section.video-new > section > header section.status-settings ul li.current, section.video-edit > section > header section.status-settings ul li.current, section.problem-new > section > header section.status-settings ul li.current, section.problem-edit > section > header section.status-settings ul li.current { - background: #eee; } - section.video-new > section > header section.status-settings a.settings, section.video-edit > section > header section.status-settings a.settings, section.problem-new > section > header section.status-settings a.settings, section.problem-edit > section > header section.status-settings a.settings { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; - margin: 0 20px; - border: 1px solid #999; - padding: 6px; } - section.video-new > section > header section.status-settings select, section.video-edit > section > header section.status-settings select, section.problem-new > section > header section.status-settings select, section.problem-edit > section > header section.status-settings select { - float: right; } - section.video-new > section > header section.meta, section.video-edit > section > header section.meta, section.problem-new > section > header section.meta, section.problem-edit > section > header section.meta { - background: #eee; - padding: 10px; - margin: 20px 0; - zoom: 1; } - section.video-new > section > header section.meta:before, section.video-new > section > header section.meta:after, section.video-edit > section > header section.meta:before, section.video-edit > section > header section.meta:after, section.problem-new > section > header section.meta:before, section.problem-new > section > header section.meta:after, section.problem-edit > section > header section.meta:before, section.problem-edit > section > header section.meta:after { - content: ""; - display: table; } - section.video-new > section > header section.meta:after, section.video-edit > section > header section.meta:after, section.problem-new > section > header section.meta:after, section.problem-edit > section > header section.meta:after { - clear: both; } - section.video-new > section > header section.meta div, section.video-edit > section > header section.meta div, section.problem-new > section > header section.meta div, section.problem-edit > section > header section.meta div { - float: left; - margin-right: 20px; } - section.video-new > section > header section.meta div h2, section.video-edit > section > header section.meta div h2, section.problem-new > section > header section.meta div h2, section.problem-edit > section > header section.meta div h2 { - font-size: 14px; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.video-new > section > header section.meta div p, section.video-edit > section > header section.meta div p, section.problem-new > section > header section.meta div p, section.problem-edit > section > header section.meta div p { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; } - section.video-new > section section.notes, section.video-edit > section section.notes, section.problem-new > section section.notes, section.problem-edit > section section.notes { - margin-top: 20px; - padding: 6px; - background: #eee; - border: 1px solid #ccc; } - section.video-new > section section.notes textarea, section.video-edit > section section.notes textarea, section.problem-new > section section.notes textarea, section.problem-edit > section section.notes textarea { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - display: block; - width: 100%; } - section.video-new > section section.notes h2, section.video-edit > section section.notes h2, section.problem-new > section section.notes h2, section.problem-edit > section section.notes h2 { - font-size: 14px; - margin-bottom: 6px; } - section.video-new > section section.notes input[type="submit"], section.video-edit > section section.notes input[type="submit"], section.problem-new > section section.notes input[type="submit"], section.problem-edit > section section.notes input[type="submit"] { - margin-top: 10px; } diff --git a/cms/static/css/style.css b/cms/static/css/style.css deleted file mode 100644 index e69de29bb2..0000000000 From a392d5a4c152219cfcb5fd0be1f666eb54082f12 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 19 Jun 2012 14:21:38 -0400 Subject: [PATCH 104/123] Rename test directory for consistancy --- common/lib/keystore/{test => tests}/test_location.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename common/lib/keystore/{test => tests}/test_location.py (100%) diff --git a/common/lib/keystore/test/test_location.py b/common/lib/keystore/tests/test_location.py similarity index 100% rename from common/lib/keystore/test/test_location.py rename to common/lib/keystore/tests/test_location.py From 3e2031cd43c633c9ca9d5b562282f7c262c8973c Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 19 Jun 2012 14:27:34 -0400 Subject: [PATCH 105/123] Update jQuery and jQuery-UI to latest version --- lms/static/js/vendor/jquery-ui.min.js | 125 ++++++++++++++++++++++++++ lms/static/js/vendor/jquery.min.js | 4 + lms/templates/main.html | 6 +- 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100755 lms/static/js/vendor/jquery-ui.min.js create mode 100644 lms/static/js/vendor/jquery.min.js diff --git a/lms/static/js/vendor/jquery-ui.min.js b/lms/static/js/vendor/jquery-ui.min.js new file mode 100755 index 0000000000..3fe9ccb7b7 --- /dev/null +++ b/lms/static/js/vendor/jquery-ui.min.js @@ -0,0 +1,125 @@ +/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.core.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.position.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.draggable.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
          ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f=k&&g<=l||h>=k&&h<=l||gl)&&(e>=i&&e<=j||f>=i&&f<=j||ej);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();g:for(var h=0;h
    • ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e
      ');h.css({zIndex:c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){if(c.disabled)return;a(this).removeClass("ui-resizable-autohide"),b._handles.show()},function(){if(c.disabled)return;b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}return this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement),this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");return a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b),!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);return l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui()),!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}return a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),ea.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;return p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null),a},_proportionallyResize:function(){var b=this.options;if(!this._proportionallyResizeElements.length)return;var c=this.helper||this.element;for(var d=0;d
      ');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.21"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!i)return;e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/d.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*d.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.selectable.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("
      ")},destroy:function(){return this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy(),this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(this.options.disabled)return;var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");return d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element}),!1}})},_mouseDrag:function(b){var c=this;this.dragged=!0;if(this.options.disabled)return;var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}return this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!i||i.element==c.element[0])return;var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.righth||i.bottome&&i.rightf&&i.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f)return e=a(this),!1});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}return this.currentItem=e,this._removeCurrentsFromItems(),!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));return a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b),!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}return this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(b,c){if(!b)return;a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"="),d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")}),d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+jf&&b+ka[this.floating?"width":"height"]?l:f0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){return this._refreshItems(a),this.refreshPositions(),this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return e||(b.style.visibility="hidden"),b},update:function(a,b){if(e&&!d.forcePlaceholderSize)return;b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.containers[d].floating?this.items[i].item.offset().left:this.items[i].item.offset().top;Math.abs(j-h)0?"down":"up")}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;return d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height()),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.leftthis.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.topthis.containment[3]?h-this.offset.click.topthis.containment[2]?i-this.offset.click.left=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");return(b.autoHeight||b.fillHeight)&&c.css("height",""),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(this.options.disabled||b.altKey||b.ctrlKey)return;var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}return f?(a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus(),!1):!0},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];return this._clickHandler({target:b},b),this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(d.disabled)return;if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!g)return;return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(this.running)return;this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data)}}),a.extend(a.ui.accordion,{version:"1.8.21",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size()){b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);return}if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.autocomplete.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("
        ").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length").data("item.autocomplete",c).append(a("").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend(""),d.secondary&&b.append(""),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.dialog.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
        ")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("
        ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
        ").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()
        ").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b
        ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;ic&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i),j===!1?!1:(this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0,!0))},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);return this._slide(a,this._handleIndex,c),!1},_mouseStop:function(a){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;return this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e,this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};return this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c1){this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);return}if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;return Math.abs(c)*2>=b&&(d+=c>0?b:-b),parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.tabs.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){function e(){return++c}function f(){return++d}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
        ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
      • #{label}
      • "},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash)return e.selected=a,!1}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1)return this.blur(),!1;e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected"))return e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur(),!1;if(!f.length)return e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur(),!1}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){return typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},destroy:function(){var b=this.options;return this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie),this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);return j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e])),this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();return d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0])),this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)==-1)return;return this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b])),this},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;return a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))),this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;return this.anchors.eq(a).trigger(this.options.event+".tabs"),this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}return this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs"),this},abort:function(){return this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup(),this},url:function(a,b){return this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b),this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.21"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a
        '))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
        ')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
        '+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
        ":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
        '+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
        '+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
        '+this._get(a,"weekHeader")+"
        '+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
        "+(j?""+(g[0]>0&&N==g[1]-1?'
        ':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
        ',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
        ",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.progressbar.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("
        ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){return a===b?this._value():(this._setOption("value",a),this)},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;return typeof a!="number"&&(a=0),Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.effects.core.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:(c=/rgba\(0, 0, 0, 0\)/.exec(b))?e.transparent:e[a.trim(b).toLowerCase()]}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};return a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete,[b,c,d,e]}function l(b){return!b||typeof b=="number"||a.fx.speeds[b]?!0:typeof b=="string"&&!a.effects[b]?!0:!1}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){return a.isFunction(d)&&(e=d,d=null),this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class")||"";a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.21",save:function(a,b){for(var c=0;c").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return b==0?c:b==e?c+d:(b/=e/2)<1?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){return(b/=e/2)<1?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.effects.fade.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.effects.fold.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.effects.highlight.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.effects.pulsate.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show"),e=(b.options.times||5)*2-1,f=b.duration?b.duration/2:a.fx.speeds._default/2,g=c.is(":visible"),h=0;g||(c.css("opacity",0).show(),h=1),(d=="hide"&&g||d=="show"&&!g)&&e--;for(var i=0;i').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);; \ No newline at end of file diff --git a/lms/static/js/vendor/jquery.min.js b/lms/static/js/vendor/jquery.min.js new file mode 100644 index 0000000000..16ad06c5ac --- /dev/null +++ b/lms/static/js/vendor/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
        a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
        "+""+"
        ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
        t
        ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
        ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

        ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
        ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
        ","
        "],thead:[1,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],col:[2,"","
        "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
        ","
        "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
        ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/lms/templates/main.html b/lms/templates/main.html index d611a73ddd..e0fc50304d 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -15,10 +15,10 @@ % endif - - - + + + % if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']: <%static:js group='application'/> From 40871e0eed8e7604bc369edc6cb7906cdaa6d87c Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 19 Jun 2012 16:19:45 -0400 Subject: [PATCH 106/123] Fix accordion rendering bug * needed to clean the chapter and section vars before rendering --- lms/djangoapps/courseware/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 7bc8b323ce..3478c88d4b 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -256,15 +256,13 @@ def index(request, course=None, chapter=None, section=None, {'content': module-error message} ''' # Can't modify variables of outer scope, so need new ones - chapter_ = clean(chapter) - section_ = clean(section) user = request.user - module_xml = get_module_xml(user, course, chapter_, section_) + module_xml = get_module_xml(user, course, chapter, section) if module_xml is None: log.exception("couldn't get module_xml: course/chapter/section: '%s/%s/%s'", - course, chapter_, section_) + course, chapter, section) return {'content' : render_to_string("module-error.html", {})} student_module_cache = preload_student_modules(module_xml) @@ -289,6 +287,9 @@ def index(request, course=None, chapter=None, section=None, # keep track of current course being viewed in django's request.session request.session['coursename'] = course + chapter = clean(chapter) + section = clean(section) + context = { 'csrf': csrf(request)['csrf_token'], 'accordion': render_accordion(request, course, chapter, section), From 4a6722778e9a5ecea463af25a586a9800ece9867 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 19 Jun 2012 16:37:33 -0400 Subject: [PATCH 107/123] remove obsolete comment --- lms/djangoapps/courseware/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 3478c88d4b..aa3d1b7781 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -255,8 +255,6 @@ def index(request, course=None, chapter=None, section=None, If there's an error, returns {'content': module-error message} ''' - # Can't modify variables of outer scope, so need new ones - user = request.user module_xml = get_module_xml(user, course, chapter, section) From 6dc481516c045311dd605c2070324c00cf2f3716 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 19 Jun 2012 18:15:34 -0400 Subject: [PATCH 108/123] Add Progress class and tests --- common/lib/xmodule/progress.py | 124 +++++++++++++++++++++++++++++++++ common/lib/xmodule/tests.py | 120 ++++++++++++++++++++++++++++++- common/lib/xmodule/x_module.py | 11 +++ 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 common/lib/xmodule/progress.py diff --git a/common/lib/xmodule/progress.py b/common/lib/xmodule/progress.py new file mode 100644 index 0000000000..1ce5d821f3 --- /dev/null +++ b/common/lib/xmodule/progress.py @@ -0,0 +1,124 @@ +''' +Progress class for modules. Represents where a student is in a module. +''' + +from collections import namedtuple +import numbers + +class Progress(object): + '''Represents a progress of a/b (a out of b done) + + a and b must be numeric, but not necessarily integer, with + 0 <= a <= b and b > 0. + + Progress can only represent Progress for modules where that makes sense. Other + modules (e.g. html) should return None from get_progress(). + ''' + + def __init__(self, a, b): + '''Construct a Progress object. a and b must be numbers, and must have + 0 <= a <= b and b > 0 + ''' + + # Want to do all checking at construction time, so explicitly check types + if not (isinstance(a, numbers.Number) and + isinstance(b, numbers.Number)): + raise TypeError('a and b must be numbers. Passed {0}/{1}'.format(a, b)) + + if not (0 <= a <= b and b > 0): + raise ValueError( + 'fraction a/b = {0}/{1} must have 0 <= a <= b and b > 0'.format(a, b)) + + self._a = a + self._b = b + + def frac(self): + ''' Return tuple (a,b) representing progress of a/b''' + return (self._a, self._b) + + def percent(self): + ''' Returns a percentage progress as a float between 0 and 100. + + subclassing note: implemented in terms of frac(), assumes sanity + checking is done at construction time. + ''' + (a, b) = self.frac() + return 100.0 * a / b + + def started(self): + ''' Returns True if fractional progress is greater than 0. + + subclassing note: implemented in terms of frac(), assumes sanity + checking is done at construction time. + ''' + return self.frac()[0] > 0 + + + def inprogress(self): + ''' Returns True if fractional progress is strictly between 0 and 1. + + subclassing note: implemented in terms of frac(), assumes sanity + checking is done at construction time. + ''' + (a, b) = self.frac() + return a > 0 and a < b + + def done(self): + ''' Return True if this represents done. + + subclassing note: implemented in terms of frac(), assumes sanity + checking is done at construction time. + ''' + (a, b) = self.frac() + return a==b + + + def ternary_str(self): + ''' Return a string version of this progress: either + "none", "in_progress", or "done". + + subclassing note: implemented in terms of frac() + ''' + (a, b) = self.frac() + if a == 0: + return "none" + if a < b: + return "in_progress" + return "done" + + def __eq__(self, other): + ''' Two Progress objects are equal if they have identical values. + Implemented in terms of frac()''' + if not isinstance(other, Progress): + return False + (a, b) = self.frac() + (a2, b2) = other.frac() + return a == a2 and b == b2 + + def __ne__(self, other): + ''' The opposite of equal''' + return not self.__eq__(other) + + + def __str__(self): + ''' Return a string representation of this string. + + subclassing note: implemented in terms of frac(). + ''' + (a, b) = self.frac() + return "{0}/{1}".format(a, b) + + @staticmethod + def add_counts(a, b): + '''Add two progress indicators, assuming that each represents items done: + (a / b) + (c / d) = (a + c) / (b + d). + If either is None, returns the other. + ''' + if a is None: + return b + if b is None: + return a + # get numerators + denominators + (n, d) = a.frac() + (n2, d2) = b.frac() + return Progress(n + n2, d + d2) diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 370b3befe5..73096ce8da 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -13,8 +13,9 @@ import numpy import xmodule import capa.calc as calc import capa.capa_problem as lcp -from xmodule import graders +from xmodule import graders, x_module from xmodule.graders import Score, aggregate_scores +from xmodule.progress import Progress from nose.plugins.skip import SkipTest class I4xSystem(object): @@ -26,7 +27,9 @@ class I4xSystem(object): def __init__(self): self.ajax_url = '/' self.track_function = lambda x: None + self.filestore = None self.render_function = lambda x: {} # Probably incorrect + self.module_from_xml = lambda x: None # May need a real impl... self.exception404 = Exception self.DEBUG = True def __repr__(self): @@ -488,3 +491,118 @@ class GraderTest(unittest.TestCase): #TODO: How do we test failure cases? The parser only logs an error when it can't parse something. Maybe it should throw exceptions? +# -------------------------------------------------------------------------- +# Module progress tests + +class ProgressTest(unittest.TestCase): + ''' Test that basic Progress objects work. A Progress represents a + fraction between 0 and 1. + ''' + not_started = Progress(0, 17) + part_done = Progress(2, 6) + half_done = Progress(3, 6) + also_half_done = Progress(1, 2) + done = Progress(7, 7) + + def test_create_object(self): + # These should work: + p = Progress(0, 2) + p = Progress(1, 2) + p = Progress(2, 2) + + p = Progress(2.5, 5.0) + p = Progress(3.7, 12.3333) + + # These shouldn't + self.assertRaises(ValueError, Progress, 0, 0) + self.assertRaises(ValueError, Progress, 2, 0) + self.assertRaises(ValueError, Progress, 1, -2) + self.assertRaises(ValueError, Progress, 3, 2) + self.assertRaises(ValueError, Progress, -2, 5) + + self.assertRaises(TypeError, Progress, 0, "all") + # check complex numbers just for the heck of it :) + self.assertRaises(TypeError, Progress, 2j, 3) + + def test_frac(self): + p = Progress(1, 2) + (a, b) = p.frac() + self.assertEqual(a, 1) + self.assertEqual(b, 2) + + def test_percent(self): + self.assertEqual(self.not_started.percent(), 0) + self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333) + self.assertEqual(self.half_done.percent(), 50) + self.assertEqual(self.done.percent(), 100) + + self.assertEqual(self.half_done.percent(), self.also_half_done.percent()) + + def test_started(self): + self.assertFalse(self.not_started.started()) + + self.assertTrue(self.part_done.started()) + self.assertTrue(self.half_done.started()) + self.assertTrue(self.done.started()) + + def test_inprogress(self): + # only true if working on it + self.assertFalse(self.done.inprogress()) + self.assertFalse(self.not_started.inprogress()) + + self.assertTrue(self.part_done.inprogress()) + self.assertTrue(self.half_done.inprogress()) + + def test_done(self): + self.assertTrue(self.done.done()) + self.assertFalse(self.half_done.done()) + self.assertFalse(self.not_started.done()) + + def test_str(self): + self.assertEqual(str(self.not_started), "0/17") + self.assertEqual(str(self.part_done), "2/6") + self.assertEqual(str(self.done), "7/7") + + def test_ternary_str(self): + self.assertEqual(self.not_started.ternary_str(), "none") + self.assertEqual(self.half_done.ternary_str(), "in_progress") + self.assertEqual(self.done.ternary_str(), "done") + + def test_add(self): + '''Test the Progress.add_counts() method''' + p = Progress(0, 2) + p2 = Progress(1, 3) + p3 = Progress(2, 5) + pNone = None + add = lambda a, b: Progress.add_counts(a, b).frac() + + self.assertEqual(add(p, p), (0, 4)) + self.assertEqual(add(p, p2), (1, 5)) + self.assertEqual(add(p2, p3), (3, 8)) + + self.assertEqual(add(p2, pNone), p2.frac()) + self.assertEqual(add(pNone, p2), p2.frac()) + + def test_equality(self): + '''Test that comparing Progress objects for equality + works correctly.''' + p = Progress(1, 2) + p2 = Progress(2, 4) + p3 = Progress(1, 2) + self.assertTrue(p == p3) + self.assertFalse(p == p2) + + # Check != while we're at it + self.assertTrue(p != p2) + self.assertFalse(p != p3) + + +class ModuleProgressTest(unittest.TestCase): + ''' Test that get_progress() does the right thing for the different modules + ''' + def test_xmodule_default(self): + '''Make sure default get_progress exists, returns None''' + xm = x_module.XModule(i4xs, "", "dummy") + p = xm.get_progress() + self.assertEqual(p, None) + diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index 9f960843d9..3a5bd05286 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -1,7 +1,9 @@ from lxml import etree import pkg_resources import logging + from keystore import Location +from progress import Progress log = logging.getLogger('mitx.' + __name__) @@ -118,6 +120,15 @@ class XModule(object): ''' return "Unimplemented" + def get_progress(self): + ''' Return a progress.Progress object that represents how far the student has gone + in this module. Must be implemented to get correct progress tracking behavior in + nesting modules like sequence and vertical. + + If this module has no notion of progress, return None. + ''' + return None + def handle_ajax(self, dispatch, get): ''' dispatch is last part of the URL. get is a dictionary-like object ''' From b5368f2a4ff71b835c083bd6f0aa45f43c9da1f2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 19 Jun 2012 18:16:14 -0400 Subject: [PATCH 109/123] Initial progress display. * add module_from_xml param to I4xSystem * use it to implement xmodule.get_children() * fix a few comments here and there * Render-time progress display for seq and vertical modules. - Computes fraction of subproblems done. * Pass problem state back to js during ajax calls. * general cleanup in capa_module.py * add progress_changed and progress fields to json returned from each ajax handler * Coffeescript changes to hook up sequence tracking of problem progress * net result: sequence 'a' tags now have a progress class * properly set css class on initial load * fire event when progress changes after ajax calls * also save state in 'progress' property of problems-wrapper tag * event handler finds those tags, computes updated progress --- common/lib/capa/capa_problem.py | 3 +- common/lib/xmodule/capa_module.py | 240 +++++++++++------- common/lib/xmodule/progress.py | 2 + common/lib/xmodule/seq_module.py | 30 ++- common/lib/xmodule/vertical_module.py | 9 + common/lib/xmodule/x_module.py | 8 + lms/djangoapps/courseware/grades.py | 2 + lms/djangoapps/courseware/module_render.py | 38 ++- lms/static/coffee/src/modules/problem.coffee | 21 +- lms/static/coffee/src/modules/sequence.coffee | 59 ++++- 10 files changed, 309 insertions(+), 103 deletions(-) diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py index 4ee2a2113d..a06ac1d7b6 100644 --- a/common/lib/capa/capa_problem.py +++ b/common/lib/capa/capa_problem.py @@ -169,7 +169,8 @@ class LoncapaProblem(object): def get_score(self): ''' Compute score for this problem. The score is the number of points awarded. - Returns an integer, from 0 to get_max_score(). + Returns a dictionary {'score': integer, from 0 to get_max_score(), + 'total': get_max_score()}. ''' correct = 0 for key in self.correct_map: diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 55534f8a3e..7c75d1666a 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -11,6 +11,7 @@ from datetime import timedelta from lxml import etree from x_module import XModule, XModuleDescriptor +from progress import Progress from capa.capa_problem import LoncapaProblem from capa.responsetypes import StudentInputError @@ -79,24 +80,41 @@ class Module(XModule): def get_xml_tags(c): return ["problem"] + def get_state(self): state = self.lcp.get_state() state['attempts'] = self.attempts return json.dumps(state) + def get_score(self): return self.lcp.get_score() + def max_score(self): return self.lcp.get_max_score() + + def get_progress(self): + ''' For now, just return score / max_score + ''' + d = self.get_score() + score = d['score'] + total = d['total'] + return Progress(score, total) + + def get_html(self): return self.system.render_template('problem_ajax.html', { 'id': self.item_id, 'ajax_url': self.ajax_url, }) + def get_problem_html(self, encapsulate=True): + '''Return html for the problem. Adds check, reset, save buttons + as necessary based on the problem config and state.''' + html = self.lcp.get_html() content = {'name': self.name, 'html': html, @@ -109,7 +127,7 @@ class Module(XModule): reset_button = True save_button = True - # If we're after deadline, or user has exhuasted attempts, + # If we're after deadline, or user has exhausted attempts, # question is read-only. if self.closed(): check_button = False @@ -154,11 +172,13 @@ class Module(XModule): 'attempts_used': self.attempts, 'attempts_allowed': self.max_attempts, 'explain': explain, + 'progress': self.get_progress(), } html = self.system.render_template('problem.html', context) if encapsulate: - html = '
        '.format(id=self.item_id, ajax_url=self.ajax_url) + html + "
        " + html = '
        '.format( + id=self.item_id, ajax_url=self.ajax_url) + html + "
        " return html @@ -170,7 +190,8 @@ class Module(XModule): dom2 = etree.fromstring(xml) - self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'), default="closed") + self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'), + default="closed") # TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed") self.explain_available = only_one(dom2.xpath('/problem/@explain_available')) @@ -190,19 +211,19 @@ class Module(XModule): self.grace_period = None self.close_date = self.display_due_date - self.max_attempts =only_one(dom2.xpath('/problem/@attempts')) - if len(self.max_attempts)>0: - self.max_attempts =int(self.max_attempts) + self.max_attempts = only_one(dom2.xpath('/problem/@attempts')) + if len(self.max_attempts) > 0: + self.max_attempts = int(self.max_attempts) else: - self.max_attempts =None + self.max_attempts = None - self.show_answer =only_one(dom2.xpath('/problem/@showanswer')) + self.show_answer = only_one(dom2.xpath('/problem/@showanswer')) - if self.show_answer =="": - self.show_answer ="closed" + if self.show_answer == "": + self.show_answer = "closed" - self.rerandomize =only_one(dom2.xpath('/problem/@rerandomize')) - if self.rerandomize =="" or self.rerandomize=="always" or self.rerandomize=="true": + self.rerandomize = only_one(dom2.xpath('/problem/@rerandomize')) + if self.rerandomize == "" or self.rerandomize=="always" or self.rerandomize=="true": self.rerandomize="always" elif self.rerandomize=="false" or self.rerandomize=="per_student": self.rerandomize="per_student" @@ -253,23 +274,33 @@ class Module(XModule): def handle_ajax(self, dispatch, get): ''' - This is called by courseware.module_render, to handle an AJAX call. "get" is request.POST + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } ''' - if dispatch=='problem_get': - response = self.get_problem(get) - elif False: #self.close_date > - return json.dumps({"error":"Past due date"}) - elif dispatch=='problem_check': - response = self.check_problem(get) - elif dispatch=='problem_reset': - response = self.reset_problem(get) - elif dispatch=='problem_save': - response = self.save_problem(get) - elif dispatch=='problem_show': - response = self.get_answer(get) - else: - return "Error" - return response + handlers = { + 'problem_get': self.get_problem, + 'problem_check': self.check_problem, + 'problem_reset': self.reset_problem, + 'problem_save': self.save_problem, + 'problem_show': self.get_answer, + } + + if dispatch not in handlers: + return 'Error' + + before = self.get_progress() + d = handlers[dispatch](get) + after = self.get_progress() + d.update({ + 'progress_changed' : after != before, + 'progress' : after.ternary_str(), + }) + return json.dumps(d, cls=ComplexEncoder) def closed(self): ''' Is the student still allowed to submit answers? ''' @@ -283,24 +314,22 @@ class Module(XModule): def answer_available(self): ''' Is the user allowed to see an answer? - TODO: simplify. ''' if self.show_answer == '': return False + if self.show_answer == "never": return False - if self.show_answer == 'attempted' and self.attempts == 0: - return False - if self.show_answer == 'attempted' and self.attempts > 0: - return True - if self.show_answer == 'answered' and self.lcp.done: - return True - if self.show_answer == 'answered' and not self.lcp.done: - return False - if self.show_answer == 'closed' and self.closed(): - return True - if self.show_answer == 'closed' and not self.closed(): - return False + + if self.show_answer == 'attempted': + return self.attempts > 0 + + if self.show_answer == 'answered': + return self.lcp.done + + if self.show_answer == 'closed': + return self.closed() + if self.show_answer == 'always': return True raise self.system.exception404 #TODO: Not 404 @@ -310,45 +339,64 @@ class Module(XModule): For the "show answer" button. TODO: show answer events should be logged here, not just in the problem.js + + Returns the answers: {'answers' : answers} ''' if not self.answer_available(): raise self.system.exception404 else: answers = self.lcp.get_question_answers() - return json.dumps(answers, - cls=ComplexEncoder) + return {'answers' : answers} + # Figure out if we should move these to capa_problem? def get_problem(self, get): - ''' Same as get_problem_html -- if we want to reconfirm we - have the right thing e.g. after several AJAX calls.''' - return self.get_problem_html(encapsulate=False) + ''' Return results of get_problem_html, as a simple dict for json-ing. + { 'html': } + + Used if we want to reconfirm we have the right thing e.g. after + several AJAX calls. + ''' + return {'html' : self.get_problem_html(encapsulate=False)} + + @staticmethod + def make_dict_of_responses(get): + '''Make dictionary of student responses (aka "answers") + get is POST dictionary. + ''' + answers = dict() + for key in get: + # e.g. input_resistor_1 ==> resistor_1 + answers['_'.join(key.split('_')[1:])] = get[key] + + return answers def check_problem(self, get): ''' Checks whether answers to a problem are correct, and - returns a map of correct/incorrect answers''' + returns a map of correct/incorrect answers: + + {'success' : bool, + 'contents' : html} + ''' event_info = dict() event_info['state'] = self.lcp.get_state() event_info['filename'] = self.filename - # make a dict of all the student responses ("answers"). - answers=dict() - # input_resistor_1 ==> resistor_1 - for key in get: - answers['_'.join(key.split('_')[1:])]=get[key] + answers = self.make_dict_of_responses(get) - event_info['answers']=answers + event_info['answers'] = answers # Too late. Cannot submit if self.closed(): - event_info['failure']='closed' + event_info['failure'] = 'closed' self.tracker('save_problem_check_fail', event_info) + # TODO: probably not 404? raise self.system.exception404 # Problem submitted. Student should reset before checking # again. if self.lcp.done and self.rerandomize == "always": - event_info['failure']='unreset' + event_info['failure'] = 'unreset' self.tracker('save_problem_check_fail', event_info) raise self.system.exception404 @@ -357,89 +405,107 @@ class Module(XModule): lcp_id = self.lcp.problem_id correct_map = self.lcp.grade_answers(answers) except StudentInputError as inst: - self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state, system=self.system) + # TODO: why is this line here? + self.lcp = LoncapaProblem(self.filestore.open(self.filename), + id=lcp_id, state=old_state, system=self.system) traceback.print_exc() - return json.dumps({'success':inst.message}) + return {'success': inst.message} except: - self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state, system=self.system) + # TODO: why is this line here? + self.lcp = LoncapaProblem(self.filestore.open(self.filename), + id=lcp_id, state=old_state, system=self.system) traceback.print_exc() raise Exception,"error in capa_module" - return json.dumps({'success':'Unknown Error'}) + # TODO: Dead code... is this a bug, or just old? + return {'success':'Unknown Error'} self.attempts = self.attempts + 1 - self.lcp.done=True + self.lcp.done = True - success = 'correct' # success = correct if ALL questions in this problem are correct + # success = correct if ALL questions in this problem are correct + success = 'correct' for answer_id in correct_map: if not correct_map.is_correct(answer_id): success = 'incorrect' - event_info['correct_map']=correct_map.get_dict() # log this in the tracker - event_info['success']=success + event_info['correct_map'] = correct_map.get_dict() # log this in the tracker + event_info['success'] = success self.tracker('save_problem_check', event_info) try: html = self.get_problem_html(encapsulate=False) # render problem into HTML except Exception,err: log.error('failed to generate html') - raise Exception,err + raise Exception, err + + return {'success': success, + 'contents': html, + } - return json.dumps({'success': success, - 'contents': html, - }) def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' event_info = dict() event_info['state'] = self.lcp.get_state() event_info['filename'] = self.filename - answers=dict() - for key in get: - answers['_'.join(key.split('_')[1:])]=get[key] + answers = self.make_dict_of_responses(get) event_info['answers'] = answers # Too late. Cannot submit if self.closed(): - event_info['failure']='closed' + event_info['failure'] = 'closed' self.tracker('save_problem_fail', event_info) - return "Problem is closed" + return {'success': False, + 'error': "Problem is closed"} # Problem submitted. Student should reset before saving # again. if self.lcp.done and self.rerandomize == "always": - event_info['failure']='done' + event_info['failure'] = 'done' self.tracker('save_problem_fail', event_info) - return "Problem needs to be reset prior to save." + return {'success' : False, + 'error' : "Problem needs to be reset prior to save."} - self.lcp.student_answers=answers + self.lcp.student_answers = answers + # TODO: should this be save_problem_fail? Looks like success to me... self.tracker('save_problem_fail', event_info) - return json.dumps({'success':True}) + return {'success': True} def reset_problem(self, get): ''' Changes problem state to unfinished -- removes student answers, - and causes problem to rerender itself. ''' + and causes problem to rerender itself. + + Returns problem html as { 'html' : html-string }. + ''' event_info = dict() - event_info['old_state']=self.lcp.get_state() - event_info['filename']=self.filename + event_info['old_state'] = self.lcp.get_state() + event_info['filename'] = self.filename if self.closed(): - event_info['failure']='closed' + event_info['failure'] = 'closed' self.tracker('reset_problem_fail', event_info) return "Problem is closed" if not self.lcp.done: - event_info['failure']='not_done' + event_info['failure'] = 'not_done' self.tracker('reset_problem_fail', event_info) return "Refresh the page and make an attempt before resetting." - self.lcp.do_reset() # call method in LoncapaProblem to reset itself + self.lcp.do_reset() if self.rerandomize == "always": - self.lcp.seed=None # reset random number generator seed (note the self.lcp.get_state() in next line) - - self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, self.lcp.get_state(), system=self.system) + # reset random number generator seed (note the self.lcp.get_state() in next line) + self.lcp.seed=None + + self.lcp = LoncapaProblem(self.filestore.open(self.filename), + self.item_id, self.lcp.get_state(), system=self.system) - event_info['new_state']=self.lcp.get_state() + event_info['new_state'] = self.lcp.get_state() self.tracker('reset_problem', event_info) - return json.dumps(self.get_problem_html(encapsulate=False)) + return {'html' : self.get_problem_html(encapsulate=False)} diff --git a/common/lib/xmodule/progress.py b/common/lib/xmodule/progress.py index 1ce5d821f3..b9e242f2b2 100644 --- a/common/lib/xmodule/progress.py +++ b/common/lib/xmodule/progress.py @@ -13,6 +13,8 @@ class Progress(object): Progress can only represent Progress for modules where that makes sense. Other modules (e.g. html) should return None from get_progress(). + + TODO: add tag for module type? Would allow for smarter merging. ''' def __init__(self, a, b): diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index b394227aa7..598fc4443e 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -1,8 +1,12 @@ import json +import logging from lxml import etree from x_module import XModule, XModuleDescriptor +from xmodule.progress import Progress + +log = logging.getLogger("mitx.common.lib.seq_module") # HACK: This shouldn't be hard-coded to two types # OBSOLETE: This obsoletes 'type' @@ -37,6 +41,16 @@ class Module(XModule): self.render() return self.destroy_js + def get_progress(self): + ''' Return the total progress, adding total done and total available. + (assumes that each submodule uses the same "units" for progress.) + ''' + # TODO: Cache progress or children array? + children = self.get_children() + progresses = [child.get_progress() for child in children] + progress = reduce(Progress.add_counts, progresses) + return progress + def handle_ajax(self, dispatch, get): # TODO: bounds checking ''' get = request.POST instance ''' if dispatch=='goto_position': @@ -53,10 +67,15 @@ class Module(XModule): titles = ["\n".join([i.get("name").strip() for i in e.iter() if i.get("name") is not None]) \ for e in self.xmltree] + children = self.get_children() + progresses = [child.get_progress() for child in children] + self.contents = self.rendered_children() - for contents, title in zip(self.contents, titles): + for contents, title, progress in zip(self.contents, titles, progresses): contents['title'] = title + contents['progress_str'] = str(progress) if progress is not None else "" + contents['progress_stat'] = progress.ternary_str() if progress is not None else "" for (content, element_class) in zip(self.contents, child_classes): new_class = 'other' @@ -68,16 +87,17 @@ class Module(XModule): # Split tags -- browsers handle this as end # of script, even if it occurs mid-string. Do this after json.dumps()ing # so that we can be sure of the quotations being used - params={'items':json.dumps(self.contents).replace('', '<"+"/script>'), - 'id':self.item_id, + params={'items': json.dumps(self.contents).replace('', '<"+"/script>'), + 'id': self.item_id, 'position': self.position, - 'titles':titles, - 'tag':self.xmltree.tag} + 'titles': titles, + 'tag': self.xmltree.tag} if self.xmltree.tag in ['sequential', 'videosequence']: self.content = self.system.render_template('seq_module.html', params) if self.xmltree.tag == 'tab': self.content = self.system.render_template('tab_module.html', params) + log.debug("rendered content: %s", content) self.rendered = True def __init__(self, system, xml, item_id, state=None): diff --git a/common/lib/xmodule/vertical_module.py b/common/lib/xmodule/vertical_module.py index 7eb6dda0b8..b3feec8bae 100644 --- a/common/lib/xmodule/vertical_module.py +++ b/common/lib/xmodule/vertical_module.py @@ -1,12 +1,14 @@ import json from x_module import XModule, XModuleDescriptor +from xmodule.progress import Progress from lxml import etree class ModuleDescriptor(XModuleDescriptor): pass class Module(XModule): + ''' Layout module for laying out submodules vertically.''' id_attribute = 'id' def get_state(self): @@ -21,6 +23,13 @@ class Module(XModule): 'items': self.contents }) + def get_progress(self): + # TODO: Cache progress or children array? + children = self.get_children() + progresses = [child.get_progress() for child in children] + progress = reduce(Progress.add_counts, progresses) + return progress + def __init__(self, system, xml, item_id, state=None): XModule.__init__(self, system, xml, item_id, state) xmltree=etree.fromstring(xml) diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py index 3a5bd05286..043a830500 100644 --- a/common/lib/xmodule/x_module.py +++ b/common/lib/xmodule/x_module.py @@ -59,6 +59,13 @@ class XModule(object): else: raise "We should iterate through children and find a default name" + def get_children(self): + ''' + Return module instances for all the children of this module. + ''' + children = [self.module_from_xml(e) for e in self.__xmltree] + return children + def rendered_children(self): ''' Render all children. @@ -92,6 +99,7 @@ class XModule(object): self.tracker = system.track_function self.filestore = system.filestore self.render_function = system.render_function + self.module_from_xml = system.module_from_xml self.DEBUG = system.DEBUG self.system = system diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index 354cf5991f..4a11ec2d51 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -174,6 +174,8 @@ def get_score(user, problem, cache, coursename=None): else: ## HACK 1: We shouldn't specifically reference capa_module ## HACK 2: Backwards-compatibility: This should be written when a grade is saved, and removed from the system + # TODO: These are no longer correct params for I4xSystem -- figure out what this code + # does, clean it up. from module_render import I4xSystem system = I4xSystem(None, None, None, coursename=coursename) total=float(xmodule.capa_module.Module(system, etree.tostring(problem), "id").max_score()) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index af5fec6b85..9b5e7e4940 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -34,7 +34,8 @@ class I4xSystem(object): and user, or other environment-specific info. ''' def __init__(self, ajax_url, track_function, render_function, - render_template, request=None, filestore=None): + module_from_xml, render_template, request=None, + filestore=None): ''' Create a closure around the system environment. @@ -43,6 +44,8 @@ class I4xSystem(object): or otherwise tracking the event. TODO: Not used, and has inconsistent args in different files. Update or remove. + module_from_xml - function that takes (module_xml) and returns a corresponding + module instance object. render_function - function that takes (module_xml) and renders it, returning a dictionary with a context for rendering the module to html. Dictionary will contain keys 'content' @@ -62,6 +65,7 @@ class I4xSystem(object): if settings.DEBUG: log.info("[courseware.module_render.I4xSystem] filestore path = %s", filestore) + self.module_from_xml = module_from_xml self.render_function = render_function self.render_template = render_template self.exception404 = Http404 @@ -127,6 +131,18 @@ def grade_histogram(module_id): return [] return grades + +def make_module_from_xml_fn(user, request, student_module_cache, position): + '''Create the make_from_xml() function''' + def module_from_xml(xml): + '''Modules need a way to convert xml to instance objects. + Pass the rest of the context through.''' + (instance, sm, module_type) = get_module( + user, request, xml, student_module_cache, position) + return instance + return module_from_xml + + def get_module(user, request, module_xml, student_module_cache, position=None): ''' Get an instance of the xmodule class corresponding to module_xml, setting the state based on an existing StudentModule, or creating one if none @@ -165,6 +181,9 @@ def get_module(user, request, module_xml, student_module_cache, position=None): # Setup system context for module instance ajax_url = settings.MITX_ROOT_URL + '/modx/' + module_type + '/' + module_id + '/' + module_from_xml = make_module_from_xml_fn( + user, request, student_module_cache, position) + system = I4xSystem(track_function = make_track_function(request), render_function = lambda xml: render_x_module( user, request, xml, student_module_cache, position), @@ -172,6 +191,7 @@ def get_module(user, request, module_xml, student_module_cache, position=None): ajax_url = ajax_url, request = request, filestore = OSFS(data_root), + module_from_xml = module_from_xml, ) # pass position specified in URL to module through I4xSystem system.set('position', position) @@ -295,9 +315,17 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): response = HttpResponse(json.dumps({'success': error_msg})) return response + # TODO: This doesn't have a cache of child student modules. Just + # passing the current one. If ajax calls end up needing children, + # this won't work (but fixing it may cause performance issues...) + # Figure out :) + module_from_xml = make_module_from_xml_fn( + request.user, request, [s], None) + # Create the module system = I4xSystem(track_function = make_track_function(request), - render_function = None, + render_function = None, + module_from_xml = module_from_xml, render_template = render_to_string, ajax_url = ajax_url, request = request, @@ -316,7 +344,11 @@ def modx_dispatch(request, module=None, dispatch=None, id=None): return response # Let the module handle the AJAX - ajax_return = instance.handle_ajax(dispatch, request.POST) + try: + ajax_return = instance.handle_ajax(dispatch, request.POST) + except: + log.exception("error processing ajax call") + raise # Save the state back to the database s.state = instance.get_state() diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index aad41f23d4..1b254d5d3f 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -17,12 +17,20 @@ class @Problem @$('section.action input.save').click @save @$('input.math').keyup(@refreshMath).each(@refreshMath) + update_progress: (response) => + if response.progress_changed + @element.attr progress: response.progress + @element.trigger('progressChanged') + render: (content) -> if content @element.html(content) @bind() else - @element.load @content_url, @bind + $.postWithPrefix "/modx/problem/#{@id}/problem_get", '', (response) => + @element.html(response.html) + @bind() + check: => Logger.log 'problem_check', @answers @@ -30,19 +38,22 @@ class @Problem switch response.success when 'incorrect', 'correct' @render(response.contents) + @update_progress response else alert(response.success) reset: => Logger.log 'problem_reset', @answers - $.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (content) => - @render(content) + $.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) => + @render(response.html) + @update_progress response show: => if !@element.hasClass 'showed' Logger.log 'problem_show', problem: @id $.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) => - $.each response, (key, value) => + answers = response.answers + $.each answers, (key, value) => if $.isArray(value) for choice in value @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' @@ -51,6 +62,7 @@ class @Problem MathJax.Hub.Queue ["Typeset", MathJax.Hub] @$('.show').val 'Hide Answer' @element.addClass 'showed' + @update_progress response else @$('[id^=answer_], [id^=solution_]').text '' @$('[correct_answer]').attr correct_answer: null @@ -62,6 +74,7 @@ class @Problem $.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) => if response.success alert 'Saved' + @update_progress response refreshMath: (event, element) => element = event.target unless element diff --git a/lms/static/coffee/src/modules/sequence.coffee b/lms/static/coffee/src/modules/sequence.coffee index 463bf419fc..72a1c82ab6 100644 --- a/lms/static/coffee/src/modules/sequence.coffee +++ b/lms/static/coffee/src/modules/sequence.coffee @@ -2,6 +2,7 @@ class @Sequence constructor: (@id, @elements, @tag, position) -> @element = $("#sequence_#{@id}") @buildNavigation() + @initProgress() @bind() @render position @@ -11,11 +12,52 @@ class @Sequence bind: -> @$('#sequence-list a').click @goto + initProgress: -> + @progressTable = {} # "#problem_#{id}" -> progress + + + hookUpProgressEvent: -> + $('.problems-wrapper').bind 'progressChanged', @updateProgress + + mergeProgress: (p1, p2) -> + if p1 == "done" and p2 == "done" + return "done" + # not done, so if any progress on either, in_progress + w1 = p1 == "done" or p1 == "in_progress" + w2 = p2 == "done" or p2 == "in_progress" + if w1 or w2 + return "in_progress" + + return "none" + + updateProgress: => + new_progress = "none" + _this = this + $('.problems-wrapper').each (index) -> + progress = $(this).attr 'progress' + new_progress = _this.mergeProgress progress, new_progress + + @progressTable[@position] = new_progress + @setProgress(new_progress, @link_for(@position)) + + setProgress: (progress, element) -> + element.removeClass('progress-none') + .removeClass('progress-some') + .removeClass('progress-done') + switch progress + when 'none' then element.addClass('progress-none') + when 'in_progress' then element.addClass('progress-some') + when 'done' then element.addClass('progress-done') + buildNavigation: -> $.each @elements, (index, item) => link = $('').attr class: "seq_#{item.type}_inactive", 'data-element': index + 1 title = $('

        ').html(item.title) + # TODO: add item.progress_str either to the title or somewhere else. + # Make sure it gets updated after ajax calls list_item = $('

      • ').append(link.append(title)) + @setProgress item.progress_stat, link + @$('#sequence-list').append list_item toggleArrows: => @@ -36,13 +78,14 @@ class @Sequence if @position != undefined @mark_visited @position $.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position - + @mark_active new_position @$('#seq_content').html @elements[new_position - 1].content MathJax.Hub.Queue(["Typeset", MathJax.Hub]) @position = new_position @toggleArrows() + @hookUpProgressEvent() @element.trigger 'contentChanged' goto: (event) => @@ -67,7 +110,17 @@ class @Sequence @$("#sequence-list a[data-element=#{position}]") mark_visited: (position) -> - @link_for(position).attr class: "seq_#{@elements[position - 1].type}_visited" + # Don't overwrite class attribute to avoid changing Progress class + type = @elements[position - 1].type + element = @link_for(position) + element.removeClass("seq_#{type}_inactive") + .removeClass("seq_#{type}_active") + .addClass("seq_#{type}_visited") mark_active: (position) -> - @link_for(position).attr class: "seq_#{@elements[position - 1].type}_active" + # Don't overwrite class attribute to avoid changing Progress class + type = @elements[position - 1].type + element = @link_for(position) + element.removeClass("seq_#{type}_inactive") + .removeClass("seq_#{type}_visited") + .addClass("seq_#{type}_active") From 4590f9763f3ecd2ba0cbf5edab75c0b6812db932 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 20 Jun 2012 08:33:46 -0400 Subject: [PATCH 110/123] Add note about optional revision in location --- common/lib/keystore/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py index 2605424517..801ba57f80 100644 --- a/common/lib/keystore/__init__.py +++ b/common/lib/keystore/__init__.py @@ -43,6 +43,8 @@ class Location(object): } Location: another Location object + In both the dict and list forms, the revision is optional, and can be ommitted. + None of the components of a location may contain the '/' character Components may be set to None, which may be interpreted by some contexts to mean From b8c248fd758caf66f61b9e598712c98ebef35f53 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 10:31:37 -0400 Subject: [PATCH 111/123] minor edits to address Calen's comments. --- common/lib/xmodule/capa_module.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 7c75d1666a..3ace45cff4 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -367,7 +367,8 @@ class Module(XModule): answers = dict() for key in get: # e.g. input_resistor_1 ==> resistor_1 - answers['_'.join(key.split('_')[1:])] = get[key] + _, _, name = key.partition('_') + answers[name] = get[key] return answers @@ -390,7 +391,7 @@ class Module(XModule): if self.closed(): event_info['failure'] = 'closed' self.tracker('save_problem_check_fail', event_info) - # TODO: probably not 404? + # TODO (vshnayder): probably not 404? raise self.system.exception404 # Problem submitted. Student should reset before checking @@ -405,7 +406,7 @@ class Module(XModule): lcp_id = self.lcp.problem_id correct_map = self.lcp.grade_answers(answers) except StudentInputError as inst: - # TODO: why is this line here? + # TODO (vshnayder): why is this line here? self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state, system=self.system) traceback.print_exc() @@ -436,7 +437,7 @@ class Module(XModule): html = self.get_problem_html(encapsulate=False) # render problem into HTML except Exception,err: log.error('failed to generate html') - raise Exception, err + raise return {'success': success, 'contents': html, From 2723f0519de67dd4a119a737b4fec507679ed13c Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 11:31:21 -0400 Subject: [PATCH 112/123] Fix render of lms index with no chapter or section. --- lms/djangoapps/courseware/views.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index aa3d1b7781..5cbbe18d7d 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -82,7 +82,11 @@ def profile(request, student_id=None): def render_accordion(request, course, chapter, section): ''' Draws navigation bar. Takes current position in accordion as - parameter. Returns (initialization_javascript, content)''' + parameter. + + If chapter and section are '' or None, renders a default accordion. + + Returns (initialization_javascript, content)''' if not course: course = "6.002 Spring 2012" @@ -221,10 +225,8 @@ def index(request, course=None, chapter=None, section=None, ''' Fixes URLs -- we convert spaces to _ in URLs to prevent funny encoding characters and keep the URLs readable. This undoes that transformation. - - TODO: Properly replace underscores. (Q: what is properly?) ''' - return s.replace('_', ' ') + return s.replace('_', ' ') if s is not None else None def get_submodule_ids(module_xml): ''' From c35eb20e082e53bb27d714d94a3a3444a033ff4b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 20 Jun 2012 15:06:49 -0400 Subject: [PATCH 113/123] Fix other references to jquery after update to latest version --- lms/static/coffee/files.json | 4 ++-- lms/templates/dogfood.html | 4 ++-- lms/templates/marketing.html | 4 ++-- lms/templates/quickedit.html | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lms/static/coffee/files.json b/lms/static/coffee/files.json index 1e5e010d73..28b843021f 100644 --- a/lms/static/coffee/files.json +++ b/lms/static/coffee/files.json @@ -1,7 +1,7 @@ { "js_files": [ - "/static/js/jquery-1.6.2.min.js", - "/static/js/jquery-ui-1.8.16.custom.min.js", + "/static/js/jquery.min.js", + "/static/js/jquery-ui.min.js", "/static/js/jquery.leanModal.js", "/static/js/flot/jquery.flot.js" ], diff --git a/lms/templates/dogfood.html b/lms/templates/dogfood.html index 301f319cf0..9a3c08b528 100644 --- a/lms/templates/dogfood.html +++ b/lms/templates/dogfood.html @@ -19,8 +19,8 @@ ## % endif - - + + % if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']: diff --git a/lms/templates/marketing.html b/lms/templates/marketing.html index dada0fc8c1..6d34a47839 100644 --- a/lms/templates/marketing.html +++ b/lms/templates/marketing.html @@ -15,8 +15,8 @@ <%static:css group='marketing-ie'/> - - + + diff --git a/lms/templates/quickedit.html b/lms/templates/quickedit.html index ff44108a4f..3fee3864aa 100644 --- a/lms/templates/quickedit.html +++ b/lms/templates/quickedit.html @@ -19,8 +19,8 @@ ## % endif - - + + % if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']: From c4b9d339a84cbcc21fa4cb110a008a8b979bbeb7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 15:53:59 -0400 Subject: [PATCH 114/123] Add scaffolding comments for progress tracking in video modules. --- common/lib/xmodule/video_module.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/video_module.py b/common/lib/xmodule/video_module.py index d7c7f80291..f3d615fd3d 100644 --- a/common/lib/xmodule/video_module.py +++ b/common/lib/xmodule/video_module.py @@ -4,6 +4,7 @@ import logging from lxml import etree from x_module import XModule, XModuleDescriptor +from progress import Progress log = logging.getLogger("mitx.courseware.modules") @@ -15,17 +16,32 @@ class Module(XModule): video_time = 0 def handle_ajax(self, dispatch, get): + ''' + Handle ajax calls to this video. + TODO (vshnayder): This is not being called right now, so the position + is not being saved. + ''' log.debug(u"GET {0}".format(get)) log.debug(u"DISPATCH {0}".format(dispatch)) if dispatch == 'goto_position': self.position = int(float(get['position'])) - log.debug(u"NEW POSITION {0}".format(self.position)) + log.info(u"NEW POSITION {0}".format(self.position)) return json.dumps({'success':True}) raise Http404() + def get_progress(self): + ''' TODO (vshnayder): Get and save duration of youtube video, then return + fraction watched. + (Be careful to notice when video link changes and update) + + For now, we have no way of knowing if the video has even been watched, so + just return None. + ''' + return None + def get_state(self): log.debug(u"STATE POSITION {0}".format(self.position)) - return json.dumps({ 'position':self.position }) + return json.dumps({ 'position': self.position }) @classmethod def get_xml_tags(c): @@ -41,15 +57,16 @@ class Module(XModule): 'id': self.item_id, 'position': self.position, 'name': self.name, - 'annotations': self.annotations + 'annotations': self.annotations, }) def __init__(self, system, xml, item_id, state=None): XModule.__init__(self, system, xml, item_id, state) - xmltree=etree.fromstring(xml) + xmltree = etree.fromstring(xml) self.youtube = xmltree.get('youtube') self.name = xmltree.get('name') self.position = 0 + if state is not None: state = json.loads(state) if 'position' in state: From 17f8d04c28f24f10332690bdfdbe81a16c32e241 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 15:55:21 -0400 Subject: [PATCH 115/123] Initial UI for sequence progress. * change bottom border of links: red for not started yellow for in_progress green for done * This should probably be designed at some point. * Obvious problems: the yellow is not very visible, and lots of people are red/green color-blind. --- .../sass_old/courseware/_sequence-nav.scss | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lms/static/sass_old/courseware/_sequence-nav.scss b/lms/static/sass_old/courseware/_sequence-nav.scss index 9a2aee57af..4472724e6d 100644 --- a/lms/static/sass_old/courseware/_sequence-nav.scss +++ b/lms/static/sass_old/courseware/_sequence-nav.scss @@ -66,6 +66,26 @@ nav.sequence-nav { @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; + } + //video &.seq_video_inactive { @extend .inactive; From 3961a5d8ca938078fccf4a2880a2aa11c96c813f Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 16:01:56 -0400 Subject: [PATCH 116/123] Progress tracking cleanup. * use clearer names for the two status strings passed to js * add functions to do canonical conversion of progress to js string * fix updating bug in sequence.coffee * add some todo comments and other docs to make future expansion easier --- common/lib/xmodule/capa_module.py | 2 +- common/lib/xmodule/progress.py | 36 ++++++++++++++++++- common/lib/xmodule/seq_module.py | 4 +-- common/lib/xmodule/tests.py | 16 +++++++++ lms/djangoapps/courseware/module_render.py | 2 +- lms/static/coffee/src/modules/problem.coffee | 2 +- lms/static/coffee/src/modules/sequence.coffee | 19 +++++++--- 7 files changed, 71 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 3ace45cff4..2d21f14d1e 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -298,7 +298,7 @@ class Module(XModule): after = self.get_progress() d.update({ 'progress_changed' : after != before, - 'progress' : after.ternary_str(), + 'progress_status' : Progress.to_js_status_str(after), }) return json.dumps(d, cls=ComplexEncoder) diff --git a/common/lib/xmodule/progress.py b/common/lib/xmodule/progress.py index b9e242f2b2..fe4793cca4 100644 --- a/common/lib/xmodule/progress.py +++ b/common/lib/xmodule/progress.py @@ -1,6 +1,17 @@ ''' Progress class for modules. Represents where a student is in a module. -''' + +Useful things to know: + - Use Progress.to_js_status_str() to convert a progress into a simple + status string to pass to js. + - Use Progress.to_js_detail_str() to convert a progress into a more detailed + string to pass to js. + +In particular, these functions have a canonical handing of None. + +For most subclassing needs, you should only need to reimplement +frac() and __str__(). +''' from collections import namedtuple import numbers @@ -124,3 +135,26 @@ class Progress(object): (n, d) = a.frac() (n2, d2) = b.frac() return Progress(n + n2, d + d2) + + @staticmethod + def to_js_status_str(progress): + ''' + Return the "status string" version of the passed Progress + object that should be passed to js. Use this function when + sending Progress objects to js to limit dependencies. + ''' + if progress is None: + return "NA" + return progress.ternary_str() + + + @staticmethod + def to_js_detail_str(progress): + ''' + Return the "detail string" version of the passed Progress + object that should be passed to js. Use this function when + passing Progress objects to js to limit dependencies. + ''' + if progress is None: + return "NA" + return str(progress) diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py index 598fc4443e..8c3a148a51 100644 --- a/common/lib/xmodule/seq_module.py +++ b/common/lib/xmodule/seq_module.py @@ -74,8 +74,8 @@ class Module(XModule): for contents, title, progress in zip(self.contents, titles, progresses): contents['title'] = title - contents['progress_str'] = str(progress) if progress is not None else "" - contents['progress_stat'] = progress.ternary_str() if progress is not None else "" + contents['progress_status'] = Progress.to_js_status_str(progress) + contents['progress_detail'] = Progress.to_js_detail_str(progress) for (content, element_class) in zip(self.contents, child_classes): new_class = 'other' diff --git a/common/lib/xmodule/tests.py b/common/lib/xmodule/tests.py index 73096ce8da..90187abc2a 100644 --- a/common/lib/xmodule/tests.py +++ b/common/lib/xmodule/tests.py @@ -568,6 +568,22 @@ class ProgressTest(unittest.TestCase): self.assertEqual(self.half_done.ternary_str(), "in_progress") self.assertEqual(self.done.ternary_str(), "done") + def test_to_js_status(self): + '''Test the Progress.to_js_status_str() method''' + + self.assertEqual(Progress.to_js_status_str(self.not_started), "none") + self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress") + self.assertEqual(Progress.to_js_status_str(self.done), "done") + self.assertEqual(Progress.to_js_status_str(None), "NA") + + def test_to_js_detail_str(self): + '''Test the Progress.to_js_detail_str() method''' + f = Progress.to_js_detail_str + for p in (self.not_started, self.half_done, self.done): + self.assertEqual(f(p), str(p)) + # But None should be encoded as NA + self.assertEqual(f(None), "NA") + def test_add(self): '''Test the Progress.add_counts() method''' p = Progress(0, 2) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 9b5e7e4940..3a6fcbfb45 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -199,7 +199,7 @@ def get_module(user, request, module_xml, student_module_cache, position=None): etree.tostring(module_xml), module_id, state=state) - + # If StudentModule for this instance wasn't already in the database, # and this isn't a guest user, create it. if not smod and user.is_authenticated(): diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee index 1b254d5d3f..a1759b28af 100644 --- a/lms/static/coffee/src/modules/problem.coffee +++ b/lms/static/coffee/src/modules/problem.coffee @@ -19,7 +19,7 @@ class @Problem update_progress: (response) => if response.progress_changed - @element.attr progress: response.progress + @element.attr progress: response.progress_status @element.trigger('progressChanged') render: (content) -> diff --git a/lms/static/coffee/src/modules/sequence.coffee b/lms/static/coffee/src/modules/sequence.coffee index 72a1c82ab6..32a90f51a5 100644 --- a/lms/static/coffee/src/modules/sequence.coffee +++ b/lms/static/coffee/src/modules/sequence.coffee @@ -20,8 +20,16 @@ class @Sequence $('.problems-wrapper').bind 'progressChanged', @updateProgress mergeProgress: (p1, p2) -> + # if either is "NA", return the other one + if p1 == "NA" + return p2 + if p2 == "NA" + return p1 + + # Both real progresses if p1 == "done" and p2 == "done" return "done" + # not done, so if any progress on either, in_progress w1 = p1 == "done" or p1 == "in_progress" w2 = p2 == "done" or p2 == "in_progress" @@ -31,7 +39,7 @@ class @Sequence return "none" updateProgress: => - new_progress = "none" + new_progress = "NA" _this = this $('.problems-wrapper').each (index) -> progress = $(this).attr 'progress' @@ -41,6 +49,7 @@ class @Sequence @setProgress(new_progress, @link_for(@position)) setProgress: (progress, element) -> + # If progress is "NA", don't add any css class element.removeClass('progress-none') .removeClass('progress-some') .removeClass('progress-done') @@ -53,10 +62,12 @@ class @Sequence $.each @elements, (index, item) => link = $('').attr class: "seq_#{item.type}_inactive", 'data-element': index + 1 title = $('

        ').html(item.title) - # TODO: add item.progress_str either to the title or somewhere else. - # Make sure it gets updated after ajax calls + # TODO (vshnayder): add item.progress_detail either to the title or somewhere else. + # Make sure it gets updated after ajax calls. + # implementation note: will need to figure out how to handle combining detail + # statuses of multiple modules in js. list_item = $('

      • ').append(link.append(title)) - @setProgress item.progress_stat, link + @setProgress item.progress_status, link @$('#sequence-list').append list_item From 8a1e275c8d9170d0ad86de490041c6fa03a4667a Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 20 Jun 2012 17:20:34 -0400 Subject: [PATCH 117/123] bugfix: lcp score can apparently be 0/0 --- common/lib/xmodule/capa_module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py index 2d21f14d1e..3566f9b5e3 100644 --- a/common/lib/xmodule/capa_module.py +++ b/common/lib/xmodule/capa_module.py @@ -101,7 +101,9 @@ class Module(XModule): d = self.get_score() score = d['score'] total = d['total'] - return Progress(score, total) + if total > 0: + return Progress(score, total) + return None def get_html(self): From 7e82f3243f6f86f7a3580959bb73158538a90c9d Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 20 Jun 2012 21:14:58 -0400 Subject: [PATCH 118/123] Add scanning of data directory for courses. These are then displayed at /courses. --- lms/djangoapps/courseware/courses.py | 78 ++++++++++++++++++++++++++++ lms/djangoapps/courseware/views.py | 8 +++ lms/djangoapps/student/views.py | 5 -- lms/envs/common.py | 5 ++ lms/templates/course.html | 6 +-- lms/templates/courses.html | 3 +- lms/urls.py | 4 +- requirements.txt | 1 + 8 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 lms/djangoapps/courseware/courses.py diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py new file mode 100644 index 0000000000..fefb0f9c1e --- /dev/null +++ b/lms/djangoapps/courseware/courses.py @@ -0,0 +1,78 @@ +from collections import namedtuple +import logging +import os + +from path import path +import yaml + +log = logging.getLogger('mitx.courseware.courses') + +_FIELDS = ['number', # 6.002x + 'title', # Circuits and Electronics + 'short_title', # Circuits + 'run_id', # Spring 2012 + 'path', # /some/absolute/filepath/6.002x --> course.xml is in here. + 'instructors', # ['Anant Agarwal'] + 'institution', # "MIT" + 'grader', # a courseware.graders.CourseGrader object + + #'start', # These should be datetime fields + #'end' + ] + +class CourseInfoLoadError(Exception): + pass + +class Course(namedtuple('Course', _FIELDS)): + """Course objects encapsulate general information about a given run of a + course. This includes things like name, grading policy, etc. + """ + @property + def id(self): + return "{0.institution},{0.number},{0.run_id}".format(self) + + @classmethod + def load_from_path(cls, course_path): + course_path = path(course_path) # convert it from string if necessary + try: + with open(course_path / "course_info.yaml") as course_info_file: + course_info = yaml.load(course_info_file) + summary = course_info['course'] + summary.update(path=course_path, grader=None) + return cls(**summary) + except Exception as ex: + log.exception(ex) + raise CourseInfoLoadError("Could not read course info: {0}:{1}" + .format(type(ex).__name__, ex)) + +def load_courses(courses_path): + """Given a directory of courses, returns a list of Course objects. For the + sake of backwards compatibility, if you point it at the top level of a + specific course, it will return a list with one Course object in it. + """ + courses_path = path(courses_path) + def _is_course_path(p): + return os.path.exists(p / "course_info.yaml") + + log.info("Loading courses from {0}".format(courses_path)) + + # Compatibility: courses_path is the path for a single course + if _is_course_path(courses_path): + log.warning("course_info.yaml found in top-level ({0})" + .format(courses_path) + + " -- assuming there is only a single course.") + return [Course.load_from_path(courses_path)] + + # Default: Each dir in courses_path is a separate course + courses = [] + log.info("Reading courses from {0}".format(courses_path)) + for course_dir_name in os.listdir(courses_path): + course_path = courses_path / course_dir_name + if _is_course_path(course_path): + log.info("Initializing course {0}".format(course_path)) + courses.append(Course.load_from_path(course_path)) + + return courses + +def create_lookup_table(courses): + return dict((c.id, c) for c in courses) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 5cbbe18d7d..34b3c9bb04 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -33,6 +33,14 @@ etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False, template_imports={'urllib':urllib} +@ensure_csrf_cookie +def courses(request): + csrf_token = csrf(request)['csrf_token'] + # TODO: Clean up how 'error' is done. + context = {'courses' : settings.COURSES, + 'csrf' : csrf_token} + return render_to_response("courses.html", context) + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def gradebook(request): if 'course_admin' not in content_parser.user_groups(request.user): diff --git a/lms/djangoapps/student/views.py b/lms/djangoapps/student/views.py index af28654a59..dcfb7bbf82 100644 --- a/lms/djangoapps/student/views.py +++ b/lms/djangoapps/student/views.py @@ -471,8 +471,3 @@ def course_info(request): # TODO: Couse should be a model return render_to_response('course_info.html', {'csrf': csrf_token }) -@ensure_csrf_cookie -def courses(request): - csrf_token = csrf(request)['csrf_token'] - # TODO: Clean up how 'error' is done. - return render_to_response('courses.html', {'csrf': csrf_token }) diff --git a/lms/envs/common.py b/lms/envs/common.py index ec2e1f81de..7e985a691b 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -64,6 +64,11 @@ sys.path.append(PROJECT_ROOT / 'lib') sys.path.append(COMMON_ROOT / 'djangoapps') sys.path.append(COMMON_ROOT / 'lib') +######### EDX dormsbee/portal changes ################# +from courseware.courses import load_courses +COURSES = load_courses(ENV_ROOT / "data") +####################################################### + ################################## MITXWEB ##################################### # This is where we stick our compiled template files. Most of the app uses Mako # templates diff --git a/lms/templates/course.html b/lms/templates/course.html index 0b27e191ea..8841035186 100644 --- a/lms/templates/course.html +++ b/lms/templates/course.html @@ -1,6 +1,6 @@ <%namespace name='static' file='static_content.html'/> -%for i in xrange(6): +%for course in courses:
        @@ -10,8 +10,8 @@
        -

        18th Century History

        -

        Adam Smith, Harvard University

        +

        ${course.title}

        +

        ${",".join(course.instructors)} — ${course.institution}

        Register
        diff --git a/lms/templates/courses.html b/lms/templates/courses.html index 5d381b74e3..c821db4737 100644 --- a/lms/templates/courses.html +++ b/lms/templates/courses.html @@ -11,7 +11,8 @@
        - <%include file="course_filter.html" /> + ## I'm removing this for now since we aren't using it for the fall. + ## <%include file="course_filter.html" />
        <%include file="course.html" />
        diff --git a/lms/urls.py b/lms/urls.py index 6064e72d09..021b730c42 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -14,7 +14,6 @@ urlpatterns = ('', url(r'^$', 'student.views.index'), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard'), url(r'^course_info$', 'student.views.course_info'), - url(r'^courses$', 'student.views.courses'), url(r'^change_email$', 'student.views.change_email_request'), url(r'^email_confirm/(?P[^/]*)$', 'student.views.confirm_email_change'), url(r'^change_name$', 'student.views.change_name_request'), @@ -74,6 +73,9 @@ if settings.COURSEWARE_ENABLED: url(r'^save_circuit/(?P[^/]*)$', 'circuit.views.save_circuit'), url(r'^calculate$', 'util.views.calculate'), url(r'^heartbeat$', include('heartbeat.urls')), + + # Multicourse related: + url(r'^courses$', 'courseware.views.courses'), ) if settings.ENABLE_MULTICOURSE: diff --git a/requirements.txt b/requirements.txt index 2618336630..092cfdc4ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ django_nose nosexcover rednose -e common/lib/xmodule +PyYAML From a32aeac0135ac8d87114e198d379c6b200186938 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 20 Jun 2012 23:02:21 -0400 Subject: [PATCH 119/123] Add info page support with multiple courses --- common/djangoapps/util/views.py | 18 ++++++++++++++++-- lms/djangoapps/courseware/courses.py | 2 +- lms/envs/common.py | 3 ++- lms/templates/course.html | 2 ++ lms/templates/info.html | 8 ++++---- lms/templates/main.html | 2 +- lms/templates/navigation.html | 3 +++ lms/urls.py | 3 ++- 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index c1f2bb39ea..132f6dfe5a 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -57,9 +57,23 @@ def send_feedback(request): ) return HttpResponse(json.dumps({'success':True})) -def info(request): +def info(request, course_id=None): ''' Info page (link from main header) ''' - return render_to_response("info.html", {}) + try: + course = settings.COURSES_BY_ID[course_id] + except KeyError: + raise Http404("Course not found") + + # We're bypassing the templating system for this part. We should cache + # this. + sections = ["updates", "handouts", "guest_updates", "guest_handouts"] + sections_to_content = {} + for section in sections: + filename = section + ".html" + with open(course.path / "info" / filename) as f: + sections_to_content[section] = f.read() + + return render_to_response("info.html", sections_to_content) # From http://djangosnippets.org/snippets/1042/ def parse_accept_header(accept): diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index fefb0f9c1e..7299dff068 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -29,7 +29,7 @@ class Course(namedtuple('Course', _FIELDS)): """ @property def id(self): - return "{0.institution},{0.number},{0.run_id}".format(self) + return "{0.institution},{0.number},{0.run_id}".format(self).replace(" ", "_") @classmethod def load_from_path(cls, course_path): diff --git a/lms/envs/common.py b/lms/envs/common.py index 7e985a691b..c2156d35ad 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -65,8 +65,9 @@ sys.path.append(COMMON_ROOT / 'djangoapps') sys.path.append(COMMON_ROOT / 'lib') ######### EDX dormsbee/portal changes ################# -from courseware.courses import load_courses +from courseware.courses import create_lookup_table, load_courses COURSES = load_courses(ENV_ROOT / "data") +COURSES_BY_ID = create_lookup_table(COURSES) ####################################################### ################################## MITXWEB ##################################### diff --git a/lms/templates/course.html b/lms/templates/course.html index 8841035186..4169e4ccfb 100644 --- a/lms/templates/course.html +++ b/lms/templates/course.html @@ -12,6 +12,7 @@

        ${course.title}

        ${",".join(course.instructors)} — ${course.institution}

        +

        ${course.id}

        Register
        @@ -22,5 +23,6 @@
        +

        Hackish temp link to courseware

        %endfor diff --git a/lms/templates/info.html b/lms/templates/info.html index 97bf413638..e09ec34ef2 100644 --- a/lms/templates/info.html +++ b/lms/templates/info.html @@ -7,17 +7,17 @@
        % if user.is_authenticated():
        - <%include file="updates.html" /> + ${updates}
        - <%include file="handouts.html" /> + ${handouts}
        % else:
        - <%include file="guest_updates.html" /> + ${guest_updates}
        - <%include file="guest_handouts.html" /> + ${guest_handouts}
        % endif
        diff --git a/lms/templates/main.html b/lms/templates/main.html index e0fc50304d..3f399d736b 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -2,7 +2,7 @@ - <%block name="title">MITx 6.002x + <%block name="title">edX diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index e249464751..4c0c2d68de 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -1,3 +1,6 @@ +## TODO: Split this into two files, one for people who are authenticated, and +## one for people who aren't. Assume a Course object is passed to the former, +## instead of using settings.COURSE_TITLE <%namespace name='static' file='static_content.html'/>
        diff --git a/lms/urls.py b/lms/urls.py index 021b730c42..15ea0ea4cb 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -50,7 +50,6 @@ if settings.PERFSTATS: if settings.COURSEWARE_ENABLED: urlpatterns += ( url(r'^courseware/$', 'courseware.views.index', name="courseware"), - url(r'^info$', 'util.views.info'), url(r'^wiki/', include('simplewiki.urls')), url(r'^masquerade/', include('masquerade.urls')), url(r'^courseware/(?P[^/]*)/(?P[^/]*)/(?P
        [^/]*)/(?P[^/]*)$', 'courseware.views.index'), @@ -76,6 +75,8 @@ if settings.COURSEWARE_ENABLED: # Multicourse related: url(r'^courses$', 'courseware.views.courses'), + url(r'^courses/(?P[^/]*)/info$', 'util.views.info'), + ) if settings.ENABLE_MULTICOURSE: From d82b32b3e2763fa7a11753a7c15fb2890b8fa362 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 21 Jun 2012 10:19:47 -0400 Subject: [PATCH 120/123] add url route to new courseware --- lms/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/urls.py b/lms/urls.py index 15ea0ea4cb..f904fde466 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -76,6 +76,7 @@ if settings.COURSEWARE_ENABLED: # Multicourse related: url(r'^courses$', 'courseware.views.courses'), url(r'^courses/(?P[^/]*)/info$', 'util.views.info'), + url(r'^courses/(?P[^/]*)/courseware$', 'courseware.views.index'), ) From 014109ab1aa07a6948dfd0687b45cdca0443ba01 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 21 Jun 2012 10:20:51 -0400 Subject: [PATCH 121/123] add corresponding view to new courseware url route :-P --- lms/djangoapps/courseware/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 34b3c9bb04..40f0eecaf1 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -211,7 +211,7 @@ def get_module_xml(user, course, chapter, section): @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) def index(request, course=None, chapter=None, section=None, - position=None): + position=None, course_id=None): ''' Displays courseware accordion, and any associated content. If course, chapter, and section aren't all specified, just returns the accordion. If they are specified, returns an error if they don't From 4239a0b3088f7d1fd273d8e66fcd5e403fb2406b Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 21 Jun 2012 10:44:47 -0400 Subject: [PATCH 122/123] add courses to front page --- lms/djangoapps/student/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/student/views.py b/lms/djangoapps/student/views.py index dcfb7bbf82..8b75cd88a4 100644 --- a/lms/djangoapps/student/views.py +++ b/lms/djangoapps/student/views.py @@ -42,7 +42,8 @@ def index(request): else: csrf_token = csrf(request)['csrf_token'] # TODO: Clean up how 'error' is done. - return render_to_response('index.html', {'csrf': csrf_token }) + return render_to_response('index.html', {'courses' : settings.COURSES, + 'csrf': csrf_token }) @ensure_csrf_cookie def dashboard(request): From 9202c1e2cc144892b27abf7a8bc14f726c229571 Mon Sep 17 00:00:00 2001 From: Matthew Mongeau Date: Thu, 21 Jun 2012 11:02:50 -0400 Subject: [PATCH 123/123] Redirect to /courses instead of /info --- lms/djangoapps/student/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/student/views.py b/lms/djangoapps/student/views.py index 8b75cd88a4..b13a57e4c4 100644 --- a/lms/djangoapps/student/views.py +++ b/lms/djangoapps/student/views.py @@ -38,7 +38,7 @@ def index(request): ''' Redirects to main page -- info page if user authenticated, or marketing if not ''' if settings.COURSEWARE_ENABLED and request.user.is_authenticated(): - return redirect('/info') + return redirect('/courses') else: csrf_token = csrf(request)['csrf_token'] # TODO: Clean up how 'error' is done.