Commit c95b44d8 by cahrens

Merge branch 'master' into feature/christina/metadata-ui

Conflicts:
	cms/envs/common.py
	cms/templates/base.html
	common/lib/xmodule/xmodule/combined_open_ended_module.py
parents c8d6d5d2 a4ad495b
...@@ -26,6 +26,7 @@ Gemfile.lock ...@@ -26,6 +26,7 @@ Gemfile.lock
conf/locale/en/LC_MESSAGES/*.po conf/locale/en/LC_MESSAGES/*.po
!messages.po !messages.po
lms/static/sass/*.css lms/static/sass/*.css
lms/static/sass/application.scss
cms/static/sass/*.css cms/static/sass/*.css
lms/lib/comment_client/python lms/lib/comment_client/python
nosetests.xml nosetests.xml
......
REVIEWBOARD_URL = "https://rbcommons.com/s/edx/"
GUESS_FIELDS = True
...@@ -42,7 +42,7 @@ COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video'] ...@@ -42,7 +42,7 @@ COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"] OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"]
NOTE_COMPONENT_TYPES = ['notes'] NOTE_COMPONENT_TYPES = ['notes']
ADVANCED_COMPONENT_TYPES = ['annotatable' + 'word_cloud'] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES ADVANCED_COMPONENT_TYPES = ['annotatable', 'word_cloud'] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_CATEGORY = 'advanced' ADVANCED_COMPONENT_CATEGORY = 'advanced'
ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules' ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
......
...@@ -8,7 +8,7 @@ from mitxmako.shortcuts import render_to_response ...@@ -8,7 +8,7 @@ from mitxmako.shortcuts import render_to_response
from external_auth.views import ssl_login_shortcut from external_auth.views import ssl_login_shortcut
from .user import index from .user import index
__all__ = ['signup', 'old_login_redirect', 'login_page', 'howitworks', 'ux_alerts'] __all__ = ['signup', 'old_login_redirect', 'login_page', 'howitworks']
""" """
Public views Public views
...@@ -49,10 +49,3 @@ def howitworks(request): ...@@ -49,10 +49,3 @@ def howitworks(request):
return index(request) return index(request)
else: else:
return render_to_response('howitworks.html', {}) return render_to_response('howitworks.html', {})
def ux_alerts(request):
"""
static/proof-of-concept views
"""
return render_to_response('ux-alerts.html', {})
...@@ -21,7 +21,7 @@ def event(request): ...@@ -21,7 +21,7 @@ def event(request):
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
console logs don't get distracted :-) console logs don't get distracted :-)
''' '''
return HttpResponse(True) return HttpResponse(status=204)
def get_request_method(request): def get_request_method(request):
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
This config file extends the test environment configuration This config file extends the test environment configuration
so that we can run the lettuce acceptance tests. so that we can run the lettuce acceptance tests.
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
from .test import * from .test import *
# You need to start the server in debug mode, # You need to start the server in debug mode,
......
""" """
This is the default template for our main set of AWS servers. This is the default template for our main set of AWS servers.
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
import json import json
from .common import * from .common import *
......
...@@ -19,6 +19,10 @@ Longer TODO: ...@@ -19,6 +19,10 @@ Longer TODO:
multiple sites, but we do need a way to map their data assets. multiple sites, but we do need a way to map their data assets.
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
import sys import sys
import lms.envs.common import lms.envs.common
from path import path from path import path
...@@ -217,7 +221,10 @@ PIPELINE_JS = { ...@@ -217,7 +221,10 @@ PIPELINE_JS = {
'source_filenames': sorted( 'source_filenames': sorted(
rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.js') + rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.js') +
rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.js') rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.js')
) + ['js/hesitate.js', 'js/base.js', 'js/models/metadata_model.js'], ) + ['js/hesitate.js', 'js/base.js',
'js/models/feedback.js', 'js/views/feedback.js',
'js/models/section.js', 'js/views/section.js',
'js/models/metadata_model.js'],
'output_filename': 'js/cms-application.js', 'output_filename': 'js/cms-application.js',
'test_order': 0 'test_order': 0
}, },
......
""" """
This config file runs the simplest dev environment""" This config file runs the simplest dev environment"""
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
from .common import * from .common import *
from logsettings import get_logger_config from logsettings import get_logger_config
......
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
# dev environment for ichuang/mit # dev environment for ichuang/mit
# FORCE_SCRIPT_NAME = '/cms' # FORCE_SCRIPT_NAME = '/cms'
......
...@@ -8,6 +8,10 @@ The worker can be executed using: ...@@ -8,6 +8,10 @@ The worker can be executed using:
django_admin.py celery worker django_admin.py celery worker
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
from dev import * from dev import *
################################# CELERY ###################################### ################################# CELERY ######################################
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
This configuration is used for running jasmine tests This configuration is used for running jasmine tests
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
from .test import * from .test import *
from logsettings import get_logger_config from logsettings import get_logger_config
......
...@@ -7,6 +7,11 @@ sessions. Assumes structure: ...@@ -7,6 +7,11 @@ sessions. Assumes structure:
/mitx # The location of this repo /mitx # The location of this repo
/log # Where we're going to write log files /log # Where we're going to write log files
""" """
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
from .common import * from .common import *
import os import os
from path import path from path import path
......
...@@ -11,11 +11,11 @@ ...@@ -11,11 +11,11 @@
<span class="int"><%= percentChecked %></span>% of checklist completed</span></span> <span class="int"><%= percentChecked %></span>% of checklist completed</span></span>
<header> <header>
<h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist"> <h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist">
<i class="ss-icon ss-symbolicons-standard icon-arrow ui-toggle-expansion">&#x25BE;</i> <i class="icon-caret-down ui-toggle-expansion"></i>
<%= checklistShortDescription %></h3> <%= checklistShortDescription %></h3>
<span class="checklist-status status"> <span class="checklist-status status">
Tasks Completed: <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span> Tasks Completed: <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span>
<i class="ss-icon ss-symbolicons-standard icon-confirm">&#x2713;</i> <i class="icon-ok"></i>
</span> </span>
</header> </header>
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
"js/vendor/json2.js", "js/vendor/json2.js",
"js/vendor/underscore-min.js", "js/vendor/underscore-min.js",
"js/vendor/backbone-min.js", "js/vendor/backbone-min.js",
"js/vendor/jquery.leanModal.min.js" "js/vendor/jquery.leanModal.min.js",
"js/vendor/sinon-1.7.1.js",
"js/test/i18n.js"
] ]
} }
../../../templates/js/section-name-edit.underscore
\ No newline at end of file
../../../templates/js/system-feedback.underscore
\ No newline at end of file
jasmine.getFixtures().fixturesPath = 'fixtures'
# Stub jQuery.cookie # Stub jQuery.cookie
@stubCookies = @stubCookies =
csrftoken: "stubCSRFToken" csrftoken: "stubCSRFToken"
......
...@@ -22,3 +22,37 @@ describe "main helper", -> ...@@ -22,3 +22,37 @@ describe "main helper", ->
it "setup AJAX CSRF token", -> it "setup AJAX CSRF token", ->
expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken") expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken")
describe "AJAX Errors", ->
tpl = readFixtures('system-feedback.underscore')
beforeEach ->
setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
appendSetFixtures(sandbox({id: "page-notification"}))
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
afterEach ->
@xhr.restore()
it "successful AJAX request does not pop an error notification", ->
expect($("#page-notification")).toBeEmpty()
$.ajax("/test")
expect($("#page-notification")).toBeEmpty()
@requests[0].respond(200)
expect($("#page-notification")).toBeEmpty()
it "AJAX request with error should pop an error notification", ->
$.ajax("/test")
@requests[0].respond(500)
expect($("#page-notification")).not.toBeEmpty()
expect($("#page-notification")).toContain('div.wrapper-notification-error')
it "can override AJAX request with error so it does not pop an error notification", ->
$.ajax
url: "/test"
notifyOnError: false
@requests[0].respond(500)
expect($("#page-notification")).toBeEmpty()
describe "CMS.Models.SystemFeedback", ->
beforeEach ->
@model = new CMS.Models.SystemFeedback()
it "should have an empty message by default", ->
expect(@model.get("message")).toEqual("")
it "should have an empty title by default", ->
expect(@model.get("title")).toEqual("")
it "should not have an intent set by default", ->
expect(@model.get("intent")).toBeNull()
describe "CMS.Models.WarningMessage", ->
beforeEach ->
@model = new CMS.Models.WarningMessage()
it "should have the correct intent", ->
expect(@model.get("intent")).toEqual("warning")
describe "CMS.Models.ErrorMessage", ->
beforeEach ->
@model = new CMS.Models.ErrorMessage()
it "should have the correct intent", ->
expect(@model.get("intent")).toEqual("error")
describe "CMS.Models.ConfirmationMessage", ->
beforeEach ->
@model = new CMS.Models.ConfirmationMessage()
it "should have the correct intent", ->
expect(@model.get("intent")).toEqual("confirmation")
describe "CMS.Models.Section", ->
describe "basic", ->
beforeEach ->
@model = new CMS.Models.Section({
id: 42,
name: "Life, the Universe, and Everything"
})
it "should take an id argument", ->
expect(@model.get("id")).toEqual(42)
it "should take a name argument", ->
expect(@model.get("name")).toEqual("Life, the Universe, and Everything")
it "should have a URL set", ->
expect(@model.url).toEqual("/save_item")
it "should serialize to JSON correctly", ->
expect(@model.toJSON()).toEqual({
id: 42,
metadata: {
display_name: "Life, the Universe, and Everything"
}
})
describe "XHR", ->
beforeEach ->
spyOn(CMS.Models.Section.prototype, 'showNotification')
spyOn(CMS.Models.Section.prototype, 'hideNotification')
@model = new CMS.Models.Section({
id: 42,
name: "Life, the Universe, and Everything"
})
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
afterEach ->
@xhr.restore()
it "show/hide a notification when it saves to the server", ->
@model.save()
expect(CMS.Models.Section.prototype.showNotification).toHaveBeenCalled()
@requests[0].respond(200)
expect(CMS.Models.Section.prototype.hideNotification).toHaveBeenCalled()
it "don't hide notification when saving fails", ->
# this is handled by the global AJAX error handler
@model.save()
@requests[0].respond(500)
expect(CMS.Models.Section.prototype.hideNotification).not.toHaveBeenCalled()
tpl = readFixtures('system-feedback.underscore')
beforeEach ->
setFixtures(sandbox({id: "page-alert"}))
appendSetFixtures(sandbox({id: "page-notification"}))
appendSetFixtures(sandbox({id: "page-prompt"}))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
@addMatchers
toBeShown: ->
@actual.hasClass("is-shown") and not @actual.hasClass("is-hiding")
toBeHiding: ->
@actual.hasClass("is-hiding") and not @actual.hasClass("is-shown")
toContainText: (text) ->
# remove this when we upgrade jasmine-jquery
trimmedText = $.trim(@actual.text())
if text and $.isFunction(text.test)
return text.test(trimmedText)
else
return trimmedText.indexOf(text) != -1;
describe "CMS.Views.Alert as base class", ->
beforeEach ->
@model = new CMS.Models.ConfirmationMessage({
title: "Portal"
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
})
# it will be interesting to see when this.render is called, so lets spy on it
spyOn(CMS.Views.Alert.prototype, 'render').andCallThrough()
it "renders on initalize", ->
view = new CMS.Views.Alert({model: @model})
expect(view.render).toHaveBeenCalled()
it "renders the template", ->
view = new CMS.Views.Alert({model: @model})
expect(view.$(".action-close")).toBeDefined()
expect(view.$('.wrapper')).toBeShown()
expect(view.$el).toContainText(@model.get("title"))
expect(view.$el).toContainText(@model.get("message"))
it "close button sends a .hide() message", ->
spyOn(CMS.Views.Alert.prototype, 'hide').andCallThrough()
view = new CMS.Views.Alert({model: @model})
view.$(".action-close").click()
expect(CMS.Views.Alert.prototype.hide).toHaveBeenCalled()
expect(view.$('.wrapper')).toBeHiding()
describe "CMS.Views.Prompt", ->
beforeEach ->
@model = new CMS.Models.ConfirmationMessage({
title: "Portal"
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
})
# for some reason, expect($("body")) blows up the test runner, so this test
# just exercises the Prompt rather than asserting on anything. Best I can
# do for now. :(
it "changes class on body", ->
# expect($("body")).not.toHaveClass("prompt-is-shown")
view = new CMS.Views.Prompt({model: @model})
# expect($("body")).toHaveClass("prompt-is-shown")
view.hide()
# expect($("body")).not.toHaveClass("prompt-is-shown")
describe "CMS.Views.Alert click events", ->
beforeEach ->
@model = new CMS.Models.WarningMessage(
title: "Unsaved",
message: "Your content is currently Unsaved.",
actions:
primary:
text: "Save",
class: "save-button",
click: jasmine.createSpy('primaryClick')
secondary: [{
text: "Revert",
class: "cancel-button",
click: jasmine.createSpy('secondaryClick')
}]
)
@view = new CMS.Views.Alert({model: @model})
it "should trigger the primary event on a primary click", ->
@view.primaryClick()
expect(@model.get('actions').primary.click).toHaveBeenCalled()
it "should trigger the secondary event on a secondary click", ->
@view.secondaryClick()
expect(@model.get('actions').secondary[0].click).toHaveBeenCalled()
it "should apply class to primary action", ->
expect(@view.$(".action-primary")).toHaveClass("save-button")
it "should apply class to secondary action", ->
expect(@view.$(".action-secondary")).toHaveClass("cancel-button")
describe "CMS.Views.Notification minShown and maxShown", ->
beforeEach ->
@model = new CMS.Models.SystemFeedback(
intent: "saving"
title: "Saving"
)
spyOn(CMS.Views.Notification.prototype, 'show').andCallThrough()
spyOn(CMS.Views.Notification.prototype, 'hide').andCallThrough()
@clock = sinon.useFakeTimers()
afterEach ->
@clock.restore()
it "a minShown view should not hide too quickly", ->
view = new CMS.Views.Notification({model: @model, minShown: 1000})
expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# call hide() on it, but the minShown should prevent it from hiding right away
view.hide()
expect(view.$('.wrapper')).toBeShown()
# wait for the minShown timeout to expire, and check again
@clock.tick(1001)
expect(view.$('.wrapper')).toBeHiding()
it "a maxShown view should hide by itself", ->
view = new CMS.Views.Notification({model: @model, maxShown: 1000})
expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# wait for the maxShown timeout to expire, and check again
@clock.tick(1001)
expect(view.$('.wrapper')).toBeHiding()
it "a minShown view can stay visible longer", ->
view = new CMS.Views.Notification({model: @model, minShown: 1000})
expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# wait for the minShown timeout to expire, and check again
@clock.tick(1001)
expect(CMS.Views.Notification.prototype.hide).not.toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# can now hide immediately
view.hide()
expect(view.$('.wrapper')).toBeHiding()
it "a maxShown view can hide early", ->
view = new CMS.Views.Notification({model: @model, maxShown: 1000})
expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# wait 50 milliseconds, and hide it early
@clock.tick(50)
view.hide()
expect(view.$('.wrapper')).toBeHiding()
# wait for timeout to expire, make sure it doesn't do anything weird
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
it "a view can have both maxShown and minShown", ->
view = new CMS.Views.Notification({model: @model, minShown: 1000, maxShown: 2000})
# can't hide early
@clock.tick(50)
view.hide()
expect(view.$('.wrapper')).toBeShown()
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
# show it again, and let it hide automatically
view.show()
@clock.tick(1050)
expect(view.$('.wrapper')).toBeShown()
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
describe "CMS.Views.SectionShow", ->
describe "Basic", ->
beforeEach ->
spyOn(CMS.Views.SectionShow.prototype, "switchToEditView")
.andCallThrough()
@model = new CMS.Models.Section({
id: 42
name: "Life, the Universe, and Everything"
})
@view = new CMS.Views.SectionShow({model: @model})
@view.render()
it "should contain the model name", ->
expect(@view.$el).toHaveText(@model.get('name'))
it "should call switchToEditView when clicked", ->
@view.$el.click()
expect(@view.switchToEditView).toHaveBeenCalled()
it "should pass the same element to SectionEdit when switching views", ->
spyOn(CMS.Views.SectionEdit.prototype, 'initialize').andCallThrough()
@view.switchToEditView()
expect(CMS.Views.SectionEdit.prototype.initialize).toHaveBeenCalled()
expect(CMS.Views.SectionEdit.prototype.initialize.mostRecentCall.args[0].el).toEqual(@view.el)
describe "CMS.Views.SectionEdit", ->
describe "Basic", ->
tpl = readFixtures('section-name-edit.underscore')
feedback_tpl = readFixtures('system-feedback.underscore')
beforeEach ->
setFixtures($("<script>", {id: "section-name-edit-tpl", type: "text/template"}).text(tpl))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedback_tpl))
spyOn(CMS.Views.SectionEdit.prototype, "switchToShowView")
.andCallThrough()
spyOn(CMS.Views.SectionEdit.prototype, "showInvalidMessage")
.andCallThrough()
window.analytics = jasmine.createSpyObj('analytics', ['track'])
window.course_location_analytics = jasmine.createSpy()
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
@model = new CMS.Models.Section({
id: 42
name: "Life, the Universe, and Everything"
})
@view = new CMS.Views.SectionEdit({model: @model})
@view.render()
afterEach ->
@xhr.restore()
delete window.analytics
delete window.course_location_analytics
it "should have the model name as the default text value", ->
expect(@view.$("input[type=text]").val()).toEqual(@model.get('name'))
it "should call switchToShowView when cancel button is clicked", ->
@view.$("input.cancel-button").click()
expect(@view.switchToShowView).toHaveBeenCalled()
it "should save model when save button is clicked", ->
spyOn(@model, 'save')
@view.$("input[type=submit]").click()
expect(@model.save).toHaveBeenCalled()
it "should call switchToShowView when save() is successful", ->
@view.$("input[type=submit]").click()
@requests[0].respond(200)
expect(@view.switchToShowView).toHaveBeenCalled()
it "should call showInvalidMessage when validation is unsuccessful", ->
spyOn(@model, 'validate').andReturn("BLARRGH")
@view.$("input[type=submit]").click()
expect(@view.showInvalidMessage).toHaveBeenCalledWith(
jasmine.any(Object), "BLARRGH", jasmine.any(Object))
expect(@view.switchToShowView).not.toHaveBeenCalled()
it "should not save when validation is unsuccessful", ->
spyOn(@model, 'validate').andReturn("BLARRGH")
@view.$("input[type=text]").val("changed")
@view.$("input[type=submit]").click()
expect(@model.get('name')).not.toEqual("changed")
...@@ -15,6 +15,15 @@ $ -> ...@@ -15,6 +15,15 @@ $ ->
headers : { 'X-CSRFToken': $.cookie 'csrftoken' } headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
dataType: 'json' dataType: 'json'
$(document).ajaxError (event, jqXHR, ajaxSettings, thrownError) ->
if ajaxSettings.notifyOnError is false
return
msg = new CMS.Models.ErrorMessage(
"title": gettext("Studio's having trouble saving your work")
"message": jqXHR.responseText || gettext("This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.")
)
new CMS.Views.Notification({model: msg})
window.onTouchBasedDevice = -> window.onTouchBasedDevice = ->
navigator.userAgent.match /iPhone|iPod|iPad/i navigator.userAgent.match /iPhone|iPod|iPad/i
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -140,11 +140,6 @@ $(document).ready(function() { ...@@ -140,11 +140,6 @@ $(document).ready(function() {
$('.new-course-button').bind('click', addNewCourse); $('.new-course-button').bind('click', addNewCourse);
// section name editing
$('.section-name').bind('click', editSectionName);
$('.edit-section-name-cancel').bind('click', cancelEditSectionName);
// $('.edit-section-name-save').bind('click', saveEditSectionName);
// section date setting // section date setting
$('.set-publish-date').bind('click', setSectionScheduleDate); $('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate); $('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
...@@ -209,8 +204,8 @@ function toggleSections(e) { ...@@ -209,8 +204,8 @@ function toggleSections(e) {
$section = $('.courseware-section'); $section = $('.courseware-section');
sectionCount = $section.length; sectionCount = $section.length;
$button = $(this); $button = $(this);
$labelCollapsed = $('<i class="ss-icon ss-symbolicons-block">up</i> <span class="label">Collapse All Sections</span>'); $labelCollapsed = $('<i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span>');
$labelExpanded = $('<i class="ss-icon ss-symbolicons-block">down</i> <span class="label">Expand All Sections</span>'); $labelExpanded = $('<i class="icon-arrow-down"></i> <span class="label">Expand All Sections</span>');
var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded; var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
$button.toggleClass('is-activated').html(buttonLabel); $button.toggleClass('is-activated').html(buttonLabel);
...@@ -763,72 +758,6 @@ function cancelNewSubsection(e) { ...@@ -763,72 +758,6 @@ function cancelNewSubsection(e) {
$(this).parents('li.branch').remove(); $(this).parents('li.branch').remove();
} }
function editSectionName(e) {
e.preventDefault();
$(this).unbind('click', editSectionName);
$(this).children('.section-name-edit').show();
$(this).find('.edit-section-name').focus();
$(this).children('.section-name-span').hide();
$(this).find('.section-name-edit').bind('submit', saveEditSectionName);
$(this).find('.edit-section-name-cancel').bind('click', cancelNewSection);
$body.bind('keyup', {
$cancelButton: $(this).find('.edit-section-name-cancel')
}, checkForCancel);
}
function cancelEditSectionName(e) {
e.preventDefault();
$(this).parent().hide();
$(this).parent().siblings('.section-name-span').show();
$(this).closest('.section-name').bind('click', editSectionName);
e.stopPropagation();
}
function saveEditSectionName(e) {
e.preventDefault();
$(this).closest('.section-name').unbind('click', editSectionName);
var id = $(this).closest('.courseware-section').data('id');
var display_name = $.trim($(this).find('.edit-section-name').val());
$(this).closest('.courseware-section .section-name').append($spinner);
$spinner.show();
if (display_name == '') {
alert("You must specify a name before saving.");
return;
}
analytics.track('Edited Section Name', {
'course': course_location_analytics,
'display_name': display_name,
'id': id
});
var $_this = $(this);
// call into server to commit the new order
$.ajax({
url: "/save_item",
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'id': id,
'metadata': {
'display_name': display_name
}
})
}).success(function() {
$spinner.delay(250).fadeOut(250);
$_this.closest('h3').find('.section-name-span').html(display_name).show();
$_this.hide();
$_this.closest('.section-name').bind('click', editSectionName);
e.stopPropagation();
});
}
function setSectionScheduleDate(e) { function setSectionScheduleDate(e) {
e.preventDefault(); e.preventDefault();
$(this).closest("h4").hide(); $(this).closest("h4").hide();
......
CMS.Models.SystemFeedback = Backbone.Model.extend({
defaults: {
"intent": null, // "warning", "confirmation", "error", "announcement", "step-required", etc
"title": "",
"message": ""
/* could also have an "actions" hash: here is an example demonstrating
the expected structure
"actions": {
"primary": {
"text": "Save",
"class": "action-save",
"click": function() {
// do something when Save is clicked
// `this` refers to the model
}
},
"secondary": [
{
"text": "Cancel",
"class": "action-cancel",
"click": function() {}
}, {
"text": "Discard Changes",
"class": "action-discard",
"click": function() {}
}
]
}
*/
}
});
CMS.Models.WarningMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "warning"
})
});
CMS.Models.ErrorMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "error"
})
});
CMS.Models.ConfirmationMessage = CMS.Models.SystemFeedback.extend({
defaults: $.extend({}, CMS.Models.SystemFeedback.prototype.defaults, {
"intent": "confirmation"
})
});
CMS.Models.Section = Backbone.Model.extend({
defaults: {
"name": ""
},
validate: function(attrs, options) {
if (!attrs.name) {
return gettext("You must specify a name");
}
},
url: "/save_item",
toJSON: function() {
return {
id: this.get("id"),
metadata: {
display_name: this.get("name")
}
};
},
initialize: function() {
this.listenTo(this, "request", this.showNotification);
this.listenTo(this, "sync", this.hideNotification);
},
showNotification: function() {
if(!this.msg) {
this.msg = new CMS.Models.SystemFeedback({
intent: "saving",
title: gettext("Saving&hellip;")
});
}
if(!this.msgView) {
this.msgView = new CMS.Views.Notification({
model: this.msg,
closeIcon: false,
minShown: 1250
});
}
this.msgView.show();
},
hideNotification: function() {
if(!this.msgView) { return; }
this.msgView.hide();
}
});
...@@ -40,7 +40,6 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({ ...@@ -40,7 +40,6 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({
// data // data
data : JSON.stringify({ deleteKeys : self.deleteKeys}) data : JSON.stringify({ deleteKeys : self.deleteKeys})
}) })
.fail(function(hdr, status, error) { CMS.ServerError(self, "Deleting keys:" + status); })
.done(function(data, status, error) { .done(function(data, status, error) {
// clear deleteKeys on success // clear deleteKeys on success
self.deleteKeys = []; self.deleteKeys = [];
......
...@@ -22,8 +22,7 @@ CMS.Views.Checklists = Backbone.View.extend({ ...@@ -22,8 +22,7 @@ CMS.Views.Checklists = Backbone.View.extend({
} }
); );
}, },
reset: true, reset: true
error: CMS.ServerError
} }
); );
}, },
...@@ -90,8 +89,7 @@ CMS.Views.Checklists = Backbone.View.extend({ ...@@ -90,8 +89,7 @@ CMS.Views.Checklists = Backbone.View.extend({
'task': model.attributes.items[task_index].short_description, 'task': model.attributes.items[task_index].short_description,
'state': model.attributes.items[task_index].is_checked 'state': model.attributes.items[task_index].is_checked
}); });
}, }
error : CMS.ServerError
}); });
} }
}); });
...@@ -105,7 +105,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -105,7 +105,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event); var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() }); targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change // push change to display, hide the editor, submit the change
targetModel.save({}, {error : CMS.ServerError}); targetModel.save({});
this.closeEditor(this); this.closeEditor(this);
analytics.track('Saved Course Update', { analytics.track('Saved Course Update', {
...@@ -166,11 +166,9 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -166,11 +166,9 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
success: function() { success: function() {
cacheThis.render(); cacheThis.render();
}, },
reset: true, reset: true
error: CMS.ServerError
}); });
}, }
error : CMS.ServerError
}); });
}, },
...@@ -254,8 +252,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -254,8 +252,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
} }
); );
}, },
reset: true, reset: true
error: CMS.ServerError
}); });
}, },
...@@ -296,7 +293,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -296,7 +293,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onSave: function(event) { onSave: function(event) {
this.model.set('data', this.$codeMirror.getValue()); this.model.set('data', this.$codeMirror.getValue());
this.render(); this.render();
this.model.save({}, {error: CMS.ServerError}); this.model.save({});
this.$form.hide(); this.$form.hide();
this.closeEditor(this); this.closeEditor(this);
......
CMS.Views.Alert = Backbone.View.extend({
options: {
type: "alert",
shown: true, // is this view currently being shown?
icon: true, // should we render an icon related to the message intent?
closeIcon: true, // should we render a close button in the top right corner?
minShown: 0, // length of time after this view has been shown before it can be hidden (milliseconds)
maxShown: Infinity // length of time after this view has been shown before it will be automatically hidden (milliseconds)
},
initialize: function() {
var tpl = $("#system-feedback-tpl").text();
if(!tpl) {
console.error("Couldn't load system-feedback template");
}
this.template = _.template(tpl);
this.setElement($("#page-"+this.options.type));
this.listenTo(this.model, 'change', this.render);
return this.show();
},
render: function() {
var attrs = $.extend({}, this.options, this.model.attributes);
this.$el.html(this.template(attrs));
return this;
},
events: {
"click .action-close": "hide",
"click .action-primary": "primaryClick",
"click .action-secondary": "secondaryClick"
},
show: function() {
clearTimeout(this.hideTimeout);
this.options.shown = true;
this.shownAt = new Date();
this.render();
if($.isNumeric(this.options.maxShown)) {
this.hideTimeout = setTimeout($.proxy(this.hide, this),
this.options.maxShown);
}
return this;
},
hide: function() {
if(this.shownAt && $.isNumeric(this.options.minShown) &&
this.options.minShown > new Date() - this.shownAt)
{
clearTimeout(this.hideTimeout);
this.hideTimeout = setTimeout($.proxy(this.hide, this),
this.options.minShown - (new Date() - this.shownAt));
} else {
this.options.shown = false;
delete this.shownAt;
this.render();
}
return this;
},
primaryClick: function() {
var actions = this.model.get("actions");
if(!actions) { return; }
var primary = actions.primary;
if(!primary) { return; }
if(primary.click) {
primary.click.call(this.model, this);
}
},
secondaryClick: function(e) {
var actions = this.model.get("actions");
if(!actions) { return; }
var secondaryList = actions.secondary;
if(!secondaryList) { return; }
// which secondary action was clicked?
var i = 0; // default to the first secondary action (easier for testing)
if(e && e.target) {
i = _.indexOf(this.$(".action-secondary"), e.target);
}
var secondary = this.model.get("actions").secondary[i];
if(secondary.click) {
secondary.click.call(this.model, this);
}
}
});
CMS.Views.Notification = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "notification",
closeIcon: false
})
});
CMS.Views.Prompt = CMS.Views.Alert.extend({
options: $.extend({}, CMS.Views.Alert.prototype.options, {
type: "prompt",
closeIcon: false,
icon: false
}),
render: function() {
if(!window.$body) { window.$body = $(document.body); }
if(this.options.shown) {
$body.addClass('prompt-is-shown');
} else {
$body.removeClass('prompt-is-shown');
}
// super() in Javascript has awkward syntax :(
return CMS.Views.Alert.prototype.render.apply(this, arguments);
}
});
...@@ -35,7 +35,7 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({ ...@@ -35,7 +35,7 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({
// TODO move to a template file // TODO move to a template file
'<h4 class="status-label"><%= assignmentType %></h4>' + '<h4 class="status-label"><%= assignmentType %></h4>' +
'<a data-tooltip="Mark/unmark this subsection as graded" class="menu-toggle" href="#">' + '<a data-tooltip="Mark/unmark this subsection as graded" class="menu-toggle" href="#">' +
'<% if (!hideSymbol) {%><span class="ss-icon ss-standard">&#x2713;</span><%};%>' + '<% if (!hideSymbol) {%><i class="icon-ok"></i><%};%>' +
'</a>' + '</a>' +
'<ul class="menu">' + '<ul class="menu">' +
'<% graders.each(function(option) { %>' + '<% graders.each(function(option) { %>' +
......
CMS.Views.SectionShow = Backbone.View.extend({
template: _.template('<span data-tooltip="<%= gettext("Edit this section\'s name") %>" class="section-name-span"><%= name %></span>'),
render: function() {
var attrs = {
name: this.model.escape('name')
};
this.$el.html(this.template(attrs));
this.delegateEvents();
return this;
},
events: {
"click": "switchToEditView"
},
switchToEditView: function() {
if(!this.editView) {
this.editView = new CMS.Views.SectionEdit({
model: this.model, el: this.el, showView: this});
}
this.undelegateEvents();
this.editView.render();
}
});
CMS.Views.SectionEdit = Backbone.View.extend({
render: function() {
var attrs = {
name: this.model.escape('name')
};
this.$el.html(this.template(attrs));
this.delegateEvents();
return this;
},
initialize: function() {
this.template = _.template($("#section-name-edit-tpl").text());
this.listenTo(this.model, "invalid", this.showInvalidMessage);
this.render();
},
events: {
"click .save-button": "saveName",
"submit": "saveName",
"click .cancel-button": "switchToShowView"
},
saveName: function(e) {
if (e) { e.preventDefault(); }
var name = this.$("input[type=text]").val();
var that = this;
this.model.save("name", name, {
success: function() {
analytics.track('Edited Section Name', {
'course': course_location_analytics,
'display_name': that.model.get('name'),
'id': that.model.get('id')
});
that.switchToShowView();
}
});
},
switchToShowView: function() {
if(!this.showView) {
this.showView = new CMS.Views.SectionShow({
model: this.model, el: this.el, editView: this});
}
this.undelegateEvents();
this.stopListening();
this.showView.render();
},
showInvalidMessage: function(model, error, options) {
model.set("name", model.previous("name"));
var that = this;
var msg = new CMS.Models.ErrorMessage({
title: gettext("Your change could not be saved"),
message: error,
actions: {
primary: {
text: gettext("Return and resolve this issue"),
click: function(view) {
view.hide();
that.$("input[type=text]").focus();
}
}
}
});
new CMS.Views.Prompt({model: msg});
}
});
CMS.ServerError = function(model, error) {
// this handler is for the client:server communication not the validation errors which handleValidationError catches
window.alert("Server Error: " + error.responseText);
};
...@@ -23,7 +23,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -23,7 +23,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
// because these are outside of this.$el, they can't be in the event hash // because these are outside of this.$el, they can't be in the event hash
$('.save-button').on('click', this, this.saveView); $('.save-button').on('click', this, this.saveView);
$('.cancel-button').on('click', this, this.revertView); $('.cancel-button').on('click', this, this.revertView);
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
}, },
render: function() { render: function() {
...@@ -144,8 +143,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -144,8 +143,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
'course': course_location_analytics 'course': course_location_analytics
}); });
}, }
error : CMS.ServerError
}); });
}, },
revertView : function(event) { revertView : function(event) {
...@@ -155,8 +153,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ ...@@ -155,8 +153,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
self.model.clear({silent : true}); self.model.clear({silent : true});
self.model.fetch({ self.model.fetch({
success : function() { self.render(); }, success : function() { self.render(); },
reset: true, reset: true
error : CMS.ServerError
}); });
}, },
renderTemplate: function (key, value) { renderTemplate: function (key, value) {
......
...@@ -16,7 +16,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -16,7 +16,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}, },
initialize : function() { initialize : function() {
this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="ss-icon ss-standard">&#x1F4C4;</i><%= filename %></a>'); this.fileAnchorTemplate = _.template('<a href="<%= fullpath %>"> <i class="icon-file"></i><%= filename %></a>');
// fill in fields // fill in fields
this.$el.find("#course-name").val(this.model.get('location').get('name')); this.$el.find("#course-name").val(this.model.get('location').get('name'));
this.$el.find("#course-organization").val(this.model.get('location').get('org')); this.$el.find("#course-organization").val(this.model.get('location').get('org'));
...@@ -26,7 +26,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -26,7 +26,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
var dateIntrospect = new Date(); var dateIntrospect = new Date();
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")"); this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap); this.selectorToField = _.invert(this.fieldToSelectorMap);
}, },
......
...@@ -44,7 +44,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -44,7 +44,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
self.render(); self.render();
} }
); );
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.model.get('graders').on('remove', this.render, this); this.model.get('graders').on('remove', this.render, this);
this.model.get('graders').on('reset', this.render, this); this.model.get('graders').on('reset', this.render, this);
...@@ -317,7 +316,6 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({ ...@@ -317,7 +316,6 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
'blur :input' : "inputUnfocus" 'blur :input' : "inputUnfocus"
}, },
initialize : function() { initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap); this.selectorToField = _.invert(this.fieldToSelectorMap);
this.render(); this.render();
...@@ -362,8 +360,7 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({ ...@@ -362,8 +360,7 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
} }
}, },
deleteModel : function(e) { deleteModel : function(e) {
this.model.destroy( this.model.destroy();
{ error : CMS.ServerError});
e.preventDefault(); e.preventDefault();
} }
......
...@@ -3,7 +3,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -3,7 +3,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({
// decorates the fields. Needs wiring per class, but this initialization shows how // decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents // either have your init call this one or copy the contents
initialize : function() { initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap); this.selectorToField = _.invert(this.fieldToSelectorMap);
}, },
......
...@@ -314,7 +314,7 @@ p, ul, ol, dl { ...@@ -314,7 +314,7 @@ p, ul, ol, dl {
} }
.upload-button .icon-create { .upload-button .icon-plus {
@extend .t-action2; @extend .t-action2;
line-height: 0 !important; line-height: 0 !important;
} }
...@@ -750,11 +750,11 @@ hr.divide { ...@@ -750,11 +750,11 @@ hr.divide {
display: block; display: block;
} }
.icon-create { .icon-plus {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: ($baseline/4); margin-right: ($baseline/4);
margin-top: ($baseline/10); margin-top: -2px;
line-height: 0; line-height: 0;
} }
} }
...@@ -768,12 +768,12 @@ hr.divide { ...@@ -768,12 +768,12 @@ hr.divide {
display: block; display: block;
} }
.icon-view { .icon-eye-open {
@include font-size(15); @include font-size(16);
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: ($baseline/2); margin-right: 8px;
margin-top: ($baseline/5); margin-top: -3px;
line-height: 0; line-height: 0;
} }
} }
...@@ -888,7 +888,7 @@ body.js { ...@@ -888,7 +888,7 @@ body.js {
@extend .text-sr; @extend .text-sr;
} }
.ss-icon { [class^="icon-"] {
@include font-size(18); @include font-size(18);
color: $white; color: $white;
} }
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
// talbs: we need to slowly ween ourselves off of these // talbs: we need to slowly ween ourselves off of these
// ==================== // ====================
// line-height (old way)
@function lh($amount: 1) {
@return $body-line-height * $amount;
}
// inherited - vertical and horizontal centering // inherited - vertical and horizontal centering
@mixin vertically-and-horizontally-centered ($height, $width) { @mixin vertically-and-horizontally-centered ($height, $width) {
left: 50%; left: 50%;
......
...@@ -92,37 +92,6 @@ ...@@ -92,37 +92,6 @@
// ==================== // ====================
// notifications slide up then down
@mixin notificationsSlideUpDown {
0%, 100% {
@include transform(translateY(0));
}
15%, 85% {
@include transform(translateY(-($notification-height)));
}
20%, 80% {
@include transform(translateY(-($notification-height*0.99)));
}
}
@-moz-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@-webkit-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@-o-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); }
@keyframes notificationsSlideUpDown { @include notificationsSlideUpDown();}
@mixin anim-notificationsSlideUpDown($duration, $timing: ease-in-out, $count: 1, $delay: 0) {
@include animation-name(notificationsSlideUpDown);
@include animation-duration($duration);
@include animation-delay($delay);
@include animation-timing-function($timing);
@include animation-iteration-count($count);
@include animation-fill-mode(both);
}
// ====================
// bounce in // bounce in
@mixin bounceIn { @mixin bounceIn {
0% { 0% {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
// ==================== // ====================
@import 'vendor/normalize'; @import 'vendor/normalize';
@import 'reset'; @import 'reset';
@import 'vendor/font-awesome';
// BASE *default edX offerings* // BASE *default edX offerings*
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
padding: ($baseline/2) ($baseline*0.75); padding: ($baseline/2) ($baseline*0.75);
background: transparent; background: transparent;
.ss-icon { [class^="icon-"] {
@include transition(top .25s ease-in-out .25s); @include transition(top .25s ease-in-out .25s);
@include font-size(15); @include font-size(15);
display: inline-block; display: inline-block;
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
&:hover, &:active { &:hover, &:active {
color: $gray-d2; color: $gray-d2;
.ss-icon { [class^="icon-"] {
color: $gray-d2; color: $gray-d2;
} }
} }
......
...@@ -219,10 +219,11 @@ ...@@ -219,10 +219,11 @@
.nav-item { .nav-item {
position: relative; position: relative;
.icon-expand { .icon-caret-down {
@include font-size(14); @include font-size(14);
@include transition (color 0.5s ease-in-out, opacity 0.5s ease-in-out); @include transition (color 0.5s ease-in-out, opacity 0.5s ease-in-out);
display: inline-block; display: inline-block;
vertical-align: middle;
margin-left: 2px; margin-left: 2px;
opacity: 0.5; opacity: 0.5;
color: $gray-l2; color: $gray-l2;
...@@ -230,7 +231,7 @@ ...@@ -230,7 +231,7 @@
&:hover { &:hover {
.icon-expand { .icon-caret-down {
color: $blue; color: $blue;
opacity: 1.0; opacity: 1.0;
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
} }
.ss-icon { [class^="icon-"] {
} }
......
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
// ==================== // ====================
.modal-cover { .modal-cover {
@extend .depth3;
display: none; display: none;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1000;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, .8); background: rgba(0, 0, 0, .8);
} }
.modal { .modal {
@extend .depth4;
display: none; display: none;
position: fixed; position: fixed;
top: 60px; top: 60px;
left: 50%; left: 50%;
z-index: 1001;
width: 930px; width: 930px;
height: 540px; height: 540px;
margin-left: -465px; margin-left: -465px;
...@@ -61,8 +61,8 @@ ...@@ -61,8 +61,8 @@
// lean modal alternative // lean modal alternative
#lean_overlay { #lean_overlay {
@extend .depth4;
position: fixed; position: fixed;
z-index: 10000;
top: 0px; top: 0px;
left: 0px; left: 0px;
display: none; display: none;
......
...@@ -33,8 +33,9 @@ ...@@ -33,8 +33,9 @@
padding: ($baseline/2) $baseline; padding: ($baseline/2) $baseline;
color: $gray; color: $gray;
.icon { [class^="icon-"] {
@include font-size(14); @include font-size(14);
margin-right: ($baseline/4);
} }
&:hover { &:hover {
...@@ -62,10 +63,6 @@ ...@@ -62,10 +63,6 @@
@extend .t-title4; @extend .t-title4;
} }
.ss-icon {
@extend .t-icon;
@extend .icon-inline;
}
} }
// shared elements // shared elements
...@@ -98,8 +95,11 @@ ...@@ -98,8 +95,11 @@
@extend .t-action4; @extend .t-action4;
display: block; display: block;
.icon { [class^="icon-"] {
@include font-size(18); @include font-size(18);
vertical-align: middle;
margin-right: $baseline/4;
} }
&:hover, &:active { &:hover, &:active {
......
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
// prompts // prompts
.wrapper-prompt { .wrapper-prompt {
@extend .depth4; @extend .depth5;
@include transition(all 0.05s ease-in-out); @include transition(all 0.05s ease-in-out);
position: fixed; position: fixed;
top: 0; top: 0;
...@@ -204,7 +204,7 @@ ...@@ -204,7 +204,7 @@
// types of prompts - error // types of prompts - error
.prompt.error { .prompt.error {
.icon-error { [class^="icon"] {
color: $red-l1; color: $red-l1;
} }
...@@ -216,7 +216,7 @@ ...@@ -216,7 +216,7 @@
// types of prompts - confirmation // types of prompts - confirmation
.prompt.confirmation { .prompt.confirmation {
.icon-error { [class^="icon"] {
color: $green; color: $green;
} }
...@@ -228,7 +228,7 @@ ...@@ -228,7 +228,7 @@
// types of prompts - error // types of prompts - error
.prompt.warning { .prompt.warning {
.icon-warning { [class^="icon"] {
color: $orange; color: $orange;
} }
...@@ -242,7 +242,7 @@ ...@@ -242,7 +242,7 @@
// notifications // notifications
.wrapper-notification { .wrapper-notification {
@extend .depth3; @extend .depth5;
@include clearfix(); @include clearfix();
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $blue); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $blue);
position: fixed; position: fixed;
...@@ -253,7 +253,7 @@ ...@@ -253,7 +253,7 @@
&.wrapper-notification-warning { &.wrapper-notification-warning {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $orange); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $orange);
.icon-warning { [class^="icon"] {
color: $orange; color: $orange;
} }
} }
...@@ -261,7 +261,7 @@ ...@@ -261,7 +261,7 @@
&.wrapper-notification-error { &.wrapper-notification-error {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $red-l1); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $red-l1);
.icon-error { [class^="icon"] {
color: $red-l1; color: $red-l1;
} }
} }
...@@ -269,7 +269,7 @@ ...@@ -269,7 +269,7 @@
&.wrapper-notification-confirmation { &.wrapper-notification-confirmation {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $green); @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $green);
.icon-confirmation { [class^="icon"] {
color: $green; color: $green;
} }
} }
...@@ -294,13 +294,13 @@ ...@@ -294,13 +294,13 @@
max-width: none; max-width: none;
min-width: none; min-width: none;
.icon, .copy { [class^="icon"], .copy {
float: none; float: none;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
.icon { [class^="icon"] {
width: $baseline; width: $baseline;
height: ($baseline*1.25); height: ($baseline*1.25);
margin-right: ($baseline*0.75); margin-right: ($baseline*0.75);
...@@ -329,7 +329,7 @@ ...@@ -329,7 +329,7 @@
max-width: none; max-width: none;
min-width: none; min-width: none;
.icon-help { [class^="icon"] {
width: $baseline; width: $baseline;
margin-right: ($baseline*0.75); margin-right: ($baseline*0.75);
} }
...@@ -357,13 +357,13 @@ ...@@ -357,13 +357,13 @@
font-weight: 700; font-weight: 700;
} }
.icon, .copy { [class^="icon"], .copy {
float: left; float: left;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
.icon { [class^="icon"] {
@include transition (color 0.5s ease-in-out); @include transition (color 0.5s ease-in-out);
@include font-size(22); @include font-size(22);
width: flex-grid(1, 12); width: flex-grid(1, 12);
...@@ -389,7 +389,7 @@ ...@@ -389,7 +389,7 @@
// with actions // with actions
&.has-actions { &.has-actions {
.icon { [class^="icon"] {
width: flex-grid(1, 12); width: flex-grid(1, 12);
} }
...@@ -436,9 +436,11 @@ ...@@ -436,9 +436,11 @@
&.saving { &.saving {
.icon-saving { [class^="icon"] {
@include anim-rotateClockwise(3s, linear, infinite); @include anim-rotateClockwise(3s, linear, infinite);
width: 22px; width: 25px;
margin: -4px 10px 0 0;
@include transform-origin(52% 60%);
} }
.copy p { .copy p {
...@@ -472,7 +474,7 @@ ...@@ -472,7 +474,7 @@
&.wrapper-alert-warning { &.wrapper-alert-warning {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange);
.icon-warning { [class^="icon"] {
color: $orange; color: $orange;
} }
} }
...@@ -480,7 +482,7 @@ ...@@ -480,7 +482,7 @@
&.wrapper-alert-error { &.wrapper-alert-error {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $red-l1); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $red-l1);
.icon-error { [class^="icon"] {
color: $red-l1; color: $red-l1;
} }
} }
...@@ -488,7 +490,7 @@ ...@@ -488,7 +490,7 @@
&.wrapper-alert-confirmation { &.wrapper-alert-confirmation {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green);
.icon-confirmation { [class^="icon"] {
color: $green; color: $green;
} }
} }
...@@ -496,7 +498,7 @@ ...@@ -496,7 +498,7 @@
&.wrapper-alert-announcement { &.wrapper-alert-announcement {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue);
.icon-announcement { [class^="icon"] {
color: $blue; color: $blue;
} }
} }
...@@ -504,7 +506,7 @@ ...@@ -504,7 +506,7 @@
&.wrapper-alert-step-required { &.wrapper-alert-step-required {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $pink); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $pink);
.icon-step-required { [class^="icon"] {
color: $pink; color: $pink;
} }
} }
...@@ -524,11 +526,11 @@ ...@@ -524,11 +526,11 @@
font-weight: 700; font-weight: 700;
} }
.icon, .copy { [class^="icon"], .copy {
float: left; float: left;
} }
.icon { [class^="icon"] {
@include transition (color 0.5s ease-in-out); @include transition (color 0.5s ease-in-out);
@include font-size(22); @include font-size(22);
width: flex-grid(1, 12); width: flex-grid(1, 12);
...@@ -550,7 +552,7 @@ ...@@ -550,7 +552,7 @@
// with actions // with actions
&.has-actions { &.has-actions {
.icon { [class^="icon"] {
width: flex-grid(1, 12); width: flex-grid(1, 12);
} }
...@@ -600,7 +602,7 @@ ...@@ -600,7 +602,7 @@
@extend .text-sr; @extend .text-sr;
} }
.icon { [class^="icon"] {
@include font-size(14); @include font-size(14);
color: $white; color: $white;
width: auto; width: auto;
...@@ -669,10 +671,6 @@ ...@@ -669,10 +671,6 @@
&.is-hiding { &.is-hiding {
@include anim-notificationsSlideDown(0.25s); @include anim-notificationsSlideDown(0.25s);
} }
&.is-fleeting {
@include anim-notificationsSlideUpDown(2s);
}
} }
} }
......
...@@ -11,54 +11,54 @@ ...@@ -11,54 +11,54 @@
.t-title1 { .t-title1 {
@extend .t-title; @extend .t-title;
@include font-size(60); @include font-size(60);
@include lh(60); @include line-height(60);
} }
.t-title2 { .t-title2 {
@extend .t-title; @extend .t-title;
@include font-size(48); @include font-size(48);
@include lh(48); @include line-height(48);
} }
.t-title3 { .t-title3 {
@include font-size(36); @include font-size(36);
@include lh(36); @include line-height(36);
} }
.t-title4 { .t-title4 {
@extend .t-title; @extend .t-title;
@include font-size(24); @include font-size(24);
@include lh(24); @include line-height(24);
} }
.t-title5 { .t-title5 {
@extend .t-title; @extend .t-title;
@include font-size(18); @include font-size(18);
@include lh(18); @include line-height(18);
} }
.t-title6 { .t-title6 {
@extend .t-title; @extend .t-title;
@include font-size(16); @include font-size(16);
@include lh(16); @include line-height(16);
} }
.t-title7 { .t-title7 {
@extend .t-title; @extend .t-title;
@include font-size(14); @include font-size(14);
@include lh(14); @include line-height(14);
} }
.t-title8 { .t-title8 {
@extend .t-title; @extend .t-title;
@include font-size(12); @include font-size(12);
@include lh(12); @include line-height(12);
} }
.t-title9 { .t-title9 {
@extend .t-title; @extend .t-title;
@include font-size(11); @include font-size(11);
@include lh(11); @include line-height(11);
} }
// ==================== // ====================
...@@ -71,31 +71,31 @@ ...@@ -71,31 +71,31 @@
.t-copy-base { .t-copy-base {
@extend .t-copy; @extend .t-copy;
@include font-size(16); @include font-size(16);
@include lh(16); @include line-height(16);
} }
.t-copy-lead1 { .t-copy-lead1 {
@extend .t-copy; @extend .t-copy;
@include font-size(18); @include font-size(18);
@include lh(18); @include line-height(18);
} }
.t-copy-lead2 { .t-copy-lead2 {
@extend .t-copy; @extend .t-copy;
@include font-size(24); @include font-size(24);
@include lh(24); @include line-height(24);
} }
.t-copy-sub1 { .t-copy-sub1 {
@extend .t-copy; @extend .t-copy;
@include font-size(14); @include font-size(14);
@include lh(14); @include line-height(14);
} }
.t-copy-sub2 { .t-copy-sub2 {
@extend .t-copy; @extend .t-copy;
@include font-size(12); @include font-size(12);
@include lh(12); @include line-height(12);
} }
// ==================== // ====================
...@@ -103,22 +103,22 @@ ...@@ -103,22 +103,22 @@
// actions/labels // actions/labels
.t-action1 { .t-action1 {
@include font-size(18); @include font-size(18);
@include lh(18); @include line-height(18);
} }
.t-action2 { .t-action2 {
@include font-size(16); @include font-size(16);
@include lh(16); @include line-height(16);
} }
.t-action3 { .t-action3 {
@include font-size(14); @include font-size(14);
@include lh(14); @include line-height(14);
} }
.t-action4 { .t-action4 {
@include font-size(12); @include font-size(12);
@include lh(12); @include line-height(12);
} }
...@@ -131,7 +131,48 @@ ...@@ -131,7 +131,48 @@
// ==================== // ====================
// misc // icons
.t-icon { .t-icon1 {
line-height: 0; @include font-size(48);
@include line-height(48);
}
.t-icon2 {
@include font-size(36);
@include line-height(36);
}
.t-icon3 {
@include font-size(24);
@include line-height(24);
}
.t-icon4 {
@include font-size(18);
@include line-height(18);
}
.t-icon5 {
@include font-size(16);
@include line-height(16);
}
.t-icon6 {
@include font-size(14);
@include line-height(14);
}
.t-icon7 {
@include font-size(12);
@include line-height(12);
}
.t-icon8 {
@include font-size(11);
@include line-height(11);
}
.t-icon9 {
@include font-size(10);
@include line-height(10);
} }
...@@ -272,9 +272,9 @@ body.signup, body.signin { ...@@ -272,9 +272,9 @@ body.signup, body.signin {
background: $yellow-d1; background: $yellow-d1;
color: $white; color: $white;
.ss-icon { [class^="icon-"] {
position: relative; position: relative;
top: 3px; top: 1px;
@include font-size(16); @include font-size(16);
display: inline-block; display: inline-block;
margin-right: ($baseline/2); margin-right: ($baseline/2);
......
...@@ -3,12 +3,24 @@ ...@@ -3,12 +3,24 @@
body.course.uploads { body.course.uploads {
.nav-actions {
.icon-cloud-upload {
@include font-size(16);
vertical-align: bottom;
margin-right: ($baseline/5);
}
}
input.asset-search-input { input.asset-search-input {
float: left; float: left;
width: 260px; width: 260px;
background-color: #fff; background-color: #fff;
} }
.asset-library { .asset-library {
@include clearfix; @include clearfix;
......
...@@ -62,7 +62,7 @@ body.course.checklists { ...@@ -62,7 +62,7 @@ body.course.checklists {
.ui-toggle-expansion { .ui-toggle-expansion {
@include transition(rotate .15s ease-in-out .25s); @include transition(rotate .15s ease-in-out .25s);
@include font-size(14); @include font-size(21);
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: ($baseline/2); margin-right: ($baseline/2);
...@@ -91,10 +91,9 @@ body.course.checklists { ...@@ -91,10 +91,9 @@ body.course.checklists {
color: $gray-l2; color: $gray-l2;
.icon-confirm { .icon-ok {
@include font-size(20); @include font-size(20);
display: inline-block; display: inline-block;
vertical-align: middle;
margin-left: ($baseline/2); margin-left: ($baseline/2);
color: $gray-l4; color: $gray-l4;
} }
...@@ -184,13 +183,13 @@ body.course.checklists { ...@@ -184,13 +183,13 @@ body.course.checklists {
header { header {
.checklist-title, .icon-confirm { .checklist-title, .icon-caret-down {
color: $green; color: $green;
} }
.checklist-status { .checklist-status {
.status-count, .status-amount, .icon-confirm { .status-count, .status-amount, .icon-ok {
color: $green; color: $green;
} }
} }
......
...@@ -164,11 +164,11 @@ body.index { ...@@ -164,11 +164,11 @@ body.index {
right: ($baseline/2); right: ($baseline/2);
opacity: 0; opacity: 0;
.ss-icon { [class^="icon-"] {
@include font-size(18); @include font-size(18);
@include border-top-radius(3px); @include border-top-radius(3px);
display: inline-block; display: inline-block;
padding: ($baseline/4) ($baseline/2); padding: ($baseline/2);
background: $blue; background: $blue;
color: $white; color: $white;
text-align: center; text-align: center;
......
...@@ -56,6 +56,12 @@ body.course.outline { ...@@ -56,6 +56,12 @@ body.course.outline {
} }
} }
[class^="icon-"] {
vertical-align: middle;
margin-top: -5px;
display: inline-block;
}
.menu { .menu {
@include font-size(12); @include font-size(12);
@include border-radius(4px); @include border-radius(4px);
...@@ -331,6 +337,7 @@ body.course.outline { ...@@ -331,6 +337,7 @@ body.course.outline {
&:hover, &.is-active { &:hover, &.is-active {
color: $blue; color: $blue;
} }
} }
.menu { .menu {
...@@ -533,7 +540,7 @@ body.course.outline { ...@@ -533,7 +540,7 @@ body.course.outline {
display: block; display: block;
} }
.ss-icon { [class^="icon-"] {
@include font-size(11); @include font-size(11);
@include border-radius(20px); @include border-radius(20px);
position: relative; position: relative;
......
...@@ -328,11 +328,12 @@ body.course.settings { ...@@ -328,11 +328,12 @@ body.course.settings {
@extend .t-action-3; @extend .t-action-3;
font-weight: 600; font-weight: 600;
.icon { [class^="icon-"] {
@extend .t-icon; @extend .t-icon;
@include font-size(16); @include font-size(16);
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-top: -3px;
} }
} }
} }
......
...@@ -10,6 +10,16 @@ body.course.static-pages { ...@@ -10,6 +10,16 @@ body.course.static-pages {
padding: 12px 0; padding: 12px 0;
} }
.nav-introduction-supplementary {
.icon-question-sign {
display: inline-block;
vertical-align: baseline;
margin-right: ($baseline/4);
}
}
.unit-body { .unit-body {
padding: 0; padding: 0;
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<h3 class="sr">Page Actions</h3> <h3 class="sr">Page Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button upload-button new-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#xEB40;</i> Upload New File</a> <a href="#" class="button upload-button new-button"><i class="icon-cloud-upload"></i> Upload New File</a>
</li> </li>
</ul> </ul>
</nav> </nav>
......
...@@ -31,6 +31,13 @@ ...@@ -31,6 +31,13 @@
<body class="<%block name='bodyclass'></%block> hide-wip"> <body class="<%block name='bodyclass'></%block> hide-wip">
<%include file="courseware_vendor_js.html"/> <%include file="courseware_vendor_js.html"/>
## js templates
<script id="system-feedback-tpl" type="text/template">
<%static:include path="js/system-feedback.underscore" />
</script>
## javascript
<script type="text/javascript" src="/jsi18n/"></script> <script type="text/javascript" src="/jsi18n/"></script>
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
...@@ -41,7 +48,6 @@ ...@@ -41,7 +48,6 @@
<script src="${static.url('js/vendor/symbolset.ss-symbolicons.js')}"></script> <script src="${static.url('js/vendor/symbolset.ss-symbolicons.js')}"></script>
<script src="${static.url('js/vendor/html5-input-polyfills/number-polyfill.js')}"></script> <script src="${static.url('js/vendor/html5-input-polyfills/number-polyfill.js')}"></script>
<%static:js group='main'/> <%static:js group='main'/>
<%static:js group='module-js'/> <%static:js group='module-js'/>
<script src="${static.url('js/vendor/jquery.inlineedit.js')}"></script> <script src="${static.url('js/vendor/jquery.inlineedit.js')}"></script>
...@@ -52,6 +58,7 @@ ...@@ -52,6 +58,7 @@
<script src="${static.url('js/vendor/jquery.smooth-scroll.min.js')}"></script> <script src="${static.url('js/vendor/jquery.smooth-scroll.min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/CodeMirror/htmlmixed.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/CodeMirror/htmlmixed.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/CodeMirror/css.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/CodeMirror/css.js')}"></script>
<script type="text/javascript" src="//www.youtube.com/player_api"></script>
<!--TODO: not the right place--> <!--TODO: not the right place-->
<script type="text/javascript" src="${static.url('js/models/metadata_model.js')}"></script> <script type="text/javascript" src="${static.url('js/models/metadata_model.js')}"></script>
...@@ -62,12 +69,14 @@ ...@@ -62,12 +69,14 @@
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>'); document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
</script> </script>
<script src="${static.url('js/models/feedback.js')}"></script>
<script src="${static.url('js/views/feedback.js')}"></script>
<!-- view --> <!-- view -->
<div class="wrapper wrapper-view"> <div class="wrapper wrapper-view">
<%include file="widgets/header.html" /> <%include file="widgets/header.html" />
<%block name="view_alerts"></%block> <div id="page-alert"></div>
<%block name="view_banners"></%block>
<%block name="content"></%block> <%block name="content"></%block>
...@@ -78,10 +87,10 @@ ...@@ -78,10 +87,10 @@
<%include file="widgets/footer.html" /> <%include file="widgets/footer.html" />
<%include file="widgets/tender.html" /> <%include file="widgets/tender.html" />
<%block name="view_notifications"></%block> <div id="page-notification"></div>
</div> </div>
<%block name="view_prompts"></%block> <div id="page-prompt"></div>
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
</body> </body>
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<h3 class="sr">Page Actions</h3> <h3 class="sr">Page Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class=" button new-button new-update-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Update</a> <a href="#" class=" button new-button new-update-button"><i class="icon-plus"></i> New Update</a>
</li> </li>
</ul> </ul>
</nav> </nav>
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<h3 class="sr">Page Actions</h3> <h3 class="sr">Page Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button new-button new-tab"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Page</a> <a href="#" class="button new-button new-tab"><i class="icon-plus"></i> New Page</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<nav class="nav-introduction-supplementary"> <nav class="nav-introduction-supplementary">
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a rel="modal" href="#preview-lms-staticpages"><i class="ss-icon ss-symbolicons-block icon icon-information">&#x2753;</i>How do Static Pages look to students in my course?</a> <a rel="modal" href="#preview-lms-staticpages"><i class="icon-question-sign"></i>How do Static Pages look to students in my course?</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
</figure> </figure>
<a href="#" rel="view" class="action action-modal-close"> <a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon-close icon">&#x2421;</i> <i class="icon-remove-sign"></i>
<span class="label">close modal</span> <span class="label">close modal</span>
</a> </a>
</div> </div>
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" /> <img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption> <figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
<span class="action-zoom"> <span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i> <i class="icon-zoom-in"></i>
</span> </span>
</a> </a>
</figure> </figure>
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
<img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" /> <img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" />
<figcaption class="sr">Learning is More than Just Lectures</figcaption> <figcaption class="sr">Learning is More than Just Lectures</figcaption>
<span class="action-zoom"> <span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i> <i class="icon-zoom-in"></i>
</span> </span>
</a> </a>
</figure> </figure>
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." /> <img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption> <figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
<span class="action-zoom"> <span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i> <i class="icon-zoom-in"></i>
</span> </span>
</a> </a>
</figure> </figure>
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
</figure> </figure>
<a href="" rel="view" class="action action-modal-close"> <a href="" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i> <i class="icon-remove-sign"></i>
<span class="label">close modal</span> <span class="label">close modal</span>
</a> </a>
</div> </div>
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
</figure> </figure>
<a href="" rel="view" class="action action-modal-close"> <a href="" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i> <i class="icon-remove-sign"></i>
<span class="label">close modal</span> <span class="label">close modal</span>
</a> </a>
</div> </div>
...@@ -178,7 +178,7 @@ ...@@ -178,7 +178,7 @@
</figure> </figure>
<a href="" rel="view" class="action action-modal-close"> <a href="" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i> <i class="icon-remove-sign"></i>
<span class="label">close modal</span> <span class="label">close modal</span>
</a> </a>
</div> </div>
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
% if not disable_course_creation: % if not disable_course_creation:
<a href="#" class="button new-button new-course-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> ${_("New Course")}</a> <a href="#" class="button new-button new-course-button"><i class="icon-plus"></i> ${_("New Course")}</a>
% elif settings.MITX_FEATURES.get('STAFF_EMAIL',''): % elif settings.MITX_FEATURES.get('STAFF_EMAIL',''):
<a href="mailto:${settings.MITX_FEATURES.get('STAFF_EMAIL','')}">${_("Email staff to create course")}</a> <a href="mailto:${settings.MITX_FEATURES.get('STAFF_EMAIL','')}">${_("Email staff to create course")}</a>
% endif % endif
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
<a class="class-link" href="${url}" class="class-name"> <a class="class-link" href="${url}" class="class-name">
<span class="class-name">${course}</span> <span class="class-name">${course}</span>
</a> </a>
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view">&#xE010;</i>View Live</a> <a href="${lms_link}" rel="external" class="button view-button view-live-button">View Live</a>
</li> </li>
%endfor %endfor
</ul> </ul>
......
<form class="section-name-edit">
<input type="text" value="<%= name %>" autocomplete="off"/>
<input type="submit" class="save-button" value="<%= gettext("Save") %>" />
<input type="button" class="cancel-button" value="<%= gettext("Cancel") %>" />
</form>
<div class="wrapper wrapper-<%= type %> wrapper-<%= type %>-<%= intent %>
<% if(obj.shown) { %>is-shown<% } else { %>is-hiding<% } %>
<% if(_.contains(['help', 'saving'], intent)) { %>wrapper-<%= type %>-status<% } %>"
id="<%= type %>-<%= intent %>"
aria-hidden="<% if(obj.shown) { %>false<% } else { %>true<% } %>"
aria-labelledby="<%= type %>-<%= intent %>-title"
<% if (obj.message) { %>aria-describedby="<%= type %>-<%= intent %>-description" <% } %>
<% if (obj.actions) { %>role="dialog"<% } %>
>
<div class="<%= type %> <%= intent %> <% if(obj.actions) { %>has-actions<% } %>">
<% if(obj.icon) { %>
<% var iconClass = {"warning": "warning-sign", "confirmation": "ok", "error": "warning-sign", "announcement": "bullhorn", "step-required": "exclamation-sign", "help": "question-sign", "saving": "cog"} %>
<i class="icon-<%= iconClass[intent] %>"></i>
<% } %>
<div class="copy">
<h2 class="title title-3" id="<%= type %>-<%= intent %>-title"><%= title %></h2>
<% if(obj.message) { %><p class="message" id="<%= type %>-<%= intent %>-description"><%= message %></p><% } %>
</div>
<% if(obj.actions) { %>
<nav class="nav-actions">
<h3 class="sr"><%= type %> Actions</h3>
<ul>
<% if(actions.primary) { %>
<li class="nav-item">
<a href="#" class="button action-primary <%= actions.primary.class %>"><%= actions.primary.text %></a>
</li>
<% } %>
<% if(actions.secondary) {
_.each(actions.secondary, function(secondary) { %>
<li class="nav-item">
<a href="#" class="button action-secondary <%= secondary.class %>"><%= secondary.text %></a>
</li>
<% });
} %>
</ul>
</nav>
<% } %>
<% if(obj.closeIcon) { %>
<a href="#" rel="view" class="action action-close action-<%= type %>-close">
<i class="icon-remove-sign"></i>
<span class="label">close <%= type %></span>
</a>
<% } %>
</div>
</div>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<ul> <ul>
%if allow_actions: %if allow_actions:
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button new-button new-user-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New User</a> <a href="#" class="button new-button new-user-button"><i class="icon-plus"></i> New User</a>
</li> </li>
%endif %endif
</ul> </ul>
......
...@@ -20,6 +20,12 @@ ...@@ -20,6 +20,12 @@
<script type="text/javascript" src="${static.url('js/views/overview.js')}"></script> <script type="text/javascript" src="${static.url('js/views/overview.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/template" id="section-name-edit-tpl">
<%static:include path="js/section-name-edit.underscore" />
</script>
<script type="text/javascript" src="${static.url('js/models/section.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/section.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
// TODO figure out whether these should be in window or someplace else or whether they're only needed as local vars // TODO figure out whether these should be in window or someplace else or whether they're only needed as local vars
...@@ -37,6 +43,14 @@ ...@@ -37,6 +43,14 @@
graders : window.graderTypes graders : window.graderTypes
}); });
}); });
$(".section-name").each(function() {
var model = new CMS.Models.Section({
id: $(this).parent(".item-details").data("id"),
name: $(this).data("name")
});
new CMS.Views.SectionShow({model: model, el: this}).render();
})
}); });
</script> </script>
...@@ -115,13 +129,13 @@ ...@@ -115,13 +129,13 @@
<h3 class="sr">Page Actions</h3> <h3 class="sr">Page Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="toggle-button toggle-button-sections"><i class="ss-icon ss-symbolicons-block icon">up</i> <span class="label">Collapse All Sections</span></a> <a href="#" class="toggle-button toggle-button-sections"><i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button new-button new-courseware-section-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">&#x002B;</i> New Section</a> <a href="#" class="button new-button new-courseware-section-button"><i class="icon-plus"></i> New Section</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view">&#xE010;</i>View Live</a> <a href="${lms_link}" rel="external" class="button view-button view-live-button">View Live</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -137,14 +151,7 @@ ...@@ -137,14 +151,7 @@
<a href="#" data-tooltip="Expand/collapse this section" class="expand-collapse-icon collapse"></a> <a href="#" data-tooltip="Expand/collapse this section" class="expand-collapse-icon collapse"></a>
<div class="item-details" data-id="${section.location}"> <div class="item-details" data-id="${section.location}">
<h3 class="section-name"> <h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3>
<span data-tooltip="Edit this section's name" class="section-name-span">${section.display_name_with_default}</span>
<form class="section-name-edit" style="display:none">
<input type="text" value="${section.display_name_with_default | h}" class="edit-section-name" autocomplete="off"/>
<input type="submit" class="save-button edit-section-name-save" value="Save" />
<input type="button" class="cancel-button edit-section-name-cancel" value="Cancel" />
</form>
</h3>
<div class="section-published-date"> <div class="section-published-date">
<% <%
start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y') start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y')
......
...@@ -92,7 +92,7 @@ from contentstore import utils ...@@ -92,7 +92,7 @@ from contentstore import utils
<ul class="list-actions"> <ul class="list-actions">
<li class="action-item"> <li class="action-item">
<a title="Send a note to students via email" href="mailto:someone@domain.com?Subject=Enroll%20in%20${context_course.display_name_with_default}&body=The%20course%20&quot;${context_course.display_name_with_default}&quot;,%20provided%20by%20edX,%20is%20open%20for%20enrollment.%20Please%20navigate%20to%20this%20course%20at%20https:${utils.get_lms_link_for_about_page(course_location)}%20to%20enroll." class="action action-primary"><i class="ss-icon icon ss-symbolicons-standard icon icon-inline icon-announcement">&#x2709;</i> Invite your students</a> <a title="Send a note to students via email" href="mailto:someone@domain.com?Subject=Enroll%20in%20${context_course.display_name_with_default}&body=The%20course%20&quot;${context_course.display_name_with_default}&quot;,%20provided%20by%20edX,%20is%20open%20for%20enrollment.%20Please%20navigate%20to%20this%20course%20at%20https:${utils.get_lms_link_for_about_page(course_location)}%20to%20enroll." class="action action-primary"><i class="icon-envelope-alt icon-inline"></i> Invite your students</a>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -110,7 +110,7 @@ editor.render(); ...@@ -110,7 +110,7 @@ editor.render();
<!-- notification: change has been made and a save is needed --> <!-- notification: change has been made and a save is needed -->
<div class="wrapper wrapper-notification wrapper-notification-warning" aria-hidden="true" role="dialog" aria-labelledby="notification-changesMade-title" aria-describedby="notification-changesMade-description"> <div class="wrapper wrapper-notification wrapper-notification-warning" aria-hidden="true" role="dialog" aria-labelledby="notification-changesMade-title" aria-describedby="notification-changesMade-description">
<div class="notification warning has-actions"> <div class="notification warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i> <i class="icon-warning-sign"></i>
<div class="copy"> <div class="copy">
<h2 class="title title-3" id="notification-changesMade-title">You've Made Some Changes</h2> <h2 class="title title-3" id="notification-changesMade-title">You've Made Some Changes</h2>
...@@ -136,7 +136,7 @@ editor.render(); ...@@ -136,7 +136,7 @@ editor.render();
<!-- alert: save confirmed with close --> <!-- alert: save confirmed with close -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" role="status"> <div class="wrapper wrapper-alert wrapper-alert-confirmation" role="status">
<div class="alert confirmation"> <div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i> <i class="icon-ok"></i>
<div class="copy"> <div class="copy">
<h2 class="title title-3">Your policy changes have been saved.</h2> <h2 class="title title-3">Your policy changes have been saved.</h2>
...@@ -144,7 +144,7 @@ editor.render(); ...@@ -144,7 +144,7 @@ editor.render();
</div> </div>
<a href="" rel="view" class="action action-alert-close"> <a href="" rel="view" class="action action-alert-close">
<i class="ss-icon ss-symbolicons-block icon icon-close">&#x2421;</i> <i class="icon-remove-sign"></i>
<span class="label">close alert</span> <span class="label">close alert</span>
</a> </a>
</div> </div>
...@@ -153,7 +153,7 @@ editor.render(); ...@@ -153,7 +153,7 @@ editor.render();
<!-- alert: error --> <!-- alert: error -->
<div class="wrapper wrapper-alert wrapper-alert-error" role="status"> <div class="wrapper wrapper-alert wrapper-alert-error" role="status">
<div class="alert error"> <div class="alert error">
<i class="ss-icon ss-symbolicons-block icon icon-error">&#x26A0;</i> <i class="icon-warning-sign"></i>
<div class="copy"> <div class="copy">
<h2 class="title title-3">There was an error saving your information</h2> <h2 class="title title-3">There was an error saving your information</h2>
......
...@@ -117,7 +117,7 @@ from contentstore import utils ...@@ -117,7 +117,7 @@ from contentstore import utils
<div class="actions"> <div class="actions">
<a href="#" class="new-button new-course-grading-item add-grading-data"> <a href="#" class="new-button new-course-grading-item add-grading-data">
<span class="plus-icon white"></span>New Assignment Type <i class="icon-plus"></i>New Assignment Type
</a> </a>
</div> </div>
</section> </section>
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<ol> <ol>
<li class="nav-item nav-course-courseware"> <li class="nav-item nav-course-courseware">
<h3 class="title"><span class="label-prefix">Course </span>Content <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3> <h3 class="title"><span class="label-prefix">Course </span>Content <i class="icon-caret-down"></i></h3>
<div class="wrapper wrapper-nav-sub"> <div class="wrapper wrapper-nav-sub">
<div class="nav-sub"> <div class="nav-sub">
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
</li> </li>
<li class="nav-item nav-course-settings"> <li class="nav-item nav-course-settings">
<h3 class="title"><span class="label-prefix">Course </span>Settings <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3> <h3 class="title"><span class="label-prefix">Course </span>Settings <i class="icon-caret-down"></i></h3>
<div class="wrapper wrapper-nav-sub"> <div class="wrapper wrapper-nav-sub">
<div class="nav-sub"> <div class="nav-sub">
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
</li> </li>
<li class="nav-item nav-course-tools"> <li class="nav-item nav-course-tools">
<h3 class="title">Tools <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3> <h3 class="title">Tools <i class="icon-caret-down"></i></h3>
<div class="wrapper wrapper-nav-sub"> <div class="wrapper wrapper-nav-sub">
<div class="nav-sub"> <div class="nav-sub">
...@@ -76,10 +76,10 @@ ...@@ -76,10 +76,10 @@
<li class="nav-item nav-account-username"> <li class="nav-item nav-account-username">
<a href="#" class="title"> <a href="#" class="title">
<span class="account-username"> <span class="account-username">
<i class="ss-icon ss-symbolicons-standard icon-user">&#x1F464;</i> <i class="icon-user"></i>
${ user.username } ${ user.username }
</span> </span>
<i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i> <i class="icon-caret-down"></i>
</a> </a>
<div class="wrapper wrapper-nav-sub"> <div class="wrapper wrapper-nav-sub">
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
<div class="wrapper-sock wrapper"> <div class="wrapper-sock wrapper">
<ul class="list-actions list-cta"> <ul class="list-actions list-cta">
<li class="action-item"> <li class="action-item">
<a href="#sock" class="cta cta-show-sock"><i class="ss-icon ss-symbolicons-block icon icon-inline icon-help">&#x2753;</i> <span class="copy">Looking for Help with Studio?</span></a> <a href="#sock" class="cta cta-show-sock"><i class="icon-question-sign"></i> <span class="copy">Looking for Help with Studio?</span></a>
</li> </li>
</ul> </ul>
<div class="wrapper-inner wrapper"> <div class="wrapper-inner wrapper">
<section class="sock" id="sock"> <section class="sock" id="sock">
<header> <header>
<h2 class="title sr"><i class="ss-icon ss-symbolicons-block icon icon-inline icon-help">&#x2753;</i> edX Studio Help</h2> <h2 class="title sr">edX Studio Help</h2>
</header> </header>
<div class="support"> <div class="support">
...@@ -21,15 +21,15 @@ ...@@ -21,15 +21,15 @@
<ul class="list-actions"> <ul class="list-actions">
<li class="action-item"> <li class="action-item">
<a href="http://files.edx.org/Getting_Started_with_Studio.pdf" class="action action-primary" title="This is a PDF Document"><i class="ss-icon icon ss-symbolicons-block icon icon-inline icon-pdf">&#xEC00;</i> Download Studio Documentation</a> <a href="http://files.edx.org/Getting_Started_with_Studio.pdf" class="action action-primary" title="This is a PDF Document">Download Studio Documentation</a>
<span class="tip">How to use Studio to build your course</span> <span class="tip">How to use Studio to build your course</span>
</li> </li>
<li class="action-item"> <li class="action-item">
<a href="http://help.edge.edx.org/" rel="external" class="action action-primary"><i class="ss-icon icon ss-symbolicons-block icon icon-inline icon-help">&#xEE11;</i> Studio Help Center</a> <a href="http://help.edge.edx.org/" rel="external" class="action action-primary">Studio Help Center</a>
<span class="tip"><i class="ss-icon ss-symbolicons-block icon-help">&#xEE11;</i> Studio Help Center</span> <span class="tip">Studio Help Center</span>
</li> </li>
<li class="action-item"> <li class="action-item">
<a href="https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about" rel="external" class="action action-primary"><i class="ss-icon icon ss-symbolicons-block icon icon-inline icon-enroll">&#x1F393;</i> Enroll in edX101</a> <a href="https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about" rel="external" class="action action-primary">Enroll in edX101</a>
<span class="tip">How to use Studio to build your course</span> <span class="tip">How to use Studio to build your course</span>
</li> </li>
</ul> </ul>
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<ul class="list-actions"> <ul class="list-actions">
<li class="action-item"> <li class="action-item">
<a href="http://help.edge.edx.org/discussion/new" class="action action-primary show-tender" title="Use our feedback tool, Tender, to share your feedback"><i class="ss-icon ss-symbolicons-block icon icon-inline icon-feedback">&#xE398;</i> Contact Us</a> <a href="http://help.edge.edx.org/discussion/new" class="action action-primary show-tender" title="Use our feedback tool, Tender, to share your feedback"><i class="icon-comments"></i> Contact Us</a>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -115,9 +115,6 @@ urlpatterns += ( ...@@ -115,9 +115,6 @@ urlpatterns += (
url(r'^login_post$', 'student.views.login_user', name='login_post'), url(r'^login_post$', 'student.views.login_user', name='login_post'),
url(r'^logout$', 'student.views.logout_user', name='logout'), url(r'^logout$', 'student.views.logout_user', name='logout'),
# static/proof-of-concept views
url(r'^ux-alerts$', 'contentstore.views.ux_alerts', name='ux-alerts')
) )
js_info_dict = { js_info_dict = {
......
...@@ -28,3 +28,8 @@ except: ...@@ -28,3 +28,8 @@ except:
% endfor % endfor
%endif %endif
</%def> </%def>
<%def name="include(path)"><%
from django.template.loaders.filesystem import _loader
source, template_path = _loader.load_template_source(path)
%>${source}</%def>
...@@ -74,6 +74,9 @@ def initial_setup(server): ...@@ -74,6 +74,9 @@ def initial_setup(server):
if not success: if not success:
raise IOError("Could not acquire valid ChromeDriver browser session.") raise IOError("Could not acquire valid ChromeDriver browser session.")
# Set the browser size to 1280x1024
world.browser.driver.set_window_size(1280, 1024)
@before.each_scenario @before.each_scenario
def reset_data(scenario): def reset_data(scenario):
......
...@@ -2,7 +2,7 @@ from setuptools import setup ...@@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name="calc", name="calc",
version="0.1", version="0.1.1",
py_modules=["calc"], py_modules=["calc"],
install_requires=[ install_requires=[
"pyparsing==1.5.6", "pyparsing==1.5.6",
......
...@@ -475,12 +475,11 @@ class LoncapaProblem(object): ...@@ -475,12 +475,11 @@ class LoncapaProblem(object):
msg = "Error while executing script code: %s" % str(err).replace('<', '&lt;') msg = "Error while executing script code: %s" % str(err).replace('<', '&lt;')
raise responsetypes.LoncapaProblemError(msg) raise responsetypes.LoncapaProblemError(msg)
# store code source in context # Store code source in context, along with the Python path needed to run it correctly.
context['script_code'] = all_code context['script_code'] = all_code
context['python_path'] = python_path
return context return context
def _extract_html(self, problemtree): # private def _extract_html(self, problemtree): # private
''' '''
Main (private) function which converts Problem XML tree to HTML. Main (private) function which converts Problem XML tree to HTML.
......
...@@ -286,7 +286,7 @@ class LoncapaResponse(object): ...@@ -286,7 +286,7 @@ class LoncapaResponse(object):
} }
try: try:
safe_exec.safe_exec(code, globals_dict) safe_exec.safe_exec(code, globals_dict, python_path=self.context['python_path'])
except Exception as err: except Exception as err:
msg = 'Error %s in evaluating hint function %s' % (err, hintfn) msg = 'Error %s in evaluating hint function %s' % (err, hintfn)
msg += "\nSee XML source line %s" % getattr( msg += "\nSee XML source line %s" % getattr(
...@@ -972,7 +972,7 @@ class CustomResponse(LoncapaResponse): ...@@ -972,7 +972,7 @@ class CustomResponse(LoncapaResponse):
'ans': ans, 'ans': ans,
} }
globals_dict.update(kwargs) globals_dict.update(kwargs)
safe_exec.safe_exec(code, globals_dict, cache=self.system.cache) safe_exec.safe_exec(code, globals_dict, python_path=self.context['python_path'])
return globals_dict['cfn_return'] return globals_dict['cfn_return']
return check_function return check_function
......
...@@ -39,6 +39,9 @@ class TestLazyMod(unittest.TestCase): ...@@ -39,6 +39,9 @@ class TestLazyMod(unittest.TestCase):
self.assertEqual(hsv[0], 0.25) self.assertEqual(hsv[0], 0.25)
def test_dotted(self): def test_dotted(self):
self.assertNotIn("email.utils", sys.modules) # wsgiref is a module with submodules that is not already imported.
email_utils = LazyModule("email.utils") # Any similar module would do. This test demonstrates that the module
self.assertEqual(email_utils.quote('"hi"'), r'\"hi\"') # is not already im
self.assertNotIn("wsgiref.util", sys.modules)
wsgiref_util = LazyModule("wsgiref.util")
self.assertEqual(wsgiref_util.guess_scheme({}), "http")
...@@ -2,7 +2,7 @@ from setuptools import setup ...@@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name="chem", name="chem",
version="0.1", version="0.1.1",
packages=["chem"], packages=["chem"],
install_requires=[ install_requires=[
"pyparsing==1.5.6", "pyparsing==1.5.6",
......
...@@ -2,7 +2,7 @@ from setuptools import setup ...@@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name="sandbox-packages", name="sandbox-packages",
version="0.1", version="0.1.1",
packages=[ packages=[
"verifiers", "verifiers",
], ],
......
...@@ -48,13 +48,14 @@ class VersionInteger(Integer): ...@@ -48,13 +48,14 @@ class VersionInteger(Integer):
class CombinedOpenEndedFields(object): class CombinedOpenEndedFields(object):
display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings)
current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state) current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state)
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state) task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state)
state = String(help="Which step within the current task that the student is on.", default="initial", state = String(help="Which step within the current task that the student is on.", default="initial",
scope=Scope.user_state) scope=Scope.user_state)
student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0,
scope=Scope.user_state) scope=Scope.user_state)
ready_to_reset = Boolean(help="If the problem is ready to be reset or not.", default=False, ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state) scope=Scope.user_state)
attempts = StringyInteger(display_name="Maximum Attempts", attempts = StringyInteger(display_name="Maximum Attempts",
help="The number of times the student can try to answer this problem.", default=1, help="The number of times the student can try to answer this problem.", default=1,
...@@ -226,10 +227,3 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -226,10 +227,3 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
has_score = True has_score = True
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "combinedopenended" template_dir_name = "combinedopenended"
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(CombinedOpenEndedDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([CombinedOpenEndedDescriptor.due, CombinedOpenEndedDescriptor.graceperiod,
CombinedOpenEndedDescriptor.version])
return non_editable_fields
...@@ -27,7 +27,10 @@ class @VideoCaption extends Subview ...@@ -27,7 +27,10 @@ class @VideoCaption extends Subview
@fetchCaption() @fetchCaption()
fetchCaption: -> fetchCaption: ->
$.getWithPrefix @captionURL(), (captions) => $.ajaxWithPrefix
url: @captionURL()
notifyOnError: false
success: (captions) =>
@captions = captions.text @captions = captions.text
@start = captions.start @start = captions.start
......
...@@ -27,7 +27,10 @@ class @VideoCaptionAlpha extends SubviewAlpha ...@@ -27,7 +27,10 @@ class @VideoCaptionAlpha extends SubviewAlpha
@fetchCaption() @fetchCaption()
fetchCaption: -> fetchCaption: ->
$.getWithPrefix @captionURL(), (captions) => $.ajaxWithPrefix
url: @captionURL()
notifyOnError: false
success: (captions) =>
@captions = captions.text @captions = captions.text
@start = captions.start @start = captions.start
......
...@@ -290,7 +290,6 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -290,7 +290,6 @@ class XMLModuleStore(ModuleStoreBase):
if course_dirs is None: if course_dirs is None:
course_dirs = sorted([d for d in os.listdir(self.data_dir) if course_dirs = sorted([d for d in os.listdir(self.data_dir) if
os.path.exists(self.data_dir / d / "course.xml")]) os.path.exists(self.data_dir / d / "course.xml")])
for course_dir in course_dirs: for course_dir in course_dirs:
self.try_load_course(course_dir) self.try_load_course(course_dir)
......
...@@ -6,7 +6,7 @@ log = logging.getLogger(__name__) ...@@ -6,7 +6,7 @@ log = logging.getLogger(__name__)
class ControllerQueryService(GradingService): class ControllerQueryService(GradingService):
""" """
Interface to staff grading backend. Interface to controller query backend.
""" """
def __init__(self, config, system): def __init__(self, config, system):
...@@ -77,6 +77,50 @@ class ControllerQueryService(GradingService): ...@@ -77,6 +77,50 @@ class ControllerQueryService(GradingService):
return response return response
class MockControllerQueryService(object):
"""
Mock controller query service for testing
"""
def __init__(self, config, system):
pass
def check_if_name_is_unique(self, **params):
"""
Mock later if needed. Stub function for now.
@param params:
@return:
"""
pass
def check_for_eta(self, **params):
"""
Mock later if needed. Stub function for now.
@param params:
@return:
"""
pass
def check_combined_notifications(self, **params):
combined_notifications = '{"flagged_submissions_exist": false, "version": 1, "new_student_grading_to_view": false, "success": true, "staff_needs_to_grade": false, "student_needs_to_peer_grade": true, "overall_need_to_check": true}'
return combined_notifications
def get_grading_status_list(self, **params):
grading_status_list = '{"version": 1, "problem_list": [{"problem_name": "Science Question -- Machine Assessed", "grader_type": "NA", "eta_available": true, "state": "Waiting to be Graded", "eta": 259200, "location": "i4x://MITx/oe101x/combinedopenended/Science_SA_ML"}, {"problem_name": "Humanities Question -- Peer Assessed", "grader_type": "NA", "eta_available": true, "state": "Waiting to be Graded", "eta": 259200, "location": "i4x://MITx/oe101x/combinedopenended/Humanities_SA_Peer"}], "success": true}'
return grading_status_list
def get_flagged_problem_list(self, **params):
flagged_problem_list = '{"version": 1, "success": false, "error": "No flagged submissions exist for course: MITx/oe101x/2012_Fall"}'
return flagged_problem_list
def take_action_on_flags(self, **params):
"""
Mock later if needed. Stub function for now.
@param params:
@return:
"""
pass
def convert_seconds_to_human_readable(seconds): def convert_seconds_to_human_readable(seconds):
if seconds < 60: if seconds < 60:
human_string = "{0} seconds".format(seconds) human_string = "{0} seconds".format(seconds)
......
window.gettext = window.ngettext = function(){};
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
} }
// mixins - line height // mixins - line height
@mixin lh($fontSize: auto){ @mixin line-height($fontSize: auto){
line-height: ($fontSize*1.48) + px; line-height: ($fontSize*1.48) + px;
line-height: (($fontSize/10)*1.48) + rem; line-height: (($fontSize/10)*1.48) + rem;
} }
......
...@@ -414,6 +414,11 @@ def modx_dispatch(request, dispatch, location, course_id): ...@@ -414,6 +414,11 @@ def modx_dispatch(request, dispatch, location, course_id):
through the part before the first '?'. through the part before the first '?'.
- location -- the module location. Used to look up the XModule instance - location -- the module location. Used to look up the XModule instance
- course_id -- defines the course context for this request. - course_id -- defines the course context for this request.
Raises PermissionDenied if the user is not logged in. Raises Http404 if
the location and course_id do not identify a valid module, the module is
not accessible by the user, or the module raises NotFoundError. If the
module raises any other error, it will escape this function.
''' '''
# ''' (fix emacs broken parsing) # ''' (fix emacs broken parsing)
...@@ -442,8 +447,19 @@ def modx_dispatch(request, dispatch, location, course_id): ...@@ -442,8 +447,19 @@ def modx_dispatch(request, dispatch, location, course_id):
return HttpResponse(json.dumps({'success': file_too_big_msg})) return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles p[fileinput_id] = inputfiles
try:
descriptor = modulestore().get_instance(course_id, location)
except ItemNotFoundError:
log.warn(
"Invalid location for course id {course_id}: {location}".format(
course_id=course_id,
location=location
)
)
raise Http404
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id,
request.user, modulestore().get_instance(course_id, location)) request.user, descriptor)
instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax') instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax')
if instance is None: if instance is None:
......
...@@ -69,19 +69,38 @@ class ModuleRenderTestCase(LoginEnrollmentTestCase): ...@@ -69,19 +69,38 @@ class ModuleRenderTestCase(LoginEnrollmentTestCase):
json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' % json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %
(inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))})) (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))}))
mock_request_3 = MagicMock() mock_request_3 = MagicMock()
mock_request_3.POST.copy.return_value = {} mock_request_3.POST.copy.return_value = {'position': 1}
mock_request_3.FILES = False mock_request_3.FILES = False
mock_request_3.user = UserFactory() mock_request_3.user = UserFactory()
inputfile_2 = Stub() inputfile_2 = Stub()
inputfile_2.size = 1 inputfile_2.size = 1
inputfile_2.name = 'name' inputfile_2.name = 'name'
self.assertRaises(ItemNotFoundError, render.modx_dispatch,
mock_request_3, 'dummy', self.location, 'toy')
self.assertRaises(Http404, render.modx_dispatch, mock_request_3, 'dummy',
self.location, self.course_id)
mock_request_3.POST.copy.return_value = {'position': 1}
self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position', self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position',
self.location, self.course_id), HttpResponse) self.location, self.course_id), HttpResponse)
self.assertRaises(
Http404,
render.modx_dispatch,
mock_request_3,
'goto_position',
self.location,
'bad_course_id'
)
self.assertRaises(
Http404,
render.modx_dispatch,
mock_request_3,
'goto_position',
['i4x', 'edX', 'toy', 'chapter', 'bad_location'],
self.course_id
)
self.assertRaises(
Http404,
render.modx_dispatch,
mock_request_3,
'bad_dispatch',
self.location,
self.course_id
)
def test_get_score_bucket(self): def test_get_score_bucket(self):
self.assertEquals(render.get_score_bucket(0, 10), 'incorrect') self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
......
...@@ -174,6 +174,9 @@ def forum_form_discussion(request, course_id): ...@@ -174,6 +174,9 @@ def forum_form_discussion(request, course_id):
try: try:
unsafethreads, query_params = get_threads(request, course_id) # This might process a search query unsafethreads, query_params = get_threads(request, course_id) # This might process a search query
threads = [utils.safe_content(thread) for thread in unsafethreads] threads = [utils.safe_content(thread) for thread in unsafethreads]
except (cc.utils.CommentClientMaintenanceError) as err:
log.warning("Forum is in maintenance mode")
return render_to_response('discussion/maintenance.html', {})
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
log.error("Error loading forum discussion threads: %s" % str(err)) log.error("Error loading forum discussion threads: %s" % str(err))
raise Http404 raise Http404
......
...@@ -21,6 +21,7 @@ import open_ended_notifications ...@@ -21,6 +21,7 @@ import open_ended_notifications
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import search from xmodule.modulestore import search
from xmodule.modulestore.exceptions import ItemNotFoundError
from django.http import HttpResponse, Http404, HttpResponseRedirect from django.http import HttpResponse, Http404, HttpResponseRedirect
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
...@@ -30,10 +31,10 @@ log = logging.getLogger(__name__) ...@@ -30,10 +31,10 @@ log = logging.getLogger(__name__)
system = ModuleSystem( system = ModuleSystem(
ajax_url=None, ajax_url=None,
track_function=None, track_function=None,
get_module = None, get_module=None,
render_template=render_to_string, render_template=render_to_string,
replace_urls = None, replace_urls=None,
xblock_model_data= {} xblock_model_data={}
) )
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system) controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
...@@ -90,40 +91,61 @@ def staff_grading(request, course_id): ...@@ -90,40 +91,61 @@ def staff_grading(request, course_id):
'staff_access': True, }) 'staff_access': True, })
@cache_control(no_cache=True, no_store=True, must_revalidate=True) def find_peer_grading_module(course):
def peer_grading(request, course_id): """
''' Given a course, finds the first peer grading module in it.
Show a peer grading interface @param course: A course object.
''' @return: boolean found_module, string problem_url
"""
#Get the current course
course = get_course_with_access(request.user, course_id, 'load')
course_id_parts = course.id.split("/")
false_dict = [False, "False", "false", "FALSE"]
#Reverse the base course url #Reverse the base course url
base_course_url = reverse('courses') base_course_url = reverse('courses')
try: found_module = False
#TODO: This will not work with multiple runs of a course. Make it work. The last key in the Location passed problem_url = ""
#to get_items is called revision. Is this the same as run?
#Get the peer grading modules currently in the course #Get the course id and split it
items = modulestore().get_items(['i4x', None, course_id_parts[1], 'peergrading', None]) course_id_parts = course.id.split("/")
log.info("COURSE ID PARTS")
log.info(course_id_parts)
#Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
items = modulestore().get_items(['i4x', course_id_parts[0], course_id_parts[1], 'peergrading', None],
course_id=course.id)
#See if any of the modules are centralized modules (ie display info from multiple problems) #See if any of the modules are centralized modules (ie display info from multiple problems)
items = [i for i in items if getattr(i,"use_for_single_location", True) in false_dict] items = [i for i in items if not getattr(i, "use_for_single_location", True)]
#Get the first one #Get the first one
if len(items) > 0:
item_location = items[0].location item_location = items[0].location
#Generate a url for the first module and redirect the user to it #Generate a url for the first module and redirect the user to it
problem_url_parts = search.path_to_location(modulestore(), course.id, item_location) problem_url_parts = search.path_to_location(modulestore(), course.id, item_location)
problem_url = generate_problem_url(problem_url_parts, base_course_url) problem_url = generate_problem_url(problem_url_parts, base_course_url)
found_module = True
return HttpResponseRedirect(problem_url) return found_module, problem_url
except:
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def peer_grading(request, course_id):
'''
When a student clicks on the "peer grading" button in the open ended interface, link them to a peer grading
xmodule in the course.
'''
#Get the current course
course = get_course_with_access(request.user, course_id, 'load')
found_module, problem_url = find_peer_grading_module(course)
if not found_module:
#This is a student_facing_error #This is a student_facing_error
error_message = "Error with initializing peer grading. Centralized module does not exist. Please contact course staff." error_message = """
Error with initializing peer grading.
There has not been a peer grading module created in the courseware that would allow you to grade others.
Please check back later for this.
"""
#This is a dev_facing_error #This is a dev_facing_error
log.exception(error_message + "Current course is: {0}".format(course_id)) log.exception(error_message + "Current course is: {0}".format(course_id))
return HttpResponse(error_message) return HttpResponse(error_message)
return HttpResponseRedirect(problem_url)
def generate_problem_url(problem_url_parts, base_course_url): def generate_problem_url(problem_url_parts, base_course_url):
""" """
...@@ -145,7 +167,8 @@ def generate_problem_url(problem_url_parts, base_course_url): ...@@ -145,7 +167,8 @@ def generate_problem_url(problem_url_parts, base_course_url):
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
def student_problem_list(request, course_id): def student_problem_list(request, course_id):
''' '''
Show a student problem list Show a student problem list to a student. Fetch the list from the grading controller server, get some metadata,
and then show it to the student.
''' '''
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
student_id = unique_id_for_user(request.user) student_id = unique_id_for_user(request.user)
...@@ -157,6 +180,7 @@ def student_problem_list(request, course_id): ...@@ -157,6 +180,7 @@ def student_problem_list(request, course_id):
base_course_url = reverse('courses') base_course_url = reverse('courses')
try: try:
#Get list of all open ended problems that the grading server knows about
problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user)) problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user))
problem_list_dict = json.loads(problem_list_json) problem_list_dict = json.loads(problem_list_json)
success = problem_list_dict['success'] success = problem_list_dict['success']
...@@ -166,8 +190,22 @@ def student_problem_list(request, course_id): ...@@ -166,8 +190,22 @@ def student_problem_list(request, course_id):
else: else:
problem_list = problem_list_dict['problem_list'] problem_list = problem_list_dict['problem_list']
#A list of problems to remove (problems that can't be found in the course)
list_to_remove = []
for i in xrange(0, len(problem_list)): for i in xrange(0, len(problem_list)):
try:
#Try to load each problem in the courseware to get links to them
problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location']) problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location'])
except ItemNotFoundError:
#If the problem cannot be found at the location received from the grading controller server, it has been deleted by the course author.
#Continue with the rest of the location to construct the list
error_message = "Could not find module for course {0} at location {1}".format(course.id,
problem_list[i][
'location'])
log.error(error_message)
#Mark the problem for removal from the list
list_to_remove.append(i)
continue
problem_url = generate_problem_url(problem_url_parts, base_course_url) problem_url = generate_problem_url(problem_url_parts, base_course_url)
problem_list[i].update({'actual_url': problem_url}) problem_list[i].update({'actual_url': problem_url})
eta_available = problem_list[i]['eta_available'] eta_available = problem_list[i]['eta_available']
...@@ -197,6 +235,8 @@ def student_problem_list(request, course_id): ...@@ -197,6 +235,8 @@ def student_problem_list(request, course_id):
log.error("Problem with results from external grading service for open ended.") log.error("Problem with results from external grading service for open ended.")
success = False success = False
#Remove problems that cannot be found in the courseware from the list
problem_list = [problem_list[i] for i in xrange(0, len(problem_list)) if i not in list_to_remove]
ajax_url = _reverse_with_slash('open_ended_problems', course_id) ajax_url = _reverse_with_slash('open_ended_problems', course_id)
return render_to_response('open_ended_problems/open_ended_problems.html', { return render_to_response('open_ended_problems/open_ended_problems.html', {
...@@ -300,6 +340,15 @@ def combined_notifications(request, course_id): ...@@ -300,6 +340,15 @@ def combined_notifications(request, course_id):
'description': description, 'description': description,
'alert_message': alert_message 'alert_message': alert_message
} }
#The open ended panel will need to link the "peer grading" button in the panel to a peer grading
#xmodule defined in the course. This checks to see if the human name of the server notification
#that we are currently processing is "peer grading". If it is, it looks for a peer grading
#module in the course. If none exists, it removes the peer grading item from the panel.
if human_name == "Peer Grading":
found_module, problem_url = find_peer_grading_module(course)
if found_module:
notification_list.append(notification_item)
else:
notification_list.append(notification_item) notification_list.append(notification_item)
ajax_url = _reverse_with_slash('open_ended_notifications', course_id) ajax_url = _reverse_with_slash('open_ended_notifications', course_id)
...@@ -311,9 +360,7 @@ def combined_notifications(request, course_id): ...@@ -311,9 +360,7 @@ def combined_notifications(request, course_id):
'ajax_url': ajax_url, 'ajax_url': ajax_url,
} }
return render_to_response('open_ended_problems/combined_notifications.html', return render_to_response('open_ended_problems/combined_notifications.html', combined_dict)
combined_dict
)
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment