Commit dcb95291 by Will Daly

Merge branch 'master' into feature/will/capa-template-tests

parents a28aed58 256a3677
......@@ -9,7 +9,7 @@
:2e#
.AppleDouble
database.sqlite
private-requirements.txt
requirements/private.txt
courseware/static/js/mathjax/*
flushdb.sh
build
......@@ -26,6 +26,7 @@ Gemfile.lock
conf/locale/en/LC_MESSAGES/*.po
!messages.po
lms/static/sass/*.css
lms/static/sass/application.scss
cms/static/sass/*.css
lms/lib/comment_client/python
nosetests.xml
......
REVIEWBOARD_URL = "https://rbcommons.com/s/edx/"
GUESS_FIELDS = True
Piotr Mitros <pmitros@edx.org>
Kyle Fiedler <kyle@kylefiedler.com>
Ernie Park <eipark@mit.edu>
Bridger Maxwell <bridger@mit.edu>
Lyla Fischer <lyla@edx.org>
David Ormsbee <dave@edx.org>
Chris Terman <cjt@edx.org>
Reda Lemeden <reda@thoughtbot.com>
Anant Agarwal <agarwal@edx.org>
Jean-Michel Claus <jmc@edx.org>
Calen Pennington <calen.pennington@gmail.com>
JM Van Thong <jm@edx.org>
Prem Sichanugrist <psichanugrist@thoughtbot.com>
Isaac Chuang <ichuang@mit.edu>
Galen Frechette <galen@thoughtbot.com>
Edward Loveall <edward@edwardloveall.com>
Matt Jankowski <mjankowski@thoughtbot.com>
John Jarvis <jarv@edx.org>
Victor Shnayder <victor@edx.org>
Matthew Mongeau <halogenandtoast@gmail.com>
Tony Kim <kimth@edx.org>
Arjun Singh <arjun810@gmail.com>
John Hess <mgojohn@gmail.com>
Carlos Andrés Rocha <rocha@edx.org>
Mike Chen <ccp0101@gmail.com>
Rocky Duan <dementrock@gmail.com>
Sidhanth Rao <sidhanth@mitx.mit.edu>
Brittany Cheng <bcheng42@gmail.com>
Dhaval Adjodah <dhaval@mit.edu>
Tom Giannattasio <tom@mitx.mit.edu>
Ibrahim Awwal <ibrahim.awwal@gmail.com>
Sarina Canelake <sarina@edx.org>
Mark L. Chang <mark.chang@gmail.com>
Dean Dieker <ddieker@gmail.com>
Tommy MacWilliam <tmacwilliam@cs.harvard.edu>
Nate Hardison <natehardison@gmail.com>
Chris Dodge <cdodge@edx.org>
Kevin Chugh <kevinchugh@edx.org>
Ned Batchelder <ned@nedbatchelder.com>
Alexander Kryklia <kryklia@gmail.com>
Vik Paruchuri <vik@edx.org>
Louis Sobel <sobel@edx.org>
Brian Wilson <brian@edx.org>
Ashley Penney <apenney@edx.org>
Don Mitchell <dmitchell@edx.org>
Aaron Culich <aculich@edx.org>
Brian Talbot <btalbot@edx.org>
Jay Zoldak <jzoldak@edx.org>
Valera Rozuvan <valera.rozuvan@gmail.com>
Diana Huang <dkh@edx.org>
Marco Morales <marcotuts@gmail.com>
Christina Roberts <christina@edx.org>
Robert Chirwa <robert@edx.org>
Ed Zarecor <ed@edx.org>
Deena Wang <thedeenawang@gmail.com>
Jean Manuel-Nater <jnater@edx.org>
Emily Zhang <1800.ehz.hang@gmail.com>
Jennifer Akana <jaakana@gmail.com>
Peter Baratta <peter.baratta@gmail.com>
Julian Arni <julian@edx.org>
Arthur Barrett <abarrett@edx.org>
Vasyl Nakvasiuk <vaxxxa@gmail.com>
Will Daly <will@edx.org>
James Tauber <jtauber@jtauber.com>
Greg Price <gprice@edx.org>
Joe Blaylock <jrbl@stanford.edu>
Sef Kloninger <sef@kloninger.com>
Anto Stupak <s2pak.anton@gmail.com>
David Adams <dcadams@stanford.edu>
Steve Strassmann <straz@edx.org>
Giulio Gratta <giulio@giuliogratta.com>
David Baumgold <david@davidbaumgold.com>
Jason Bau <jbau@stanford.edu>
Frances Botsford <frances@edx.org>
......@@ -77,8 +77,8 @@ environment), and Node has a library installer called
Once you've got your languages and virtual environments set up, install
the libraries like so:
$ pip install -r requirements/base.txt
$ pip install -r requirements/post.txt
$ pip install -r requirements/edx/base.txt
$ pip install -r requirements/edx/post.txt
$ bundle install
$ npm install
......
......@@ -11,7 +11,8 @@ Feature: Advanced (manual) course policy
Given I am on the Advanced Course Settings page in Studio
Then the settings are alphabetized
@skip-phantom
# Skipped because Ubuntu ChromeDriver cannot click notification "Cancel"
@skip
Scenario: Test cancel editing key value
Given I am on the Advanced Course Settings page in Studio
When I edit the value of a policy key
......@@ -20,7 +21,8 @@ Feature: Advanced (manual) course policy
And I reload the page
Then the policy key value is unchanged
@skip-phantom
# Skipped because Ubuntu ChromeDriver cannot click notification "Save"
@skip
Scenario: Test editing key value
Given I am on the Advanced Course Settings page in Studio
When I edit the value of a policy key and save
......@@ -28,7 +30,8 @@ Feature: Advanced (manual) course policy
And I reload the page
Then the policy key value is changed
@skip-phantom
# Skipped because Ubuntu ChromeDriver cannot edit CodeMirror input
@skip
Scenario: Test how multi-line input appears
Given I am on the Advanced Course Settings page in Studio
When I create a JSON object as a value
......@@ -36,7 +39,8 @@ Feature: Advanced (manual) course policy
And I reload the page
Then it is displayed as formatted
@skip-phantom
# Skipped because Ubuntu ChromeDriver cannot edit CodeMirror input
@skip
Scenario: Test automatic quoting of non-JSON values
Given I am on the Advanced Course Settings page in Studio
When I create a non-JSON value not in quotes
......
......@@ -19,9 +19,7 @@ DISPLAY_NAME_VALUE = '"Robot Super Course"'
############### ACTIONS ####################
@step('I select the Advanced Settings$')
def i_select_advanced_settings(step):
expand_icon_css = 'li.nav-course-settings i.icon-expand'
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
world.click_course_settings()
link_css = 'li.nav-course-settings-advanced a'
world.css_click(link_css)
......
......@@ -10,8 +10,6 @@ Feature: Course checklists
Then I can check and uncheck tasks in a checklist
And They are correctly selected after I reload the page
@skip-phantom
@skip-firefox
Scenario: A task can link to a location within Studio
Given I have opened Checklists
When I select a link to the course outline
......@@ -19,8 +17,6 @@ Feature: Course checklists
And I press the browser back button
Then I am brought back to the course outline in the correct state
@skip-phantom
@skip-firefox
Scenario: A task can link to a location outside Studio
Given I have opened Checklists
When I select a link to help page
......
......@@ -10,9 +10,7 @@ from selenium.common.exceptions import StaleElementReferenceException
############### ACTIONS ####################
@step('I select Checklists from the Tools menu$')
def i_select_checklists(step):
expand_icon_css = 'li.nav-course-tools i.icon-expand'
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
world.click_tools()
link_css = 'li.nav-course-tools-checklists a'
world.css_click(link_css)
......
Feature: Course Settings
As a course author, I want to be able to configure my course settings.
@skip-phantom
Scenario: User can set course dates
Given I have opened a new course in Studio
When I select Schedule and Details
And I set course dates
Then I see the set dates on refresh
@skip-phantom
Scenario: User can clear previously set course dates (except start date)
Given I have set course dates
And I clear all the dates except start
Then I see cleared dates on refresh
@skip-phantom
Scenario: User cannot clear the course start date
Given I have set course dates
And I clear the course start date
......
......@@ -25,9 +25,7 @@ DEFAULT_TIME = "00:00"
############### ACTIONS ####################
@step('I select Schedule and Details$')
def test_i_select_schedule_and_details(step):
expand_icon_css = 'li.nav-course-settings i.icon-expand'
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
world.click_course_settings()
link_css = 'li.nav-course-settings-schedule a'
world.css_click(link_css)
......
......@@ -62,4 +62,4 @@ def i_am_on_tab(step, tab_name):
@step('I see a link for adding a new section$')
def i_see_new_section_link(step):
link_css = 'a.new-courseware-section-button'
assert world.css_has_text(link_css, '+ New Section')
assert world.css_has_text(link_css, 'New Section')
......@@ -3,7 +3,6 @@ Feature: Create Section
As a course author
I want to create and edit sections
@skip-phantom
Scenario: Add a new section to a course
Given I have opened a new course in Studio
When I click the New Section link
......@@ -27,7 +26,8 @@ Feature: Create Section
And I save a new section release date
Then the section release date is updated
@skip-phantom
# Skipped because Ubuntu ChromeDriver hangs on alert
@skip
Scenario: Delete section
Given I have opened a new course in Studio
And I have added a new section
......
......@@ -62,7 +62,7 @@ def i_click_to_edit_section_name(step):
@step('I see the complete section name with a quote in the editor$')
def i_see_complete_section_name_with_quote_in_editor(step):
css = '.edit-section-name'
css = '.section-name-edit input[type=text]'
assert world.is_css_present(css)
assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"')
......
......@@ -18,10 +18,7 @@ def i_fill_in_the_registration_form(step):
@step('I press the Create My Account button on the registration form$')
def i_press_the_button_on_the_registration_form(step):
submit_css = 'form#register_form button#submit'
# Workaround for click not working on ubuntu
# for some unknown reason.
e = world.css_find(submit_css)
e.type(' ')
world.css_click(submit_css)
@step('I should see be on the studio home page$')
......
......@@ -14,7 +14,6 @@ Feature: Overview Toggle Section
When I navigate to the course overview page
Then I do not see the "Collapse All Sections" link
@skip-phantom
Scenario: Collapse link appears after creating first section of a course
Given I have a course with no sections
When I navigate to the course overview page
......@@ -22,7 +21,8 @@ Feature: Overview Toggle Section
Then I see the "Collapse All Sections" link
And all sections are expanded
@skip-phantom
# Skipped because Ubuntu ChromeDriver hangs on alert
@skip
Scenario: Collapse link is not removed after last section of a course is deleted
Given I have a course with 1 section
And I navigate to the course overview page
......
......@@ -3,14 +3,12 @@ Feature: Create Subsection
As a course author
I want to create and edit subsections
@skip-phantom
Scenario: Add a new subsection to a section
Given I have opened a new course section in Studio
When I click the New Subsection link
And I enter the subsection name and click save
Then I see my subsection on the Courseware page
@skip-phantom
Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216)
Given I have opened a new course section in Studio
When I click the New Subsection link
......@@ -27,7 +25,6 @@ Feature: Create Subsection
And I reload the page
Then I see it marked as Homework
@skip-phantom
Scenario: Set a due date in a different year (bug #256)
Given I have opened a new subsection in Studio
And I have set a release date and due date in different years
......@@ -35,7 +32,8 @@ Feature: Create Subsection
And I reload the page
Then I see the correct dates
@skip-phantom
# Skipped because Ubuntu ChromeDriver hangs on alert
@skip
Scenario: Delete a subsection
Given I have opened a new course section in Studio
And I have added a new subsection
......
......@@ -63,14 +63,6 @@ def test_have_set_dates_in_different_years(step):
set_date_and_time('input#due_date', '01/02/2012', 'input#due_time', '04:00')
@step('I see the correct dates$')
def i_see_the_correct_dates(step):
assert_equal('12/25/2011', world.css_find('input#start_date').first.value)
assert_equal('03:00', world.css_find('input#start_time').first.value)
assert_equal('01/02/2012', world.css_find('input#due_date').first.value)
assert_equal('04:00', world.css_find('input#due_time').first.value)
@step('I mark it as Homework$')
def i_mark_it_as_homework(step):
world.css_click('a.menu-toggle')
......@@ -101,8 +93,20 @@ def the_subsection_does_not_exist(step):
assert world.browser.is_element_not_present_by_css(css)
@step('I see the correct dates$')
def i_see_the_correct_dates(step):
assert_equal('12/25/2011', get_date('input#start_date'))
assert_equal('03:00', get_date('input#start_time'))
assert_equal('01/02/2012', get_date('input#due_date'))
assert_equal('04:00', get_date('input#due_time'))
############ HELPER METHODS ###################
def get_date(css):
return world.css_find(css).first.value.strip()
def save_subsection_name(name):
name_css = 'input.new-subsection-name-input'
save_css = 'input.new-subsection-name-save'
......
......@@ -17,7 +17,6 @@ from xmodule.modulestore.tests.factories import CourseFactory
from models.settings.course_metadata import CourseMetadata
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.django import modulestore
from xmodule.fields import Date
......@@ -256,7 +255,7 @@ class CourseMetadataEditingTest(CourseTestCase):
def setUp(self):
CourseTestCase.setUp(self)
# add in the full class too
import_from_xml(modulestore(), 'common/test/data/', ['full'])
import_from_xml(get_modulestore(self.course_location), 'common/test/data/', ['full'])
self.fullcourse_location = Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])
def test_fetch_initial_fields(self):
......
from contentstore.utils import get_modulestore, get_url_reverse
from contentstore.tests.test_course_settings import CourseTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from django.core.urlresolvers import reverse
class DeleteItem(CourseTestCase):
def setUp(self):
""" Creates the test course with a static page in it. """
super(DeleteItem, self).setUp()
self.course = CourseFactory.create(org='mitX', number='333', display_name='Dummy Course')
def testDeleteStaticPage(self):
# Add static tab
data = {
'parent_location': 'i4x://mitX/333/course/Dummy_Course',
'template': 'i4x://edx/templates/static_tab/Empty'
}
resp = self.client.post(reverse('clone_item'), data)
self.assertEqual(resp.status_code, 200)
# Now delete it. There was a bug that the delete was failing (static tabs do not exist in draft modulestore).
resp = self.client.post(reverse('delete_item'), resp.content, "application/json")
self.assertEqual(resp.status_code, 200)
""" Tests for utils. """
from contentstore import utils
import mock
import collections
import copy
from django.test import TestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
......@@ -70,3 +72,79 @@ class UrlReverseTestCase(ModuleStoreTestCase):
'https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about',
utils.get_url_reverse('https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about', course)
)
class ExtraPanelTabTestCase(TestCase):
""" Tests adding and removing extra course tabs. """
def get_tab_type_dicts(self, tab_types):
""" Returns an array of tab dictionaries. """
if tab_types:
return [{'tab_type': tab_type} for tab_type in tab_types.split(',')]
else:
return []
def get_course_with_tabs(self, tabs=[]):
""" Returns a mock course object with a tabs attribute. """
course = collections.namedtuple('MockCourse', ['tabs'])
if isinstance(tabs, basestring):
course.tabs = self.get_tab_type_dicts(tabs)
else:
course.tabs = tabs
return course
def test_add_extra_panel_tab(self):
""" Tests if a tab can be added to a course tab list. """
for tab_type in utils.EXTRA_TAB_PANELS.keys():
tab = utils.EXTRA_TAB_PANELS.get(tab_type)
# test adding with changed = True
for tab_setup in ['', 'x', 'x,y,z']:
course = self.get_course_with_tabs(tab_setup)
expected_tabs = copy.copy(course.tabs)
expected_tabs.append(tab)
changed, actual_tabs = utils.add_extra_panel_tab(tab_type, course)
self.assertTrue(changed)
self.assertEqual(actual_tabs, expected_tabs)
# test adding with changed = False
tab_test_setup = [
[tab],
[tab, self.get_tab_type_dicts('x,y,z')],
[self.get_tab_type_dicts('x,y'), tab, self.get_tab_type_dicts('z')],
[self.get_tab_type_dicts('x,y,z'), tab]]
for tab_setup in tab_test_setup:
course = self.get_course_with_tabs(tab_setup)
expected_tabs = copy.copy(course.tabs)
changed, actual_tabs = utils.add_extra_panel_tab(tab_type, course)
self.assertFalse(changed)
self.assertEqual(actual_tabs, expected_tabs)
def test_remove_extra_panel_tab(self):
""" Tests if a tab can be removed from a course tab list. """
for tab_type in utils.EXTRA_TAB_PANELS.keys():
tab = utils.EXTRA_TAB_PANELS.get(tab_type)
# test removing with changed = True
tab_test_setup = [
[tab],
[tab, self.get_tab_type_dicts('x,y,z')],
[self.get_tab_type_dicts('x,y'), tab, self.get_tab_type_dicts('z')],
[self.get_tab_type_dicts('x,y,z'), tab]]
for tab_setup in tab_test_setup:
course = self.get_course_with_tabs(tab_setup)
expected_tabs = [t for t in course.tabs if t != utils.EXTRA_TAB_PANELS.get(tab_type)]
changed, actual_tabs = utils.remove_extra_panel_tab(tab_type, course)
self.assertTrue(changed)
self.assertEqual(actual_tabs, expected_tabs)
# test removing with changed = False
for tab_setup in ['', 'x', 'x,y,z']:
course = self.get_course_with_tabs(tab_setup)
expected_tabs = copy.copy(course.tabs)
changed, actual_tabs = utils.remove_extra_panel_tab(tab_type, course)
self.assertFalse(changed)
self.assertEqual(actual_tabs, expected_tabs)
......@@ -9,6 +9,8 @@ DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_ta
#In order to instantiate an open ended tab automatically, need to have this data
OPEN_ENDED_PANEL = {"name": "Open Ended Panel", "type": "open_ended"}
NOTES_PANEL = {"name": "My Notes", "type": "notes"}
EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]])
def get_modulestore(location):
......@@ -192,9 +194,10 @@ class CoursePageNames:
Checklists = "checklists"
def add_open_ended_panel_tab(course):
def add_extra_panel_tab(tab_type, course):
"""
Used to add the open ended panel tab to a course if it does not exist.
Used to add the panel tab to a course if it does not exist.
@param tab_type: A string representing the tab type.
@param course: A course object from the modulestore.
@return: Boolean indicating whether or not a tab was added and a list of tabs for the course.
"""
......@@ -202,16 +205,19 @@ def add_open_ended_panel_tab(course):
course_tabs = copy.copy(course.tabs)
changed = False
#Check to see if open ended panel is defined in the course
if OPEN_ENDED_PANEL not in course_tabs:
tab_panel = EXTRA_TAB_PANELS.get(tab_type)
if tab_panel not in course_tabs:
#Add panel to the tabs if it is not defined
course_tabs.append(OPEN_ENDED_PANEL)
course_tabs.append(tab_panel)
changed = True
return changed, course_tabs
def remove_open_ended_panel_tab(course):
def remove_extra_panel_tab(tab_type, course):
"""
Used to remove the open ended panel tab from a course if it exists.
Used to remove the panel tab from a course if it exists.
@param tab_type: A string representing the tab type.
@param course: A course object from the modulestore.
@return: Boolean indicating whether or not a tab was added and a list of tabs for the course.
"""
......@@ -219,8 +225,10 @@ def remove_open_ended_panel_tab(course):
course_tabs = copy.copy(course.tabs)
changed = False
#Check to see if open ended panel is defined in the course
if OPEN_ENDED_PANEL in course_tabs:
tab_panel = EXTRA_TAB_PANELS.get(tab_type)
if tab_panel in course_tabs:
#Add panel to the tabs if it is not defined
course_tabs = [ct for ct in course_tabs if ct != OPEN_ENDED_PANEL]
course_tabs = [ct for ct in course_tabs if ct != tab_panel]
changed = True
return changed, course_tabs
......@@ -41,7 +41,8 @@ log = logging.getLogger(__name__)
COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"]
ADVANCED_COMPONENT_TYPES = ['annotatable', 'word_cloud'] + OPEN_ENDED_COMPONENT_TYPES
NOTE_COMPONENT_TYPES = ['notes']
ADVANCED_COMPONENT_TYPES = ['annotatable', 'word_cloud'] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_CATEGORY = 'advanced'
ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
......
......@@ -13,17 +13,13 @@ from django.core.urlresolvers import reverse
from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions \
import ItemNotFoundError, InvalidLocationError
from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
from xmodule.modulestore import Location
from contentstore.course_info_model \
import get_course_updates, update_course_updates, delete_course_update
from contentstore.utils \
import get_lms_link_for_item, add_open_ended_panel_tab, \
remove_open_ended_panel_tab
from models.settings.course_details \
import CourseDetails, CourseSettingsEncoder
from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update
from contentstore.utils import get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab
from models.settings.course_details import CourseDetails, CourseSettingsEncoder
from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata
from auth.authz import create_all_course_groups
......@@ -32,7 +28,12 @@ from util.json_request import expect_json
from .access import has_access, get_location_and_verify_access
from .requests import get_request_method
from .tabs import initialize_course_tabs
from .component import OPEN_ENDED_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from .component import OPEN_ENDED_COMPONENT_TYPES, \
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from django_comment_common.utils import seed_permissions_roles
# TODO: should explicitly enumerate exports with __all__
__all__ = ['course_index', 'create_new_course', 'course_info',
'course_info_updates', 'get_course_settings',
......@@ -135,6 +136,9 @@ def create_new_course(request):
create_all_course_groups(request.user, new_course.location)
# seed the forums
seed_permissions_roles(new_course.location.course_id)
return HttpResponse(json.dumps({'id': new_course.location.url()}))
......@@ -352,38 +356,52 @@ def course_advanced_updates(request, org, course, name):
request_body = json.loads(request.body)
# Whether or not to filter the tabs key out of the settings metadata
filter_tabs = True
# Check to see if the user instantiated any advanced components.
# This is a hack to add the open ended panel tab
# to a course automatically if the user has indicated that they want
# to edit the combinedopenended or peergrading
# module, and to remove it if they have removed the open ended elements.
#Check to see if the user instantiated any advanced components. This is a hack
#that does the following :
# 1) adds/removes the open ended panel tab to a course automatically if the user
# has indicated that they want to edit the combinedopendended or peergrading module
# 2) adds/removes the notes panel tab to a course automatically if the user has
# indicated that they want the notes module enabled in their course
# TODO refactor the above into distinct advanced policy settings
if ADVANCED_COMPONENT_POLICY_KEY in request_body:
# Check to see if the user instantiated any open ended components
found_oe_type = False
# Get the course so that we can scrape current tabs
#Get the course so that we can scrape current tabs
course_module = modulestore().get_item(location)
for oe_type in OPEN_ENDED_COMPONENT_TYPES:
if oe_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
# Add an open ended tab to the course if needed
changed, new_tabs = add_open_ended_panel_tab(course_module)
# If a tab has been added to the course, then send the
# metadata along to CourseMetadata.update_from_json
#Maps tab types to components
tab_component_map = {
'open_ended': OPEN_ENDED_COMPONENT_TYPES,
'notes': NOTE_COMPONENT_TYPES,
}
#Check to see if the user instantiated any notes or open ended components
for tab_type in tab_component_map.keys():
component_types = tab_component_map.get(tab_type)
found_ac_type = False
for ac_type in component_types:
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
#Add tab to the course if needed
changed, new_tabs = add_extra_panel_tab(tab_type, course_module)
#If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
if changed:
course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs})
#Indicate that tabs should not be filtered out of the metadata
filter_tabs = False
#Set this flag to avoid the tab removal code below.
found_ac_type = True
break
#If we did not find a module type in the advanced settings,
# we may need to remove the tab from the course.
if not found_ac_type:
#Remove tab from the course if needed
changed, new_tabs = remove_extra_panel_tab(tab_type, course_module)
if changed:
course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs})
# Indicate that tabs should not be filtered out of the metadata
#Indicate that tabs should *not* be filtered out of the metadata
filter_tabs = False
# Set this flag to avoid the open ended tab removal code below.
found_oe_type = True
break
# If we did not find an open ended module type in the advanced settings,
# we may need to remove the open ended tab from the course.
if not found_oe_type:
# Remove open ended tab to the course if needed
changed, new_tabs = remove_open_ended_panel_tab(course_module)
if changed:
request_body.update({'tabs': new_tabs})
# Indicate that tabs should not be filtered out of the metadata
filter_tabs = False
response_json = json.dumps(CourseMetadata.update_from_json(location,
request_body,
filter_tabs=filter_tabs))
......
......@@ -113,7 +113,7 @@ def delete_item(request):
delete_children = request.POST.get('delete_children', False)
delete_all_versions = request.POST.get('delete_all_versions', False)
store = modulestore()
store = get_modulestore(item_location)
item = store.get_item(item_location)
......
......@@ -8,7 +8,7 @@ from mitxmako.shortcuts import render_to_response
from external_auth.views import ssl_login_shortcut
from .user import index
__all__ = ['signup', 'old_login_redirect', 'login_page', 'howitworks', 'ux_alerts']
__all__ = ['signup', 'old_login_redirect', 'login_page', 'howitworks']
"""
Public views
......@@ -49,10 +49,3 @@ def howitworks(request):
return index(request)
else:
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):
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 :-)
'''
return HttpResponse(True)
return HttpResponse(status=204)
def get_request_method(request):
......
......@@ -2,33 +2,52 @@
This config file extends the test environment configuration
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 *
# You need to start the server in debug mode,
# otherwise the browser will not render the pages correctly
DEBUG = True
# Show the courses that are in the data directory
COURSES_ROOT = ENV_ROOT / "data"
DATA_DIR = COURSES_ROOT
# MODULESTORE = {
# 'default': {
# 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
# 'OPTIONS': {
# 'data_dir': DATA_DIR,
# 'default_class': 'xmodule.hidden_module.HiddenDescriptor',
# }
# }
# }
# Disable warnings for acceptance tests, to make the logs readable
import logging
logging.disable(logging.ERROR)
MODULESTORE_OPTIONS = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'host': 'localhost',
'db': 'test_xmodule',
'collection': 'acceptance_modulestore',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
}
MODULESTORE = {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
'OPTIONS': MODULESTORE_OPTIONS
},
'direct': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
'OPTIONS': MODULESTORE_OPTIONS
},
'draft': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
'OPTIONS': MODULESTORE_OPTIONS
}
}
# Set this up so that rake lms[acceptance] and running the
# harvest command both use the same (test) database
# which they can flush without messing up your dev db
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "test_mitx.db",
'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db",
'NAME': TEST_ROOT / "db" / "test_mitx.db",
'TEST_NAME': TEST_ROOT / "db" / "test_mitx.db",
}
}
......
"""
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
from .common import *
......@@ -28,6 +33,48 @@ EMAIL_BACKEND = 'django_ses.SESBackend'
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
###################################### CELERY ################################
# Don't use a connection pool, since connections are dropped by ELB.
BROKER_POOL_LIMIT = 0
BROKER_CONNECTION_TIMEOUT = 1
# For the Result Store, use the django cache named 'celery'
CELERY_RESULT_BACKEND = 'cache'
CELERY_CACHE_BACKEND = 'celery'
# When the broker is behind an ELB, use a heartbeat to refresh the
# connection and to detect if it has been dropped.
BROKER_HEARTBEAT = 10.0
BROKER_HEARTBEAT_CHECKRATE = 2
# Each worker should only fetch one message at a time
CELERYD_PREFETCH_MULTIPLIER = 1
# Skip djcelery migrations, since we don't use the database as the broker
SOUTH_MIGRATION_MODULES = {
'djcelery': 'ignore',
}
# Rename the exchange and queues for each variant
QUEUE_VARIANT = CONFIG_PREFIX.lower()
CELERY_DEFAULT_EXCHANGE = 'edx.{0}core'.format(QUEUE_VARIANT)
HIGH_PRIORITY_QUEUE = 'edx.{0}core.high'.format(QUEUE_VARIANT)
DEFAULT_PRIORITY_QUEUE = 'edx.{0}core.default'.format(QUEUE_VARIANT)
LOW_PRIORITY_QUEUE = 'edx.{0}core.low'.format(QUEUE_VARIANT)
CELERY_DEFAULT_QUEUE = DEFAULT_PRIORITY_QUEUE
CELERY_DEFAULT_ROUTING_KEY = DEFAULT_PRIORITY_QUEUE
CELERY_QUEUES = {
HIGH_PRIORITY_QUEUE: {},
LOW_PRIORITY_QUEUE: {},
DEFAULT_PRIORITY_QUEUE: {}
}
############# NON-SECURE ENV CONFIG ##############################
# Things like server locations, ports, etc.
with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file:
......@@ -78,3 +125,14 @@ CONTENTSTORE = AUTH_TOKENS['CONTENTSTORE']
# Datadog for events!
DATADOG_API = AUTH_TOKENS.get("DATADOG_API")
# Celery Broker
CELERY_BROKER_TRANSPORT = ENV_TOKENS.get("CELERY_BROKER_TRANSPORT", "")
CELERY_BROKER_HOSTNAME = ENV_TOKENS.get("CELERY_BROKER_HOSTNAME", "")
CELERY_BROKER_USER = AUTH_TOKENS.get("CELERY_BROKER_USER", "")
CELERY_BROKER_PASSWORD = AUTH_TOKENS.get("CELERY_BROKER_PASSWORD", "")
BROKER_URL = "{0}://{1}:{2}@{3}".format(CELERY_BROKER_TRANSPORT,
CELERY_BROKER_USER,
CELERY_BROKER_PASSWORD,
CELERY_BROKER_HOSTNAME)
......@@ -19,6 +19,10 @@ Longer TODO:
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 lms.envs.common
from path import path
......@@ -34,6 +38,9 @@ MITX_FEATURES = {
'STAFF_EMAIL': '', # email address for staff (eg to request course creation)
'STUDIO_NPS_SURVEY': True,
'SEGMENT_IO': True,
# Enable URL that shows information about the status of variuous services
'ENABLE_SERVICE_STATUS': False,
}
ENABLE_JASMINE = False
......@@ -214,7 +221,9 @@ PIPELINE_JS = {
'source_filenames': sorted(
rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.js') +
rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.js')
) + ['js/hesitate.js', 'js/base.js'],
) + ['js/hesitate.js', 'js/base.js',
'js/models/feedback.js', 'js/views/feedback.js',
'js/models/section.js', 'js/views/section.js'],
'output_filename': 'js/cms-application.js',
'test_order': 0
},
......@@ -240,6 +249,51 @@ STATICFILES_IGNORE_PATTERNS = (
PIPELINE_YUI_BINARY = 'yui-compressor'
################################# CELERY ######################################
# Message configuration
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_MESSAGE_COMPRESSION = 'gzip'
# Results configuration
CELERY_IGNORE_RESULT = False
CELERY_STORE_ERRORS_EVEN_IF_IGNORED = True
# Events configuration
CELERY_TRACK_STARTED = True
CELERY_SEND_EVENTS = True
CELERY_SEND_TASK_SENT_EVENT = True
# Exchange configuration
CELERY_DEFAULT_EXCHANGE = 'edx.core'
CELERY_DEFAULT_EXCHANGE_TYPE = 'direct'
# Queues configuration
HIGH_PRIORITY_QUEUE = 'edx.core.high'
DEFAULT_PRIORITY_QUEUE = 'edx.core.default'
LOW_PRIORITY_QUEUE = 'edx.core.low'
CELERY_QUEUE_HA_POLICY = 'all'
CELERY_CREATE_MISSING_QUEUES = True
CELERY_DEFAULT_QUEUE = DEFAULT_PRIORITY_QUEUE
CELERY_DEFAULT_ROUTING_KEY = DEFAULT_PRIORITY_QUEUE
CELERY_QUEUES = {
HIGH_PRIORITY_QUEUE: {},
LOW_PRIORITY_QUEUE: {},
DEFAULT_PRIORITY_QUEUE: {}
}
############################ APPS #####################################
INSTALLED_APPS = (
......@@ -249,8 +303,12 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'djcelery',
'south',
# Monitor the status of services
'service_status',
# For CMS
'contentstore',
'auth',
......@@ -264,4 +322,11 @@ INSTALLED_APPS = (
'pipeline',
'staticfiles',
'static_replace',
# comment common
'django_comment_common',
)
################# EDX MARKETING SITE ##################################
EDXMKTG_COOKIE_NAME = 'edxloggedin'
"""
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 logsettings import get_logger_config
......@@ -116,10 +120,14 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
################################# CELERY ######################################
# By default don't use a worker, execute tasks as if they were local functions
CELERY_ALWAYS_EAGER = True
################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo')
MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = (
......@@ -151,5 +159,8 @@ DEBUG_TOOLBAR_MONGO_STACKTRACES = True
# disable NPS survey in dev mode
MITX_FEATURES['STUDIO_NPS_SURVEY'] = False
# Enable URL that shows information about the status of variuous services
MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
# segment-io key for dev
SEGMENT_IO_KEY = 'mty8edrrsg'
# 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
# FORCE_SCRIPT_NAME = '/cms'
......
"""
This config file follows the dev enviroment, but adds the
requirement of a celery worker running in the background to process
celery tasks.
The worker can be executed using:
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 *
################################# CELERY ######################################
# Requires a separate celery worker
CELERY_ALWAYS_EAGER = False
# Use django db as the broker and result store
BROKER_URL = 'django://'
INSTALLED_APPS += ('djcelery.transport', )
CELERY_RESULT_BACKEND = 'database'
DJKOMBU_POLLING_INTERVAL = 1.0
# Disable transaction management because we are using a worker. Views
# that request a task and wait for the result will deadlock otherwise.
MIDDLEWARE_CLASSES = tuple(
c for c in MIDDLEWARE_CLASSES
if c != 'django.middleware.transaction.TransactionMiddleware')
# Note: other alternatives for disabling transactions don't work in 1.4
# https://code.djangoproject.com/ticket/2304
# https://code.djangoproject.com/ticket/16039
......@@ -2,6 +2,10 @@
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 logsettings import get_logger_config
......@@ -32,8 +36,13 @@ PIPELINE_JS['spec'] = {
}
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
JASMINE_REPORT_DIR = os.environ.get('JASMINE_REPORT_DIR', 'reports/cms/jasmine')
TEMPLATE_CONTEXT_PROCESSORS += ('settings_context_processor.context_processors.settings',)
TEMPLATE_VISIBLE_SETTINGS = ('JASMINE_REPORT_DIR', )
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib')
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/jasmine-reporters/src')
# Remove the localization middleware class because it requires the test database
# to be sync'd and migrated in order to run the jasmine tests interactively
......@@ -41,4 +50,4 @@ STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib')
MIDDLEWARE_CLASSES = tuple(e for e in MIDDLEWARE_CLASSES \
if e != 'django.middleware.locale.LocaleMiddleware')
INSTALLED_APPS += ('django_jasmine', )
INSTALLED_APPS += ('django_jasmine', 'settings_context_processor')
......@@ -7,6 +7,11 @@ sessions. Assumes structure:
/mitx # The location of this repo
/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 *
import os
from path import path
......@@ -41,14 +46,14 @@ MODULESTORE_OPTIONS = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'host': 'localhost',
'db': 'test_xmodule',
'collection': 'modulestore',
'fs_root': GITHUB_REPO_ROOT,
'collection': 'test_modulestore',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
}
MODULESTORE = {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
'OPTIONS': MODULESTORE_OPTIONS
},
'direct': {
......@@ -108,6 +113,12 @@ CACHES = {
}
}
################################# CELERY ######################################
CELERY_ALWAYS_EAGER = True
CELERY_RESULT_BACKEND = 'cache'
BROKER_TRANSPORT = 'memory'
################### Make tests faster
#http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/
PASSWORD_HASHERS = (
......@@ -121,3 +132,4 @@ SEGMENT_IO_KEY = '***REMOVED***'
# disable NPS survey in test mode
MITX_FEATURES['STUDIO_NPS_SURVEY'] = False
MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
......@@ -11,11 +11,11 @@
<span class="int"><%= percentChecked %></span>% of checklist completed</span></span>
<header>
<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>
<span class="checklist-status status">
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>
</header>
......@@ -58,4 +58,4 @@
<% taskIndex+=1; }) %>
</ul>
</section>
\ No newline at end of file
</section>
......@@ -8,6 +8,8 @@
"js/vendor/json2.js",
"js/vendor/underscore-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
@stubCookies =
csrftoken: "stubCSRFToken"
......
......@@ -22,3 +22,37 @@ describe "main helper", ->
it "setup AJAX CSRF token", ->
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 @@ $ ->
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
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 = ->
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() {
$('.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
$('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
......@@ -209,8 +204,8 @@ function toggleSections(e) {
$section = $('.courseware-section');
sectionCount = $section.length;
$button = $(this);
$labelCollapsed = $('<i class="ss-icon ss-symbolicons-block">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>');
$labelCollapsed = $('<i class="icon-arrow-up"></i> <span class="label">Collapse 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;
$button.toggleClass('is-activated').html(buttonLabel);
......@@ -763,72 +758,6 @@ function cancelNewSubsection(e) {
$(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) {
e.preventDefault();
$(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({
// data
data : JSON.stringify({ deleteKeys : self.deleteKeys})
})
.fail(function(hdr, status, error) { CMS.ServerError(self, "Deleting keys:" + status); })
.done(function(data, status, error) {
// clear deleteKeys on success
self.deleteKeys = [];
......
......@@ -22,8 +22,7 @@ CMS.Views.Checklists = Backbone.View.extend({
}
);
},
reset: true,
error: CMS.ServerError
reset: true
}
);
},
......@@ -90,8 +89,7 @@ CMS.Views.Checklists = Backbone.View.extend({
'task': model.attributes.items[task_index].short_description,
'state': model.attributes.items[task_index].is_checked
});
},
error : CMS.ServerError
}
});
}
});
......@@ -105,7 +105,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change
targetModel.save({}, {error : CMS.ServerError});
targetModel.save({});
this.closeEditor(this);
analytics.track('Saved Course Update', {
......@@ -166,11 +166,9 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
success: function() {
cacheThis.render();
},
reset: true,
error: CMS.ServerError
reset: true
});
},
error : CMS.ServerError
}
});
},
......@@ -254,8 +252,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
}
);
},
reset: true,
error: CMS.ServerError
reset: true
});
},
......@@ -296,7 +293,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onSave: function(event) {
this.model.set('data', this.$codeMirror.getValue());
this.render();
this.model.save({}, {error: CMS.ServerError});
this.model.save({});
this.$form.hide();
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({
// TODO move to a template file
'<h4 class="status-label"><%= assignmentType %></h4>' +
'<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>' +
'<ul class="menu">' +
'<% 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({
// because these are outside of this.$el, they can't be in the event hash
$('.save-button').on('click', this, this.saveView);
$('.cancel-button').on('click', this, this.revertView);
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
},
render: function() {
......@@ -144,8 +143,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
'course': course_location_analytics
});
},
error : CMS.ServerError
}
});
},
revertView : function(event) {
......@@ -155,8 +153,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
self.model.clear({silent : true});
self.model.fetch({
success : function() { self.render(); },
reset: true,
error : CMS.ServerError
reset: true
});
},
renderTemplate: function (key, value) {
......
......@@ -16,7 +16,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
},
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
this.$el.find("#course-name").val(this.model.get('location').get('name'));
this.$el.find("#course-organization").val(this.model.get('location').get('org'));
......@@ -26,7 +26,6 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
var dateIntrospect = new Date();
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
......
......@@ -44,7 +44,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
self.render();
}
);
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.model.get('graders').on('remove', this.render, this);
this.model.get('graders').on('reset', this.render, this);
......@@ -65,7 +64,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
gradeCollection.each(function(gradeModel) {
$(gradelist).append(self.template({model : gradeModel }));
var newEle = gradelist.children().last();
var newView = new CMS.Views.Settings.GraderView({el: newEle,
var newView = new CMS.Views.Settings.GraderView({el: newEle,
model : gradeModel, collection : gradeCollection });
});
......@@ -119,7 +118,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
gradeBarWidth : null, // cache of value since it won't change (more certain)
renderCutoffBar: function() {
var gradeBar =this.$el.find('.grade-bar');
var gradeBar =this.$el.find('.grade-bar');
this.gradeBarWidth = gradeBar.width();
var gradelist = gradeBar.children('.grades');
// HACK fixing a duplicate call issue by undoing previous call effect. Need to figure out why called 2x
......@@ -128,11 +127,11 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// Can probably be simplified to one variable now.
var removable = false;
var draggable = false; // first and last are not removable, first is not draggable
_.each(this.descendingCutoffs,
_.each(this.descendingCutoffs,
function(cutoff, index) {
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
var newBar = this.gradeCutoffTemplate({
descriptor : cutoff['designation'] ,
width : nextWidth,
removable : removable });
gradelist.append(newBar);
if (draggable) {
......@@ -152,7 +151,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
},
this);
// add fail which is not in data
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
var failBar = this.gradeCutoffTemplate({ descriptor : this.failLabel(),
width : nextWidth, removable : false});
$(failBar).find("span[contenteditable=true]").attr("contenteditable", false);
gradelist.append(failBar);
......@@ -221,12 +220,12 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
},
saveCutoffs: function() {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
this.model.save('grade_cutoffs',
_.reduce(this.descendingCutoffs,
function(object, cutoff) {
object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object;
},
},
{}));
},
......@@ -244,7 +243,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
this.descendingCutoffs.push({designation: this.GRADES[gradeLength], cutoff: failBarWidth});
this.descendingCutoffs[gradeLength - 1]['cutoff'] = Math.round(targetWidth);
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
var $newGradeBar = this.gradeCutoffTemplate({ descriptor : this.GRADES[gradeLength],
width : targetWidth, removable : true });
var gradeDom = this.$el.find('.grades');
gradeDom.children().last().before($newGradeBar);
......@@ -317,7 +316,6 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
'blur :input' : "inputUnfocus"
},
initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
this.render();
......@@ -362,9 +360,8 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
}
},
deleteModel : function(e) {
this.model.destroy(
{ error : CMS.ServerError});
this.model.destroy();
e.preventDefault();
}
});
\ No newline at end of file
});
......@@ -3,7 +3,6 @@ CMS.Views.ValidatingView = Backbone.View.extend({
// decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents
initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError);
this.listenTo(this.model, 'invalid', this.handleValidationError);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
......
......@@ -314,7 +314,7 @@ p, ul, ol, dl {
}
.upload-button .icon-create {
.upload-button .icon-plus {
@extend .t-action2;
line-height: 0 !important;
}
......@@ -750,11 +750,11 @@ hr.divide {
display: block;
}
.icon-create {
.icon-plus {
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/4);
margin-top: ($baseline/10);
margin-top: -2px;
line-height: 0;
}
}
......@@ -768,12 +768,12 @@ hr.divide {
display: block;
}
.icon-view {
@include font-size(15);
.icon-eye-open {
@include font-size(16);
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/2);
margin-top: ($baseline/5);
margin-right: 8px;
margin-top: -3px;
line-height: 0;
}
}
......@@ -801,7 +801,8 @@ hr.divide {
}
.tooltip {
@extend .t-copy-sub2;
@include font-size(12);
@include transition(opacity 0.1s ease-out);
position: absolute;
top: 0;
left: 0;
......@@ -811,10 +812,9 @@ hr.divide {
background: rgba(0, 0, 0, 0.85);
font-weight: normal;
line-height: 26px;
color: #fff;
color: $white;
pointer-events: none;
opacity: 0;
@include transition(opacity 0.1s ease-out);
&:after {
content: '▾';
......@@ -888,7 +888,7 @@ body.js {
@extend .text-sr;
}
.ss-icon {
[class^="icon-"] {
@include font-size(18);
color: $white;
}
......
......@@ -5,6 +5,12 @@
// 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
@mixin vertically-and-horizontally-centered ($height, $width) {
left: 50%;
......
......@@ -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
@mixin bounceIn {
0% {
......
// studio - assets - fonts
// NOTE: Sass currently can't process the standard Google Web Font import method, so a @font-face with src declaration of the .woff file that the Google @import method uses is needed :/
// ====================
// import from google fonts - Open Sans
@import url(http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,700,300);
// Open Sans - http://www.google.com/fonts/specimen/Open+Sans
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/k3k702ZOKiLJc3WVjuplzKRDOzjiPcYnFooOUGCOsRk.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/DXI1ORHCpsQm3Vp6mXoaTaRDOzjiPcYnFooOUGCOsRk.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/PRmiXeptR36kaC0GEAetxhbnBKKEOwRKgsHDreGcocg.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSansLight-Italic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/PRmiXeptR36kaC0GEAetxvR_54zmj3SbGZQh3vCOwvY.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/xjAJXh38I15wypJXxuGMBrrIa-7acMAeDBVuclsi6Gc.woff) format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans'), local('OpenSans'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/cJZKeOuBrn4kERxqtaUH3bO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
}
// import from google fonts - Bree
@import url(http://fonts.googleapis.com/css?family=Bree+Serif);
// Bree Serif - http://www.google.com/fonts/specimen/Bree+Serif
@font-face {
font-family: 'Bree Serif';
font-style: normal;
font-weight: 400;
src: local('Bree Serif'), local('BreeSerif'), url(http://themes.googleusercontent.com/static/fonts/breeserif/v2/LQ7WLTaITDg4OSRuOZCps73hpw3pgy2gAi-Ip7WPMi0.woff) format('woff');
}
......@@ -10,6 +10,7 @@
// ====================
@import 'vendor/normalize';
@import 'reset';
@import 'vendor/font-awesome';
// BASE *default edX offerings*
......
......@@ -48,7 +48,7 @@
padding: ($baseline/2) ($baseline*0.75);
background: transparent;
.ss-icon {
[class^="icon-"] {
@include transition(top .25s ease-in-out .25s);
@include font-size(15);
display: inline-block;
......@@ -60,7 +60,7 @@
&:hover, &:active {
color: $gray-d2;
.ss-icon {
[class^="icon-"] {
color: $gray-d2;
}
}
......
......@@ -106,13 +106,19 @@
width: 1px;
}
.course-org {
margin-right: ($baseline/4);
}
.course-number, .course-org {
@include font-size(12);
display: inline-block;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.3;
}
.course-org {
margin-right: ($baseline/4);
max-width: 140px;
}
.course-title {
......@@ -132,9 +138,9 @@
// specific elements - course nav
.nav-course {
width: 285px;
width: 290px;
@extend .t-copy-sub1;
margin-top: -($baseline/4);
@include font-size(14);
> ol > .nav-item {
vertical-align: bottom;
......@@ -152,8 +158,8 @@
color: $gray-d3;
.label-prefix {
display: block;
@include font-size(11);
display: block;
font-weight: 400;
}
}
......@@ -213,10 +219,11 @@
.nav-item {
position: relative;
.icon-expand {
.icon-caret-down {
@include font-size(14);
@include transition (color 0.5s ease-in-out, opacity 0.5s ease-in-out);
display: inline-block;
vertical-align: middle;
margin-left: 2px;
opacity: 0.5;
color: $gray-l2;
......@@ -224,7 +231,7 @@
&:hover {
.icon-expand {
.icon-caret-down {
color: $blue;
opacity: 1.0;
}
......
......@@ -5,12 +5,12 @@
}
.ss-icon {
[class^="icon-"] {
}
.icon-inline {
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/4);
}
\ No newline at end of file
}
......@@ -2,22 +2,22 @@
// ====================
.modal-cover {
@extend .depth3;
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .8);
}
.modal {
@extend .depth4;
display: none;
position: fixed;
top: 60px;
left: 50%;
z-index: 1001;
width: 930px;
height: 540px;
margin-left: -465px;
......@@ -61,12 +61,12 @@
// lean modal alternative
#lean_overlay {
@extend .depth4;
position: fixed;
z-index: 10000;
top: 0px;
left: 0px;
display: none;
height: 100%;
width: 100%;
background: $black;
}
\ No newline at end of file
}
......@@ -33,8 +33,9 @@
padding: ($baseline/2) $baseline;
color: $gray;
.icon {
[class^="icon-"] {
@include font-size(14);
margin-right: ($baseline/4);
}
&:hover {
......@@ -62,10 +63,6 @@
@extend .t-title4;
}
.ss-icon {
@extend .t-icon;
@extend .icon-inline;
}
}
// shared elements
......@@ -98,8 +95,11 @@
@extend .t-action4;
display: block;
.icon {
[class^="icon-"] {
@include font-size(18);
vertical-align: middle;
margin-right: $baseline/4;
}
&:hover, &:active {
......
......@@ -144,7 +144,7 @@
// prompts
.wrapper-prompt {
@extend .depth4;
@extend .depth5;
@include transition(all 0.05s ease-in-out);
position: fixed;
top: 0;
......@@ -204,7 +204,7 @@
// types of prompts - error
.prompt.error {
.icon-error {
[class^="icon"] {
color: $red-l1;
}
......@@ -216,7 +216,7 @@
// types of prompts - confirmation
.prompt.confirmation {
.icon-error {
[class^="icon"] {
color: $green;
}
......@@ -228,7 +228,7 @@
// types of prompts - error
.prompt.warning {
.icon-warning {
[class^="icon"] {
color: $orange;
}
......@@ -242,7 +242,7 @@
// notifications
.wrapper-notification {
@extend .depth3;
@extend .depth5;
@include clearfix();
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $blue);
position: fixed;
......@@ -253,7 +253,7 @@
&.wrapper-notification-warning {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $orange);
.icon-warning {
[class^="icon"] {
color: $orange;
}
}
......@@ -261,7 +261,7 @@
&.wrapper-notification-error {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $red-l1);
.icon-error {
[class^="icon"] {
color: $red-l1;
}
}
......@@ -269,7 +269,7 @@
&.wrapper-notification-confirmation {
@include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $green);
.icon-confirmation {
[class^="icon"] {
color: $green;
}
}
......@@ -294,13 +294,13 @@
max-width: none;
min-width: none;
.icon, .copy {
[class^="icon"], .copy {
float: none;
display: inline-block;
vertical-align: middle;
}
.icon {
[class^="icon"] {
width: $baseline;
height: ($baseline*1.25);
margin-right: ($baseline*0.75);
......@@ -329,7 +329,7 @@
max-width: none;
min-width: none;
.icon-help {
[class^="icon"] {
width: $baseline;
margin-right: ($baseline*0.75);
}
......@@ -357,13 +357,13 @@
font-weight: 700;
}
.icon, .copy {
[class^="icon"], .copy {
float: left;
display: inline-block;
vertical-align: middle;
}
.icon {
[class^="icon"] {
@include transition (color 0.5s ease-in-out);
@include font-size(22);
width: flex-grid(1, 12);
......@@ -389,7 +389,7 @@
// with actions
&.has-actions {
.icon {
[class^="icon"] {
width: flex-grid(1, 12);
}
......@@ -436,9 +436,11 @@
&.saving {
.icon-saving {
[class^="icon"] {
@include anim-rotateClockwise(3s, linear, infinite);
width: 22px;
width: 25px;
margin: -4px 10px 0 0;
@include transform-origin(52% 60%);
}
.copy p {
......@@ -472,7 +474,7 @@
&.wrapper-alert-warning {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange);
.icon-warning {
[class^="icon"] {
color: $orange;
}
}
......@@ -480,7 +482,7 @@
&.wrapper-alert-error {
@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;
}
}
......@@ -488,7 +490,7 @@
&.wrapper-alert-confirmation {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green);
.icon-confirmation {
[class^="icon"] {
color: $green;
}
}
......@@ -496,7 +498,7 @@
&.wrapper-alert-announcement {
@include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue);
.icon-announcement {
[class^="icon"] {
color: $blue;
}
}
......@@ -504,7 +506,7 @@
&.wrapper-alert-step-required {
@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;
}
}
......@@ -524,11 +526,11 @@
font-weight: 700;
}
.icon, .copy {
[class^="icon"], .copy {
float: left;
}
.icon {
[class^="icon"] {
@include transition (color 0.5s ease-in-out);
@include font-size(22);
width: flex-grid(1, 12);
......@@ -550,7 +552,7 @@
// with actions
&.has-actions {
.icon {
[class^="icon"] {
width: flex-grid(1, 12);
}
......@@ -600,7 +602,7 @@
@extend .text-sr;
}
.icon {
[class^="icon"] {
@include font-size(14);
color: $white;
width: auto;
......@@ -669,10 +671,6 @@
&.is-hiding {
@include anim-notificationsSlideDown(0.25s);
}
&.is-fleeting {
@include anim-notificationsSlideUpDown(2s);
}
}
}
......
......@@ -11,54 +11,54 @@
.t-title1 {
@extend .t-title;
@include font-size(60);
@include lh(60);
@include line-height(60);
}
.t-title2 {
@extend .t-title;
@include font-size(48);
@include lh(48);
@include line-height(48);
}
.t-title3 {
@include font-size(36);
@include lh(36);
@include line-height(36);
}
.t-title4 {
@extend .t-title;
@include font-size(24);
@include lh(24);
@include line-height(24);
}
.t-title5 {
@extend .t-title;
@include font-size(18);
@include lh(18);
@include line-height(18);
}
.t-title6 {
@extend .t-title;
@include font-size(16);
@include lh(16);
@include line-height(16);
}
.t-title7 {
@extend .t-title;
@include font-size(14);
@include lh(14);
@include line-height(14);
}
.t-title8 {
@extend .t-title;
@include font-size(12);
@include lh(12);
@include line-height(12);
}
.t-title9 {
@extend .t-title;
@include font-size(11);
@include lh(11);
@include line-height(11);
}
// ====================
......@@ -71,31 +71,31 @@
.t-copy-base {
@extend .t-copy;
@include font-size(16);
@include lh(16);
@include line-height(16);
}
.t-copy-lead1 {
@extend .t-copy;
@include font-size(18);
@include lh(18);
@include line-height(18);
}
.t-copy-lead2 {
@extend .t-copy;
@include font-size(24);
@include lh(24);
@include line-height(24);
}
.t-copy-sub1 {
@extend .t-copy;
@include font-size(14);
@include lh(14);
@include line-height(14);
}
.t-copy-sub2 {
@extend .t-copy;
@include font-size(12);
@include lh(12);
@include line-height(12);
}
// ====================
......@@ -103,22 +103,22 @@
// actions/labels
.t-action1 {
@include font-size(18);
@include lh(18);
@include line-height(18);
}
.t-action2 {
@include font-size(16);
@include lh(16);
@include line-height(16);
}
.t-action3 {
@include font-size(14);
@include lh(14);
@include line-height(14);
}
.t-action4 {
@include font-size(12);
@include lh(12);
@include line-height(12);
}
......@@ -131,7 +131,48 @@
// ====================
// misc
.t-icon {
line-height: 0;
// icons
.t-icon1 {
@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 {
background: $yellow-d1;
color: $white;
.ss-icon {
[class^="icon-"] {
position: relative;
top: 3px;
top: 1px;
@include font-size(16);
display: inline-block;
margin-right: ($baseline/2);
......
......@@ -2,13 +2,25 @@
// ====================
body.course.uploads {
.nav-actions {
.icon-cloud-upload {
@include font-size(16);
vertical-align: bottom;
margin-right: ($baseline/5);
}
}
input.asset-search-input {
float: left;
width: 260px;
background-color: #fff;
}
.asset-library {
@include clearfix;
......@@ -102,7 +114,7 @@ body.course.uploads {
}
.upload-modal {
display: none;
display: none;
width: 640px !important;
margin-left: -320px !important;
......@@ -187,4 +199,4 @@ body.course.uploads {
display: none;
margin-bottom: 100px;
}
}
\ No newline at end of file
}
......@@ -62,7 +62,7 @@ body.course.checklists {
.ui-toggle-expansion {
@include transition(rotate .15s ease-in-out .25s);
@include font-size(14);
@include font-size(21);
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/2);
......@@ -91,10 +91,9 @@ body.course.checklists {
color: $gray-l2;
.icon-confirm {
.icon-ok {
@include font-size(20);
display: inline-block;
vertical-align: middle;
margin-left: ($baseline/2);
color: $gray-l4;
}
......@@ -184,13 +183,13 @@ body.course.checklists {
header {
.checklist-title, .icon-confirm {
.checklist-title, .icon-caret-down {
color: $green;
}
.checklist-status {
.status-count, .status-amount, .icon-confirm {
.status-count, .status-amount, .icon-ok {
color: $green;
}
}
......
......@@ -164,11 +164,11 @@ body.index {
right: ($baseline/2);
opacity: 0;
.ss-icon {
[class^="icon-"] {
@include font-size(18);
@include border-top-radius(3px);
display: inline-block;
padding: ($baseline/4) ($baseline/2);
padding: ($baseline/2);
background: $blue;
color: $white;
text-align: center;
......
......@@ -56,6 +56,12 @@ body.course.outline {
}
}
[class^="icon-"] {
vertical-align: middle;
margin-top: -5px;
display: inline-block;
}
.menu {
@include font-size(12);
@include border-radius(4px);
......@@ -331,6 +337,7 @@ body.course.outline {
&:hover, &.is-active {
color: $blue;
}
}
.menu {
......@@ -533,7 +540,7 @@ body.course.outline {
display: block;
}
.ss-icon {
[class^="icon-"] {
@include font-size(11);
@include border-radius(20px);
position: relative;
......
......@@ -328,11 +328,12 @@ body.course.settings {
@extend .t-action-3;
font-weight: 600;
.icon {
[class^="icon-"] {
@extend .t-icon;
@include font-size(16);
display: inline-block;
vertical-align: middle;
margin-top: -3px;
}
}
}
......
......@@ -10,6 +10,16 @@ body.course.static-pages {
padding: 12px 0;
}
.nav-introduction-supplementary {
.icon-question-sign {
display: inline-block;
vertical-align: baseline;
margin-right: ($baseline/4);
}
}
.unit-body {
padding: 0;
......
......@@ -44,7 +44,7 @@
<h3 class="sr">Page Actions</h3>
<ul>
<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>
</ul>
</nav>
......
......@@ -20,8 +20,6 @@
<%static:css group='base-style'/>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<%include file="widgets/segment-io.html" />
......@@ -30,14 +28,19 @@
<body class="<%block name='bodyclass'></%block> hide-wip">
<%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="${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/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script>
<script src="${static.url('js/vendor/symbolset.ss-standard.js')}"></script>
<script src="${static.url('js/vendor/symbolset.ss-symbolicons.js')}"></script>
<%static:js group='main'/>
<%static:js group='module-js'/>
......@@ -49,17 +52,16 @@
<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/css.js')}"></script>
<script type="text/javascript">
document.write('\x3Cscript type="text/javascript" src="' +
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
</script>
<script type="text/javascript" src="//www.youtube.com/player_api"></script>
<script src="${static.url('js/models/feedback.js')}"></script>
<script src="${static.url('js/views/feedback.js')}"></script>
<!-- view -->
<div class="wrapper wrapper-view">
<%include file="widgets/header.html" />
<%block name="view_alerts"></%block>
<%block name="view_banners"></%block>
<div id="page-alert"></div>
<%block name="content"></%block>
......@@ -70,10 +72,10 @@
<%include file="widgets/footer.html" />
<%include file="widgets/tender.html" />
<%block name="view_notifications"></%block>
<div id="page-notification"></div>
</div>
<%block name="view_prompts"></%block>
<div id="page-prompt"></div>
<%block name="jsextra"></%block>
</body>
......
......@@ -9,7 +9,6 @@
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/checklists_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/checklists.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript">
$(document).ready(function () {
......
......@@ -11,7 +11,6 @@
<script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script src="${static.url('js/vendor/timepicker/datepair.js')}"></script>
......@@ -53,7 +52,7 @@
<h3 class="sr">Page Actions</h3>
<ul>
<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>
</ul>
</nav>
......
......@@ -27,7 +27,7 @@
<h3 class="sr">Page Actions</h3>
<ul>
<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>
</ul>
</nav>
......@@ -41,7 +41,7 @@
<nav class="nav-introduction-supplementary">
<ul>
<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>
</ul>
</nav>
......@@ -76,7 +76,7 @@
</figure>
<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>
</a>
</div>
......
......@@ -28,7 +28,7 @@
<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>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
<i class="icon-zoom-in"></i>
</span>
</a>
</figure>
......@@ -62,7 +62,7 @@
<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>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
<i class="icon-zoom-in"></i>
</span>
</a>
</figure>
......@@ -96,7 +96,7 @@
<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>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block icon icon-zoom">&#xE002;</i>
<i class="icon-zoom-in"></i>
</span>
</a>
</figure>
......@@ -132,7 +132,7 @@
<header>
<h2 class="sr">Sign Up for Studio Today!</h2>
</header>
<ul class="list-actions">
<li class="action-item">
<a href="${reverse('signup')}" class="action action-primary">Sign Up &amp; Start Making an edX Course</a>
......@@ -152,7 +152,7 @@
</figure>
<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>
</a>
</div>
......@@ -165,7 +165,7 @@
</figure>
<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>
</a>
</div>
......@@ -178,8 +178,8 @@
</figure>
<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>
</a>
</div>
</%block>
\ No newline at end of file
</%block>
......@@ -45,7 +45,7 @@
<ul>
<li class="nav-item">
% 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',''):
<a href="mailto:${settings.MITX_FEATURES.get('STAFF_EMAIL','')}">${_("Email staff to create course")}</a>
% endif
......@@ -76,7 +76,7 @@
<a class="class-link" href="${url}" class="class-name">
<span class="class-name">${course}</span>
</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>
%endfor
</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 @@
<ul>
%if allow_actions:
<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>
%endif
</ul>
......
......@@ -20,6 +20,12 @@
<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/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">
$(document).ready(function(){
// 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 @@
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>
......@@ -115,13 +129,13 @@
<h3 class="sr">Page Actions</h3>
<ul>
<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 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 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>
</ul>
</nav>
......@@ -137,14 +151,7 @@
<a href="#" data-tooltip="Expand/collapse this section" class="expand-collapse-icon collapse"></a>
<div class="item-details" data-id="${section.location}">
<h3 class="section-name">
<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>
<h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3>
<div class="section-published-date">
<%
start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y')
......
......@@ -15,7 +15,6 @@ from contentstore import utils
<script src="${static.url('js/vendor/date.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script>
......@@ -92,7 +91,7 @@ from contentstore import utils
<ul class="list-actions">
<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>
</ul>
</div>
......
......@@ -11,7 +11,6 @@ from contentstore import utils
<%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/advanced.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/advanced_view.js')}"></script>
......@@ -110,7 +109,7 @@ editor.render();
<!-- 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="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">
<h2 class="title title-3" id="notification-changesMade-title">You've Made Some Changes</h2>
......@@ -136,7 +135,7 @@ editor.render();
<!-- alert: save confirmed with close -->
<div class="wrapper wrapper-alert wrapper-alert-confirmation" role="status">
<div class="alert confirmation">
<i class="ss-icon ss-symbolicons-standard icon icon-confirmation">&#x2713;</i>
<i class="icon-ok"></i>
<div class="copy">
<h2 class="title title-3">Your policy changes have been saved.</h2>
......@@ -144,7 +143,7 @@ editor.render();
</div>
<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>
</a>
</div>
......@@ -153,7 +152,7 @@ editor.render();
<!-- alert: error -->
<div class="wrapper wrapper-alert wrapper-alert-error" role="status">
<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">
<h2 class="title title-3">There was an error saving your information</h2>
......
......@@ -6,27 +6,26 @@
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
from contentstore import utils
%>
<%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
});
</script>
</%block>
<%block name="content">
<!-- -->
<div class="main-wrapper">
<div class="inner-wrapper">
<div class="inner-wrapper">
<h1>Settings</h1>
<article class="settings-overview">
<div class="settings-page-section main-column">
......@@ -74,7 +73,7 @@ from contentstore import utils
<div class="field">
<textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
</div>
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a>
......@@ -102,7 +101,7 @@ from contentstore import utils
<a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo">
<span class="upload-icon"></span>Upload Faculty Photo
</a>
<span class="tip tip-inline">Max size: 30KB</span>
<span class="tip tip-inline">Max size: 30KB</span>
</div>
</div>
</div>
......@@ -114,7 +113,7 @@ from contentstore import utils
<textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div>
</div>
</div>
</div>
</li>
</ul>
......@@ -143,7 +142,7 @@ from contentstore import utils
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-general-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
......@@ -217,7 +216,7 @@ from contentstore import utils
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always">
<div class="copy">
<label for="course-problems-assignment-1-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
......@@ -283,7 +282,7 @@ from contentstore import utils
<section class="settings-discussions">
<h2 class="title">Discussions</h2>
<section class="settings-discussions-general">
<header>
<h3>General Settings</h3>
......@@ -296,7 +295,7 @@ from contentstore import utils
<div class="field">
<div class="input input-radio">
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
......@@ -320,7 +319,7 @@ from contentstore import utils
<div class="field">
<div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
......@@ -329,7 +328,7 @@ from contentstore import utils
<div class="input input-radio">
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span>
......@@ -351,7 +350,7 @@ from contentstore import utils
<a href="#" class="drag-handle"></a>
</li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group">
<label for="course-discussions-categories-2-name">Category Name: </label>
......
......@@ -12,7 +12,6 @@ from contentstore import utils
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
......@@ -117,7 +116,7 @@ from contentstore import utils
<div class="actions">
<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>
</div>
</section>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
phantom-jasmine @ a54d435b
This diff is collapsed. Click to expand it.
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