diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0e161e4f72..3dda49928b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
+Blades: Small UX fix on capa multiple-choice problems. Make labels only
+as wide as the text to reduce accidental choice selections.
+
Studio: Remove XML from the video component editor. All settings are
moved to be edited as metadata.
diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py
index 2360baea5a..1661e1c391 100644
--- a/cms/djangoapps/contentstore/features/advanced-settings.py
+++ b/cms/djangoapps/contentstore/features/advanced-settings.py
@@ -27,7 +27,7 @@ def i_am_on_advanced_course_settings(step):
@step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(step, name):
- css = 'a.%s-button' % name.lower()
+ css = 'a.action-%s' % name.lower()
# Save was clicked if either the save notification bar is gone, or we have a error notification
# overlaying it (expected in the case of typing Object into display_name).
diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py
index e0f20d3d6e..bdf07fc5ae 100644
--- a/cms/djangoapps/contentstore/features/common.py
+++ b/cms/djangoapps/contentstore/features/common.py
@@ -3,7 +3,6 @@
from lettuce import world, step
from nose.tools import assert_true
-from nose.tools import assert_equal
from auth.authz import get_user_by_email
@@ -13,8 +12,13 @@ import time
from logging import getLogger
logger = getLogger(__name__)
+_COURSE_NAME = 'Robot Super Course'
+_COURSE_NUM = '999'
+_COURSE_ORG = 'MITx'
+
########### STEP HELPERS ##############
+
@step('I (?:visit|access|open) the Studio homepage$')
def i_visit_the_studio_homepage(_step):
# To make this go to port 8001, put
@@ -54,6 +58,7 @@ def i_have_opened_a_new_course(_step):
####### HELPER FUNCTIONS ##############
def open_new_course():
world.clear_courses()
+ create_studio_user()
log_into_studio()
create_a_course()
@@ -73,10 +78,11 @@ def create_studio_user(
registration.register(studio_user)
registration.activate()
+
def fill_in_course_info(
- name='Robot Super Course',
- org='MITx',
- num='101'):
+ name=_COURSE_NAME,
+ org=_COURSE_ORG,
+ num=_COURSE_NUM):
world.css_fill('.new-course-name', name)
world.css_fill('.new-course-org', org)
world.css_fill('.new-course-number', num)
@@ -85,10 +91,7 @@ def fill_in_course_info(
def log_into_studio(
uname='robot',
email='robot+studio@edx.org',
- password='test',
- is_staff=False):
-
- create_studio_user(uname=uname, email=email, is_staff=is_staff)
+ password='test'):
world.browser.cookies.delete()
world.visit('/')
@@ -106,14 +109,14 @@ def log_into_studio(
def create_a_course():
- world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
+ world.CourseFactory.create(org=_COURSE_ORG, course=_COURSE_NUM, display_name=_COURSE_NAME)
# Add the user to the instructor group of the course
# so they will have the permissions to see it in studio
- g = world.GroupFactory.create(name='instructor_MITx/999/Robot_Super_Course')
- u = get_user_by_email('robot+studio@edx.org')
- u.groups.add(g)
- u.save()
+ course = world.GroupFactory.create(name='instructor_MITx/{course_num}/{course_name}'.format(course_num=_COURSE_NUM, course_name=_COURSE_NAME.replace(" ", "_")))
+ user = get_user_by_email('robot+studio@edx.org')
+ user.groups.add(course)
+ user.save()
world.browser.reload()
course_link_css = 'span.class-name'
diff --git a/cms/djangoapps/contentstore/features/course-team.feature b/cms/djangoapps/contentstore/features/course-team.feature
new file mode 100644
index 0000000000..fc1212f398
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/course-team.feature
@@ -0,0 +1,34 @@
+Feature: Course Team
+ As a course author, I want to be able to add others to my team
+
+ Scenario: Users can add other users
+ Given I have opened a new course in Studio
+ And the user "alice" exists
+ And I am viewing the course team settings
+ When I add "alice" to the course team
+ And "alice" logs in
+ Then she does see the course on her page
+
+ Scenario: Added users cannot delete or add other users
+ Given I have opened a new course in Studio
+ And the user "bob" exists
+ And I am viewing the course team settings
+ When I add "bob" to the course team
+ And "bob" logs in
+ Then he cannot delete users
+ And he cannot add users
+
+ Scenario: Users can delete other users
+ Given I have opened a new course in Studio
+ And the user "carol" exists
+ And I am viewing the course team settings
+ When I add "carol" to the course team
+ And I delete "carol" from the course team
+ And "carol" logs in
+ Then she does not see the course on her page
+
+ Scenario: Users cannot add users that do not exist
+ Given I have opened a new course in Studio
+ And I am viewing the course team settings
+ When I add "dennis" to the course team
+ Then I should see "Could not find user by email address" somewhere on the page
diff --git a/cms/djangoapps/contentstore/features/course-team.py b/cms/djangoapps/contentstore/features/course-team.py
new file mode 100644
index 0000000000..c126773db6
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/course-team.py
@@ -0,0 +1,67 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+from common import create_studio_user, log_into_studio, _COURSE_NAME
+
+PASSWORD = 'test'
+EMAIL_EXTENSION = '@edx.org'
+
+
+@step(u'I am viewing the course team settings')
+def view_grading_settings(_step):
+ world.click_course_settings()
+ link_css = 'li.nav-course-settings-team a'
+ world.css_click(link_css)
+
+
+@step(u'the user "([^"]*)" exists$')
+def create_other_user(_step, name):
+ create_studio_user(uname=name, password=PASSWORD, email=(name + EMAIL_EXTENSION))
+
+
+@step(u'I add "([^"]*)" to the course team')
+def add_other_user(_step, name):
+ new_user_css = 'a.new-user-button'
+ world.css_click(new_user_css)
+
+ email_css = 'input.email-input'
+ f = world.css_find(email_css)
+ f._element.send_keys(name, EMAIL_EXTENSION)
+
+ confirm_css = '#add_user'
+ world.css_click(confirm_css)
+
+
+@step(u'I delete "([^"]*)" from the course team')
+def delete_other_user(_step, name):
+ to_delete_css = 'a.remove-user[data-id="{name}{extension}"]'.format(name=name, extension=EMAIL_EXTENSION)
+ world.css_click(to_delete_css)
+
+
+@step(u'"([^"]*)" logs in$')
+def other_user_login(_step, name):
+ log_into_studio(uname=name, password=PASSWORD, email=name + EMAIL_EXTENSION)
+
+
+@step(u's?he does( not)? see the course on (his|her) page')
+def see_course(_step, doesnt_see_course, gender):
+ class_css = 'span.class-name'
+ all_courses = world.css_find(class_css)
+ all_names = [item.html for item in all_courses]
+ if doesnt_see_course:
+ assert not _COURSE_NAME in all_names
+ else:
+ assert _COURSE_NAME in all_names
+
+
+@step(u's?he cannot delete users')
+def cannot_delete(_step):
+ to_delete_css = 'a.remove-user'
+ assert world.is_css_not_present(to_delete_css)
+
+
+@step(u's?he cannot add users')
+def cannot_add(_step):
+ add_css = 'a.new-user'
+ assert world.is_css_not_present(add_css)
diff --git a/cms/djangoapps/contentstore/features/course-updates.feature b/cms/djangoapps/contentstore/features/course-updates.feature
new file mode 100644
index 0000000000..81714c43ae
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/course-updates.feature
@@ -0,0 +1,37 @@
+Feature: Course updates
+ As a course author, I want to be able to provide updates to my students
+
+ Scenario: Users can add updates
+ Given I have opened a new course in Studio
+ And I go to the course updates page
+ When I add a new update with the text "Hello"
+ Then I should see the update "Hello"
+
+ Scenario: Users can edit updates
+ Given I have opened a new course in Studio
+ And I go to the course updates page
+ When I add a new update with the text "Hello"
+ And I modify the text to "Goodbye"
+ Then I should see the update "Goodbye"
+
+ Scenario: Users can delete updates
+ Given I have opened a new course in Studio
+ And I go to the course updates page
+ And I add a new update with the text "Hello"
+ When I will confirm all alerts
+ And I delete the update
+ Then I should not see the update "Hello"
+
+
+ Scenario: Users can edit update dates
+ Given I have opened a new course in Studio
+ And I go to the course updates page
+ And I add a new update with the text "Hello"
+ When I edit the date to "June 1, 2013"
+ Then I should see the date "June 1, 2013"
+
+ Scenario: Users can change handouts
+ Given I have opened a new course in Studio
+ And I go to the course updates page
+ When I modify the handout to "
Test
"
+ Then I see the handout "Test"
diff --git a/cms/djangoapps/contentstore/features/course-updates.py b/cms/djangoapps/contentstore/features/course-updates.py
new file mode 100644
index 0000000000..d838061698
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/course-updates.py
@@ -0,0 +1,84 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+from selenium.webdriver.common.keys import Keys
+from common import type_in_codemirror
+
+
+@step(u'I go to the course updates page')
+def go_to_updates(_step):
+ menu_css = 'li.nav-course-courseware'
+ updates_css = 'li.nav-course-courseware-updates'
+ world.css_click(menu_css)
+ world.css_click(updates_css)
+
+
+@step(u'I add a new update with the text "([^"]*)"$')
+def add_update(_step, text):
+ update_css = 'a.new-update-button'
+ world.css_click(update_css)
+ change_text(text)
+
+
+@step(u'I should( not)? see the update "([^"]*)"$')
+def check_update(_step, doesnt_see_update, text):
+ update_css = 'div.update-contents'
+ update = world.css_find(update_css)
+ if doesnt_see_update:
+ assert len(update) == 0 or not text in update.html
+ else:
+ assert text in update.html
+
+
+@step(u'I modify the text to "([^"]*)"$')
+def modify_update(_step, text):
+ button_css = 'div.post-preview a.edit-button'
+ world.css_click(button_css)
+ change_text(text)
+
+
+@step(u'I delete the update$')
+def click_button(_step):
+ button_css = 'div.post-preview a.delete-button'
+ world.css_click(button_css)
+
+
+@step(u'I edit the date to "([^"]*)"$')
+def change_date(_step, new_date):
+ button_css = 'div.post-preview a.edit-button'
+ world.css_click(button_css)
+ date_css = 'input.date'
+ date = world.css_find(date_css)
+ for i in range(len(date.value)):
+ date._element.send_keys(Keys.END, Keys.BACK_SPACE)
+ date._element.send_keys(new_date)
+ save_css = 'a.save-button'
+ world.css_click(save_css)
+
+
+@step(u'I should see the date "([^"]*)"$')
+def check_date(_step, date):
+ date_css = 'span.date-display'
+ date_html = world.css_find(date_css)
+ assert date == date_html.html
+
+
+@step(u'I modify the handout to "([^"]*)"$')
+def edit_handouts(_step, text):
+ edit_css = 'div.course-handouts > a.edit-button'
+ world.css_click(edit_css)
+ change_text(text)
+
+
+@step(u'I see the handout "([^"]*)"$')
+def check_handout(_step, handout):
+ handout_css = 'div.handouts-content'
+ handouts = world.css_find(handout_css)
+ assert handout in handouts.html
+
+
+def change_text(text):
+ type_in_codemirror(0, text)
+ save_css = 'a.save-button'
+ world.css_click(save_css)
diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py
index a3e838a9d1..5b279d402f 100644
--- a/cms/djangoapps/contentstore/features/courses.py
+++ b/cms/djangoapps/contentstore/features/courses.py
@@ -10,6 +10,7 @@ from common import *
@step('There are no courses$')
def no_courses(step):
world.clear_courses()
+ create_studio_user()
@step('I click the New Course button$')
diff --git a/cms/djangoapps/contentstore/features/static-pages.feature b/cms/djangoapps/contentstore/features/static-pages.feature
new file mode 100644
index 0000000000..9997df69f0
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/static-pages.feature
@@ -0,0 +1,24 @@
+Feature: Static Pages
+ As a course author, I want to be able to add static pages
+
+ Scenario: Users can add static pages
+ Given I have opened a new course in Studio
+ And I go to the static pages page
+ When I add a new page
+ Then I should see a "Empty" static page
+
+ Scenario: Users can delete static pages
+ Given I have opened a new course in Studio
+ And I go to the static pages page
+ And I add a new page
+ When I will confirm all alerts
+ And I "delete" the "Empty" page
+ Then I should not see a "Empty" static page
+
+ Scenario: Users can edit static pages
+ Given I have opened a new course in Studio
+ And I go to the static pages page
+ And I add a new page
+ When I "edit" the "Empty" page
+ And I change the name to "New"
+ Then I should see a "New" static page
diff --git a/cms/djangoapps/contentstore/features/static-pages.py b/cms/djangoapps/contentstore/features/static-pages.py
new file mode 100644
index 0000000000..a16a3246da
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/static-pages.py
@@ -0,0 +1,59 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+from selenium.webdriver.common.keys import Keys
+
+
+@step(u'I go to the static pages page')
+def go_to_static(_step):
+ menu_css = 'li.nav-course-courseware'
+ static_css = 'li.nav-course-courseware-pages'
+ world.css_find(menu_css).click()
+ world.css_find(static_css).click()
+
+
+@step(u'I add a new page')
+def add_page(_step):
+ button_css = 'a.new-button'
+ world.css_find(button_css).click()
+
+
+@step(u'I should( not)? see a "([^"]*)" static page$')
+def see_page(_step, doesnt, page):
+ index = get_index(page)
+ if doesnt:
+ assert index == -1
+ else:
+ assert index != -1
+
+
+@step(u'I "([^"]*)" the "([^"]*)" page$')
+def click_edit_delete(_step, edit_delete, page):
+ button_css = 'a.%s-button' % edit_delete
+ index = get_index(page)
+ assert index != -1
+ world.css_find(button_css)[index].click()
+
+
+@step(u'I change the name to "([^"]*)"$')
+def change_name(_step, new_name):
+ settings_css = '#settings-mode'
+ world.css_find(settings_css).click()
+ input_css = 'input.setting-input'
+ name_input = world.css_find(input_css)
+ old_name = name_input.value
+ for count in range(len(old_name)):
+ name_input._element.send_keys(Keys.END, Keys.BACK_SPACE)
+ name_input._element.send_keys(new_name)
+ save_button = 'a.save-button'
+ world.css_find(save_button).click()
+
+
+def get_index(name):
+ page_name_css = 'section[data-type="HTMLModule"]'
+ all_pages = world.css_find(page_name_css)
+ for i in range(len(all_pages)):
+ if all_pages[i].html == '\n {name}\n'.format(name=name):
+ return i
+ return -1
diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py
index 468099f417..1fbd965871 100644
--- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py
+++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py
@@ -50,7 +50,8 @@ def have_a_course_with_two_sections(step):
@step(u'I navigate to the course overview page$')
def navigate_to_the_course_overview_page(step):
- log_into_studio(is_staff=True)
+ create_studio_user(is_staff=True)
+ log_into_studio()
course_locator = '.class-name'
world.css_click(course_locator)
diff --git a/cms/djangoapps/contentstore/features/upload.feature b/cms/djangoapps/contentstore/features/upload.feature
new file mode 100644
index 0000000000..b3c1fc2ce3
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/upload.feature
@@ -0,0 +1,38 @@
+Feature: Upload Files
+ As a course author, I want to be able to upload files for my students
+
+ Scenario: Users can upload files
+ Given I have opened a new course in Studio
+ And I go to the files and uploads page
+ When I upload the file "test"
+ Then I should see the file "test" was uploaded
+ And The url for the file "test" is valid
+
+ Scenario: Users can update files
+ Given I have opened a new course in studio
+ And I go to the files and uploads page
+ When I upload the file "test"
+ And I upload the file "test"
+ Then I should see only one "test"
+
+ Scenario: Users can delete uploaded files
+ Given I have opened a new course in studio
+ And I go to the files and uploads page
+ When I upload the file "test"
+ And I delete the file "test"
+ Then I should not see the file "test" was uploaded
+
+ Scenario: Users can download files
+ Given I have opened a new course in studio
+ And I go to the files and uploads page
+ When I upload the file "test"
+ Then I can download the correct "test" file
+
+ Scenario: Users can download updated files
+ Given I have opened a new course in studio
+ And I go to the files and uploads page
+ When I upload the file "test"
+ And I modify "test"
+ And I reload the page
+ And I upload the file "test"
+ Then I can download the correct "test" file
diff --git a/cms/djangoapps/contentstore/features/upload.py b/cms/djangoapps/contentstore/features/upload.py
new file mode 100644
index 0000000000..258fc5ebcf
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/upload.py
@@ -0,0 +1,108 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+from django.conf import settings
+import requests
+import string
+import random
+import os
+
+TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
+HTTP_PREFIX = "http://localhost:8001"
+
+
+@step(u'I go to the files and uploads page')
+def go_to_uploads(_step):
+ menu_css = 'li.nav-course-courseware'
+ uploads_css = 'li.nav-course-courseware-uploads'
+ world.css_find(menu_css).click()
+ world.css_find(uploads_css).click()
+
+
+@step(u'I upload the file "([^"]*)"$')
+def upload_file(_step, file_name):
+ upload_css = 'a.upload-button'
+ world.css_find(upload_css).click()
+
+ file_css = 'input.file-input'
+ upload = world.css_find(file_css)
+ #uploading the file itself
+ path = os.path.join(TEST_ROOT, 'uploads/', file_name)
+ upload._element.send_keys(os.path.abspath(path))
+
+ close_css = 'a.close-button'
+ world.css_find(close_css).click()
+
+
+@step(u'I should( not)? see the file "([^"]*)" was uploaded$')
+def check_upload(_step, do_not_see_file, file_name):
+ index = get_index(file_name)
+ if do_not_see_file:
+ assert index == -1
+ else:
+ assert index != -1
+
+
+@step(u'The url for the file "([^"]*)" is valid$')
+def check_url(_step, file_name):
+ r = get_file(file_name)
+ assert r.status_code == 200
+
+
+@step(u'I delete the file "([^"]*)"$')
+def delete_file(_step, file_name):
+ index = get_index(file_name)
+ assert index != -1
+ delete_css = "a.remove-asset-button"
+ world.css_click(delete_css, index=index)
+
+ prompt_confirm_css = 'li.nav-item > a.action-primary'
+ world.css_click(prompt_confirm_css)
+
+
+@step(u'I should see only one "([^"]*)"$')
+def no_duplicate(_step, file_name):
+ names_css = 'td.name-col > a.filename'
+ all_names = world.css_find(names_css)
+ only_one = False
+ for i in range(len(all_names)):
+ if file_name == all_names[i].html:
+ only_one = not only_one
+ assert only_one
+
+
+@step(u'I can download the correct "([^"]*)" file$')
+def check_download(_step, file_name):
+ path = os.path.join(TEST_ROOT, 'uploads/', file_name)
+ with open(os.path.abspath(path), 'r') as cur_file:
+ cur_text = cur_file.read()
+ r = get_file(file_name)
+ downloaded_text = r.text
+ assert cur_text == downloaded_text
+
+
+@step(u'I modify "([^"]*)"$')
+def modify_upload(_step, file_name):
+ new_text = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
+ path = os.path.join(TEST_ROOT, 'uploads/', file_name)
+ with open(os.path.abspath(path), 'w') as cur_file:
+ cur_file.write(new_text)
+
+
+def get_index(file_name):
+ names_css = 'td.name-col > a.filename'
+ all_names = world.css_find(names_css)
+ for i in range(len(all_names)):
+ if file_name == all_names[i].html:
+ return i
+ return -1
+
+
+def get_file(file_name):
+ index = get_index(file_name)
+ assert index != -1
+
+ url_css = 'input.embeddable-xml-input'
+ url = world.css_find(url_css)[index].value
+ return requests.get(HTTP_PREFIX + url)
diff --git a/cms/envs/dev.py b/cms/envs/dev.py
index 07630bdf31..2dcb3640ca 100644
--- a/cms/envs/dev.py
+++ b/cms/envs/dev.py
@@ -181,6 +181,6 @@ if SEGMENT_IO_KEY:
#####################################################################
# Lastly, see if the developer has any local overrides.
try:
- from .private import *
+ from .private import * # pylint: disable=F0401
except ImportError:
pass
diff --git a/cms/static/coffee/spec/views/feedback_spec.coffee b/cms/static/coffee/spec/views/feedback_spec.coffee
index a3950c0b3c..e5916c5ed3 100644
--- a/cms/static/coffee/spec/views/feedback_spec.coffee
+++ b/cms/static/coffee/spec/views/feedback_spec.coffee
@@ -100,11 +100,10 @@ describe "CMS.Views.SystemFeedback click events", ->
text: "Save",
class: "save-button",
click: @primaryClickSpy
- secondary: [{
+ secondary:
text: "Revert",
class: "cancel-button",
click: @secondaryClickSpy
- }]
)
@view.show()
@@ -124,6 +123,46 @@ describe "CMS.Views.SystemFeedback click events", ->
it "should apply class to secondary action", ->
expect(@view.$(".action-secondary")).toHaveClass("cancel-button")
+
+describe "CMS.Views.SystemFeedback multiple secondary actions", ->
+ beforeEach ->
+ @secondarySpyOne = jasmine.createSpy('secondarySpyOne')
+ @secondarySpyTwo = jasmine.createSpy('secondarySpyTwo')
+ @view = new CMS.Views.Notification.Warning(
+ title: "No Primary",
+ message: "Pick a secondary action",
+ actions:
+ secondary: [
+ {
+ text: "Option One"
+ class: "option-one"
+ click: @secondarySpyOne
+ }, {
+ text: "Option Two"
+ class: "option-two"
+ click: @secondarySpyTwo
+ }
+ ]
+ )
+ @view.show()
+
+ it "should render both", ->
+ expect(@view.el).toContain(".action-secondary.option-one")
+ expect(@view.el).toContain(".action-secondary.option-two")
+ expect(@view.el).not.toContain(".action-secondary.option-one.option-two")
+ expect(@view.$(".action-secondary.option-one")).toContainText("Option One")
+ expect(@view.$(".action-secondary.option-two")).toContainText("Option Two")
+
+ it "should differentiate clicks (1)", ->
+ @view.$(".option-one").click()
+ expect(@secondarySpyOne).toHaveBeenCalled()
+ expect(@secondarySpyTwo).not.toHaveBeenCalled()
+
+ it "should differentiate clicks (2)", ->
+ @view.$(".option-two").click()
+ expect(@secondarySpyOne).not.toHaveBeenCalled()
+ expect(@secondarySpyTwo).toHaveBeenCalled()
+
describe "CMS.Views.Notification minShown and maxShown", ->
beforeEach ->
@showSpy = spyOn(CMS.Views.Notification.Saving.prototype, 'show')
diff --git a/cms/static/js/base.js b/cms/static/js/base.js
index 92a16b8417..d1cffdc427 100644
--- a/cms/static/js/base.js
+++ b/cms/static/js/base.js
@@ -25,7 +25,6 @@ $(document).ready(function() {
$newComponentTemplatePickers = $('.new-component-templates');
$newComponentButton = $('.new-component-button');
$spinner = $('');
- $body.bind('keyup', onKeyUp);
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
@@ -413,12 +412,6 @@ function hideModal(e) {
}
}
-function onKeyUp(e) {
- if (e.which == 87) {
- $body.toggleClass('show-wip hide-wip');
- }
-}
-
function toggleSock(e) {
e.preventDefault();
diff --git a/cms/static/js/views/feedback.js b/cms/static/js/views/feedback.js
index 0cfd6fa4ef..3f161d5b1f 100644
--- a/cms/static/js/views/feedback.js
+++ b/cms/static/js/views/feedback.js
@@ -49,6 +49,11 @@ CMS.Views.SystemFeedback = Backbone.View.extend({
}
this.template = _.template(tpl);
this.setElement($("#page-"+this.options.type));
+ // handle single "secondary" action
+ if (this.options.actions && this.options.actions.secondary &&
+ !_.isArray(this.options.actions.secondary)) {
+ this.options.actions.secondary = [this.options.actions.secondary];
+ }
return this;
},
// public API: show() and hide()
diff --git a/cms/static/js/views/settings/advanced_view.js b/cms/static/js/views/settings/advanced_view.js
index 863393d341..302a918de1 100644
--- a/cms/static/js/views/settings/advanced_view.js
+++ b/cms/static/js/views/settings/advanced_view.js
@@ -20,9 +20,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
self.render();
}
);
- // 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, 'invalid', this.handleValidationError);
},
render: function() {
@@ -45,7 +42,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
var policyValues = listEle$.find('.json');
_.each(policyValues, this.attachJSONEditor, this);
- this.showMessage();
return this;
},
attachJSONEditor : function (textarea) {
@@ -61,7 +57,9 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
mode: "application/json", lineNumbers: false, lineWrapping: false,
onChange: function(instance, changeobj) {
// this event's being called even when there's no change :-(
- if (instance.getValue() !== oldValue) self.showSaveCancelButtons();
+ if (instance.getValue() !== oldValue && !self.notificationBarShowing) {
+ self.showNotificationBar();
+ }
},
onFocus : function(mirror) {
$(textarea).parent().children('label').addClass("is-focused");
@@ -99,59 +97,65 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
}
});
},
- showMessage: function (type) {
- $(".wrapper-alert").removeClass("is-shown");
- if (type) {
- if (type === this.error_saving) {
- $(".wrapper-alert-error").addClass("is-shown").attr('aria-hidden','false');
- }
- else if (type === this.successful_changes) {
- $(".wrapper-alert-confirmation").addClass("is-shown").attr('aria-hidden','false');
- this.hideSaveCancelButtons();
- }
- }
- else {
- // This is the case of the page first rendering, or when Cancel is pressed.
- this.hideSaveCancelButtons();
+ showNotificationBar: function() {
+ var self = this;
+ var message = gettext("Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.")
+ var confirm = new CMS.Views.Notification.Warning({
+ title: gettext("You've Made Some Changes"),
+ message: message,
+ actions: {
+ primary: {
+ "text": gettext("Save Changes"),
+ "class": "action-save",
+ "click": function() {
+ self.saveView();
+ confirm.hide();
+ self.notificationBarShowing = false;
+ }
+ },
+ secondary: [{
+ "text": gettext("Cancel"),
+ "class": "action-cancel",
+ "click": function() {
+ self.revertView();
+ confirm.hide();
+ self.notificationBarShowing = false;
+ }
+ }]
+ }});
+ this.notificationBarShowing = true;
+ confirm.show();
+ if(this.saved) {
+ this.saved.hide();
}
},
- showSaveCancelButtons: function(event) {
- if (!this.notificationBarShowing) {
- this.$el.find(".message-status").removeClass("is-shown");
- $('.wrapper-notification').removeClass('is-hiding').addClass('is-shown').attr('aria-hidden','false');
- this.notificationBarShowing = true;
- }
- },
- hideSaveCancelButtons: function() {
- if (this.notificationBarShowing) {
- $('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true');
- this.notificationBarShowing = false;
- }
- },
- saveView : function(event) {
- window.CmsUtils.smoothScrollTop(event);
+ saveView : function() {
// TODO one last verification scan:
// call validateKey on each to ensure proper format
// check for dupes
- var self = event.data;
- self.model.save({},
+ var self = this;
+ this.model.save({},
{
success : function() {
self.render();
- self.showMessage(self.successful_changes);
+ var message = gettext("Please note that validation of your policy key and value pairs is not currently in place yet. If you are having difficulties, please review your policy pairs.");
+ self.saved = new CMS.Views.Alert.Confirmation({
+ title: gettext("Your policy changes have been saved."),
+ message: message,
+ closeIcon: false
+ });
+ self.saved.show();
analytics.track('Saved Advanced Settings', {
'course': course_location_analytics
});
-
}
});
},
- revertView : function(event) {
- event.preventDefault();
- var self = event.data;
- self.model.deleteKeys = [];
- self.model.clear({silent : true});
- self.model.fetch({
+ revertView : function() {
+ var self = this;
+ this.model.deleteKeys = [];
+ this.model.clear({silent : true});
+ this.model.fetch({
success : function() { self.render(); },
reset: true
});
diff --git a/cms/templates/base.html b/cms/templates/base.html
index 11e8d41496..695a97f1da 100644
--- a/cms/templates/base.html
+++ b/cms/templates/base.html
@@ -61,8 +61,6 @@
<%include file="widgets/header.html" />
- ## remove this block after advanced settings notification is rewritten
- <%block name="view_alerts">%block>
<%block name="content">%block>
@@ -74,13 +72,9 @@
<%include file="widgets/footer.html" />
<%include file="widgets/tender.html" />
- ## remove this block after advanced settings notification is rewritten
- <%block name="view_notifications">%block>
- ## remove this block after advanced settings notification is rewritten
- <%block name="view_prompts">%block>
<%block name="jsextra">%block>