Merge branch 'master' of github.com:MITx/mitx into fix/cdodge/studio-forum-improvements
Conflicts: cms/one_time_startup.py common/lib/xmodule/xmodule/modulestore/mongo.py
This commit is contained in:
@@ -41,7 +41,8 @@ disable=
|
||||
# R0902: Too many instance attributes
|
||||
# R0903: Too few public methods (1/2)
|
||||
# R0904: Too many public methods
|
||||
W0141,W0142,R0201,R0901,R0902,R0903,R0904
|
||||
# R0913: Too many arguments
|
||||
W0141,W0142,R0201,R0901,R0902,R0903,R0904,R0913
|
||||
|
||||
|
||||
[REPORTS]
|
||||
@@ -137,7 +138,7 @@ bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Regular expression which should only match functions or classes name which do
|
||||
# not require a docstring
|
||||
no-docstring-rgx=__.*__
|
||||
no-docstring-rgx=(__.*__|test_.*)
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from lxml import html, etree
|
||||
from lxml import html
|
||||
import re
|
||||
from django.http import HttpResponseBadRequest
|
||||
import logging
|
||||
import django.utils
|
||||
|
||||
## TODO store as array of { date, content } and override course_info_module.definition_from_xml
|
||||
## This should be in a class which inherits from XmlDescriptor
|
||||
# # TODO store as array of { date, content } and override course_info_module.definition_from_xml
|
||||
# # This should be in a class which inherits from XmlDescriptor
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_course_updates(location):
|
||||
@@ -26,9 +28,11 @@ def get_course_updates(location):
|
||||
|
||||
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||
try:
|
||||
course_html_parsed = etree.fromstring(course_updates.data)
|
||||
except etree.XMLSyntaxError:
|
||||
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||
course_html_parsed = html.fromstring(course_updates.data)
|
||||
except:
|
||||
log.error("Cannot parse: " + course_updates.data)
|
||||
escaped = django.utils.html.escape(course_updates.data)
|
||||
course_html_parsed = html.fromstring("<ol><li>" + escaped + "</li></ol>")
|
||||
|
||||
# Confirm that root is <ol>, iterate over <li>, pull out <h2> subs and then rest of val
|
||||
course_upd_collection = []
|
||||
@@ -64,9 +68,11 @@ def update_course_updates(location, update, passed_id=None):
|
||||
|
||||
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||
try:
|
||||
course_html_parsed = etree.fromstring(course_updates.data)
|
||||
except etree.XMLSyntaxError:
|
||||
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||
course_html_parsed = html.fromstring(course_updates.data)
|
||||
except:
|
||||
log.error("Cannot parse: " + course_updates.data)
|
||||
escaped = django.utils.html.escape(course_updates.data)
|
||||
course_html_parsed = html.fromstring("<ol><li>" + escaped + "</li></ol>")
|
||||
|
||||
# No try/catch b/c failure generates an error back to client
|
||||
new_html_parsed = html.fromstring('<li><h2>' + update['date'] + '</h2>' + update['content'] + '</li>')
|
||||
@@ -85,12 +91,19 @@ def update_course_updates(location, update, passed_id=None):
|
||||
passed_id = course_updates.location.url() + "/" + str(idx)
|
||||
|
||||
# update db record
|
||||
course_updates.data = etree.tostring(course_html_parsed)
|
||||
course_updates.data = html.tostring(course_html_parsed)
|
||||
modulestore('direct').update_item(location, course_updates.data)
|
||||
|
||||
return {"id" : passed_id,
|
||||
"date" : update['date'],
|
||||
"content" :update['content']}
|
||||
if (len(new_html_parsed) == 1):
|
||||
content = new_html_parsed[0].tail
|
||||
else:
|
||||
content = "\n".join([html.tostring(ele)
|
||||
for ele in new_html_parsed[1:]])
|
||||
|
||||
return {"id": passed_id,
|
||||
"date": update['date'],
|
||||
"content": content}
|
||||
|
||||
|
||||
def delete_course_update(location, update, passed_id):
|
||||
"""
|
||||
@@ -108,9 +121,11 @@ def delete_course_update(location, update, passed_id):
|
||||
# TODO use delete_blank_text parser throughout and cache as a static var in a class
|
||||
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||
try:
|
||||
course_html_parsed = etree.fromstring(course_updates.data)
|
||||
except etree.XMLSyntaxError:
|
||||
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||
course_html_parsed = html.fromstring(course_updates.data)
|
||||
except:
|
||||
log.error("Cannot parse: " + course_updates.data)
|
||||
escaped = django.utils.html.escape(course_updates.data)
|
||||
course_html_parsed = html.fromstring("<ol><li>" + escaped + "</li></ol>")
|
||||
|
||||
if course_html_parsed.tag == 'ol':
|
||||
# ??? Should this use the id in the json or in the url or does it matter?
|
||||
@@ -121,7 +136,7 @@ def delete_course_update(location, update, passed_id):
|
||||
course_html_parsed.remove(element_to_delete)
|
||||
|
||||
# update db record
|
||||
course_updates.data = etree.tostring(course_html_parsed)
|
||||
course_updates.data = html.tostring(course_html_parsed)
|
||||
store = modulestore('direct')
|
||||
store.update_item(location, course_updates.data)
|
||||
|
||||
@@ -132,7 +147,6 @@ def get_idx(passed_id):
|
||||
"""
|
||||
From the url w/ idx appended, get the idx.
|
||||
"""
|
||||
# TODO compile this regex into a class static and reuse for each call
|
||||
idx_matcher = re.search(r'.*/(\d+)$', passed_id)
|
||||
idx_matcher = re.search(r'.*?/?(\d+)$', passed_id)
|
||||
if idx_matcher:
|
||||
return int(idx_matcher.group(1))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Feature: Advanced (manual) course policy
|
||||
In order to specify course policy settings for which no custom user interface exists
|
||||
I want to be able to manually enter JSON key/value pairs
|
||||
I want to be able to manually enter JSON key /value pairs
|
||||
|
||||
Scenario: A course author sees default advanced settings
|
||||
Given I have opened a new course in Studio
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
import time
|
||||
from terrain.steps import reload_the_page
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from nose.tools import assert_true, assert_false, assert_equal
|
||||
|
||||
@@ -18,13 +19,14 @@ DISPLAY_NAME_KEY = "display_name"
|
||||
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):
|
||||
css_click(expand_icon_css)
|
||||
world.css_click(expand_icon_css)
|
||||
link_css = 'li.nav-course-settings-advanced a'
|
||||
css_click(link_css)
|
||||
world.css_click(link_css)
|
||||
|
||||
|
||||
@step('I am on the Advanced Course Settings page in Studio$')
|
||||
@@ -35,24 +37,8 @@ def i_am_on_advanced_course_settings(step):
|
||||
|
||||
@step(u'I press the "([^"]*)" notification button$')
|
||||
def press_the_notification_button(step, name):
|
||||
def is_visible(driver):
|
||||
return EC.visibility_of_element_located((By.CSS_SELECTOR, css,))
|
||||
|
||||
# def is_invisible(driver):
|
||||
# return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,))
|
||||
|
||||
css = 'a.%s-button' % name.lower()
|
||||
wait_for(is_visible)
|
||||
time.sleep(float(1))
|
||||
css_click_at(css)
|
||||
|
||||
# is_invisible is not returning a boolean, not working
|
||||
# try:
|
||||
# css_click_at(css)
|
||||
# wait_for(is_invisible)
|
||||
# except WebDriverException, e:
|
||||
# css_click_at(css)
|
||||
# wait_for(is_invisible)
|
||||
world.css_click_at(css)
|
||||
|
||||
|
||||
@step(u'I edit the value of a policy key$')
|
||||
@@ -61,7 +47,7 @@ def edit_the_value_of_a_policy_key(step):
|
||||
It is hard to figure out how to get into the CodeMirror
|
||||
area, so cheat and do it from the policy key field :)
|
||||
"""
|
||||
e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.ARROW_LEFT, ' ', 'X')
|
||||
|
||||
|
||||
@@ -85,7 +71,7 @@ def i_see_default_advanced_settings(step):
|
||||
|
||||
@step('the settings are alphabetized$')
|
||||
def they_are_alphabetized(step):
|
||||
key_elements = css_find(KEY_CSS)
|
||||
key_elements = world.css_find(KEY_CSS)
|
||||
all_keys = []
|
||||
for key in key_elements:
|
||||
all_keys.append(key.value)
|
||||
@@ -118,13 +104,13 @@ def assert_policy_entries(expected_keys, expected_values):
|
||||
for counter in range(len(expected_keys)):
|
||||
index = get_index_of(expected_keys[counter])
|
||||
assert_false(index == -1, "Could not find key: " + expected_keys[counter])
|
||||
assert_equal(expected_values[counter], css_find(VALUE_CSS)[index].value, "value is incorrect")
|
||||
assert_equal(expected_values[counter], world.css_find(VALUE_CSS)[index].value, "value is incorrect")
|
||||
|
||||
|
||||
def get_index_of(expected_key):
|
||||
for counter in range(len(css_find(KEY_CSS))):
|
||||
for counter in range(len(world.css_find(KEY_CSS))):
|
||||
# Sometimes get stale reference if I hold on to the array of elements
|
||||
key = css_find(KEY_CSS)[counter].value
|
||||
key = world.css_find(KEY_CSS)[counter].value
|
||||
if key == expected_key:
|
||||
return counter
|
||||
|
||||
@@ -133,14 +119,14 @@ def get_index_of(expected_key):
|
||||
|
||||
def get_display_name_value():
|
||||
index = get_index_of(DISPLAY_NAME_KEY)
|
||||
return css_find(VALUE_CSS)[index].value
|
||||
return world.css_find(VALUE_CSS)[index].value
|
||||
|
||||
|
||||
def change_display_name_value(step, new_value):
|
||||
e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)]
|
||||
display_name = get_display_name_value()
|
||||
for count in range(len(display_name)):
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE)
|
||||
# Must delete "" before typing the JSON value
|
||||
e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE, Keys.BACK_SPACE, new_value)
|
||||
press_the_notification_button(step, "Save")
|
||||
press_the_notification_button(step, "Save")
|
||||
|
||||
24
cms/djangoapps/contentstore/features/checklists.feature
Normal file
24
cms/djangoapps/contentstore/features/checklists.feature
Normal file
@@ -0,0 +1,24 @@
|
||||
Feature: Course checklists
|
||||
|
||||
Scenario: A course author sees checklists defined by edX
|
||||
Given I have opened a new course in Studio
|
||||
When I select Checklists from the Tools menu
|
||||
Then I see the four default edX checklists
|
||||
|
||||
Scenario: A course author can mark tasks as complete
|
||||
Given I have opened Checklists
|
||||
Then I can check and uncheck tasks in a checklist
|
||||
And They are correctly selected after I reload the page
|
||||
|
||||
Scenario: A task can link to a location within Studio
|
||||
Given I have opened Checklists
|
||||
When I select a link to the course outline
|
||||
Then I am brought to the course outline page
|
||||
And I press the browser back button
|
||||
Then I am brought back to the course outline in the correct state
|
||||
|
||||
Scenario: A task can link to a location outside Studio
|
||||
Given I have opened Checklists
|
||||
When I select a link to help page
|
||||
Then I am brought to the help page in a new window
|
||||
|
||||
123
cms/djangoapps/contentstore/features/checklists.py
Normal file
123
cms/djangoapps/contentstore/features/checklists.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_true, assert_equal
|
||||
from terrain.steps import reload_the_page
|
||||
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)
|
||||
link_css = 'li.nav-course-tools-checklists a'
|
||||
world.css_click(link_css)
|
||||
|
||||
|
||||
@step('I have opened Checklists$')
|
||||
def i_have_opened_checklists(step):
|
||||
step.given('I have opened a new course in Studio')
|
||||
step.given('I select Checklists from the Tools menu')
|
||||
|
||||
|
||||
@step('I see the four default edX checklists$')
|
||||
def i_see_default_checklists(step):
|
||||
checklists = world.css_find('.checklist-title')
|
||||
assert_equal(4, len(checklists))
|
||||
assert_true(checklists[0].text.endswith('Getting Started With Studio'))
|
||||
assert_true(checklists[1].text.endswith('Draft a Rough Course Outline'))
|
||||
assert_true(checklists[2].text.endswith("Explore edX\'s Support Tools"))
|
||||
assert_true(checklists[3].text.endswith('Draft Your Course About Page'))
|
||||
|
||||
|
||||
@step('I can check and uncheck tasks in a checklist$')
|
||||
def i_can_check_and_uncheck_tasks(step):
|
||||
# Use the 2nd checklist as a reference
|
||||
verifyChecklist2Status(0, 7, 0)
|
||||
toggleTask(1, 0)
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
toggleTask(1, 3)
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
toggleTask(1, 6)
|
||||
verifyChecklist2Status(3, 7, 43)
|
||||
toggleTask(1, 3)
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
|
||||
|
||||
@step('They are correctly selected after I reload the page$')
|
||||
def tasks_correctly_selected_after_reload(step):
|
||||
reload_the_page(step)
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
# verify that task 7 is still selected by toggling its checkbox state and making sure that it deselects
|
||||
toggleTask(1, 6)
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
|
||||
|
||||
@step('I select a link to the course outline$')
|
||||
def i_select_a_link_to_the_course_outline(step):
|
||||
clickActionLink(1, 0, 'Edit Course Outline')
|
||||
|
||||
|
||||
@step('I am brought to the course outline page$')
|
||||
def i_am_brought_to_course_outline(step):
|
||||
assert_equal('Course Outline', world.css_find('.outline .title-1')[0].text)
|
||||
assert_equal(1, len(world.browser.windows))
|
||||
|
||||
|
||||
@step('I am brought back to the course outline in the correct state$')
|
||||
def i_am_brought_back_to_course_outline(step):
|
||||
step.given('I see the four default edX checklists')
|
||||
# In a previous step, we selected (1, 0) in order to click the 'Edit Course Outline' link.
|
||||
# Make sure the task is still showing as selected (there was a caching bug with the collection).
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
|
||||
|
||||
@step('I select a link to help page$')
|
||||
def i_select_a_link_to_the_help_page(step):
|
||||
clickActionLink(2, 0, 'Visit Studio Help')
|
||||
|
||||
|
||||
@step('I am brought to the help page in a new window$')
|
||||
def i_am_brought_to_help_page_in_new_window(step):
|
||||
step.given('I see the four default edX checklists')
|
||||
windows = world.browser.windows
|
||||
assert_equal(2, len(windows))
|
||||
world.browser.switch_to_window(windows[1])
|
||||
assert_equal('http://help.edge.edx.org/', world.browser.url)
|
||||
|
||||
|
||||
|
||||
|
||||
############### HELPER METHODS ####################
|
||||
def verifyChecklist2Status(completed, total, percentage):
|
||||
def verify_count(driver):
|
||||
try:
|
||||
statusCount = world.css_find('#course-checklist1 .status-count').first
|
||||
return statusCount.text == str(completed)
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
world.wait_for(verify_count)
|
||||
assert_equal(str(total), world.css_find('#course-checklist1 .status-amount').first.text)
|
||||
# Would like to check the CSS width, but not sure how to do that.
|
||||
assert_equal(str(percentage), world.css_find('#course-checklist1 .viz-checklist-status-value .int').first.text)
|
||||
|
||||
|
||||
def toggleTask(checklist, task):
|
||||
world.css_click('#course-checklist' + str(checklist) +'-task' + str(task))
|
||||
|
||||
|
||||
def clickActionLink(checklist, task, actionText):
|
||||
# toggle checklist item to make sure that the link button is showing
|
||||
toggleTask(checklist, task)
|
||||
action_link = world.css_find('#course-checklist' + str(checklist) + ' a')[task]
|
||||
|
||||
# text will be empty initially, wait for it to populate
|
||||
def verify_action_link_text(driver):
|
||||
return action_link.text == actionText
|
||||
|
||||
world.wait_for(verify_action_link_text)
|
||||
action_link.click()
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from lettuce.django import django_url
|
||||
from nose.tools import assert_true
|
||||
from nose.tools import assert_equal
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from terrain.factories import UserFactory, RegistrationFactory, UserProfileFactory
|
||||
from terrain.factories import CourseFactory, GroupFactory
|
||||
from xmodule.modulestore.django import _MODULESTORES, modulestore
|
||||
from xmodule.templates import update_templates
|
||||
from auth.authz import get_user_by_email
|
||||
@@ -17,14 +13,15 @@ from logging import getLogger
|
||||
logger = getLogger(__name__)
|
||||
|
||||
########### 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
|
||||
# LETTUCE_SERVER_PORT = 8001
|
||||
# in your settings.py file.
|
||||
world.browser.visit(django_url('/'))
|
||||
world.visit('/')
|
||||
signin_css = 'a.action-signin'
|
||||
assert world.browser.is_element_present_by_css(signin_css, 10)
|
||||
assert world.is_css_present(signin_css)
|
||||
|
||||
|
||||
@step('I am logged into Studio$')
|
||||
@@ -45,12 +42,12 @@ def i_press_the_category_delete_icon(step, category):
|
||||
css = 'a.delete-button.delete-subsection-button span.delete-icon'
|
||||
else:
|
||||
assert False, 'Invalid category: %s' % category
|
||||
css_click(css)
|
||||
world.css_click(css)
|
||||
|
||||
|
||||
@step('I have opened a new course in Studio$')
|
||||
def i_have_opened_a_new_course(step):
|
||||
clear_courses()
|
||||
world.clear_courses()
|
||||
log_into_studio()
|
||||
create_a_course()
|
||||
|
||||
@@ -61,7 +58,7 @@ def create_studio_user(
|
||||
email='robot+studio@edx.org',
|
||||
password='test',
|
||||
is_staff=False):
|
||||
studio_user = UserFactory.build(
|
||||
studio_user = world.UserFactory.build(
|
||||
username=uname,
|
||||
email=email,
|
||||
password=password,
|
||||
@@ -69,87 +66,20 @@ def create_studio_user(
|
||||
studio_user.set_password(password)
|
||||
studio_user.save()
|
||||
|
||||
registration = RegistrationFactory(user=studio_user)
|
||||
registration = world.RegistrationFactory(user=studio_user)
|
||||
registration.register(studio_user)
|
||||
registration.activate()
|
||||
|
||||
user_profile = UserProfileFactory(user=studio_user)
|
||||
|
||||
|
||||
def flush_xmodule_store():
|
||||
# Flush and initialize the module store
|
||||
# It needs the templates because it creates new records
|
||||
# by cloning from the template.
|
||||
# Note that if your test module gets in some weird state
|
||||
# (though it shouldn't), do this manually
|
||||
# from the bash shell to drop it:
|
||||
# $ mongo test_xmodule --eval "db.dropDatabase()"
|
||||
_MODULESTORES = {}
|
||||
modulestore().collection.drop()
|
||||
update_templates()
|
||||
|
||||
|
||||
def assert_css_with_text(css, text):
|
||||
assert_true(world.browser.is_element_present_by_css(css, 5))
|
||||
assert_equal(world.browser.find_by_css(css).text, text)
|
||||
|
||||
|
||||
def css_click(css):
|
||||
'''
|
||||
First try to use the regular click method,
|
||||
but if clicking in the middle of an element
|
||||
doesn't work it might be that it thinks some other
|
||||
element is on top of it there so click in the upper left
|
||||
'''
|
||||
try:
|
||||
css_find(css).first.click()
|
||||
except WebDriverException, e:
|
||||
css_click_at(css)
|
||||
|
||||
|
||||
def css_click_at(css, x=10, y=10):
|
||||
'''
|
||||
A method to click at x,y coordinates of the element
|
||||
rather than in the center of the element
|
||||
'''
|
||||
e = css_find(css).first
|
||||
e.action_chains.move_to_element_with_offset(e._element, x, y)
|
||||
e.action_chains.click()
|
||||
e.action_chains.perform()
|
||||
|
||||
|
||||
def css_fill(css, value):
|
||||
world.browser.find_by_css(css).first.fill(value)
|
||||
|
||||
|
||||
def css_find(css):
|
||||
def is_visible(driver):
|
||||
return EC.visibility_of_element_located((By.CSS_SELECTOR,css,))
|
||||
|
||||
world.browser.is_element_present_by_css(css, 5)
|
||||
wait_for(is_visible)
|
||||
return world.browser.find_by_css(css)
|
||||
|
||||
|
||||
def wait_for(func):
|
||||
WebDriverWait(world.browser.driver, 5).until(func)
|
||||
|
||||
|
||||
def id_find(id):
|
||||
return world.browser.find_by_id(id)
|
||||
|
||||
|
||||
def clear_courses():
|
||||
flush_xmodule_store()
|
||||
user_profile = world.UserProfileFactory(user=studio_user)
|
||||
|
||||
|
||||
def fill_in_course_info(
|
||||
name='Robot Super Course',
|
||||
org='MITx',
|
||||
num='101'):
|
||||
css_fill('.new-course-name', name)
|
||||
css_fill('.new-course-org', org)
|
||||
css_fill('.new-course-number', num)
|
||||
world.css_fill('.new-course-name', name)
|
||||
world.css_fill('.new-course-org', org)
|
||||
world.css_fill('.new-course-number', num)
|
||||
|
||||
|
||||
def log_into_studio(
|
||||
@@ -157,55 +87,56 @@ def log_into_studio(
|
||||
email='robot+studio@edx.org',
|
||||
password='test',
|
||||
is_staff=False):
|
||||
create_studio_user(uname=uname, email=email, is_staff=is_staff)
|
||||
world.browser.cookies.delete()
|
||||
world.browser.visit(django_url('/'))
|
||||
signin_css = 'a.action-signin'
|
||||
world.browser.is_element_present_by_css(signin_css, 10)
|
||||
|
||||
# click the signin button
|
||||
css_click(signin_css)
|
||||
create_studio_user(uname=uname, email=email, is_staff=is_staff)
|
||||
|
||||
world.browser.cookies.delete()
|
||||
world.visit('/')
|
||||
|
||||
signin_css = 'a.action-signin'
|
||||
world.is_css_present(signin_css)
|
||||
world.css_click(signin_css)
|
||||
|
||||
login_form = world.browser.find_by_css('form#login_form')
|
||||
login_form.find_by_name('email').fill(email)
|
||||
login_form.find_by_name('password').fill(password)
|
||||
login_form.find_by_name('submit').click()
|
||||
|
||||
assert_true(world.browser.is_element_present_by_css('.new-course-button', 5))
|
||||
assert_true(world.is_css_present('.new-course-button'))
|
||||
|
||||
|
||||
def create_a_course():
|
||||
c = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
c = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
|
||||
# Add the user to the instructor group of the course
|
||||
# so they will have the permissions to see it in studio
|
||||
g = GroupFactory.create(name='instructor_MITx/999/Robot_Super_Course')
|
||||
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()
|
||||
world.browser.reload()
|
||||
|
||||
course_link_css = 'span.class-name'
|
||||
css_click(course_link_css)
|
||||
world.css_click(course_link_css)
|
||||
course_title_css = 'span.course-title'
|
||||
assert_true(world.browser.is_element_present_by_css(course_title_css, 5))
|
||||
assert_true(world.is_css_present(course_title_css))
|
||||
|
||||
|
||||
def add_section(name='My Section'):
|
||||
link_css = 'a.new-courseware-section-button'
|
||||
css_click(link_css)
|
||||
world.css_click(link_css)
|
||||
name_css = 'input.new-section-name'
|
||||
save_css = 'input.new-section-name-save'
|
||||
css_fill(name_css, name)
|
||||
css_click(save_css)
|
||||
world.css_fill(name_css, name)
|
||||
world.css_click(save_css)
|
||||
span_css = 'span.section-name-span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_css, 5))
|
||||
assert_true(world.is_css_present(span_css))
|
||||
|
||||
|
||||
def add_subsection(name='Subsection One'):
|
||||
css = 'a.new-subsection-item'
|
||||
css_click(css)
|
||||
world.css_click(css)
|
||||
name_css = 'input.new-subsection-name-input'
|
||||
save_css = 'input.new-subsection-name-save'
|
||||
css_fill(name_css, name)
|
||||
css_click(save_css)
|
||||
world.css_fill(name_css, name)
|
||||
world.css_click(save_css)
|
||||
|
||||
25
cms/djangoapps/contentstore/features/course-settings.feature
Normal file
25
cms/djangoapps/contentstore/features/course-settings.feature
Normal file
@@ -0,0 +1,25 @@
|
||||
Feature: Course Settings
|
||||
As a course author, I want to be able to configure my course settings.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Scenario: User cannot clear the course start date
|
||||
Given I have set course dates
|
||||
And I clear the course start date
|
||||
Then I receive a warning about course start date
|
||||
And The previously set start date is shown on refresh
|
||||
|
||||
Scenario: User can correct the course start date warning
|
||||
Given I have tried to clear the course start
|
||||
And I have entered a new course start date
|
||||
Then The warning about course start date goes away
|
||||
And My new course start date is shown on refresh
|
||||
165
cms/djangoapps/contentstore/features/course-settings.py
Normal file
165
cms/djangoapps/contentstore/features/course-settings.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from terrain.steps import reload_the_page
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
import time
|
||||
|
||||
from nose.tools import assert_true, assert_false, assert_equal
|
||||
|
||||
COURSE_START_DATE_CSS = "#course-start-date"
|
||||
COURSE_END_DATE_CSS = "#course-end-date"
|
||||
ENROLLMENT_START_DATE_CSS = "#course-enrollment-start-date"
|
||||
ENROLLMENT_END_DATE_CSS = "#course-enrollment-end-date"
|
||||
|
||||
COURSE_START_TIME_CSS = "#course-start-time"
|
||||
COURSE_END_TIME_CSS = "#course-end-time"
|
||||
ENROLLMENT_START_TIME_CSS = "#course-enrollment-start-time"
|
||||
ENROLLMENT_END_TIME_CSS = "#course-enrollment-end-time"
|
||||
|
||||
DUMMY_TIME = "3:30pm"
|
||||
DEFAULT_TIME = "12:00am"
|
||||
|
||||
|
||||
############### 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)
|
||||
link_css = 'li.nav-course-settings-schedule a'
|
||||
world.css_click(link_css)
|
||||
|
||||
|
||||
@step('I have set course dates$')
|
||||
def test_i_have_set_course_dates(step):
|
||||
step.given('I have opened a new course in Studio')
|
||||
step.given('I select Schedule and Details')
|
||||
step.given('And I set course dates')
|
||||
|
||||
|
||||
@step('And I set course dates$')
|
||||
def test_and_i_set_course_dates(step):
|
||||
set_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
|
||||
set_date_or_time(COURSE_END_DATE_CSS, '12/26/2013')
|
||||
set_date_or_time(ENROLLMENT_START_DATE_CSS, '12/1/2013')
|
||||
set_date_or_time(ENROLLMENT_END_DATE_CSS, '12/10/2013')
|
||||
|
||||
set_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
|
||||
set_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME)
|
||||
|
||||
pause()
|
||||
|
||||
|
||||
@step('Then I see the set dates on refresh$')
|
||||
def test_then_i_see_the_set_dates_on_refresh(step):
|
||||
reload_the_page(step)
|
||||
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
|
||||
verify_date_or_time(COURSE_END_DATE_CSS, '12/26/2013')
|
||||
verify_date_or_time(ENROLLMENT_START_DATE_CSS, '12/01/2013')
|
||||
verify_date_or_time(ENROLLMENT_END_DATE_CSS, '12/10/2013')
|
||||
|
||||
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
|
||||
# Unset times get set to 12 AM once the corresponding date has been set.
|
||||
verify_date_or_time(COURSE_END_TIME_CSS, DEFAULT_TIME)
|
||||
verify_date_or_time(ENROLLMENT_START_TIME_CSS, DEFAULT_TIME)
|
||||
verify_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME)
|
||||
|
||||
|
||||
@step('And I clear all the dates except start$')
|
||||
def test_and_i_clear_all_the_dates_except_start(step):
|
||||
set_date_or_time(COURSE_END_DATE_CSS, '')
|
||||
set_date_or_time(ENROLLMENT_START_DATE_CSS, '')
|
||||
set_date_or_time(ENROLLMENT_END_DATE_CSS, '')
|
||||
|
||||
pause()
|
||||
|
||||
|
||||
@step('Then I see cleared dates on refresh$')
|
||||
def test_then_i_see_cleared_dates_on_refresh(step):
|
||||
reload_the_page(step)
|
||||
verify_date_or_time(COURSE_END_DATE_CSS, '')
|
||||
verify_date_or_time(ENROLLMENT_START_DATE_CSS, '')
|
||||
verify_date_or_time(ENROLLMENT_END_DATE_CSS, '')
|
||||
|
||||
verify_date_or_time(COURSE_END_TIME_CSS, '')
|
||||
verify_date_or_time(ENROLLMENT_START_TIME_CSS, '')
|
||||
verify_date_or_time(ENROLLMENT_END_TIME_CSS, '')
|
||||
|
||||
# Verify course start date (required) and time still there
|
||||
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
|
||||
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
|
||||
|
||||
|
||||
@step('I clear the course start date$')
|
||||
def test_i_clear_the_course_start_date(step):
|
||||
set_date_or_time(COURSE_START_DATE_CSS, '')
|
||||
|
||||
|
||||
@step('I receive a warning about course start date$')
|
||||
def test_i_receive_a_warning_about_course_start_date(step):
|
||||
assert_true(world.css_has_text('.message-error', 'The course must have an assigned start date.'))
|
||||
assert_true('error' in world.css_find(COURSE_START_DATE_CSS).first._element.get_attribute('class'))
|
||||
assert_true('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class'))
|
||||
|
||||
|
||||
@step('The previously set start date is shown on refresh$')
|
||||
def test_the_previously_set_start_date_is_shown_on_refresh(step):
|
||||
reload_the_page(step)
|
||||
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
|
||||
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
|
||||
|
||||
|
||||
@step('Given I have tried to clear the course start$')
|
||||
def test_i_have_tried_to_clear_the_course_start(step):
|
||||
step.given("I have set course dates")
|
||||
step.given("I clear the course start date")
|
||||
step.given("I receive a warning about course start date")
|
||||
|
||||
|
||||
@step('I have entered a new course start date$')
|
||||
def test_i_have_entered_a_new_course_start_date(step):
|
||||
set_date_or_time(COURSE_START_DATE_CSS, '12/22/2013')
|
||||
pause()
|
||||
|
||||
|
||||
@step('The warning about course start date goes away$')
|
||||
def test_the_warning_about_course_start_date_goes_away(step):
|
||||
assert_equal(0, len(world.css_find('.message-error')))
|
||||
assert_false('error' in world.css_find(COURSE_START_DATE_CSS).first._element.get_attribute('class'))
|
||||
assert_false('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class'))
|
||||
|
||||
|
||||
@step('My new course start date is shown on refresh$')
|
||||
def test_my_new_course_start_date_is_shown_on_refresh(step):
|
||||
reload_the_page(step)
|
||||
verify_date_or_time(COURSE_START_DATE_CSS, '12/22/2013')
|
||||
# Time should have stayed from before attempt to clear date.
|
||||
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
|
||||
|
||||
|
||||
############### HELPER METHODS ####################
|
||||
def set_date_or_time(css, date_or_time):
|
||||
"""
|
||||
Sets date or time field.
|
||||
"""
|
||||
world.css_fill(css, date_or_time)
|
||||
e = world.css_find(css).first
|
||||
# hit Enter to apply the changes
|
||||
e._element.send_keys(Keys.ENTER)
|
||||
|
||||
|
||||
def verify_date_or_time(css, date_or_time):
|
||||
"""
|
||||
Verifies date or time field.
|
||||
"""
|
||||
assert_equal(date_or_time, world.css_find(css).first.value)
|
||||
|
||||
|
||||
def pause():
|
||||
"""
|
||||
Must sleep briefly to allow last time save to finish,
|
||||
else refresh of browser will fail.
|
||||
"""
|
||||
time.sleep(float(1))
|
||||
@@ -10,4 +10,4 @@ Feature: Create Course
|
||||
And I fill in the new course information
|
||||
And I press the "Save" button
|
||||
Then the Courseware page has loaded in Studio
|
||||
And I see a link for adding a new section
|
||||
And I see a link for adding a new section
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
|
||||
@@ -6,12 +9,12 @@ from common import *
|
||||
|
||||
@step('There are no courses$')
|
||||
def no_courses(step):
|
||||
clear_courses()
|
||||
world.clear_courses()
|
||||
|
||||
|
||||
@step('I click the New Course button$')
|
||||
def i_click_new_course(step):
|
||||
css_click('.new-course-button')
|
||||
world.css_click('.new-course-button')
|
||||
|
||||
|
||||
@step('I fill in the new course information$')
|
||||
@@ -27,7 +30,7 @@ def i_create_a_course(step):
|
||||
@step('I click the course link in My Courses$')
|
||||
def i_click_the_course_link_in_my_courses(step):
|
||||
course_css = 'span.class-name'
|
||||
css_click(course_css)
|
||||
world.css_click(course_css)
|
||||
|
||||
############ ASSERTIONS ###################
|
||||
|
||||
@@ -35,28 +38,28 @@ def i_click_the_course_link_in_my_courses(step):
|
||||
@step('the Courseware page has loaded in Studio$')
|
||||
def courseware_page_has_loaded_in_studio(step):
|
||||
course_title_css = 'span.course-title'
|
||||
assert world.browser.is_element_present_by_css(course_title_css)
|
||||
assert world.is_css_present(course_title_css)
|
||||
|
||||
|
||||
@step('I see the course listed in My Courses$')
|
||||
def i_see_the_course_in_my_courses(step):
|
||||
course_css = 'span.class-name'
|
||||
assert_css_with_text(course_css, 'Robot Super Course')
|
||||
assert world.css_has_text(course_css, 'Robot Super Course')
|
||||
|
||||
|
||||
@step('the course is loaded$')
|
||||
def course_is_loaded(step):
|
||||
class_css = 'a.class-name'
|
||||
assert_css_with_text(class_css, 'Robot Super Course')
|
||||
assert world.css_has_text(course_css, 'Robot Super Cousre')
|
||||
|
||||
|
||||
@step('I am on the "([^"]*)" tab$')
|
||||
def i_am_on_tab(step, tab_name):
|
||||
header_css = 'div.inner-wrapper h1'
|
||||
assert_css_with_text(header_css, tab_name)
|
||||
assert world.css_has_text(header_css, 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_css_with_text(link_css, '+ New Section')
|
||||
assert world.css_has_text(link_css, '+ New Section')
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import factory
|
||||
from student.models import User, UserProfile, Registration
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class UserProfileFactory(factory.Factory):
|
||||
FACTORY_FOR = UserProfile
|
||||
|
||||
user = None
|
||||
name = 'Robot Studio'
|
||||
courseware = 'course.xml'
|
||||
|
||||
|
||||
class RegistrationFactory(factory.Factory):
|
||||
FACTORY_FOR = Registration
|
||||
|
||||
user = None
|
||||
activation_key = uuid.uuid4().hex
|
||||
|
||||
|
||||
class UserFactory(factory.Factory):
|
||||
FACTORY_FOR = User
|
||||
|
||||
username = 'robot-studio'
|
||||
email = 'robot+studio@edx.org'
|
||||
password = 'test'
|
||||
first_name = 'Robot'
|
||||
last_name = 'Studio'
|
||||
is_staff = False
|
||||
is_active = True
|
||||
is_superuser = False
|
||||
last_login = datetime.now()
|
||||
date_joined = datetime.now()
|
||||
@@ -1,3 +1,6 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
from nose.tools import assert_equal
|
||||
@@ -10,7 +13,7 @@ import time
|
||||
@step('I click the new section link$')
|
||||
def i_click_new_section_link(step):
|
||||
link_css = 'a.new-courseware-section-button'
|
||||
css_click(link_css)
|
||||
world.css_click(link_css)
|
||||
|
||||
|
||||
@step('I enter the section name and click save$')
|
||||
@@ -31,19 +34,19 @@ def i_have_added_new_section(step):
|
||||
@step('I click the Edit link for the release date$')
|
||||
def i_click_the_edit_link_for_the_release_date(step):
|
||||
button_css = 'div.section-published-date a.edit-button'
|
||||
css_click(button_css)
|
||||
world.css_click(button_css)
|
||||
|
||||
|
||||
@step('I save a new section release date$')
|
||||
def i_save_a_new_section_release_date(step):
|
||||
date_css = 'input.start-date.date.hasDatepicker'
|
||||
time_css = 'input.start-time.time.ui-timepicker-input'
|
||||
css_fill(date_css, '12/25/2013')
|
||||
world.css_fill(date_css, '12/25/2013')
|
||||
# hit TAB to get to the time field
|
||||
e = css_find(date_css).first
|
||||
e = world.css_find(date_css).first
|
||||
e._element.send_keys(Keys.TAB)
|
||||
css_fill(time_css, '12:00am')
|
||||
e = css_find(time_css).first
|
||||
world.css_fill(time_css, '12:00am')
|
||||
e = world.css_find(time_css).first
|
||||
e._element.send_keys(Keys.TAB)
|
||||
time.sleep(float(1))
|
||||
world.browser.click_link_by_text('Save')
|
||||
@@ -64,13 +67,13 @@ def i_see_my_section_name_with_quote_on_the_courseware_page(step):
|
||||
|
||||
@step('I click to edit the section name$')
|
||||
def i_click_to_edit_section_name(step):
|
||||
css_click('span.section-name-span')
|
||||
world.css_click('span.section-name-span')
|
||||
|
||||
|
||||
@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'
|
||||
assert world.browser.is_element_present_by_css(css, 5)
|
||||
assert world.is_css_present(css)
|
||||
assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"')
|
||||
|
||||
|
||||
@@ -85,7 +88,7 @@ def i_see_a_release_date_for_my_section(step):
|
||||
import re
|
||||
|
||||
css = 'span.published-status'
|
||||
assert world.browser.is_element_present_by_css(css)
|
||||
assert world.is_css_present(css)
|
||||
status_text = world.browser.find_by_css(css).text
|
||||
|
||||
# e.g. 11/06/2012 at 16:25
|
||||
@@ -99,20 +102,20 @@ def i_see_a_release_date_for_my_section(step):
|
||||
@step('I see a link to create a new subsection$')
|
||||
def i_see_a_link_to_create_a_new_subsection(step):
|
||||
css = 'a.new-subsection-item'
|
||||
assert world.browser.is_element_present_by_css(css)
|
||||
assert world.is_css_present(css)
|
||||
|
||||
|
||||
@step('the section release date picker is not visible$')
|
||||
def the_section_release_date_picker_not_visible(step):
|
||||
css = 'div.edit-subsection-publish-settings'
|
||||
assert False, world.browser.find_by_css(css).visible
|
||||
assert not world.css_visible(css)
|
||||
|
||||
|
||||
@step('the section release date is updated$')
|
||||
def the_section_release_date_is_updated(step):
|
||||
css = 'span.published-status'
|
||||
status_text = world.browser.find_by_css(css).text
|
||||
assert_equal(status_text,'Will Release: 12/25/2013 at 12:00am')
|
||||
status_text = world.css_text(css)
|
||||
assert_equal(status_text, 'Will Release: 12/25/2013 at 12:00am')
|
||||
|
||||
|
||||
############ HELPER METHODS ###################
|
||||
@@ -120,10 +123,10 @@ def the_section_release_date_is_updated(step):
|
||||
def save_section_name(name):
|
||||
name_css = '.new-section-name'
|
||||
save_css = '.new-section-name-save'
|
||||
css_fill(name_css, name)
|
||||
css_click(save_css)
|
||||
world.css_fill(name_css, name)
|
||||
world.css_click(save_css)
|
||||
|
||||
|
||||
def see_my_section_on_the_courseware_page(name):
|
||||
section_css = 'span.section-name-span'
|
||||
assert_css_with_text(section_css, name)
|
||||
assert world.css_has_text(section_css, name)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
|
||||
@@ -17,9 +20,10 @@ 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 = css_find(submit_css)
|
||||
e = world.css_find(submit_css)
|
||||
e.type(' ')
|
||||
|
||||
|
||||
@step('I should see be on the studio home page$')
|
||||
def i_should_see_be_on_the_studio_home_page(step):
|
||||
assert world.browser.find_by_css('div.inner-wrapper')
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
Feature: Overview Toggle Section
|
||||
In order to quickly view the details of a course's section or to scan the inventory of sections
|
||||
As a course author
|
||||
I want to toggle the visibility of each section's subsection details in the overview listing
|
||||
As a course author
|
||||
I want to toggle the visibility of each section's subsection details in the overview listing
|
||||
|
||||
Scenario: The default layout for the overview page is to show sections in expanded view
|
||||
Given I have a course with multiple sections
|
||||
When I navigate to the course overview page
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
When I navigate to the course overview page
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
Scenario: Expand/collapse for a course with no sections
|
||||
Scenario: Expand /collapse for a course with no sections
|
||||
Given I have a course with no sections
|
||||
When I navigate to the course overview page
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
When I navigate to the course overview page
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
|
||||
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
|
||||
And I add a section
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
When I navigate to the course overview page
|
||||
And I add a section
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
@skip-phantom
|
||||
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
|
||||
And I navigate to the course overview page
|
||||
When I press the "section" delete icon
|
||||
And I confirm the alert
|
||||
Then I see the "Collapse All Sections" link
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from terrain.factories import *
|
||||
from common import *
|
||||
from nose.tools import assert_true, assert_false, assert_equal
|
||||
|
||||
@@ -9,16 +11,16 @@ logger = getLogger(__name__)
|
||||
|
||||
@step(u'I have a course with no sections$')
|
||||
def have_a_course(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
world.clear_courses()
|
||||
course = world.CourseFactory.create()
|
||||
|
||||
|
||||
@step(u'I have a course with 1 section$')
|
||||
def have_a_course_with_1_section(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
section = ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = ItemFactory.create(
|
||||
world.clear_courses()
|
||||
course = world.CourseFactory.create()
|
||||
section = world.ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = world.ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
display_name='Subsection One',)
|
||||
@@ -26,21 +28,21 @@ def have_a_course_with_1_section(step):
|
||||
|
||||
@step(u'I have a course with multiple sections$')
|
||||
def have_a_course_with_two_sections(step):
|
||||
clear_courses()
|
||||
course = CourseFactory.create()
|
||||
section = ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = ItemFactory.create(
|
||||
world.clear_courses()
|
||||
course = world.CourseFactory.create()
|
||||
section = world.ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = world.ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
display_name='Subsection One',)
|
||||
section2 = ItemFactory.create(
|
||||
section2 = world.ItemFactory.create(
|
||||
parent_location=course.location,
|
||||
display_name='Section Two',)
|
||||
subsection2 = ItemFactory.create(
|
||||
subsection2 = world.ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
display_name='Subsection Alpha',)
|
||||
subsection3 = ItemFactory.create(
|
||||
subsection3 = world.ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
display_name='Subsection Beta',)
|
||||
@@ -50,7 +52,7 @@ def have_a_course_with_two_sections(step):
|
||||
def navigate_to_the_course_overview_page(step):
|
||||
log_into_studio(is_staff=True)
|
||||
course_locator = '.class-name'
|
||||
css_click(course_locator)
|
||||
world.css_click(course_locator)
|
||||
|
||||
|
||||
@step(u'I navigate to the courseware page of a course with multiple sections')
|
||||
@@ -67,44 +69,44 @@ def i_add_a_section(step):
|
||||
@step(u'I click the "([^"]*)" link$')
|
||||
def i_click_the_text_span(step, text):
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator))
|
||||
# first make sure that the expand/collapse text is the one you expected
|
||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||
css_click(span_locator)
|
||||
world.css_click(span_locator)
|
||||
|
||||
|
||||
@step(u'I collapse the first section$')
|
||||
def i_collapse_a_section(step):
|
||||
collapse_locator = 'section.courseware-section a.collapse'
|
||||
css_click(collapse_locator)
|
||||
world.css_click(collapse_locator)
|
||||
|
||||
|
||||
@step(u'I expand the first section$')
|
||||
def i_expand_a_section(step):
|
||||
expand_locator = 'section.courseware-section a.expand'
|
||||
css_click(expand_locator)
|
||||
world.css_click(expand_locator)
|
||||
|
||||
|
||||
@step(u'I see the "([^"]*)" link$')
|
||||
def i_see_the_span_with_text(step, text):
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
|
||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||
assert_true(world.browser.find_by_css(span_locator).visible)
|
||||
assert_true(world.is_css_present(span_locator))
|
||||
assert_equal(world.css_find(span_locator).value, text)
|
||||
assert_true(world.css_visible(span_locator))
|
||||
|
||||
|
||||
@step(u'I do not see the "([^"]*)" link$')
|
||||
def i_do_not_see_the_span_with_text(step, text):
|
||||
# Note that the span will exist on the page but not be visible
|
||||
span_locator = '.toggle-button-sections span'
|
||||
assert_true(world.browser.is_element_present_by_css(span_locator))
|
||||
assert_false(world.browser.find_by_css(span_locator).visible)
|
||||
assert_true(world.is_css_present(span_locator))
|
||||
assert_false(world.css_visible(span_locator))
|
||||
|
||||
|
||||
@step(u'all sections are expanded$')
|
||||
def all_sections_are_expanded(step):
|
||||
subsection_locator = 'div.subsection-list'
|
||||
subsections = world.browser.find_by_css(subsection_locator)
|
||||
subsections = world.css_find(subsection_locator)
|
||||
for s in subsections:
|
||||
assert_true(s.visible)
|
||||
|
||||
@@ -112,6 +114,6 @@ def all_sections_are_expanded(step):
|
||||
@step(u'all sections are collapsed$')
|
||||
def all_sections_are_expanded(step):
|
||||
subsection_locator = 'div.subsection-list'
|
||||
subsections = world.browser.find_by_css(subsection_locator)
|
||||
subsections = world.css_find(subsection_locator)
|
||||
for s in subsections:
|
||||
assert_false(s.visible)
|
||||
|
||||
@@ -17,6 +17,14 @@ Feature: Create Subsection
|
||||
And I click to edit the subsection name
|
||||
Then I see the complete subsection name with a quote in the editor
|
||||
|
||||
Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258)
|
||||
Given I have opened a new course section in Studio
|
||||
And I have added a new subsection
|
||||
And I mark it as Homework
|
||||
Then I see it marked as Homework
|
||||
And I reload the page
|
||||
Then I see it marked as Homework
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Delete a subsection
|
||||
Given I have opened a new course section in Studio
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
from nose.tools import assert_equal
|
||||
@@ -7,7 +10,7 @@ from nose.tools import assert_equal
|
||||
|
||||
@step('I have opened a new course section in Studio$')
|
||||
def i_have_opened_a_new_course_section(step):
|
||||
clear_courses()
|
||||
world.clear_courses()
|
||||
log_into_studio()
|
||||
create_a_course()
|
||||
add_section()
|
||||
@@ -15,8 +18,7 @@ def i_have_opened_a_new_course_section(step):
|
||||
|
||||
@step('I click the New Subsection link')
|
||||
def i_click_the_new_subsection_link(step):
|
||||
css = 'a.new-subsection-item'
|
||||
css_click(css)
|
||||
world.css_click('a.new-subsection-item')
|
||||
|
||||
|
||||
@step('I enter the subsection name and click save$')
|
||||
@@ -31,14 +33,14 @@ def i_save_subsection_name_with_quote(step):
|
||||
|
||||
@step('I click to edit the subsection name$')
|
||||
def i_click_to_edit_subsection_name(step):
|
||||
css_click('span.subsection-name-value')
|
||||
world.css_click('span.subsection-name-value')
|
||||
|
||||
|
||||
@step('I see the complete subsection name with a quote in the editor$')
|
||||
def i_see_complete_subsection_name_with_quote_in_editor(step):
|
||||
css = '.subsection-display-name-input'
|
||||
assert world.browser.is_element_present_by_css(css, 5)
|
||||
assert_equal(world.browser.find_by_css(css).value, 'Subsection With "Quote"')
|
||||
assert world.is_css_present(css)
|
||||
assert_equal(world.css_find(css).value, 'Subsection With "Quote"')
|
||||
|
||||
|
||||
@step('I have added a new subsection$')
|
||||
@@ -46,6 +48,17 @@ def i_have_added_a_new_subsection(step):
|
||||
add_subsection()
|
||||
|
||||
|
||||
@step('I mark it as Homework$')
|
||||
def i_mark_it_as_homework(step):
|
||||
world.css_click('a.menu-toggle')
|
||||
world.browser.click_link_by_text('Homework')
|
||||
|
||||
|
||||
@step('I see it marked as Homework$')
|
||||
def i_see_it_marked__as_homework(step):
|
||||
assert_equal(world.css_find(".status-label").value, 'Homework')
|
||||
|
||||
|
||||
############ ASSERTIONS ###################
|
||||
|
||||
|
||||
@@ -70,11 +83,12 @@ def the_subsection_does_not_exist(step):
|
||||
def save_subsection_name(name):
|
||||
name_css = 'input.new-subsection-name-input'
|
||||
save_css = 'input.new-subsection-name-save'
|
||||
css_fill(name_css, name)
|
||||
css_click(save_css)
|
||||
world.css_fill(name_css, name)
|
||||
world.css_click(save_css)
|
||||
|
||||
|
||||
def see_subsection_name(name):
|
||||
css = 'span.subsection-name'
|
||||
assert world.browser.is_element_present_by_css(css)
|
||||
assert world.is_css_present(css)
|
||||
css = 'span.subsection-name-value'
|
||||
assert_css_with_text(css, name)
|
||||
assert world.css_has_text(css, name)
|
||||
|
||||
96
cms/djangoapps/contentstore/tests/test_checklists.py
Normal file
96
cms/djangoapps/contentstore/tests/test_checklists.py
Normal file
@@ -0,0 +1,96 @@
|
||||
""" Unit tests for checklist methods in views.py. """
|
||||
from contentstore.utils import get_modulestore, get_url_reverse
|
||||
from contentstore.tests.test_course_settings import CourseTestCase
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
import json
|
||||
|
||||
|
||||
class ChecklistTestCase(CourseTestCase):
|
||||
""" Test for checklist get and put methods. """
|
||||
def setUp(self):
|
||||
""" Creates the test course. """
|
||||
super(ChecklistTestCase, self).setUp()
|
||||
self.course = CourseFactory.create(org='mitX', number='333', display_name='Checklists Course')
|
||||
|
||||
def get_persisted_checklists(self):
|
||||
""" Returns the checklists as persisted in the modulestore. """
|
||||
modulestore = get_modulestore(self.course.location)
|
||||
return modulestore.get_item(self.course.location).checklists
|
||||
|
||||
def test_get_checklists(self):
|
||||
""" Tests the get checklists method. """
|
||||
checklists_url = get_url_reverse('Checklists', self.course)
|
||||
response = self.client.get(checklists_url)
|
||||
self.assertContains(response, "Getting Started With Studio")
|
||||
payload = response.content
|
||||
|
||||
# Now delete the checklists from the course and verify they get repopulated (for courses
|
||||
# created before checklists were introduced).
|
||||
self.course.checklists = None
|
||||
modulestore = get_modulestore(self.course.location)
|
||||
modulestore.update_metadata(self.course.location, own_metadata(self.course))
|
||||
self.assertEquals(self.get_persisted_checklists(), None)
|
||||
response = self.client.get(checklists_url)
|
||||
self.assertEquals(payload, response.content)
|
||||
|
||||
def test_update_checklists_no_index(self):
|
||||
""" No checklist index, should return all of them. """
|
||||
update_url = reverse('checklists_updates', kwargs={
|
||||
'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name})
|
||||
|
||||
returned_checklists = json.loads(self.client.get(update_url).content)
|
||||
self.assertListEqual(self.get_persisted_checklists(), returned_checklists)
|
||||
|
||||
def test_update_checklists_index_ignored_on_get(self):
|
||||
""" Checklist index ignored on get. """
|
||||
update_url = reverse('checklists_updates', kwargs={'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name,
|
||||
'checklist_index': 1})
|
||||
|
||||
returned_checklists = json.loads(self.client.get(update_url).content)
|
||||
self.assertListEqual(self.get_persisted_checklists(), returned_checklists)
|
||||
|
||||
def test_update_checklists_post_no_index(self):
|
||||
""" No checklist index, will error on post. """
|
||||
update_url = reverse('checklists_updates', kwargs={'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name})
|
||||
response = self.client.post(update_url)
|
||||
self.assertContains(response, 'Could not save checklist', status_code=400)
|
||||
|
||||
def test_update_checklists_index_out_of_range(self):
|
||||
""" Checklist index out of range, will error on post. """
|
||||
update_url = reverse('checklists_updates', kwargs={'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name,
|
||||
'checklist_index': 100})
|
||||
response = self.client.post(update_url)
|
||||
self.assertContains(response, 'Could not save checklist', status_code=400)
|
||||
|
||||
def test_update_checklists_index(self):
|
||||
""" Check that an update of a particular checklist works. """
|
||||
update_url = reverse('checklists_updates', kwargs={'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name,
|
||||
'checklist_index': 2})
|
||||
payload = self.course.checklists[2]
|
||||
self.assertFalse(payload.get('is_checked'))
|
||||
payload['is_checked'] = True
|
||||
|
||||
returned_checklist = json.loads(self.client.post(update_url, json.dumps(payload), "application/json").content)
|
||||
self.assertTrue(returned_checklist.get('is_checked'))
|
||||
self.assertEqual(self.get_persisted_checklists()[2], returned_checklist)
|
||||
|
||||
def test_update_checklists_delete_unsupported(self):
|
||||
""" Delete operation is not supported. """
|
||||
update_url = reverse('checklists_updates', kwargs={'org': self.course.location.org,
|
||||
'course': self.course.location.course,
|
||||
'name': self.course.location.name,
|
||||
'checklist_index': 100})
|
||||
response = self.client.delete(update_url)
|
||||
self.assertContains(response, 'Unsupported request', status_code=400)
|
||||
@@ -37,6 +37,14 @@ TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||
|
||||
class MongoCollectionFindWrapper(object):
|
||||
def __init__(self, original):
|
||||
self.original = original
|
||||
self.counter = 0
|
||||
|
||||
def find(self, query, *args, **kwargs):
|
||||
self.counter = self.counter+1
|
||||
return self.original(query, *args, **kwargs)
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
|
||||
class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
@@ -101,6 +109,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(reverse_tabs, course_tabs)
|
||||
|
||||
def test_import_polls(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
module_store = modulestore('direct')
|
||||
found = False
|
||||
|
||||
item = None
|
||||
items = module_store.get_items(['i4x', 'edX', 'full', 'poll_question', None, None])
|
||||
found = len(items) > 0
|
||||
|
||||
self.assertTrue(found)
|
||||
# check that there's actually content in the 'question' field
|
||||
self.assertGreater(len(items[0].question),0)
|
||||
|
||||
def test_delete(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
@@ -131,8 +153,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# make sure the parent no longer points to the child object which was deleted
|
||||
self.assertFalse(sequential.location.url() in chapter.children)
|
||||
|
||||
|
||||
|
||||
def test_about_overrides(self):
|
||||
'''
|
||||
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
|
||||
@@ -193,6 +213,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_bad_contentstore_request(self):
|
||||
resp = self.client.get('http://localhost:8001/c4x/CDX/123123/asset/&images_circuits_Lab7Solution2.png')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def test_delete_course(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
@@ -293,6 +317,28 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
# note, we know the link it should be because that's what in the 'full' course in the test data
|
||||
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
|
||||
|
||||
def test_prefetch_children(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
module_store = modulestore('direct')
|
||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
|
||||
wrapper = MongoCollectionFindWrapper(module_store.collection.find)
|
||||
module_store.collection.find = wrapper.find
|
||||
course = module_store.get_item(location, depth=2)
|
||||
|
||||
# make sure we haven't done too many round trips to DB
|
||||
# note we say 4 round trips here for 1) the course, 2 & 3) for the chapters and sequentials, and
|
||||
# 4) because of the RT due to calculating the inherited metadata
|
||||
self.assertEqual(wrapper.counter, 4)
|
||||
|
||||
# make sure we pre-fetched a known sequential which should be at depth=2
|
||||
self.assertTrue(Location(['i4x', 'edX', 'full', 'sequential',
|
||||
'Administrivia_and_Circuit_Elements', None]) in course.system.module_data)
|
||||
|
||||
# make sure we don't have a specific vertical which should be at depth=3
|
||||
self.assertFalse(Location(['i4x', 'edX', 'full', 'vertical', 'vertical_58',
|
||||
None]) in course.system.module_data)
|
||||
|
||||
def test_export_course_with_unknown_metadata(self):
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
@@ -514,7 +560,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
module_store.update_children(parent.location, parent.children + [new_component_location.url()])
|
||||
|
||||
# flush the cache
|
||||
module_store.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
module_store.refresh_cached_metadata_inheritance_tree(new_component_location)
|
||||
new_module = module_store.get_item(new_component_location)
|
||||
|
||||
# check for grace period definition which should be defined at the course level
|
||||
@@ -529,7 +575,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
module_store.update_metadata(new_module.location, own_metadata(new_module))
|
||||
|
||||
# flush the cache and refetch
|
||||
module_store.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
module_store.refresh_cached_metadata_inheritance_tree(new_component_location)
|
||||
new_module = module_store.get_item(new_component_location)
|
||||
|
||||
self.assertEqual(timedelta(1), new_module.lms.graceperiod)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import datetime
|
||||
import json
|
||||
import copy
|
||||
from util import converters
|
||||
from util.converters import jsdate_to_time
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.client import Client
|
||||
@@ -15,33 +13,13 @@ from models.settings.course_details import (CourseDetails,
|
||||
from models.settings.course_grading import CourseGradingModel
|
||||
from contentstore.utils import get_modulestore
|
||||
|
||||
from django.test import TestCase
|
||||
from .utils import ModuleStoreTestCase
|
||||
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
|
||||
|
||||
|
||||
# YYYY-MM-DDThh:mm:ss.s+/-HH:MM
|
||||
class ConvertersTestCase(TestCase):
|
||||
@staticmethod
|
||||
def struct_to_datetime(struct_time):
|
||||
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour,
|
||||
struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
|
||||
|
||||
def compare_dates(self, date1, date2, expected_delta):
|
||||
dt1 = ConvertersTestCase.struct_to_datetime(date1)
|
||||
dt2 = ConvertersTestCase.struct_to_datetime(date2)
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" + str(date2) + "!=" + str(expected_delta))
|
||||
|
||||
def test_iso_to_struct(self):
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01"), converters.jsdate_to_time("2012-12-31"), datetime.timedelta(days=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00"), converters.jsdate_to_time("2012-12-31T23"), datetime.timedelta(hours=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00"), converters.jsdate_to_time("2012-12-31T23:59"), datetime.timedelta(minutes=1))
|
||||
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1))
|
||||
|
||||
from xmodule.fields import Date
|
||||
|
||||
class CourseTestCase(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
@@ -104,7 +82,7 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
self.assertIsNone(jsondetails['effort'], "effort somehow initialized")
|
||||
|
||||
def test_update_and_fetch(self):
|
||||
## NOTE: I couldn't figure out how to validly test time setting w/ all the conversions
|
||||
# # NOTE: I couldn't figure out how to validly test time setting w/ all the conversions
|
||||
jsondetails = CourseDetails.fetch(self.course_location)
|
||||
jsondetails.syllabus = "<a href='foo'>bar</a>"
|
||||
# encode - decode to convert date fields and other data which changes form
|
||||
@@ -170,19 +148,26 @@ class CourseDetailsViewTest(CourseTestCase):
|
||||
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
|
||||
self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==")
|
||||
|
||||
@staticmethod
|
||||
def struct_to_datetime(struct_time):
|
||||
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon,
|
||||
struct_time.tm_mday, struct_time.tm_hour,
|
||||
struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
|
||||
|
||||
def compare_date_fields(self, details, encoded, context, field):
|
||||
if details[field] is not None:
|
||||
date = Date()
|
||||
if field in encoded and encoded[field] is not None:
|
||||
encoded_encoded = jsdate_to_time(encoded[field])
|
||||
dt1 = ConvertersTestCase.struct_to_datetime(encoded_encoded)
|
||||
encoded_encoded = date.from_json(encoded[field])
|
||||
dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded)
|
||||
|
||||
if isinstance(details[field], datetime.datetime):
|
||||
dt2 = details[field]
|
||||
else:
|
||||
details_encoded = jsdate_to_time(details[field])
|
||||
dt2 = ConvertersTestCase.struct_to_datetime(details_encoded)
|
||||
details_encoded = date.from_json(details[field])
|
||||
dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded)
|
||||
|
||||
expected_delta = datetime.timedelta(0)
|
||||
expected_delta = datetime.timedelta(0)
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context)
|
||||
else:
|
||||
self.fail(field + " missing from encoded but in details at " + context)
|
||||
@@ -269,7 +254,7 @@ class CourseMetadataEditingTest(CourseTestCase):
|
||||
CourseTestCase.setUp(self)
|
||||
# add in the full class too
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
self.fullcourse_location = Location(['i4x','edX','full','course','6.002_Spring_2012', None])
|
||||
self.fullcourse_location = Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])
|
||||
|
||||
|
||||
def test_fetch_initial_fields(self):
|
||||
|
||||
@@ -1,31 +1,145 @@
|
||||
'''unit tests for course_info views and models.'''
|
||||
from contentstore.tests.test_course_settings import CourseTestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
import json
|
||||
|
||||
|
||||
class CourseUpdateTest(CourseTestCase):
|
||||
'''The do all and end all of unit test cases.'''
|
||||
def test_course_update(self):
|
||||
'''Go through each interface and ensure it works.'''
|
||||
# first get the update to force the creation
|
||||
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||
'name': self.course_location.name})
|
||||
url = reverse('course_info',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'name': self.course_location.name})
|
||||
self.client.get(url)
|
||||
|
||||
content = '<iframe width="560" height="315" src="http://www.youtube.com/embed/RocY-Jd93XU" frameborder="0"></iframe>'
|
||||
init_content = '<iframe width="560" height="315" src="http://www.youtube.com/embed/RocY-Jd93XU" frameborder="0">'
|
||||
content = init_content + '</iframe>'
|
||||
payload = {'content': content,
|
||||
'date': 'January 8, 2013'}
|
||||
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
|
||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||
|
||||
payload = json.loads(resp.content)
|
||||
|
||||
self.assertHTMLEqual(content, payload['content'], "single iframe")
|
||||
self.assertHTMLEqual(payload['content'], content)
|
||||
|
||||
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||
'provided_id': payload['id']})
|
||||
content += '<div>div <p>p</p></div>'
|
||||
first_update_url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': payload['id']})
|
||||
content += '<div>div <p>p<br/></p></div>'
|
||||
payload['content'] = content
|
||||
resp = self.client.post(first_update_url, json.dumps(payload),
|
||||
"application/json")
|
||||
|
||||
self.assertHTMLEqual(content, json.loads(resp.content)['content'],
|
||||
"iframe w/ div")
|
||||
|
||||
# now put in an evil update
|
||||
content = '<ol/>'
|
||||
payload = {'content': content,
|
||||
'date': 'January 11, 2013'}
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
|
||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||
|
||||
self.assertHTMLEqual(content, json.loads(resp.content)['content'], "iframe w/ div")
|
||||
payload = json.loads(resp.content)
|
||||
|
||||
self.assertHTMLEqual(content, payload['content'], "self closing ol")
|
||||
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
resp = self.client.get(url)
|
||||
payload = json.loads(resp.content)
|
||||
self.assertTrue(len(payload) == 2)
|
||||
|
||||
# can't test non-json paylod b/c expect_json throws error
|
||||
# try json w/o required fields
|
||||
self.assertContains(
|
||||
self.client.post(url, json.dumps({'garbage': 1}),
|
||||
"application/json"),
|
||||
'Failed to save', status_code=400)
|
||||
|
||||
# now try to update a non-existent update
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': '9'})
|
||||
content = 'blah blah'
|
||||
payload = {'content': content,
|
||||
'date': 'January 21, 2013'}
|
||||
self.assertContains(
|
||||
self.client.post(url, json.dumps(payload), "application/json"),
|
||||
'Failed to save', status_code=400)
|
||||
|
||||
# update w/ malformed html
|
||||
content = '<garbage tag No closing brace to force <span>error</span>'
|
||||
payload = {'content': content,
|
||||
'date': 'January 11, 2013'}
|
||||
url = reverse('course_info_json', kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
|
||||
self.assertContains(
|
||||
self.client.post(url, json.dumps(payload), "application/json"),
|
||||
'<garbage')
|
||||
|
||||
# set to valid html which would break an xml parser
|
||||
content = "<p><br><br></p>"
|
||||
payload = {'content': content,
|
||||
'date': 'January 11, 2013'}
|
||||
url = reverse('course_info_json', kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
|
||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||
payload = json.loads(resp.content)
|
||||
self.assertHTMLEqual(content, json.loads(resp.content)['content'])
|
||||
|
||||
# now try to delete a non-existent update
|
||||
url = reverse('course_info_json', kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': '19'})
|
||||
payload = {'content': content,
|
||||
'date': 'January 21, 2013'}
|
||||
self.assertContains(self.client.delete(url), "delete", status_code=400)
|
||||
|
||||
# now delete a real update
|
||||
content = 'blah blah'
|
||||
payload = {'content': content,
|
||||
'date': 'January 28, 2013'}
|
||||
url = reverse('course_info_json', kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||
payload = json.loads(resp.content)
|
||||
this_id = payload['id']
|
||||
self.assertHTMLEqual(content, payload['content'], "single iframe")
|
||||
# first count the entries
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': ''})
|
||||
resp = self.client.get(url)
|
||||
payload = json.loads(resp.content)
|
||||
before_delete = len(payload)
|
||||
|
||||
url = reverse('course_info_json',
|
||||
kwargs={'org': self.course_location.org,
|
||||
'course': self.course_location.course,
|
||||
'provided_id': this_id})
|
||||
resp = self.client.delete(url)
|
||||
payload = json.loads(resp.content)
|
||||
self.assertTrue(len(payload) == before_delete - 1)
|
||||
|
||||
@@ -1,19 +1,72 @@
|
||||
from contentstore import utils
|
||||
""" Tests for utils. """
|
||||
from contentstore import utils
|
||||
import mock
|
||||
from django.test import TestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from .utils import ModuleStoreTestCase
|
||||
|
||||
|
||||
class LMSLinksTestCase(TestCase):
|
||||
""" Tests for LMS links. """
|
||||
def about_page_test(self):
|
||||
""" Get URL for about page. """
|
||||
location = 'i4x', 'mitX', '101', 'course', 'test'
|
||||
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
||||
link = utils.get_lms_link_for_about_page(location)
|
||||
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/about")
|
||||
|
||||
def ls_link_test(self):
|
||||
def lms_link_test(self):
|
||||
""" Tests get_lms_link_for_item. """
|
||||
location = 'i4x', 'mitX', '101', 'vertical', 'contacting_us'
|
||||
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
||||
link = utils.get_lms_link_for_item(location, False)
|
||||
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")
|
||||
link = utils.get_lms_link_for_item(location, True)
|
||||
self.assertEquals(link, "//preview.localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")
|
||||
self.assertEquals(
|
||||
link,
|
||||
"//preview.localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us"
|
||||
)
|
||||
|
||||
|
||||
class UrlReverseTestCase(ModuleStoreTestCase):
|
||||
""" Tests for get_url_reverse """
|
||||
def test_CoursePageNames(self):
|
||||
""" Test the defined course pages. """
|
||||
course = CourseFactory.create(org='mitX', number='666', display_name='URL Reverse Course')
|
||||
|
||||
self.assertEquals(
|
||||
'/manage_users/i4x://mitX/666/course/URL_Reverse_Course',
|
||||
utils.get_url_reverse('ManageUsers', course)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
'/mitX/666/settings-details/URL_Reverse_Course',
|
||||
utils.get_url_reverse('SettingsDetails', course)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
'/mitX/666/settings-grading/URL_Reverse_Course',
|
||||
utils.get_url_reverse('SettingsGrading', course)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
'/mitX/666/course/URL_Reverse_Course',
|
||||
utils.get_url_reverse('CourseOutline', course)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
'/mitX/666/checklists/URL_Reverse_Course',
|
||||
utils.get_url_reverse('Checklists', course)
|
||||
)
|
||||
|
||||
def test_unknown_passes_through(self):
|
||||
""" Test that unknown values pass through. """
|
||||
course = CourseFactory.create(org='mitX', number='666', display_name='URL Reverse Course')
|
||||
self.assertEquals(
|
||||
'foobar',
|
||||
utils.get_url_reverse('foobar', course)
|
||||
)
|
||||
self.assertEquals(
|
||||
'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)
|
||||
)
|
||||
@@ -2,6 +2,7 @@ from django.conf import settings
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info']
|
||||
|
||||
@@ -159,3 +160,35 @@ def update_item(location, value):
|
||||
get_modulestore(location).delete_item(location)
|
||||
else:
|
||||
get_modulestore(location).update_item(location, value)
|
||||
|
||||
|
||||
def get_url_reverse(course_page_name, course_module):
|
||||
"""
|
||||
Returns the course URL link to the specified location. This value is suitable to use as an href link.
|
||||
|
||||
course_page_name should correspond to an attribute in CoursePageNames (for example, 'ManageUsers'
|
||||
or 'SettingsDetails'), or else it will simply be returned. This method passes back unknown values of
|
||||
course_page_names so that it can also be used for absolute (known) URLs.
|
||||
|
||||
course_module is used to obtain the location, org, course, and name properties for a course, if
|
||||
course_page_name corresponds to an attribute in CoursePageNames.
|
||||
"""
|
||||
url_name = getattr(CoursePageNames, course_page_name, None)
|
||||
ctx_loc = course_module.location
|
||||
|
||||
if CoursePageNames.ManageUsers == url_name:
|
||||
return reverse(url_name, kwargs={"location": ctx_loc})
|
||||
elif url_name in [CoursePageNames.SettingsDetails, CoursePageNames.SettingsGrading,
|
||||
CoursePageNames.CourseOutline, CoursePageNames.Checklists]:
|
||||
return reverse(url_name, kwargs={'org': ctx_loc.org, 'course': ctx_loc.course, 'name': ctx_loc.name})
|
||||
else:
|
||||
return course_page_name
|
||||
|
||||
|
||||
class CoursePageNames:
|
||||
""" Constants for pages that are recognized by get_url_reverse method. """
|
||||
ManageUsers = "manage_users"
|
||||
SettingsDetails = "settings_details"
|
||||
SettingsGrading = "settings_grading"
|
||||
CourseOutline = "course_index"
|
||||
Checklists = "checklists"
|
||||
|
||||
@@ -18,7 +18,8 @@ from django.core.files.temp import NamedTemporaryFile
|
||||
# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz'
|
||||
from PIL import Image
|
||||
|
||||
from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseServerError
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.context_processors import csrf
|
||||
@@ -50,15 +51,15 @@ from xmodule.contentstore.content import StaticContent
|
||||
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
|
||||
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
|
||||
from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups
|
||||
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState, get_course_for_item
|
||||
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, \
|
||||
get_date_display, UnitState, get_course_for_item, get_url_reverse
|
||||
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from contentstore.course_info_model import get_course_updates,\
|
||||
from contentstore.course_info_model import get_course_updates, \
|
||||
update_course_updates, delete_course_update
|
||||
from cache_toolbox.core import del_cached_content
|
||||
from xmodule.timeparse import stringify_time
|
||||
from contentstore.module_info_model import get_module_info, set_module_info
|
||||
from models.settings.course_details import CourseDetails,\
|
||||
from models.settings.course_details import CourseDetails, \
|
||||
CourseSettingsEncoder
|
||||
from models.settings.course_grading import CourseGradingModel
|
||||
from contentstore.utils import get_modulestore
|
||||
@@ -72,7 +73,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
|
||||
|
||||
ADVANCED_COMPONENT_TYPES = ['annotatable','combinedopenended', 'peergrading']
|
||||
ADVANCED_COMPONENT_TYPES = ['annotatable', 'combinedopenended', 'peergrading']
|
||||
ADVANCED_COMPONENT_CATEGORY = 'advanced'
|
||||
ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
|
||||
|
||||
@@ -140,10 +141,7 @@ def index(request):
|
||||
return render_to_response('index.html', {
|
||||
'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'),
|
||||
'courses': [(course.display_name,
|
||||
reverse('course_index', args=[
|
||||
course.location.org,
|
||||
course.location.course,
|
||||
course.location.name]),
|
||||
get_url_reverse('CourseOutline', course),
|
||||
get_lms_link_for_item(course.location, course_id=course.location.course_id))
|
||||
for course in courses],
|
||||
'user': request.user,
|
||||
@@ -180,19 +178,15 @@ def course_index(request, org, course, name):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
lms_link = get_lms_link_for_item(location)
|
||||
|
||||
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name
|
||||
})
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name
|
||||
})
|
||||
|
||||
course = modulestore().get_item(location)
|
||||
sections = course.get_children()
|
||||
@@ -248,7 +242,7 @@ def edit_subsection(request, location):
|
||||
for field
|
||||
in item.fields
|
||||
if field.name not in ['display_name', 'start', 'due', 'format'] and
|
||||
field.scope == Scope.settings
|
||||
field.scope == Scope.settings
|
||||
)
|
||||
|
||||
can_view_live = False
|
||||
@@ -260,18 +254,18 @@ def edit_subsection(request, location):
|
||||
break
|
||||
|
||||
return render_to_response('edit_subsection.html',
|
||||
{'subsection': item,
|
||||
'context_course': course,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
'parent_item': parent,
|
||||
'policy_metadata': policy_metadata,
|
||||
'subsection_units': subsection_units,
|
||||
'can_view_live': can_view_live
|
||||
})
|
||||
{'subsection': item,
|
||||
'context_course': course,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
'parent_item': parent,
|
||||
'policy_metadata': policy_metadata,
|
||||
'subsection_units': subsection_units,
|
||||
'can_view_live': can_view_live
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -321,7 +315,7 @@ def edit_unit(request, location):
|
||||
category = ADVANCED_COMPONENT_CATEGORY
|
||||
|
||||
if category in component_types:
|
||||
#This is a hack to create categories for different xmodules
|
||||
# This is a hack to create categories for different xmodules
|
||||
component_templates[category].append((
|
||||
template.display_name_with_default,
|
||||
template.location.url(),
|
||||
@@ -416,7 +410,7 @@ def assignment_type_update(request, org, course, category, name):
|
||||
if request.method == 'GET':
|
||||
return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)),
|
||||
mimetype="application/json")
|
||||
elif request.method == 'POST': # post or put, doesn't matter.
|
||||
elif request.method == 'POST': # post or put, doesn't matter.
|
||||
return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)),
|
||||
mimetype="application/json")
|
||||
|
||||
@@ -797,9 +791,7 @@ def upload_asset(request, org, course, coursename):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# construct a location from the passed in path
|
||||
location = ['i4x', org, course, 'course', coursename]
|
||||
if not has_access(request.user, location):
|
||||
return HttpResponseForbidden()
|
||||
location = get_location_and_verify_access(request, org, course, coursename)
|
||||
|
||||
# Does the course actually exist?!? Get anything from it to prove its existance
|
||||
|
||||
@@ -830,7 +822,7 @@ def upload_asset(request, org, course, coursename):
|
||||
if thumbnail_content is not None:
|
||||
content.thumbnail_location = thumbnail_location
|
||||
|
||||
#then commit the content
|
||||
# then commit the content
|
||||
contentstore().save(content)
|
||||
del_cached_content(content.location)
|
||||
|
||||
@@ -873,7 +865,7 @@ def manage_users(request, location):
|
||||
})
|
||||
|
||||
|
||||
def create_json_response(errmsg = None):
|
||||
def create_json_response(errmsg=None):
|
||||
if errmsg is not None:
|
||||
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
|
||||
else:
|
||||
@@ -951,11 +943,7 @@ def landing(request, org, course, coursename):
|
||||
@ensure_csrf_cookie
|
||||
def static_pages(request, org, course, coursename):
|
||||
|
||||
location = ['i4x', org, course, 'course', coursename]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, coursename)
|
||||
|
||||
course = modulestore().get_item(location)
|
||||
|
||||
@@ -1067,11 +1055,7 @@ def course_info(request, org, course, name, provided_id=None):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
@@ -1110,21 +1094,25 @@ def course_info_updates(request, org, course, provided_id=None):
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
|
||||
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
|
||||
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
|
||||
else:
|
||||
real_method = request.method
|
||||
real_method = get_request_method(request)
|
||||
|
||||
if request.method == 'GET':
|
||||
return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json")
|
||||
elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE
|
||||
return HttpResponse(json.dumps(delete_course_update(location, request.POST, provided_id)), mimetype="application/json")
|
||||
return HttpResponse(json.dumps(get_course_updates(location)),
|
||||
mimetype="application/json")
|
||||
elif real_method == 'DELETE':
|
||||
try:
|
||||
return HttpResponse(json.dumps(delete_course_update(location,
|
||||
request.POST, provided_id)), mimetype="application/json")
|
||||
except:
|
||||
return HttpResponseBadRequest("Failed to delete",
|
||||
content_type="text/plain")
|
||||
elif request.method == 'POST':
|
||||
try:
|
||||
return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json")
|
||||
return HttpResponse(json.dumps(update_course_updates(location,
|
||||
request.POST, provided_id)), mimetype="application/json")
|
||||
except:
|
||||
return HttpResponseBadRequest("Failed to save: malformed html", content_type="text/plain")
|
||||
return HttpResponseBadRequest("Failed to save",
|
||||
content_type="text/plain")
|
||||
|
||||
|
||||
@expect_json
|
||||
@@ -1137,11 +1125,7 @@ def module_info(request, module_location):
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
|
||||
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
|
||||
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
|
||||
else:
|
||||
real_method = request.method
|
||||
real_method = get_request_method(request)
|
||||
|
||||
rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true']
|
||||
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links))
|
||||
@@ -1166,11 +1150,7 @@ def get_course_settings(request, org, course, name):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
@@ -1193,11 +1173,7 @@ def course_config_graders_page(request, org, course, name):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
course_details = CourseGradingModel.fetch(location)
|
||||
@@ -1217,11 +1193,7 @@ def course_config_advanced_page(request, org, course, name):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
@@ -1243,11 +1215,7 @@ def course_settings_updates(request, org, course, name, section):
|
||||
org, course: Attributes of the Location for the item to edit
|
||||
section: one of details, faculty, grading, problems, discussions
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
if section == 'details':
|
||||
manager = CourseDetails
|
||||
@@ -1259,7 +1227,7 @@ def course_settings_updates(request, org, course, name, section):
|
||||
# Cannot just do a get w/o knowing the course name :-(
|
||||
return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course', name])), cls=CourseSettingsEncoder),
|
||||
mimetype="application/json")
|
||||
elif request.method == 'POST': # post or put, doesn't matter.
|
||||
elif request.method == 'POST': # post or put, doesn't matter.
|
||||
return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
|
||||
mimetype="application/json")
|
||||
|
||||
@@ -1275,31 +1243,24 @@ def course_grader_updates(request, org, course, name, grader_index=None):
|
||||
org, course: Attributes of the Location for the item to edit
|
||||
"""
|
||||
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
|
||||
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
|
||||
else:
|
||||
real_method = request.method
|
||||
real_method = get_request_method(request)
|
||||
|
||||
if real_method == 'GET':
|
||||
# Cannot just do a get w/o knowing the course name :-(
|
||||
return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course', name]), grader_index)),
|
||||
return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(location), grader_index)),
|
||||
mimetype="application/json")
|
||||
elif real_method == "DELETE":
|
||||
# ??? Shoudl this return anything? Perhaps success fail?
|
||||
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course', name]), grader_index)
|
||||
# ??? Should this return anything? Perhaps success fail?
|
||||
CourseGradingModel.delete_grader(Location(location), grader_index)
|
||||
return HttpResponse()
|
||||
elif request.method == 'POST': # post or put, doesn't matter.
|
||||
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)),
|
||||
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(location), request.POST)),
|
||||
mimetype="application/json")
|
||||
|
||||
|
||||
## NB: expect_json failed on ["key", "key2"] and json payload
|
||||
# # NB: expect_json failed on ["key", "key2"] and json payload
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_advanced_updates(request, org, course, name):
|
||||
@@ -1309,18 +1270,10 @@ def course_advanced_updates(request, org, course, name):
|
||||
|
||||
org, course: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
|
||||
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
|
||||
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
|
||||
else:
|
||||
real_method = request.method
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
real_method = get_request_method(request)
|
||||
|
||||
if real_method == 'GET':
|
||||
return HttpResponse(json.dumps(CourseMetadata.fetch(location)), mimetype="application/json")
|
||||
elif real_method == 'DELETE':
|
||||
@@ -1330,6 +1283,95 @@ def course_advanced_updates(request, org, course, name):
|
||||
return HttpResponse(json.dumps(CourseMetadata.update_from_json(location, json.loads(request.body))), mimetype="application/json")
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def get_checklists(request, org, course, name):
|
||||
"""
|
||||
Send models, views, and html for displaying the course checklists.
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
modulestore = get_modulestore(location)
|
||||
course_module = modulestore.get_item(location)
|
||||
new_course_template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
|
||||
template_module = modulestore.get_item(new_course_template)
|
||||
|
||||
# If course was created before checklists were introduced, copy them over from the template.
|
||||
copied = False
|
||||
if not course_module.checklists:
|
||||
course_module.checklists = template_module.checklists
|
||||
copied = True
|
||||
|
||||
checklists, modified = expand_checklist_action_urls(course_module)
|
||||
if copied or modified:
|
||||
modulestore.update_metadata(location, own_metadata(course_module))
|
||||
return render_to_response('checklists.html',
|
||||
{
|
||||
'context_course': course_module,
|
||||
'checklists': checklists
|
||||
})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def update_checklist(request, org, course, name, checklist_index=None):
|
||||
"""
|
||||
restful CRUD operations on course checklists. The payload is a json rep of
|
||||
the modified checklist. For PUT or POST requests, the index of the
|
||||
checklist being modified must be included; the returned payload will
|
||||
be just that one checklist. For GET requests, the returned payload
|
||||
is a json representation of the list of all checklists.
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
modulestore = get_modulestore(location)
|
||||
course_module = modulestore.get_item(location)
|
||||
|
||||
real_method = get_request_method(request)
|
||||
if real_method == 'POST' or real_method == 'PUT':
|
||||
if checklist_index is not None and 0 <= int(checklist_index) < len(course_module.checklists):
|
||||
index = int(checklist_index)
|
||||
course_module.checklists[index] = json.loads(request.body)
|
||||
checklists, modified = expand_checklist_action_urls(course_module)
|
||||
modulestore.update_metadata(location, own_metadata(course_module))
|
||||
return HttpResponse(json.dumps(checklists[index]), mimetype="application/json")
|
||||
else:
|
||||
return HttpResponseBadRequest(
|
||||
"Could not save checklist state because the checklist index was out of range or unspecified.",
|
||||
content_type="text/plain")
|
||||
elif request.method == 'GET':
|
||||
# In the JavaScript view initialize method, we do a fetch to get all the checklists.
|
||||
checklists, modified = expand_checklist_action_urls(course_module)
|
||||
if modified:
|
||||
modulestore.update_metadata(location, own_metadata(course_module))
|
||||
return HttpResponse(json.dumps(checklists), mimetype="application/json")
|
||||
else:
|
||||
return HttpResponseBadRequest("Unsupported request.", content_type="text/plain")
|
||||
|
||||
|
||||
def expand_checklist_action_urls(course_module):
|
||||
"""
|
||||
Gets the checklists out of the course module and expands their action urls
|
||||
if they have not yet been expanded.
|
||||
|
||||
Returns the checklists with modified urls, as well as a boolean
|
||||
indicating whether or not the checklists were modified.
|
||||
"""
|
||||
checklists = course_module.checklists
|
||||
modified = False
|
||||
for checklist in checklists:
|
||||
if not checklist.get('action_urls_expanded', False):
|
||||
for item in checklist.get('items'):
|
||||
item['action_url'] = get_url_reverse(item.get('action_url'), course_module)
|
||||
checklist['action_urls_expanded'] = True
|
||||
modified = True
|
||||
|
||||
return checklists, modified
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def asset_index(request, org, course, name):
|
||||
@@ -1338,18 +1380,13 @@ def asset_index(request, org, course, name):
|
||||
|
||||
org, course, name: Attributes of the Location for the item to edit
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name
|
||||
})
|
||||
'org': org,
|
||||
'course': course,
|
||||
'coursename': name
|
||||
})
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
@@ -1465,11 +1502,7 @@ def initialize_course_tabs(course):
|
||||
@login_required
|
||||
def import_course(request, org, course, name):
|
||||
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
if request.method == 'POST':
|
||||
filename = request.FILES['course-data'].name
|
||||
@@ -1532,20 +1565,14 @@ def import_course(request, org, course, name):
|
||||
return render_to_response('import.html', {
|
||||
'context_course': course_module,
|
||||
'active_tab': 'import',
|
||||
'successful_import_redirect_url': reverse('course_index', args=[
|
||||
course_module.location.org,
|
||||
course_module.location.course,
|
||||
course_module.location.name])
|
||||
'successful_import_redirect_url': get_url_reverse('CourseOutline', course_module)
|
||||
})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def generate_export_course(request, org, course, name):
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
loc = Location(location)
|
||||
export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz")
|
||||
@@ -1557,7 +1584,7 @@ def generate_export_course(request, org, course, name):
|
||||
logging.debug('root = {0}'.format(root_dir))
|
||||
|
||||
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name)
|
||||
#filename = root_dir / name + '.tar.gz'
|
||||
# filename = root_dir / name + '.tar.gz'
|
||||
|
||||
logging.debug('tar file being generated at {0}'.format(export_file.name))
|
||||
tf = tarfile.open(name=export_file.name, mode='w:gz')
|
||||
@@ -1578,11 +1605,9 @@ def generate_export_course(request, org, course, name):
|
||||
@login_required
|
||||
def export_course(request, org, course, name):
|
||||
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
location = get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
course_module = modulestore().get_item(location)
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
return render_to_response('export.html', {
|
||||
'context_course': course_module,
|
||||
@@ -1597,3 +1622,39 @@ def event(request):
|
||||
console logs don't get distracted :-)
|
||||
'''
|
||||
return HttpResponse(True)
|
||||
|
||||
|
||||
def render_404(request):
|
||||
return HttpResponseNotFound(render_to_string('404.html', {}))
|
||||
|
||||
|
||||
def render_500(request):
|
||||
return HttpResponseServerError(render_to_string('500.html', {}))
|
||||
|
||||
|
||||
def get_location_and_verify_access(request, org, course, name):
|
||||
"""
|
||||
Create the location tuple verify that the user has permissions
|
||||
to view the location. Returns the location.
|
||||
"""
|
||||
location = ['i4x', org, course, 'course', name]
|
||||
|
||||
# check that logged in user has permissions to this item
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
return location
|
||||
|
||||
|
||||
def get_request_method(request):
|
||||
"""
|
||||
Using HTTP_X_HTTP_METHOD_OVERRIDE, in the request metadata, determine
|
||||
what type of request came from the client, and return it.
|
||||
"""
|
||||
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
|
||||
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
|
||||
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
|
||||
else:
|
||||
real_method = request.method
|
||||
|
||||
return real_method
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
@@ -6,9 +5,9 @@ import json
|
||||
from json.encoder import JSONEncoder
|
||||
import time
|
||||
from contentstore.utils import get_modulestore
|
||||
from util.converters import jsdate_to_time, time_to_date
|
||||
from models.settings import course_grading
|
||||
from contentstore.utils import update_item
|
||||
from xmodule.fields import Date
|
||||
import re
|
||||
import logging
|
||||
|
||||
@@ -81,8 +80,14 @@ class CourseDetails(object):
|
||||
|
||||
dirty = False
|
||||
|
||||
# In the descriptor's setter, the date is converted to JSON using Date's to_json method.
|
||||
# Calling to_json on something that is already JSON doesn't work. Since reaching directly
|
||||
# into the model is nasty, convert the JSON Date to a Python date, which is what the
|
||||
# setter expects as input.
|
||||
date = Date()
|
||||
|
||||
if 'start_date' in jsondict:
|
||||
converted = jsdate_to_time(jsondict['start_date'])
|
||||
converted = date.from_json(jsondict['start_date'])
|
||||
else:
|
||||
converted = None
|
||||
if converted != descriptor.start:
|
||||
@@ -90,7 +95,7 @@ class CourseDetails(object):
|
||||
descriptor.start = converted
|
||||
|
||||
if 'end_date' in jsondict:
|
||||
converted = jsdate_to_time(jsondict['end_date'])
|
||||
converted = date.from_json(jsondict['end_date'])
|
||||
else:
|
||||
converted = None
|
||||
|
||||
@@ -99,7 +104,7 @@ class CourseDetails(object):
|
||||
descriptor.end = converted
|
||||
|
||||
if 'enrollment_start' in jsondict:
|
||||
converted = jsdate_to_time(jsondict['enrollment_start'])
|
||||
converted = date.from_json(jsondict['enrollment_start'])
|
||||
else:
|
||||
converted = None
|
||||
|
||||
@@ -108,7 +113,7 @@ class CourseDetails(object):
|
||||
descriptor.enrollment_start = converted
|
||||
|
||||
if 'enrollment_end' in jsondict:
|
||||
converted = jsdate_to_time(jsondict['enrollment_end'])
|
||||
converted = date.from_json(jsondict['enrollment_end'])
|
||||
else:
|
||||
converted = None
|
||||
|
||||
@@ -178,6 +183,6 @@ class CourseSettingsEncoder(json.JSONEncoder):
|
||||
elif isinstance(obj, Location):
|
||||
return obj.dict()
|
||||
elif isinstance(obj, time.struct_time):
|
||||
return time_to_date(obj)
|
||||
return Date().to_json(obj)
|
||||
else:
|
||||
return JSONEncoder.default(self, obj)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from xmodule.modulestore import Location
|
||||
from contentstore.utils import get_modulestore
|
||||
import re
|
||||
from util import converters
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
|
||||
@@ -3,19 +3,24 @@ from contentstore.utils import get_modulestore
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xblock.core import Scope
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
|
||||
class CourseMetadata(object):
|
||||
'''
|
||||
For CRUD operations on metadata fields which do not have specific editors on the other pages including any user generated ones.
|
||||
The objects have no predefined attrs but instead are obj encodings of the editable metadata.
|
||||
For CRUD operations on metadata fields which do not have specific editors
|
||||
on the other pages including any user generated ones.
|
||||
The objects have no predefined attrs but instead are obj encodings of the
|
||||
editable metadata.
|
||||
'''
|
||||
FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start', 'end', 'enrollment_start', 'enrollment_end', 'tabs', 'graceperiod']
|
||||
FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start', 'end',
|
||||
'enrollment_start', 'enrollment_end', 'tabs', 'graceperiod', 'checklists']
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, course_location):
|
||||
"""
|
||||
Fetch the key:value editable course details for the given course from persistence and return a CourseMetadata model.
|
||||
Fetch the key:value editable course details for the given course from
|
||||
persistence and return a CourseMetadata model.
|
||||
"""
|
||||
if not isinstance(course_location, Location):
|
||||
course_location = Location(course_location)
|
||||
@@ -29,7 +34,7 @@ class CourseMetadata(object):
|
||||
continue
|
||||
|
||||
if field.name not in cls.FILTERED_LIST:
|
||||
course[field.name] = field.read_from(descriptor)
|
||||
course[field.name] = field.read_json(descriptor)
|
||||
|
||||
return course
|
||||
|
||||
@@ -51,22 +56,26 @@ class CourseMetadata(object):
|
||||
|
||||
if hasattr(descriptor, k) and getattr(descriptor, k) != v:
|
||||
dirty = True
|
||||
setattr(descriptor, k, v)
|
||||
value = getattr(CourseDescriptor, k).from_json(v)
|
||||
setattr(descriptor, k, value)
|
||||
elif hasattr(descriptor.lms, k) and getattr(descriptor.lms, k) != k:
|
||||
dirty = True
|
||||
setattr(descriptor.lms, k, v)
|
||||
value = getattr(CourseDescriptor.lms, k).from_json(v)
|
||||
setattr(descriptor.lms, k, value)
|
||||
|
||||
if dirty:
|
||||
get_modulestore(course_location).update_metadata(course_location, own_metadata(descriptor))
|
||||
get_modulestore(course_location).update_metadata(course_location,
|
||||
own_metadata(descriptor))
|
||||
|
||||
# Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm
|
||||
# it persisted correctly
|
||||
# Could just generate and return a course obj w/o doing any db reads,
|
||||
# but I put the reads in as a means to confirm it persisted correctly
|
||||
return cls.fetch(course_location)
|
||||
|
||||
@classmethod
|
||||
def delete_key(cls, course_location, payload):
|
||||
'''
|
||||
Remove the given metadata key(s) from the course. payload can be a single key or [key..]
|
||||
Remove the given metadata key(s) from the course. payload can be a
|
||||
single key or [key..]
|
||||
'''
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
@@ -76,6 +85,7 @@ class CourseMetadata(object):
|
||||
elif hasattr(descriptor.lms, key):
|
||||
delattr(descriptor.lms, key)
|
||||
|
||||
get_modulestore(course_location).update_metadata(course_location, own_metadata(descriptor))
|
||||
get_modulestore(course_location).update_metadata(course_location,
|
||||
own_metadata(descriptor))
|
||||
|
||||
return cls.fetch(course_location)
|
||||
|
||||
@@ -113,6 +113,7 @@ TEMPLATE_LOADERS = (
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'contentserver.middleware.StaticContentServer',
|
||||
'request_cache.middleware.RequestCache',
|
||||
'django.middleware.cache.UpdateCacheMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
|
||||
@@ -127,8 +127,7 @@ DEBUG_TOOLBAR_PANELS = (
|
||||
'debug_toolbar.panels.sql.SQLDebugPanel',
|
||||
'debug_toolbar.panels.signals.SignalDebugPanel',
|
||||
'debug_toolbar.panels.logger.LoggingPanel',
|
||||
# This is breaking Mongo updates-- Christina is investigating.
|
||||
# 'debug_toolbar_mongo.panel.MongoDebugPanel',
|
||||
'debug_toolbar_mongo.panel.MongoDebugPanel',
|
||||
|
||||
# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
|
||||
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
|
||||
@@ -143,4 +142,4 @@ DEBUG_TOOLBAR_CONFIG = {
|
||||
|
||||
# To see stacktraces for MongoDB queries, set this to True.
|
||||
# Stacktraces slow down page loads drastically (for pages with lots of queries).
|
||||
# DEBUG_TOOLBAR_MONGO_STACKTRACES = False
|
||||
DEBUG_TOOLBAR_MONGO_STACKTRACES = False
|
||||
|
||||
@@ -9,7 +9,8 @@ from django.core.cache import get_cache, InvalidCacheBackendError
|
||||
cache = get_cache('mongo_metadata_inheritance')
|
||||
for store_name in settings.MODULESTORE:
|
||||
store = modulestore(store_name)
|
||||
store.metadata_inheritance_cache = cache
|
||||
store.metadata_inheritance_cache_subsystem = cache
|
||||
store.request_cache = RequestCache.get_request_cache()
|
||||
|
||||
modulestore_update_signal = Signal(
|
||||
providing_args=['modulestore', 'course_id', 'location']
|
||||
|
||||
61
cms/static/client_templates/checklist.html
Normal file
61
cms/static/client_templates/checklist.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<% var allChecked = itemsChecked == items.length; %>
|
||||
<section
|
||||
<% if (allChecked) { %>
|
||||
class="course-checklist is-completed"
|
||||
<% } else { %>
|
||||
class="course-checklist"
|
||||
<% } %>
|
||||
id="<%= 'course-checklist' + checklistIndex %>">
|
||||
<% var widthPercentage = 'width:' + percentChecked + '%;'; %>
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value" style="<%= widthPercentage %>">
|
||||
<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">▾</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">✓</i>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<ul class="list list-tasks">
|
||||
<% var taskIndex = 0; %>
|
||||
<% _.each(items, function(item) { %>
|
||||
<% var checked = item['is_checked']; %>
|
||||
<li
|
||||
<% if (checked) { %>
|
||||
class="task is-completed"
|
||||
<% } else { %>
|
||||
class="task"
|
||||
<% } %>
|
||||
>
|
||||
<% var taskId = 'course-checklist' + checklistIndex + '-task' + taskIndex; %>
|
||||
<input type="checkbox" class="task-input" data-checklist="<%= checklistIndex %>" data-task="<%= taskIndex %>"
|
||||
name="<%= taskId %>" id="<%= taskId %>"
|
||||
<% if (checked) { %>
|
||||
checked="checked"
|
||||
<% } %>
|
||||
>
|
||||
<label class="task-details" for="<%= taskId %>">
|
||||
<h4 class="task-name title title-3"><%= item['short_description'] %></h4>
|
||||
<p class="task-description"><%= item['long_description'] %></p>
|
||||
</label>
|
||||
|
||||
<% if (item['action_text'] !== '' && item['action_url'] !== '') { %>
|
||||
<ul class="list-actions task-actions">
|
||||
<li>
|
||||
<a href="<%= item['action_url'] %>" class="action action-primary"
|
||||
<% if (item['action_external']) { %>
|
||||
rel="external" title="This link will open in a new browser window/tab"
|
||||
<% } %>
|
||||
><%= item['action_text'] %></a>
|
||||
</li>
|
||||
</ul>
|
||||
<% } %>
|
||||
</li>
|
||||
|
||||
<% taskIndex+=1; }) %>
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"js_files": [
|
||||
"/static/js/vendor/RequireJS.js",
|
||||
"/static/js/vendor/jquery.min.js",
|
||||
"/static/js/vendor/jquery-ui.min.js",
|
||||
"/static/js/vendor/jquery.ui.draggable.js",
|
||||
"/static/js/vendor/jquery.cookie.js",
|
||||
"/static/js/vendor/json2.js",
|
||||
"/static/js/vendor/underscore-min.js",
|
||||
"/static/js/vendor/backbone-min.js"
|
||||
"static_files": [
|
||||
"js/vendor/RequireJS.js",
|
||||
"js/vendor/jquery.min.js",
|
||||
"js/vendor/jquery-ui.min.js",
|
||||
"js/vendor/jquery.ui.draggable.js",
|
||||
"js/vendor/jquery.cookie.js",
|
||||
"js/vendor/json2.js",
|
||||
"js/vendor/underscore-min.js",
|
||||
"js/vendor/backbone-min.js"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
|
||||
@$('.components').sortable(
|
||||
handle: '.drag-handle'
|
||||
update: (event, ui) => @model.save(children: @components())
|
||||
update: (event, ui) =>
|
||||
payload = children : @components()
|
||||
options = success : => @model.unset('children')
|
||||
@model.save(payload, options)
|
||||
helper: 'clone'
|
||||
opacity: '0.5'
|
||||
placeholder: 'component-placeholder'
|
||||
@@ -109,7 +112,14 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
id: $component.data('id')
|
||||
}, =>
|
||||
$component.remove()
|
||||
@model.save(children: @components())
|
||||
# b/c we don't vigilantly keep children up to date
|
||||
# get rid of it before it hurts someone
|
||||
# sorry for the js, i couldn't figure out the coffee equivalent
|
||||
`_this.model.save({children: _this.components()},
|
||||
{success: function(model) {
|
||||
model.unset('children');
|
||||
}}
|
||||
);`
|
||||
)
|
||||
|
||||
deleteDraft: (event) ->
|
||||
@@ -157,7 +167,7 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
|
||||
class CMS.Views.UnitEdit.NameEdit extends Backbone.View
|
||||
events:
|
||||
"keyup .unit-display-name-input": "saveName"
|
||||
'change .unit-display-name-input': 'saveName'
|
||||
|
||||
initialize: =>
|
||||
@model.on('change:metadata', @render)
|
||||
@@ -180,29 +190,10 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View
|
||||
# Treat the metadata dictionary as immutable
|
||||
metadata = $.extend({}, @model.get('metadata'))
|
||||
metadata.display_name = @$('.unit-display-name-input').val()
|
||||
@model.save(metadata: metadata)
|
||||
# Update name shown in the right-hand side location summary.
|
||||
$('.unit-location .editing .unit-name').html(metadata.display_name)
|
||||
|
||||
inputField = this.$el.find('input')
|
||||
|
||||
# add a spinner
|
||||
@$spinner.css({
|
||||
'position': 'absolute',
|
||||
'top': Math.floor(inputField.position().top + (inputField.outerHeight() / 2) + 3),
|
||||
'left': inputField.position().left + inputField.outerWidth() - 24,
|
||||
'margin-top': '-10px'
|
||||
});
|
||||
inputField.after(@$spinner);
|
||||
@$spinner.fadeIn(10)
|
||||
|
||||
# save the name after a slight delay
|
||||
if @timer
|
||||
clearTimeout @timer
|
||||
@timer = setTimeout( =>
|
||||
@model.save(metadata: metadata)
|
||||
@timer = null
|
||||
@$spinner.delay(500).fadeOut(150)
|
||||
, 500)
|
||||
|
||||
class CMS.Views.UnitEdit.LocationState extends Backbone.View
|
||||
initialize: =>
|
||||
@model.on('change:state', @render)
|
||||
|
||||
@@ -14,10 +14,6 @@ $(document).ready(function () {
|
||||
// scopes (namely the course-info tab)
|
||||
window.$modalCover = $modalCover;
|
||||
|
||||
// Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may
|
||||
// be a good optimization in production (it works fairly well)
|
||||
window.cachetemplates = false;
|
||||
|
||||
$body.append($modalCover);
|
||||
$newComponentItem = $('.new-component-item');
|
||||
$newComponentTypePicker = $('.new-component');
|
||||
@@ -76,10 +72,7 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
// general link management - new window/tab
|
||||
$('a[rel="external"]').attr('title', 'This link will open in a new browser window/tab').click(function (e) {
|
||||
window.open($(this).attr('href'));
|
||||
e.preventDefault();
|
||||
});
|
||||
$('a[rel="external"]').attr('title', 'This link will open in a new browser window/tab').bind('click', linkNewWindow);
|
||||
|
||||
// general link management - lean modal window
|
||||
$('a[rel="modal"]').attr('title', 'This link will open in a modal window').leanModal({overlay: 0.50, closeButton: '.action-modal-close' });
|
||||
@@ -87,6 +80,10 @@ $(document).ready(function () {
|
||||
(e).preventDefault();
|
||||
});
|
||||
|
||||
// general link management - smooth scrolling page links
|
||||
$('a[rel*="view"][href^="#"]').bind('click', smoothScrollLink);
|
||||
|
||||
|
||||
// toggling overview section details
|
||||
$(function () {
|
||||
if ($('.courseware-section').length > 0) {
|
||||
@@ -95,9 +92,9 @@ $(document).ready(function () {
|
||||
});
|
||||
$('.toggle-button-sections').bind('click', toggleSections);
|
||||
|
||||
// autosave when a field is updated on the subsection page
|
||||
$body.on('keyup', '.subsection-display-name-input, .unit-subtitle, .policy-list-value', checkForNewValue);
|
||||
$('.subsection-display-name-input, .unit-subtitle, .policy-list-name, .policy-list-value').each(function (i) {
|
||||
// autosave when leaving input field
|
||||
$body.on('change', '.subsection-display-name-input', saveSubsection);
|
||||
$('.subsection-display-name-input').each(function () {
|
||||
this.val = $(this).val();
|
||||
});
|
||||
$("#start_date, #start_time, #due_date, #due_time").bind('change', autosaveInput);
|
||||
@@ -113,11 +110,6 @@ $(document).ready(function () {
|
||||
// add new/delete subsection
|
||||
$('.new-subsection-item').bind('click', addNewSubsection);
|
||||
$('.delete-subsection-button').bind('click', deleteSubsection);
|
||||
// add/remove policy metadata button click handlers
|
||||
$('.add-policy-data').bind('click', addPolicyMetadata);
|
||||
$('.remove-policy-data').bind('click', removePolicyMetadata);
|
||||
$body.on('click', '.policy-list-element .save-button', savePolicyMetadata);
|
||||
$body.on('click', '.policy-list-element .cancel-button', cancelPolicyMetadata);
|
||||
|
||||
$('.sync-date').bind('click', syncReleaseDate);
|
||||
|
||||
@@ -156,10 +148,27 @@ $(document).ready(function () {
|
||||
});
|
||||
});
|
||||
|
||||
// function collapseAll(e) {
|
||||
// $('.branch').addClass('collapsed');
|
||||
// $('.expand-collapse-icon').removeClass('collapse').addClass('expand');
|
||||
// }
|
||||
function smoothScrollLink(e) {
|
||||
(e).preventDefault();
|
||||
|
||||
$.smoothScroll({
|
||||
offset: -200,
|
||||
easing: 'swing',
|
||||
speed: 1000,
|
||||
scrollElement: null,
|
||||
scrollTarget: $(this).attr('href')
|
||||
});
|
||||
}
|
||||
|
||||
function linkNewWindow(e) {
|
||||
window.open($(e.target).attr('href'));
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// On AWS instances, base.js gets wrapped in a separate scope as part of Django static
|
||||
// pipelining (note, this doesn't happen on local runtimes). So if we set it on window,
|
||||
// when we can access it from other scopes (namely the checklists)
|
||||
window.cmsLinkNewWindow = linkNewWindow;
|
||||
|
||||
function toggleSections(e) {
|
||||
e.preventDefault();
|
||||
@@ -219,56 +228,6 @@ function syncReleaseDate(e) {
|
||||
$("#start_time").val("");
|
||||
}
|
||||
|
||||
function addPolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
var template = $('#add-new-policy-element-template > li');
|
||||
var newNode = template.clone();
|
||||
var _parent_el = $(this).parent('ol:.policy-list');
|
||||
newNode.insertBefore('.add-policy-data');
|
||||
$('.remove-policy-data').bind('click', removePolicyMetadata);
|
||||
newNode.find('.policy-list-name').focus();
|
||||
}
|
||||
|
||||
function savePolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $policyElement = $(this).parents('.policy-list-element');
|
||||
saveSubsection()
|
||||
$policyElement.removeClass('new-policy-list-element');
|
||||
$policyElement.find('.policy-list-name').attr('disabled', 'disabled');
|
||||
$policyElement.removeClass('editing');
|
||||
}
|
||||
|
||||
function cancelPolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $policyElement = $(this).parents('.policy-list-element');
|
||||
if (!$policyElement.hasClass('editing')) {
|
||||
$policyElement.remove();
|
||||
} else {
|
||||
$policyElement.removeClass('new-policy-list-element');
|
||||
$policyElement.find('.policy-list-name').val($policyElement.data('currentValues')[0]);
|
||||
$policyElement.find('.policy-list-value').val($policyElement.data('currentValues')[1]);
|
||||
}
|
||||
$policyElement.removeClass('editing');
|
||||
}
|
||||
|
||||
function removePolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!'))
|
||||
return;
|
||||
|
||||
policy_name = $(this).data('policy-name');
|
||||
var _parent_el = $(this).parent('li:.policy-list-element');
|
||||
if ($(_parent_el).hasClass("new-policy-list-element")) {
|
||||
_parent_el.remove();
|
||||
} else {
|
||||
_parent_el.appendTo("#policy-to-delete");
|
||||
}
|
||||
saveSubsection()
|
||||
}
|
||||
|
||||
function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
|
||||
var edxTimeStr = null;
|
||||
|
||||
@@ -277,7 +236,7 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
|
||||
time_val = '00:00';
|
||||
|
||||
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
|
||||
date = Date.parse(date_val + " " + time_val);
|
||||
var date = Date.parse(date_val + " " + time_val);
|
||||
if (format == null)
|
||||
format = 'yyyy-MM-ddTHH:mm';
|
||||
|
||||
@@ -294,32 +253,8 @@ function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
|
||||
return getEdxTimeFromDateTimeVals(input_date, input_time, format);
|
||||
}
|
||||
|
||||
function checkForNewValue(e) {
|
||||
if ($(this).parents('.new-policy-list-element')[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.val) {
|
||||
this.hasChanged = this.val != $(this).val();
|
||||
} else {
|
||||
this.hasChanged = false;
|
||||
}
|
||||
|
||||
this.val = $(this).val();
|
||||
if (this.hasChanged) {
|
||||
if (this.saveTimer) {
|
||||
clearTimeout(this.saveTimer);
|
||||
}
|
||||
|
||||
this.saveTimer = setTimeout(function () {
|
||||
$changedInput = $(e.target);
|
||||
saveSubsection();
|
||||
this.saveTimer = null;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function autosaveInput(e) {
|
||||
var self = this;
|
||||
if (this.saveTimer) {
|
||||
clearTimeout(this.saveTimer);
|
||||
}
|
||||
@@ -327,11 +262,12 @@ function autosaveInput(e) {
|
||||
this.saveTimer = setTimeout(function () {
|
||||
$changedInput = $(e.target);
|
||||
saveSubsection();
|
||||
this.saveTimer = null;
|
||||
self.saveTimer = null;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function saveSubsection() {
|
||||
// Spinner is no longer used by subsection name, but is still used by date and time pickers on the right.
|
||||
if ($changedInput && !$changedInput.hasClass('no-spinner')) {
|
||||
$spinner.css({
|
||||
'position': 'absolute',
|
||||
@@ -354,20 +290,6 @@ function saveSubsection() {
|
||||
metadata[$(el).data("metadata-name")] = el.value;
|
||||
}
|
||||
|
||||
// now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
|
||||
$('ol.policy-list > li.policy-list-element').each(function (i, element) {
|
||||
var name = $(element).children('.policy-list-name').val();
|
||||
metadata[name] = $(element).children('.policy-list-value').val();
|
||||
});
|
||||
|
||||
// now add any 'removed' policy metadata which is stored in a separate hidden div
|
||||
// 'null' presented to the server means 'remove'
|
||||
$("#policy-to-delete > li.policy-list-element").each(function (i, element) {
|
||||
var name = $(element).children('.policy-list-name').val();
|
||||
if (name != "")
|
||||
metadata[name] = null;
|
||||
});
|
||||
|
||||
// Piece back together the date/time UI elements into one date/time string
|
||||
// NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
|
||||
// so make sure we're passing back the correct format
|
||||
@@ -382,6 +304,7 @@ function saveSubsection() {
|
||||
data: JSON.stringify({ 'id': id, 'metadata': metadata}),
|
||||
success: function () {
|
||||
$spinner.delay(500).fadeOut(150);
|
||||
$changedInput = null;
|
||||
},
|
||||
error: function () {
|
||||
showToastMessage('There has been an error while saving your changes.');
|
||||
|
||||
24
cms/static/js/models/checklists.js
Normal file
24
cms/static/js/models/checklists.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Model for checklists_view.js.
|
||||
CMS.Models.Checklist = Backbone.Model.extend({
|
||||
});
|
||||
|
||||
CMS.Models.ChecklistCollection = Backbone.Collection.extend({
|
||||
model : CMS.Models.Checklist,
|
||||
|
||||
parse: function(response) {
|
||||
_.each(response,
|
||||
function( element, idx ) {
|
||||
element.id = idx;
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
// Disable caching so the browser back button will work (checklists have links to other
|
||||
// places within Studio).
|
||||
fetch: function (options) {
|
||||
options.cache = false;
|
||||
return Backbone.Collection.prototype.fetch.call(this, options);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -37,6 +37,9 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
|
||||
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
|
||||
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
|
||||
var errors = {};
|
||||
if (newattrs.start_date === null) {
|
||||
errors.start_date = "The course must have an assigned start date.";
|
||||
}
|
||||
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
|
||||
errors.end_date = "The course end date cannot be before the course start date.";
|
||||
}
|
||||
|
||||
@@ -1,78 +1,79 @@
|
||||
// <!-- from https://github.com/Gazler/Underscore-Template-Loader/blob/master/index.html -->
|
||||
// TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings)
|
||||
// so this only loads the lazily loaded ones.
|
||||
(function() {
|
||||
if (typeof window.templateLoader == 'function') return;
|
||||
|
||||
var templateLoader = {
|
||||
templateVersion: "0.0.16",
|
||||
templates: {},
|
||||
loadRemoteTemplate: function(templateName, filename, callback) {
|
||||
if (!this.templates[templateName]) {
|
||||
var self = this;
|
||||
jQuery.ajax({url : filename,
|
||||
success : function(data) {
|
||||
self.addTemplate(templateName, data);
|
||||
self.saveLocalTemplates();
|
||||
callback(data);
|
||||
},
|
||||
error : function(xhdr, textStatus, errorThrown) {
|
||||
console.log(textStatus); },
|
||||
dataType : "html"
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(this.templates[templateName]);
|
||||
}
|
||||
},
|
||||
|
||||
addTemplate: function(templateName, data) {
|
||||
// is there a reason this doesn't go ahead and compile the template? _.template(data)
|
||||
// I suppose localstorage use would still req raw string rather than compiled version, but that sd work
|
||||
// if it maintains a separate cache of uncompiled ones
|
||||
this.templates[templateName] = data;
|
||||
},
|
||||
|
||||
localStorageAvailable: function() {
|
||||
try {
|
||||
// window.cachetemplates is global set in base.js to turn caching on/off
|
||||
return window.cachetemplates && 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
saveLocalTemplates: function() {
|
||||
if (this.localStorageAvailable) {
|
||||
localStorage.setItem("templates", JSON.stringify(this.templates));
|
||||
localStorage.setItem("templateVersion", this.templateVersion);
|
||||
}
|
||||
},
|
||||
|
||||
loadLocalTemplates: function() {
|
||||
if (this.localStorageAvailable) {
|
||||
var templateVersion = localStorage.getItem("templateVersion");
|
||||
if (templateVersion && templateVersion == this.templateVersion) {
|
||||
var templates = localStorage.getItem("templates");
|
||||
if (templates) {
|
||||
templates = JSON.parse(templates);
|
||||
for (var x in templates) {
|
||||
if (!this.templates[x]) {
|
||||
this.addTemplate(x, templates[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
localStorage.removeItem("templates");
|
||||
localStorage.removeItem("templateVersion");
|
||||
}
|
||||
}
|
||||
}
|
||||
(function () {
|
||||
if (typeof window.templateLoader == 'function') return;
|
||||
|
||||
var templateLoader = {
|
||||
templateVersion: "0.0.15",
|
||||
templates: {},
|
||||
// Control whether template caching in local memory occurs. Caching screws up development but may
|
||||
// be a good optimization in production (it works fairly well).
|
||||
cacheTemplates: false,
|
||||
loadRemoteTemplate: function (templateName, filename, callback) {
|
||||
if (!this.templates[templateName]) {
|
||||
var self = this;
|
||||
jQuery.ajax({url: filename,
|
||||
success: function (data) {
|
||||
self.addTemplate(templateName, data);
|
||||
self.saveLocalTemplates();
|
||||
callback(data);
|
||||
},
|
||||
error: function (xhdr, textStatus, errorThrown) {
|
||||
console.log(textStatus);
|
||||
},
|
||||
dataType: "html"
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(this.templates[templateName]);
|
||||
}
|
||||
},
|
||||
|
||||
addTemplate: function (templateName, data) {
|
||||
// is there a reason this doesn't go ahead and compile the template? _.template(data)
|
||||
// I suppose localstorage use would still req raw string rather than compiled version, but that sd work
|
||||
// if it maintains a separate cache of uncompiled ones
|
||||
this.templates[templateName] = data;
|
||||
},
|
||||
|
||||
localStorageAvailable: function () {
|
||||
try {
|
||||
return this.cacheTemplates && 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
saveLocalTemplates: function () {
|
||||
if (this.localStorageAvailable()) {
|
||||
localStorage.setItem("templates", JSON.stringify(this.templates));
|
||||
localStorage.setItem("templateVersion", this.templateVersion);
|
||||
}
|
||||
},
|
||||
|
||||
loadLocalTemplates: function () {
|
||||
if (this.localStorageAvailable()) {
|
||||
var templateVersion = localStorage.getItem("templateVersion");
|
||||
if (templateVersion && templateVersion == this.templateVersion) {
|
||||
var templates = localStorage.getItem("templates");
|
||||
if (templates) {
|
||||
templates = JSON.parse(templates);
|
||||
for (var x in templates) {
|
||||
if (!this.templates[x]) {
|
||||
this.addTemplate(x, templates[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
localStorage.removeItem("templates");
|
||||
localStorage.removeItem("templateVersion");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
templateLoader.loadLocalTemplates();
|
||||
window.templateLoader = templateLoader;
|
||||
})();
|
||||
})();
|
||||
|
||||
89
cms/static/js/views/checklists_view.js
Normal file
89
cms/static/js/views/checklists_view.js
Normal file
@@ -0,0 +1,89 @@
|
||||
if (!CMS.Views['Checklists']) CMS.Views.Checklists = {};
|
||||
|
||||
CMS.Views.Checklists = Backbone.View.extend({
|
||||
// takes CMS.Models.Checklists as model
|
||||
|
||||
events : {
|
||||
'click .course-checklist .checklist-title' : "toggleChecklist",
|
||||
'click .course-checklist .task input' : "toggleTask",
|
||||
'click a[rel="external"]' : window.cmsLinkNewWindow
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
var self = this;
|
||||
|
||||
this.collection.fetch({
|
||||
complete: function () {
|
||||
window.templateLoader.loadRemoteTemplate("checklist",
|
||||
"/static/client_templates/checklist.html",
|
||||
function (raw_template) {
|
||||
self.template = _.template(raw_template);
|
||||
self.render();
|
||||
}
|
||||
);
|
||||
},
|
||||
error: CMS.ServerError
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// catch potential outside call before template loaded
|
||||
if (!this.template) return this;
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
var self = this;
|
||||
_.each(this.collection.models,
|
||||
function(checklist, index) {
|
||||
self.$el.append(self.renderTemplate(checklist, index));
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
renderTemplate: function (checklist, index) {
|
||||
var checklistItems = checklist.attributes['items'];
|
||||
var itemsChecked = 0;
|
||||
_.each(checklistItems,
|
||||
function(checklist) {
|
||||
if (checklist['is_checked']) {
|
||||
itemsChecked +=1;
|
||||
}
|
||||
});
|
||||
var percentChecked = Math.round((itemsChecked/checklistItems.length)*100);
|
||||
return this.template({
|
||||
checklistIndex : index,
|
||||
checklistShortDescription : checklist.attributes['short_description'],
|
||||
items: checklistItems,
|
||||
itemsChecked: itemsChecked,
|
||||
percentChecked: percentChecked});
|
||||
},
|
||||
|
||||
toggleChecklist : function(e) {
|
||||
e.preventDefault();
|
||||
$(e.target).closest('.course-checklist').toggleClass('is-collapsed');
|
||||
},
|
||||
|
||||
toggleTask : function (e) {
|
||||
var self = this;
|
||||
|
||||
var completed = 'is-completed';
|
||||
var $checkbox = $(e.target);
|
||||
var $task = $checkbox.closest('.task');
|
||||
$task.toggleClass(completed);
|
||||
|
||||
var checklist_index = $checkbox.data('checklist');
|
||||
var task_index = $checkbox.data('task');
|
||||
var model = this.collection.at(checklist_index);
|
||||
model.attributes.items[task_index].is_checked = $task.hasClass(completed);
|
||||
model.save({},
|
||||
{
|
||||
success : function() {
|
||||
var updatedTemplate = self.renderTemplate(model, checklist_index);
|
||||
self.$el.find('#course-checklist'+checklist_index).first().replaceWith(updatedTemplate);
|
||||
},
|
||||
error : CMS.ServerError
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -142,8 +142,11 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
|
||||
|
||||
onDelete: function(event) {
|
||||
event.preventDefault();
|
||||
// TODO ask for confirmation
|
||||
// remove the dom element and delete the model
|
||||
|
||||
if (!confirm('Are you sure you want to delete this update? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var targetModel = this.eventModel(event);
|
||||
this.modelDom(event).remove();
|
||||
var cacheThis = this;
|
||||
|
||||
@@ -101,6 +101,12 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
|
||||
cacheModel.save(fieldName, newVal);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Clear date (note that this clears the time as well, as date and time are linked).
|
||||
// Note also that the validation logic prevents us from clearing the start date
|
||||
// (start date is required by the back end).
|
||||
cacheModel.save(fieldName, null);
|
||||
}
|
||||
};
|
||||
|
||||
// instrument as date and time pickers
|
||||
|
||||
@@ -25,11 +25,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
|
||||
for (var field in error) {
|
||||
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
|
||||
this._cacheValidationErrors.push(ele);
|
||||
if ($(ele).is('div')) {
|
||||
// put error on the contained inputs
|
||||
$(ele).find('input, textarea').addClass('error');
|
||||
}
|
||||
else $(ele).addClass('error');
|
||||
this.getInputElements(ele).addClass('error');
|
||||
$(ele).parent().append(this.errorTemplate({message : error[field]}));
|
||||
}
|
||||
},
|
||||
@@ -37,12 +33,8 @@ CMS.Views.ValidatingView = Backbone.View.extend({
|
||||
clearValidationErrors : function() {
|
||||
// error is object w/ fields and error strings
|
||||
while (this._cacheValidationErrors.length > 0) {
|
||||
var ele = this._cacheValidationErrors.pop();
|
||||
if ($(ele).is('div')) {
|
||||
// put error on the contained inputs
|
||||
$(ele).find('input, textarea').removeClass('error');
|
||||
}
|
||||
else $(ele).removeClass('error');
|
||||
var ele = this._cacheValidationErrors.pop();
|
||||
this.getInputElements(ele).removeClass('error');
|
||||
$(ele).nextAll('.message-error').remove();
|
||||
}
|
||||
},
|
||||
@@ -65,5 +57,16 @@ CMS.Views.ValidatingView = Backbone.View.extend({
|
||||
},
|
||||
inputUnfocus : function(event) {
|
||||
$("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
|
||||
},
|
||||
|
||||
getInputElements: function(ele) {
|
||||
var inputElements = 'input, textarea';
|
||||
if ($(ele).is(inputElements)) {
|
||||
return $(ele);
|
||||
}
|
||||
else {
|
||||
// put error on the contained inputs
|
||||
return $(ele).find(inputElements);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// studio base styling
|
||||
// studio - base styling
|
||||
// ====================
|
||||
|
||||
// basic reset
|
||||
// basic setup
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
overflow-y: scroll;
|
||||
@@ -9,7 +9,7 @@ html {
|
||||
|
||||
body {
|
||||
@include font-size(16);
|
||||
min-width: 980px;
|
||||
min-width: $fg-min-width;
|
||||
background: $gray-l5;
|
||||
line-height: 1.6;
|
||||
color: $baseFontColor;
|
||||
@@ -214,7 +214,7 @@ h1 {
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.title, .title-1 {
|
||||
.title-1 {
|
||||
@include font-size(32);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -283,8 +283,8 @@ h1 {
|
||||
|
||||
.title-3 {
|
||||
@include font-size(16);
|
||||
margin: 0 0 ($baseline/4) 0;
|
||||
font-weight: 500;
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.title-4 {
|
||||
@@ -327,7 +327,8 @@ h1 {
|
||||
}
|
||||
}
|
||||
|
||||
.nav-related {
|
||||
// navigation
|
||||
.nav-related, .nav-page {
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: ($baseline/4);
|
||||
@@ -350,10 +351,11 @@ h1 {
|
||||
// layout - grandfathered
|
||||
.main-wrapper {
|
||||
position: relative;
|
||||
margin: 0 40px;
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.inner-wrapper {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
max-width: 1280px;
|
||||
margin: auto;
|
||||
@@ -363,6 +365,12 @@ h1 {
|
||||
}
|
||||
}
|
||||
|
||||
.main-column {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
float: right;
|
||||
width: 28%;
|
||||
@@ -378,109 +386,6 @@ h1 {
|
||||
|
||||
// ====================
|
||||
|
||||
// forms
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
textarea.text {
|
||||
padding: 6px 8px 8px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $mediumGrey;
|
||||
border-radius: 2px;
|
||||
@include linear-gradient($lightGrey, tint($lightGrey, 90%));
|
||||
background-color: $lightGrey;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 11px;
|
||||
color: $baseFontColor;
|
||||
outline: 0;
|
||||
|
||||
&::-webkit-input-placeholder,
|
||||
&:-moz-placeholder,
|
||||
&:-ms-input-placeholder {
|
||||
color: #979faf;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
border-color: $gray-l4;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
border-color: $gray-l4;
|
||||
color: $gray-l1;
|
||||
|
||||
&:focus {
|
||||
@include linear-gradient($lightGrey, tint($lightGrey, 90%));
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// forms - specific
|
||||
input.search {
|
||||
padding: 6px 15px 8px 30px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $darkGrey;
|
||||
border-radius: 20px;
|
||||
background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: $baseFontColor;
|
||||
outline: 0;
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: #979faf;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
background: #eee;
|
||||
font-family: Monaco, monospace;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
font-size: 13px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.text-editor {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $mediumGrey;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3));
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset);
|
||||
font-family: Monaco, monospace;
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// UI - chrome
|
||||
.window {
|
||||
@include clearfix();
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(0 1px 1px $shadow-l1);
|
||||
margin-bottom: $baseline;
|
||||
border: 1px solid $gray-l2;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// UI - actions
|
||||
.new-unit-item,
|
||||
.new-subsection-item,
|
||||
@@ -787,6 +692,10 @@ hr.divide {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
hr.divider {
|
||||
@extend .sr;
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// js dependant
|
||||
@@ -861,14 +770,4 @@ body.hide-wip {
|
||||
.wip-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// needed fudges for now
|
||||
body.dashboard {
|
||||
|
||||
.my-classes {
|
||||
margin-top: $baseline;
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
section.cal {
|
||||
@include box-sizing(border-box);
|
||||
@include clearfix;
|
||||
padding: 20px;
|
||||
|
||||
> header {
|
||||
display: none;
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
opacity: .4;
|
||||
@include transition;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include inline-block();
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block;
|
||||
float: right;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&.actions {
|
||||
float: left;
|
||||
}
|
||||
|
||||
li {
|
||||
@include inline-block;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 0 6px 0 0;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@include inline-block();
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
padding: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
@include clearfix;
|
||||
border: 1px solid lighten( $dark-blue , 30% );
|
||||
background: #FFF;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@include box-shadow(0 0 5px lighten($dark-blue, 45%));
|
||||
@include border-radius(3px);
|
||||
overflow: hidden;
|
||||
|
||||
> li {
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: flex-grid(3) + ((flex-gutter() * 3) / 4);
|
||||
background-color: $light-blue;
|
||||
@include box-shadow(inset 0 0 0 1px lighten($light-blue, 8%));
|
||||
|
||||
&:hover {
|
||||
li.create-module {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(4n) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@include box-shadow(0 2px 2px $light-blue);
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
background: #FFF;
|
||||
|
||||
h1 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
padding: 6px;
|
||||
color: $bright-blue;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: $bright-blue;
|
||||
display: block;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
|
||||
&:hover {
|
||||
color: darken($bright-blue, 10%);
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
background: #fff;
|
||||
color: #888;
|
||||
border-bottom: 0;
|
||||
font-size: 12px;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0 0 1px 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid darken($light-blue, 6%);
|
||||
// @include box-shadow(0 1px 0 lighten($light-blue, 4%));
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 14%);
|
||||
|
||||
a.draggable {
|
||||
background-color: lighten($yellow, 14%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.editable {
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: lighten($dark-blue, 10%);
|
||||
display: block;
|
||||
padding: 6px 35px 6px 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
&.draggable {
|
||||
background-color: $light-blue;
|
||||
opacity: .3;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.create-module {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
@include transition(all 3s ease-in-out);
|
||||
background: darken($light-blue, 2%);
|
||||
|
||||
> div {
|
||||
background: $dark-blue;
|
||||
@include box-shadow(0 0 5px darken($light-blue, 60%));
|
||||
@include box-sizing(border-box);
|
||||
display: none;
|
||||
margin-left: 3%;
|
||||
padding: 10px;
|
||||
@include position(absolute, 30px 0 0 0);
|
||||
width: 90%;
|
||||
z-index: 99;
|
||||
|
||||
ul {
|
||||
li {
|
||||
border-bottom: 0;
|
||||
background: none;
|
||||
|
||||
input {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
|
||||
option {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $light-blue;
|
||||
float: right;
|
||||
|
||||
&:first-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.new-section {
|
||||
margin: 10px 0 40px;
|
||||
@include inline-block();
|
||||
position: relative;
|
||||
|
||||
> a {
|
||||
@extend .button;
|
||||
display: block;
|
||||
}
|
||||
|
||||
section {
|
||||
display: none;
|
||||
@include position(absolute, 30px 0 0 0);
|
||||
background: rgba(#000, .8);
|
||||
min-width: 300px;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
@include border-radius(3px);
|
||||
z-index: 99;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: rgba(#000, .8);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@include position(absolute, -5px 0 0 20%);
|
||||
@include transform(rotate(45deg));
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
border-bottom: 0;
|
||||
background: none;
|
||||
margin-bottom: 6px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
border-color: #000;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
option {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
float: right;
|
||||
|
||||
&:first-child {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.content
|
||||
section.cal {
|
||||
width: flex-grid(3);
|
||||
float: left;
|
||||
overflow: scroll;
|
||||
@include box-sizing(border-box);
|
||||
opacity: .4;
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> header {
|
||||
@include transition;
|
||||
overflow: hidden;
|
||||
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
float: none;
|
||||
display: block;
|
||||
|
||||
li {
|
||||
|
||||
ul {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
li {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
border-right: 0;
|
||||
|
||||
&.create-module {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// studio - utilities - mixins and extends
|
||||
// ====================
|
||||
|
||||
@mixin clearfix {
|
||||
&:after {
|
||||
content: '';
|
||||
|
||||
@@ -1,689 +0,0 @@
|
||||
|
||||
input.courseware-unit-search-input {
|
||||
float: left;
|
||||
width: 260px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.branch {
|
||||
|
||||
.section-item {
|
||||
@include clearfix();
|
||||
|
||||
.details {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
right: 50px;
|
||||
width: 145px;
|
||||
|
||||
.status-label {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: -5px;
|
||||
display: none;
|
||||
width: 110px;
|
||||
padding: 5px 40px 5px 10px;
|
||||
@include border-radius(3px);
|
||||
color: $lightGrey;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
color: $mediumGrey;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
display: none;
|
||||
opacity: 0.0;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 5px;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
|
||||
|
||||
li {
|
||||
width: 115px;
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
color: $darkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue;
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.courseware-section {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $mediumGrey;
|
||||
margin-top: 15px;
|
||||
padding-bottom: 12px;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
float: left;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
.datepair {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.section-published-date {
|
||||
position: absolute;
|
||||
top: 19px;
|
||||
right: 90px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 3px;
|
||||
background: $lightGrey;
|
||||
text-align: right;
|
||||
|
||||
.published-status {
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.schedule-button,
|
||||
.edit-button {
|
||||
font-size: 11px;
|
||||
padding: 3px 15px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.datepair .date,
|
||||
.datepair .time {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
@include box-shadow(none);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.datepair .date {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.datepair .time {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
&.collapsed .subsection-list,
|
||||
.collapsed .subsection-list,
|
||||
.collapsed > ol {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
header {
|
||||
min-height: 75px;
|
||||
@include clearfix();
|
||||
|
||||
.item-details, .section-published-date {
|
||||
|
||||
}
|
||||
|
||||
.item-details {
|
||||
display: inline-block;
|
||||
padding: 20px 0 10px 0;
|
||||
@include clearfix();
|
||||
|
||||
.section-name {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 350px;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.section-name-span {
|
||||
cursor: pointer;
|
||||
@include transition(color .15s);
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
|
||||
.section-name-edit {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
background: $white;
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
padding: 7px 20px 7px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
padding: 7px 20px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-published-date {
|
||||
float: right;
|
||||
width: 265px;
|
||||
margin-right: 220px;
|
||||
@include border-radius(3px);
|
||||
background: $lightGrey;
|
||||
|
||||
.published-status {
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.schedule-button,
|
||||
.edit-button {
|
||||
font-size: 11px;
|
||||
padding: 3px 15px 5px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 70px;
|
||||
width: 145px;
|
||||
|
||||
.status-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
display: none;
|
||||
width: 100px;
|
||||
padding: 10px 35px 10px 10px;
|
||||
@include border-radius(3px);
|
||||
background: $lightGrey;
|
||||
color: $lightGrey;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
color: $lightGrey;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
display: none;
|
||||
opacity: 0.0;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 2px;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
@include transition(display .15s);
|
||||
|
||||
|
||||
li {
|
||||
width: 115px;
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
color: $darkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
float: left;
|
||||
padding: 21px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
margin-top: 21px;
|
||||
margin-right: 12px;
|
||||
|
||||
.edit-button,
|
||||
.delete-button {
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-collapse-icon {
|
||||
float: left;
|
||||
margin: 29px 6px 16px 16px;
|
||||
@include transition(none);
|
||||
|
||||
&.expand {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
margin-left: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.section-name-span {
|
||||
cursor: pointer;
|
||||
@include transition(color .15s);
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
|
||||
.section-name-form {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-name-edit {
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
padding: 7px 20px 7px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
padding: 7px 20px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
color: #878e9d;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.list-header {
|
||||
@include linear-gradient(top, transparent, rgba(0, 0, 0, .1));
|
||||
background-color: #ced2db;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.subsection-list {
|
||||
margin: 0 12px;
|
||||
|
||||
> ol {
|
||||
@include tree-view;
|
||||
border-top-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.new-section {
|
||||
|
||||
header {
|
||||
height: auto;
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
.expand-collapse-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
padding: 25px 0 0 0;
|
||||
|
||||
.section-name {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-button-sections {
|
||||
display: none;
|
||||
position: relative;
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
|
||||
font-size: 13px;
|
||||
color: $darkGrey;
|
||||
|
||||
&.is-shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ss-icon {
|
||||
@include border-radius(20px);
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
line-height: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.new-section-name,
|
||||
.new-subsection-name-input {
|
||||
width: 515px;
|
||||
}
|
||||
|
||||
.new-section-name-save,
|
||||
.new-subsection-name-save {
|
||||
@include blue-button;
|
||||
padding: 4px 20px 7px;
|
||||
margin: 0 5px;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.new-section-name-cancel,
|
||||
.new-subsection-name-cancel {
|
||||
@include white-button;
|
||||
padding: 4px 20px 7px;
|
||||
color: #8891a1 !important;
|
||||
}
|
||||
|
||||
.dummy-calendar {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 110px;
|
||||
z-index: 9999;
|
||||
border: 1px solid #3C3C3C;
|
||||
@include box-shadow(0 1px 15px rgba(0, 0, 0, .2));
|
||||
}
|
||||
|
||||
.unit-name-input {
|
||||
padding: 20px 40px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
background: url(../img/preview.jpg) center top no-repeat;
|
||||
}
|
||||
|
||||
.edit-subsection-publish-settings {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
z-index: 99999;
|
||||
width: 600px;
|
||||
margin-left: -300px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
|
||||
.settings {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 34px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.picker {
|
||||
margin: 30px 0 65px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.start-date,
|
||||
.start-time {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.save-button,
|
||||
.cancel-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-all-button {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: $darkGrey;
|
||||
}
|
||||
|
||||
// sort/drag and drop
|
||||
.ui-droppable {
|
||||
@include transition (padding 0.5s ease-in-out 0s);
|
||||
min-height: 20px;
|
||||
padding: 0;
|
||||
|
||||
&.dropover {
|
||||
padding: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-draggable-dragging {
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .3));
|
||||
border: 1px solid $darkGrey;
|
||||
opacity : 0.2;
|
||||
&:hover {
|
||||
opacity : 1.0;
|
||||
.section-item {
|
||||
background: $yellow !important;
|
||||
}
|
||||
}
|
||||
|
||||
// hiding unit button - temporary fix until this semantically corrected
|
||||
.new-unit-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ol.ui-droppable .branch:first-child .section-item {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
.class-list {
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.class-link {
|
||||
z-index: 100;
|
||||
display: block;
|
||||
padding: 20px 25px;
|
||||
line-height: 1.3;
|
||||
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
|
||||
+ .view-live-button {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.class-name {
|
||||
display: block;
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.detail {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin-right: 20px;
|
||||
color: #3c3c3c;
|
||||
}
|
||||
|
||||
// view live button
|
||||
.view-live-button {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: $baseline;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-course {
|
||||
padding: 15px 25px;
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1);
|
||||
@include clearfix;
|
||||
|
||||
.row {
|
||||
margin-bottom: 15px;
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
margin-right: 4%;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.new-course-org,
|
||||
.new-course-number,
|
||||
.new-course-name {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-course-name {
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.new-course-save {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.new-course-cancel {
|
||||
@include white-button;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
.faded-hr-divider {
|
||||
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
|
||||
rgba(200,200,200, 1) 50%,
|
||||
rgba(200,200,200, 0)));
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.faded-hr-divider-medium {
|
||||
@include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%,
|
||||
rgba(240,240,240, 1) 50%,
|
||||
rgba(240,240,240, 0)));
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.faded-hr-divider-light {
|
||||
@include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%,
|
||||
rgba(255,255,255, 0.8) 50%,
|
||||
rgba(255,255,255, 0)));
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.faded-vertical-divider {
|
||||
@include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%,
|
||||
rgba(200,200,200, 1) 50%,
|
||||
rgba(200,200,200, 0)));
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.faded-vertical-divider-light {
|
||||
@include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%,
|
||||
rgba(255,255,255, 0.6) 50%,
|
||||
rgba(255,255,255, 0)));
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.vertical-divider {
|
||||
@extend .faded-vertical-divider;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
@extend .faded-vertical-divider-light;
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-divider {
|
||||
border: none;
|
||||
@extend .faded-hr-divider;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
@extend .faded-hr-divider-light;
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-right-hr-divider {
|
||||
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
|
||||
rgba(200,200,200, 1)));
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fade-left-hr-divider {
|
||||
@include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%,
|
||||
rgba(200,200,200, 0)));
|
||||
border: none;
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
// This is a temporary page, which will be replaced once we have a more extensive course catalog and marketing site for edX labs.
|
||||
|
||||
.class-landing {
|
||||
|
||||
.main-wrapper {
|
||||
width: 700px !important;
|
||||
margin: 100px auto;
|
||||
}
|
||||
|
||||
.class-info {
|
||||
padding: 30px 40px 40px;
|
||||
@extend .window;
|
||||
|
||||
hgroup {
|
||||
padding-bottom: 26px;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
}
|
||||
|
||||
h1 {
|
||||
float: none;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #5d6779;
|
||||
}
|
||||
|
||||
.class-actions {
|
||||
@include clearfix;
|
||||
padding: 15px 0;
|
||||
margin-bottom: 18px;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
}
|
||||
|
||||
.log-in-form {
|
||||
@include clearfix;
|
||||
padding: 15px 0 20px;
|
||||
margin-bottom: 18px;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
|
||||
.log-in-submit-button {
|
||||
@include blue-button;
|
||||
padding: 6px 20px 8px;
|
||||
margin: 24px 0 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
width: 41%;
|
||||
margin-right: 1%;
|
||||
|
||||
&.submit {
|
||||
width: 16%;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-family: $sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.forgot-button {
|
||||
float: right;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.sign-up-button {
|
||||
@include blue-button;
|
||||
display: block;
|
||||
width: 250px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.log-in-button {
|
||||
@include white-button;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.sign-up-button,
|
||||
.log-in-button {
|
||||
padding: 8px 0 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.class-description {
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.edx-labs-logo-small {
|
||||
display: block;
|
||||
width: 124px;
|
||||
height: 30px;
|
||||
margin: auto;
|
||||
background: url(../img/edx-labs-logo-small.png) no-repeat;
|
||||
text-indent: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.edge-logo {
|
||||
display: block;
|
||||
width: 143px;
|
||||
height: 39px;
|
||||
margin: auto;
|
||||
background: url(../images/edge-logo-small.png) no-repeat;
|
||||
text-indent: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
body {
|
||||
@include clearfix();
|
||||
height: 100%;
|
||||
font: 14px $body-font-family;
|
||||
background-color: lighten($dark-blue, 62%);
|
||||
background-image: url('/static/img/noise.png');
|
||||
|
||||
> section {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> header {
|
||||
background: $dark-blue;
|
||||
@include background-image(url('/static/img/noise.png'), linear-gradient(lighten($dark-blue, 10%), $dark-blue));
|
||||
border-bottom: 1px solid darken($dark-blue, 15%);
|
||||
@include box-shadow(inset 0 -1px 0 lighten($dark-blue, 10%));
|
||||
@include box-sizing(border-box);
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: none;
|
||||
padding: 0 20px;
|
||||
text-shadow: 0 -1px 0 darken($dark-blue, 15%);
|
||||
width: 100%;
|
||||
|
||||
nav {
|
||||
@include clearfix;
|
||||
|
||||
> a {
|
||||
@include hide-text;
|
||||
background: url('/static/img/menu.png') 0 center no-repeat;
|
||||
border-right: 1px solid darken($dark-blue, 10%);
|
||||
@include box-shadow(1px 0 0 lighten($dark-blue, 10%));
|
||||
display: block;
|
||||
float: left;
|
||||
height: 19px;
|
||||
padding: 8px 10px 8px 0;
|
||||
width: 14px;
|
||||
|
||||
&:hover, &:focus {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
border-right: 1px solid darken($dark-blue, 10%);
|
||||
@include box-shadow(1px 0 0 lighten($dark-blue, 10%));
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
padding: 8px 20px;
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(darken($dark-blue, 15%), .5);
|
||||
color: $yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(#fff, .8);
|
||||
|
||||
&:hover {
|
||||
color: rgba(#fff, .6);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
float: left;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@include clearfix;
|
||||
|
||||
&.user-nav {
|
||||
float: right;
|
||||
border-left: 1px solid darken($dark-blue, 10%);
|
||||
}
|
||||
|
||||
li {
|
||||
border-right: 1px solid darken($dark-blue, 10%);
|
||||
float: left;
|
||||
@include box-shadow(1px 0 0 lighten($dark-blue, 10%));
|
||||
|
||||
a {
|
||||
padding: 8px 20px;
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(darken($dark-blue, 15%), .5);
|
||||
color: $yellow;
|
||||
}
|
||||
|
||||
&.new-module {
|
||||
&:before {
|
||||
@include inline-block;
|
||||
content: "+";
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.content {
|
||||
section.main-content {
|
||||
border-left: 2px solid $dark-blue;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9) + flex-gutter();
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 0 lighten($dark-blue, 55%));
|
||||
@include transition();
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
.component {
|
||||
font-family: 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #3c3c3c;
|
||||
|
||||
a {
|
||||
color: #1d9dd9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
h1 {
|
||||
float: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #646464;
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 15px;
|
||||
margin-left: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 19px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h4 {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
code {
|
||||
margin: 0 2px;
|
||||
padding: 0px 5px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #eaeaea;
|
||||
white-space: nowrap;
|
||||
font-family: Monaco, monospace;
|
||||
font-size: 14px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
p + h2, ul + h2, ol + h2, p + h3 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
p + p, ul + p, ol + p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #3c3c3c;
|
||||
font: normal 1em/1.6em;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
.edx-studio-logo-large {
|
||||
display: block;
|
||||
width: 224px;
|
||||
height: 45px;
|
||||
margin: 100px auto 30px;
|
||||
background: url(../img/edx-studio-large.png) no-repeat;
|
||||
}
|
||||
|
||||
.sign-up-box,
|
||||
.log-in-box {
|
||||
width: 500px;
|
||||
margin: auto;
|
||||
border-radius: 3px;
|
||||
|
||||
header {
|
||||
height: 36px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
border: 1px solid #2c2e33;
|
||||
@include linear-gradient(top, #686b76, #54565e);
|
||||
color: #fff;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(255, 255, 255, 0.05) inset, 0 1px 0 rgba(255, 255, 255, .25) inset);
|
||||
|
||||
h1 {
|
||||
float: none;
|
||||
margin: 5px 0;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 40px;
|
||||
border: 1px solid $darkGrey;
|
||||
border-top-width: 0;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #fff;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include clearfix;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.split {
|
||||
float: left;
|
||||
width: 48%;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 4%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
@include clearfix;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.log-in-button,
|
||||
.create-account-button {
|
||||
@include blue-button;
|
||||
padding: 8px 0 10px;
|
||||
font-family: $sans-serif;
|
||||
@include transition(all .15s);
|
||||
}
|
||||
|
||||
.create-account-button {
|
||||
padding: 10px 40px 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.enrolled {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sign-up-button {
|
||||
@include white-button;
|
||||
padding: 7px 0 9px;
|
||||
}
|
||||
|
||||
.log-in-button,
|
||||
.sign-up-button {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.or {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
font-size: 15px;
|
||||
line-height: 36px;
|
||||
color: $darkGrey;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forgot-button {
|
||||
float: right;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.log-in-extra {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#login_error,
|
||||
#register_error {
|
||||
display: none;
|
||||
margin-bottom: 30px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
background: $error-red;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
section.video-new, section.video-edit, section.problem-new, section.problem-edit {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
width: flex-grid(6);
|
||||
@include box-shadow(0 0 6px #666);
|
||||
border: 1px solid #333;
|
||||
border-right: 0;
|
||||
z-index: 4;
|
||||
|
||||
> header {
|
||||
background: #666;
|
||||
@include clearfix;
|
||||
color: #fff;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #333;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
h2 {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
||||
&.save-update {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
> section {
|
||||
padding: 20px;
|
||||
|
||||
> header {
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
section {
|
||||
&.status-settings {
|
||||
ul {
|
||||
list-style: none;
|
||||
@include border-radius(2px);
|
||||
border: 1px solid #999;
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid #999;
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
margin: 0 20px;
|
||||
border: 1px solid #999;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
select {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.meta {
|
||||
background: #eee;
|
||||
padding: 10px;
|
||||
margin: 20px 0;
|
||||
@include clearfix();
|
||||
|
||||
div {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
@include inline-block();
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.notes {
|
||||
margin-top: 20px;
|
||||
padding: 6px;
|
||||
background: #eee;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
input[type="submit"]{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
section.problem-new, section.problem-edit {
|
||||
> section {
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.preview {
|
||||
background: #eee;
|
||||
@include box-sizing(border-box);
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a.save {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
// studio - utilities - reset
|
||||
// ====================
|
||||
|
||||
// * {
|
||||
// @include box-sizing(border-box);
|
||||
// }
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
@@ -18,7 +25,7 @@ time, mark, audio, video {
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
@@ -38,12 +45,6 @@ q:before, q:after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* remember to define visible focus styles!
|
||||
:focus {
|
||||
outline: ?????;
|
||||
} */
|
||||
|
||||
/* remember to highlight inserts somehow! */
|
||||
ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -56,10 +57,11 @@ table {
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Reset styles to remove ui-lightness jquery ui theme
|
||||
from the tabs component (used in the add component problem tab menu)
|
||||
*/
|
||||
// ====================
|
||||
|
||||
// grandfathered styles
|
||||
|
||||
// reset styles to remove ui-lightness jquery ui theme from the tabs component (used in the add component problem tab menu)
|
||||
.ui-tabs {
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
@@ -118,10 +120,7 @@ from the tabs component (used in the add component problem tab menu)
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* reapplying the tab styles from unit.scss after
|
||||
removing jquery ui ui-lightness styling
|
||||
*/
|
||||
|
||||
// reapplying the tab styles from unit.scss after removing jquery ui ui-lightness styling
|
||||
.problem-type-tabs {
|
||||
border:none;
|
||||
list-style-type: none;
|
||||
@@ -146,26 +145,4 @@ removing jquery ui ui-lightness styling
|
||||
border: 0px;
|
||||
}
|
||||
}
|
||||
/*
|
||||
li {
|
||||
float:left;
|
||||
display:inline-block;
|
||||
text-align:center;
|
||||
width: auto;
|
||||
//@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
|
||||
//background-color: tint($lightBluishGrey, 20%);
|
||||
//@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
|
||||
opacity:.8;
|
||||
|
||||
&:hover {
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
&.current {
|
||||
border: 0px;
|
||||
//@include active;
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
section#unit-wrapper {
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
display: none;
|
||||
opacity: .4;
|
||||
margin-bottom: 10px;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include inline-block();
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@include inline-block;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding-right: 6px;
|
||||
|
||||
&.search {
|
||||
float: right;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
&.more {
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.content {
|
||||
display: table;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
width: 100%;
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(0 0 4px lighten($dark-blue, 50%));
|
||||
|
||||
section {
|
||||
header {
|
||||
background: #fff;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
color: $bright-blue;
|
||||
// float: left;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
// line-height: 20px;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.modules {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(6, 9);
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
|
||||
&.empty {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.group {
|
||||
padding: 0;
|
||||
|
||||
header {
|
||||
padding: 6px;
|
||||
background: none;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scratch-pad {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(3, 9) + flex-gutter(9);
|
||||
vertical-align: top;
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
background: $light-blue;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.new-module a {
|
||||
background-color: darken($light-blue, 2%);
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
border-collapse: collapse;
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
.subsection .main-wrapper {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.subsection .inner-wrapper {
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
.subsection-body {
|
||||
padding: 32px 40px;
|
||||
@include clearfix;
|
||||
|
||||
> div {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.unit-subtitle {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sortable-unit-list {
|
||||
ol {
|
||||
@include tree-view;
|
||||
}
|
||||
}
|
||||
|
||||
.policy-list {
|
||||
input[disabled] {
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.policy-list-name {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.policy-list-value {
|
||||
width: 320px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.policy-list-element {
|
||||
.save-button,
|
||||
.cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&.editing,
|
||||
&.new-policy-list-element {
|
||||
.policy-list-name,
|
||||
.policy-list-value {
|
||||
border: 1px solid #b0b6c2;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .3));
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-policy-list-element {
|
||||
padding: 10px 10px 0;
|
||||
margin: 0 -10px 10px;
|
||||
border-radius: 3px;
|
||||
background: $mediumGrey;
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.new-policy-item {
|
||||
margin: 10px 0;
|
||||
|
||||
.plus-icon-small {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subsection-name-input {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.scheduled-date-input,
|
||||
.due-date-input {
|
||||
@include clearfix;
|
||||
|
||||
.date-input,
|
||||
.time-input {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.inherits-check {
|
||||
label {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.due-date-input {
|
||||
label {
|
||||
display: inline-block !important;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.date-setter {
|
||||
@include clearfix;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.remove-date {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.row.visibility {
|
||||
label {
|
||||
display: inline-block !important;
|
||||
margin-right: 10px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
height: 31px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 31px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.large-toggle {
|
||||
width: 41px;
|
||||
background: url(../img/large-toggles.png) no-repeat;
|
||||
background-position: 0 -50px;
|
||||
|
||||
.hidden {
|
||||
background-position: 0 -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gradable {
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
width: 65%;
|
||||
|
||||
.status-label {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: $blue;
|
||||
border: none;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -7px;
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
opacity: 0.0;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
|
||||
|
||||
li {
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
.unit .main-wrapper {
|
||||
@include clearfix();
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
//Problem Selector tab menu requirements
|
||||
.js .tabs .tab {
|
||||
display: none;
|
||||
}
|
||||
//end problem selector reqs
|
||||
|
||||
.main-column {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.unit-body.published {
|
||||
.components > li {
|
||||
border: none;
|
||||
|
||||
.rendered-component {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-body {
|
||||
.breadcrumbs {
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-bottom: 1px solid #cbd1db;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0) 70%);
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
|
||||
@include clearfix;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
a,
|
||||
.current-page {
|
||||
display: block;
|
||||
padding: 15px 35px 15px 30px;
|
||||
font-size: 14px;
|
||||
background: url(../img/breadcrumb-arrow.png) no-repeat right center;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 30px 40px 30px 0;
|
||||
color: #646464;
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.components {
|
||||
|
||||
> li {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
margin: 20px 40px;
|
||||
|
||||
|
||||
|
||||
.title {
|
||||
margin: 0 0 15px 0;
|
||||
color: $mediumGrey;
|
||||
|
||||
.value {
|
||||
}
|
||||
}
|
||||
|
||||
&.new-component-item {
|
||||
margin: 20px 0px;
|
||||
border-top: 1px solid $mediumGrey;
|
||||
box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
|
||||
background-color: $lightGrey;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.new-component-button {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #edf1f5;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 20px 0px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.rendered-component {
|
||||
display: none;
|
||||
background: #fff;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.new-component-type {
|
||||
|
||||
a,
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid $mediumGrey;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: #fff;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
|
||||
|
||||
.name {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-templates {
|
||||
display: none;
|
||||
margin: 20px 40px 20px 40px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $mediumGrey;
|
||||
background-color: #fff;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
|
||||
@include clearfix;
|
||||
|
||||
.cancel-button {
|
||||
margin: 20px 0px 10px 10px;
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// specific menu types
|
||||
&.new-component-problem {
|
||||
padding-bottom:10px;
|
||||
|
||||
.ss-icon, .editor-indicator {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-type,
|
||||
.new-component-template {
|
||||
@include clearfix;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
border: 1px solid $darkGreen;
|
||||
background: tint($green,20%);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: $brightGreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
list-style-type: none;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
|
||||
background-color: $lightBluishGrey;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
|
||||
|
||||
li:first-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
float:left;
|
||||
display:inline-block;
|
||||
text-align:center;
|
||||
width: auto;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
|
||||
background-color: tint($lightBluishGrey, 10%);
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
|
||||
opacity:.8;
|
||||
|
||||
&:hover {
|
||||
opacity:1;
|
||||
background-color: tint($lightBluishGrey, 20%);
|
||||
}
|
||||
|
||||
&.ui-state-active {
|
||||
border: 0px;
|
||||
@include active;
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
a{
|
||||
display: block;
|
||||
padding: 15px 25px;
|
||||
font-size: 15px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
color: #3c3c3c;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-template {
|
||||
|
||||
a {
|
||||
background: #fff;
|
||||
border: 0px;
|
||||
color: #3c3c3c;
|
||||
@include transition (none);
|
||||
|
||||
&:hover {
|
||||
background: tint($green,30%);
|
||||
color: #fff;
|
||||
@include transition(background-color .15s);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
border:none;
|
||||
border-bottom: 1px dashed $lightGrey;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
a {
|
||||
border-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
li:nth-child(2) {
|
||||
a {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include clearfix();
|
||||
display: block;
|
||||
padding: 7px 20px;
|
||||
border-bottom: none;
|
||||
font-weight: 500;
|
||||
|
||||
.name {
|
||||
float: left;
|
||||
|
||||
.ss-icon {
|
||||
@include transition(opacity .15s);
|
||||
display: inline-block;
|
||||
top: 1px;
|
||||
margin-right: 5px;
|
||||
opacity: 0.5;
|
||||
width: 17;
|
||||
height: 21px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-indicator {
|
||||
@include transition(opacity .15s);
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 12px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.ss-icon, .editor-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
||||
.ss-icon {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.editor-indicator {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// specific editor types
|
||||
.empty {
|
||||
|
||||
a {
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
background: #fff;
|
||||
color: #3c3c3c;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: tint($green,30%);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component {
|
||||
text-align: center;
|
||||
|
||||
h5 {
|
||||
color: $darkGreen;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component {
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
@include transition(none);
|
||||
|
||||
&:hover {
|
||||
border-color: #6696d7;
|
||||
|
||||
.drag-handle {
|
||||
background-color: $blue;
|
||||
border-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
z-index: auto;
|
||||
|
||||
.drag-handle,
|
||||
.component-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.component-placeholder {
|
||||
border-color: #6696d7;
|
||||
}
|
||||
|
||||
.component-actions {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 9px;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: -1px;
|
||||
right: -16px;
|
||||
z-index: 10;
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
border-radius: 0 3px 3px 0;
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
background: url(../img/white-drag-handles.png) center no-repeat $lightBluishGrey2;
|
||||
cursor: move;
|
||||
@include transition(none);
|
||||
}
|
||||
}
|
||||
|
||||
.xmodule_display {
|
||||
padding: 40px 20px 20px;
|
||||
overflow-x: auto;
|
||||
|
||||
h1 {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-component-editor {
|
||||
z-index: 9999;
|
||||
position: relative;
|
||||
background: $lightBluishGrey2;
|
||||
}
|
||||
|
||||
.component-editor {
|
||||
@include edit-box;
|
||||
@include box-shadow(none);
|
||||
display: none;
|
||||
padding: 20px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
|
||||
.metadata_edit {
|
||||
margin-bottom: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
margin-top: 10px;
|
||||
margin: 15px 8px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-settings {
|
||||
.window-contents {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.unit-actions {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
border: 1px solid #edbd3c;
|
||||
border-radius: 3px;
|
||||
background: #fbf6e1;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
|
||||
div {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-button, .view-button {
|
||||
@include white-button;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.publish-button {
|
||||
@include orange-button;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.delete-draft {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.delete-button,
|
||||
.preview-button,
|
||||
.publish-button,
|
||||
.view-button {
|
||||
font-size: 11px;
|
||||
margin-top: 10px;
|
||||
padding: 6px 15px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.unit-history {
|
||||
&.collapsed {
|
||||
h4 {
|
||||
border-bottom: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.window-contents {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
border: 1px solid #ced2db;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 6px 8px 8px 10px;
|
||||
background: #edf1f5;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fffcf1;
|
||||
|
||||
.item-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: #d1dae3;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-location {
|
||||
.url {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.draft-tag,
|
||||
.hidden-tag,
|
||||
.private-tag,
|
||||
.has-new-draft-tag {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.window-contents > ol {
|
||||
@include tree-view;
|
||||
|
||||
.section-item {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
ol {
|
||||
.section-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
ol ol {
|
||||
.section-item {
|
||||
padding-left: 34px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin: 0 0 10px 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-draft {
|
||||
.visibility,
|
||||
|
||||
.edit-draft-message,
|
||||
.view-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-public {
|
||||
.delete-draft,
|
||||
.component-actions,
|
||||
.new-component-item,
|
||||
.editing-draft-alert,
|
||||
.publish-draft-message,
|
||||
.preview-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-private {
|
||||
.delete-draft,
|
||||
.publish-draft,
|
||||
.editing-draft-alert,
|
||||
.create-draft,
|
||||
.view-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// editing units from courseware
|
||||
body.unit {
|
||||
|
||||
.component {
|
||||
padding-top: 30px;
|
||||
|
||||
.component-actions {
|
||||
@include box-sizing(border-box);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-bottom: 1px solid $lightBluishGrey2;
|
||||
background: $lightGrey;
|
||||
}
|
||||
|
||||
&.editing {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// studio - utilities - variables
|
||||
// ====================
|
||||
|
||||
$baseline: 20px;
|
||||
|
||||
// grid
|
||||
@@ -12,11 +15,18 @@ $fg-min-width: 900px;
|
||||
// type
|
||||
$sans-serif: 'Open Sans', $verdana;
|
||||
$body-line-height: golden-ratio(.875em, 1);
|
||||
$error-red: rgb(253, 87, 87);
|
||||
|
||||
// colors - new for re-org
|
||||
$black: rgb(0,0,0);
|
||||
$black-t0: rgba(0,0,0,0.125);
|
||||
$black-t1: rgba(0,0,0,0.25);
|
||||
$black-t2: rgba(0,0,0,0.50);
|
||||
$black-t3: rgba(0,0,0,0.75);
|
||||
$white: rgb(255,255,255);
|
||||
$white-t0: rgba(255,255,255,0.125);
|
||||
$white-t1: rgba(255,255,255,0.25);
|
||||
$white-t2: rgba(255,255,255,0.50);
|
||||
$white-t3: rgba(255,255,255,0.75);
|
||||
|
||||
$gray: rgb(127,127,127);
|
||||
$gray-l1: tint($gray,20%);
|
||||
@@ -24,6 +34,7 @@ $gray-l2: tint($gray,40%);
|
||||
$gray-l3: tint($gray,60%);
|
||||
$gray-l4: tint($gray,80%);
|
||||
$gray-l5: tint($gray,90%);
|
||||
$gray-l6: tint($gray,95%);
|
||||
$gray-d1: shade($gray,20%);
|
||||
$gray-d2: shade($gray,40%);
|
||||
$gray-d3: shade($gray,60%);
|
||||
@@ -39,6 +50,12 @@ $blue-d1: shade($blue,20%);
|
||||
$blue-d2: shade($blue,40%);
|
||||
$blue-d3: shade($blue,60%);
|
||||
$blue-d4: shade($blue,80%);
|
||||
$blue-s1: saturate($blue,15%);
|
||||
$blue-s2: saturate($blue,30%);
|
||||
$blue-s3: saturate($blue,45%);
|
||||
$blue-u1: desaturate($blue,15%);
|
||||
$blue-u2: desaturate($blue,30%);
|
||||
$blue-u3: desaturate($blue,45%);
|
||||
|
||||
$pink: rgb(183, 37, 103);
|
||||
$pink-l1: tint($pink,20%);
|
||||
@@ -50,6 +67,29 @@ $pink-d1: shade($pink,20%);
|
||||
$pink-d2: shade($pink,40%);
|
||||
$pink-d3: shade($pink,60%);
|
||||
$pink-d4: shade($pink,80%);
|
||||
$pink-s1: saturate($pink,15%);
|
||||
$pink-s2: saturate($pink,30%);
|
||||
$pink-s3: saturate($pink,45%);
|
||||
$pink-u1: desaturate($pink,15%);
|
||||
$pink-u2: desaturate($pink,30%);
|
||||
$pink-u3: desaturate($pink,45%);
|
||||
|
||||
$red: rgb(178, 6, 16);
|
||||
$red-l1: tint($red,20%);
|
||||
$red-l2: tint($red,40%);
|
||||
$red-l3: tint($red,60%);
|
||||
$red-l4: tint($red,80%);
|
||||
$red-l5: tint($red,90%);
|
||||
$red-d1: shade($red,20%);
|
||||
$red-d2: shade($red,40%);
|
||||
$red-d3: shade($red,60%);
|
||||
$red-d4: shade($red,80%);
|
||||
$red-s1: saturate($red,15%);
|
||||
$red-s2: saturate($red,30%);
|
||||
$red-s3: saturate($red,45%);
|
||||
$red-u1: desaturate($red,15%);
|
||||
$red-u2: desaturate($red,30%);
|
||||
$red-u3: desaturate($red,45%);
|
||||
|
||||
$green: rgb(37, 184, 90);
|
||||
$green-l1: tint($green,20%);
|
||||
@@ -61,6 +101,12 @@ $green-d1: shade($green,20%);
|
||||
$green-d2: shade($green,40%);
|
||||
$green-d3: shade($green,60%);
|
||||
$green-d4: shade($green,80%);
|
||||
$green-s1: saturate($green,15%);
|
||||
$green-s2: saturate($green,30%);
|
||||
$green-s3: saturate($green,45%);
|
||||
$green-u1: desaturate($green,15%);
|
||||
$green-u2: desaturate($green,30%);
|
||||
$green-u3: desaturate($green,45%);
|
||||
|
||||
$yellow: rgb(231, 214, 143);
|
||||
$yellow-l1: tint($yellow,20%);
|
||||
@@ -72,6 +118,29 @@ $yellow-d1: shade($yellow,20%);
|
||||
$yellow-d2: shade($yellow,40%);
|
||||
$yellow-d3: shade($yellow,60%);
|
||||
$yellow-d4: shade($yellow,80%);
|
||||
$yellow-s1: saturate($yellow,15%);
|
||||
$yellow-s2: saturate($yellow,30%);
|
||||
$yellow-s3: saturate($yellow,45%);
|
||||
$yellow-u1: desaturate($yellow,15%);
|
||||
$yellow-u2: desaturate($yellow,30%);
|
||||
$yellow-u3: desaturate($yellow,45%);
|
||||
|
||||
$orange: rgb(237, 189, 60);
|
||||
$orange-l1: tint($orange,20%);
|
||||
$orange-l2: tint($orange,40%);
|
||||
$orange-l3: tint($orange,60%);
|
||||
$orange-l4: tint($orange,80%);
|
||||
$orange-l5: tint($orange,90%);
|
||||
$orange-d1: shade($orange,20%);
|
||||
$orange-d2: shade($orange,40%);
|
||||
$orange-d3: shade($orange,60%);
|
||||
$orange-d4: shade($orange,80%);
|
||||
$orange-s1: saturate($orange,15%);
|
||||
$orange-s2: saturate($orange,30%);
|
||||
$orange-s3: saturate($orange,45%);
|
||||
$orange-u1: desaturate($orange,15%);
|
||||
$orange-u2: desaturate($orange,30%);
|
||||
$orange-u3: desaturate($orange,45%);
|
||||
|
||||
$shadow: rgba(0,0,0,0.2);
|
||||
$shadow-l1: rgba(0,0,0,0.1);
|
||||
@@ -80,8 +149,6 @@ $shadow-d1: rgba(0,0,0,0.4);
|
||||
// colors - inherited
|
||||
$baseFontColor: #3c3c3c;
|
||||
$offBlack: #3c3c3c;
|
||||
$orange: #edbd3c;
|
||||
$red: #b20610;
|
||||
$green: #108614;
|
||||
$lightGrey: #edf1f5;
|
||||
$mediumGrey: #b0b6c2;
|
||||
@@ -94,4 +161,5 @@ $brightGreen: rgb(22, 202, 87);
|
||||
$disabledGreen: rgb(124, 206, 153);
|
||||
$darkGreen: rgb(52, 133, 76);
|
||||
$lightBluishGrey: rgb(197, 207, 223);
|
||||
$lightBluishGrey2: rgb(213, 220, 228);
|
||||
$lightBluishGrey2: rgb(213, 220, 228);
|
||||
$error-red: rgb(253, 87, 87);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
section.video-new, section.video-edit {
|
||||
> section {
|
||||
|
||||
section.upload {
|
||||
padding: 6px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
a.upload-button {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
}
|
||||
}
|
||||
|
||||
section.in-use {
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div {
|
||||
background: #eee;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
a.save-update {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
section.week-edit,
|
||||
section.week-new,
|
||||
section.sequence-edit {
|
||||
|
||||
> header {
|
||||
border-bottom: 2px solid #333;
|
||||
@include clearfix();
|
||||
|
||||
div {
|
||||
@include clearfix();
|
||||
padding: 6px 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
p {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.week {
|
||||
background: #eee;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
@include inline-block();
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
@include inline-block();
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
margin-right: 10px;
|
||||
|
||||
p {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.goals {
|
||||
background: #eee;
|
||||
padding: 6px 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
color: #999;
|
||||
|
||||
li {
|
||||
margin-bottom: 6px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> section.content {
|
||||
@include box-sizing(border-box);
|
||||
padding: 20px;
|
||||
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
background: #efefef;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
|
||||
&.advanced {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
display: table;
|
||||
border: 1px solid;
|
||||
width: 100%;
|
||||
|
||||
section {
|
||||
header {
|
||||
background: #eee;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.modules {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(6, 9);
|
||||
border-right: 1px solid #333;
|
||||
|
||||
&.empty {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
@include inline-block();
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&.group {
|
||||
padding: 0;
|
||||
|
||||
header {
|
||||
padding: 6px;
|
||||
background: none;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scratch-pad {
|
||||
@include box-sizing(border-box);
|
||||
display: table-cell;
|
||||
width: flex-grid(3, 9) + flex-gutter(9);
|
||||
vertical-align: top;
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #999;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #999;
|
||||
background: #f9f9f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
a {
|
||||
@extend .button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,52 @@
|
||||
// studio - css architecture
|
||||
// ====================
|
||||
|
||||
// bourbon libs and resets
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'bourbon/addons/button';
|
||||
@import 'vendor/normalize';
|
||||
@import 'keyframes';
|
||||
|
||||
@import 'reset';
|
||||
|
||||
// utilities
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
@import 'cms_mixins';
|
||||
|
||||
@import "fonts";
|
||||
@import "variables";
|
||||
@import "cms_mixins";
|
||||
@import "extends";
|
||||
@import "base";
|
||||
@import "header";
|
||||
@import "footer";
|
||||
@import "dashboard";
|
||||
@import "courseware";
|
||||
@import "subsection";
|
||||
@import "unit";
|
||||
@import "assets";
|
||||
@import "static-pages";
|
||||
@import "users";
|
||||
@import "import";
|
||||
@import "export";
|
||||
@import "settings";
|
||||
@import "course-info";
|
||||
@import "landing";
|
||||
@import "graphics";
|
||||
@import "modal";
|
||||
@import "alerts";
|
||||
@import "login";
|
||||
@import "account";
|
||||
@import "index";
|
||||
@import 'jquery-ui-calendar';
|
||||
// assets
|
||||
@import 'assets/fonts';
|
||||
@import 'assets/graphics';
|
||||
@import 'assets/keyframes';
|
||||
|
||||
@import 'content-types';
|
||||
// base
|
||||
@import 'base';
|
||||
|
||||
// elements
|
||||
@import 'elements/header';
|
||||
@import 'elements/footer';
|
||||
@import 'elements/navigation';
|
||||
@import 'elements/forms';
|
||||
@import 'elements/modal';
|
||||
@import 'elements/alerts';
|
||||
@import 'elements/jquery-ui-calendar';
|
||||
|
||||
// specific views
|
||||
@import 'views/account';
|
||||
@import 'views/assets';
|
||||
@import 'views/updates';
|
||||
@import 'views/dashboard';
|
||||
@import 'views/export';
|
||||
@import 'views/index';
|
||||
@import 'views/import';
|
||||
@import 'views/outline';
|
||||
@import 'views/settings';
|
||||
@import 'views/static-pages';
|
||||
@import 'views/subsection';
|
||||
@import 'views/unit';
|
||||
@import 'views/users';
|
||||
@import 'views/checklists';
|
||||
|
||||
@import 'assets/content-types';
|
||||
|
||||
// xblock-related
|
||||
@import 'module/module-styles.scss';
|
||||
@import 'descriptor/module-styles.scss';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// studio - elements - alerts, notifications, prompts
|
||||
// ====================
|
||||
|
||||
// notifications
|
||||
.wrapper-notification {
|
||||
@include clearfix();
|
||||
@@ -1,4 +1,6 @@
|
||||
//studio global footer
|
||||
// studio - elements - global footer
|
||||
// ====================
|
||||
|
||||
.wrapper-footer {
|
||||
margin: ($baseline*1.5) 0 $baseline 0;
|
||||
padding: $baseline;
|
||||
76
cms/static/sass/elements/_forms.scss
Normal file
76
cms/static/sass/elements/_forms.scss
Normal file
@@ -0,0 +1,76 @@
|
||||
// studio - elements - forms
|
||||
// ====================
|
||||
|
||||
// forms - general
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
textarea.text {
|
||||
padding: 6px 8px 8px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $mediumGrey;
|
||||
border-radius: 2px;
|
||||
@include linear-gradient($lightGrey, tint($lightGrey, 90%));
|
||||
background-color: $lightGrey;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 11px;
|
||||
color: $baseFontColor;
|
||||
outline: 0;
|
||||
|
||||
&::-webkit-input-placeholder,
|
||||
&:-moz-placeholder,
|
||||
&:-ms-input-placeholder {
|
||||
color: #979faf;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include linear-gradient($paleYellow, tint($paleYellow, 90%));
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// forms - specific
|
||||
input.search {
|
||||
padding: 6px 15px 8px 30px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $darkGrey;
|
||||
border-radius: 20px;
|
||||
background: url(../img/search-icon.png) no-repeat 8px 7px #edf1f5;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: $baseFontColor;
|
||||
outline: 0;
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: #979faf;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
background: #eee;
|
||||
font-family: Monaco, monospace;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
font-size: 13px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.text-editor {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
border: 1px solid $mediumGrey;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.3));
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.1) inset);
|
||||
font-family: Monaco, monospace;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// studio global header and navigation
|
||||
// studio - elements - global header
|
||||
// ====================
|
||||
|
||||
.wrapper-header {
|
||||
@@ -1,3 +1,6 @@
|
||||
// studio - elements - JQUI calendar
|
||||
// ====================
|
||||
|
||||
.ui-datepicker {
|
||||
border-color: $darkGrey;
|
||||
border-radius: 2px;
|
||||
@@ -1,3 +1,6 @@
|
||||
// studio - elements - modal windows
|
||||
// ====================
|
||||
|
||||
.modal-cover {
|
||||
display: none;
|
||||
position: fixed;
|
||||
24
cms/static/sass/elements/_navigation.scss
Normal file
24
cms/static/sass/elements/_navigation.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
// studio - elements - navigation
|
||||
// ====================
|
||||
|
||||
// common
|
||||
|
||||
// ====================
|
||||
|
||||
// primary
|
||||
|
||||
// ====================
|
||||
|
||||
// right hand side
|
||||
|
||||
// ====================
|
||||
|
||||
// tabs
|
||||
|
||||
// ====================
|
||||
|
||||
// dropdown
|
||||
|
||||
// ====================
|
||||
|
||||
//
|
||||
@@ -1,5 +1,6 @@
|
||||
// Studio - Sign In/Up
|
||||
// studio - views - sign up/in
|
||||
// ====================
|
||||
|
||||
body.signup, body.signin {
|
||||
|
||||
.wrapper-content {
|
||||
@@ -1,4 +1,8 @@
|
||||
.uploads {
|
||||
// studio - views - assets
|
||||
// ====================
|
||||
|
||||
body.course.uploads {
|
||||
|
||||
input.asset-search-input {
|
||||
float: left;
|
||||
width: 260px;
|
||||
347
cms/static/sass/views/_checklists.scss
Normal file
347
cms/static/sass/views/_checklists.scss
Normal file
@@ -0,0 +1,347 @@
|
||||
// Studio - Course Settings
|
||||
// ====================
|
||||
body.course.checklists {
|
||||
|
||||
.content-primary, .content-supplementary {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
}
|
||||
|
||||
.content-primary {
|
||||
width: flex-grid(9, 12);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
// checklists - general
|
||||
.course-checklist {
|
||||
@extend .window;
|
||||
margin: 0 0 ($baseline*2) 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// visual status
|
||||
.viz-checklist-status {
|
||||
@include text-hide();
|
||||
@include size(100%,($baseline/4));
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: $gray-l4;
|
||||
|
||||
.viz-checklist-status-value {
|
||||
@include transition(width 2s ease-in-out .25s);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: ($baseline/4);
|
||||
background: $green;
|
||||
|
||||
.int {
|
||||
@include text-sr();
|
||||
}
|
||||
}
|
||||
}
|
||||
// <span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
// header/title
|
||||
header {
|
||||
@include clearfix();
|
||||
@include box-shadow(inset 0 -1px 1px $shadow-l1);
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
|
||||
.checklist-title {
|
||||
@include transition(color .15s .25s ease-in-out);
|
||||
width: flex-grid(6, 9);
|
||||
margin: 0 flex-gutter() 0 0;
|
||||
float: left;
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transition(rotate .15s ease-in-out .25s);
|
||||
@include font-size(14);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/2);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
&.is-selectable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
|
||||
.ui-toggle-expansion {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-status {
|
||||
@include font-size(13);
|
||||
width: flex-grid(3, 9);
|
||||
float: right;
|
||||
margin-top: ($baseline/2);
|
||||
text-align: right;
|
||||
color: $gray-l2;
|
||||
|
||||
|
||||
.icon-confirm {
|
||||
@include font-size(20);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: ($baseline/2);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
.status-count {
|
||||
@include font-size(16);
|
||||
margin-left: ($baseline/4);
|
||||
margin-right: ($baseline/4);
|
||||
color: $gray-d3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-amount {
|
||||
@include font-size(16);
|
||||
margin-left: ($baseline/4);
|
||||
color: $gray-d3;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checklist actions
|
||||
.course-checklist-actions {
|
||||
@include clearfix();
|
||||
@include box-shadow(inset 0 1px 1px $shadow-l1);
|
||||
@include transition(border .15s ease-in-out .25s);
|
||||
border-top: 1px solid $gray-l2;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
background: $gray-l4;
|
||||
|
||||
.action-primary {
|
||||
@include green-button();
|
||||
float: left;
|
||||
|
||||
.icon-add {
|
||||
@include font-size(12);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
@include font-size(14);
|
||||
@include grey-button();
|
||||
font-weight: 400;
|
||||
float: right;
|
||||
|
||||
.icon-delete {
|
||||
@include font-size(12);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state - collapsed
|
||||
&.is-collapsed {
|
||||
|
||||
header {
|
||||
@include box-shadow(none);
|
||||
|
||||
.checklist-title {
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transform(rotate(-90deg));
|
||||
@include transform-origin(50% 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-tasks {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// state - completed
|
||||
&.is-completed {
|
||||
|
||||
.viz-checklist-status {
|
||||
|
||||
.viz-checklist-status-value {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
.checklist-title, .icon-confirm {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.checklist-status {
|
||||
|
||||
.status-count, .status-amount, .icon-confirm {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state - not available
|
||||
.is-not-available {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// list of tasks
|
||||
.list-tasks {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
|
||||
.task {
|
||||
@include transition(background .15s ease-in-out .25s);
|
||||
@include transition(border .15s ease-in-out .25s);
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
border-top: 1px solid $white;
|
||||
border-bottom: 1px solid $gray-l5;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
background: $white;
|
||||
opacity: 1.0;
|
||||
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.task-input {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
float: left;
|
||||
margin: ($baseline/2) flex-gutter() 0 0;
|
||||
}
|
||||
|
||||
.task-details {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
float: left;
|
||||
width: flex-grid(6,9);
|
||||
font-weight: 500;
|
||||
|
||||
.task-name {
|
||||
@include transition(color .15s .25s ease-in-out);
|
||||
vertical-align: baseline;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.task-description {
|
||||
@include transition(color .15s .25s ease-in-out);
|
||||
@include font-size(14);
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.task-support {
|
||||
@include transition(opacity .15s .25s ease-in-out);
|
||||
@include font-size(12);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
@include transition(opacity .15s .25s ease-in-out);
|
||||
@include clearfix();
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
float: right;
|
||||
width: flex-grid(2,9);
|
||||
margin: ($baseline/2) 0 0 flex-gutter();
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
text-align: right;
|
||||
|
||||
.action-primary {
|
||||
@include blue-button;
|
||||
@include transition(all .15s);
|
||||
@include font-size(12);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
@include font-size(13);
|
||||
margin-top: ($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
// state - hover
|
||||
&:hover {
|
||||
background: $blue-l5;
|
||||
border-bottom-color: $blue-l4;
|
||||
border-top-color: $blue-l4;
|
||||
opacity: 1.0;
|
||||
|
||||
.task-details {
|
||||
|
||||
.task-support {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// state - completed
|
||||
&.is-completed {
|
||||
background: $gray-l6;
|
||||
border-top-color: $gray-l5;
|
||||
border-bottom-color: $gray-l5;
|
||||
|
||||
.task-name {
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
|
||||
.action-primary {
|
||||
@include grey-button;
|
||||
@include font-size(12);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $gray-l5;
|
||||
border-bottom-color: $gray-l4;
|
||||
border-top-color: $gray-l4;
|
||||
|
||||
.task-details {
|
||||
opacity:1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-supplementary {
|
||||
width: flex-grid(3, 12);
|
||||
}
|
||||
}
|
||||
124
cms/static/sass/views/_dashboard.scss
Normal file
124
cms/static/sass/views/_dashboard.scss
Normal file
@@ -0,0 +1,124 @@
|
||||
// studio - views - user dashboard
|
||||
// ====================
|
||||
|
||||
body.dashboard {
|
||||
|
||||
.my-classes {
|
||||
margin-top: $baseline;
|
||||
}
|
||||
|
||||
.class-list {
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.class-link {
|
||||
z-index: 100;
|
||||
display: block;
|
||||
padding: 20px 25px;
|
||||
line-height: 1.3;
|
||||
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
|
||||
+ .view-live-button {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.class-name {
|
||||
display: block;
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.detail {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin-right: 20px;
|
||||
color: #3c3c3c;
|
||||
}
|
||||
|
||||
// view live button
|
||||
.view-live-button {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: $baseline;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-course {
|
||||
padding: 15px 25px;
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $darkGrey;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1);
|
||||
@include clearfix;
|
||||
|
||||
.row {
|
||||
margin-bottom: 15px;
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
margin-right: 4%;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.new-course-org,
|
||||
.new-course-number,
|
||||
.new-course-name {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-course-name {
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.new-course-save {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.new-course-cancel {
|
||||
@include white-button;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
.export {
|
||||
// studio - views - course export
|
||||
// ====================
|
||||
|
||||
body.course.export {
|
||||
|
||||
.export-overview {
|
||||
@extend .window;
|
||||
@include clearfix;
|
||||
@@ -118,6 +122,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
.import {
|
||||
// studio - views - course import
|
||||
// ====================
|
||||
|
||||
body.course.import {
|
||||
|
||||
.import-overview {
|
||||
@extend .window;
|
||||
@include clearfix;
|
||||
@@ -1,5 +1,7 @@
|
||||
// how it works/not signed in index
|
||||
.index {
|
||||
// studio - views - how it works
|
||||
// ====================
|
||||
|
||||
body.index {
|
||||
|
||||
&.not-signedin {
|
||||
|
||||
680
cms/static/sass/views/_outline.scss
Normal file
680
cms/static/sass/views/_outline.scss
Normal file
@@ -0,0 +1,680 @@
|
||||
// studio - views - course outline
|
||||
// ====================
|
||||
|
||||
body.course.outline {
|
||||
|
||||
input.courseware-unit-search-input {
|
||||
float: left;
|
||||
width: 260px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.branch {
|
||||
|
||||
.section-item {
|
||||
@include clearfix();
|
||||
|
||||
.details {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
right: 50px;
|
||||
width: 145px;
|
||||
|
||||
.status-label {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: -5px;
|
||||
display: none;
|
||||
width: 110px;
|
||||
padding: 5px 40px 5px 10px;
|
||||
@include border-radius(3px);
|
||||
color: $lightGrey;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
color: $mediumGrey;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
display: none;
|
||||
opacity: 0.0;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 5px;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
|
||||
|
||||
li {
|
||||
width: 115px;
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
color: $darkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue;
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.courseware-section {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $mediumGrey;
|
||||
margin-top: 15px;
|
||||
padding-bottom: 12px;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
float: left;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
.datepair {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.section-published-date {
|
||||
position: absolute;
|
||||
top: 19px;
|
||||
right: 90px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 3px;
|
||||
background: $lightGrey;
|
||||
text-align: right;
|
||||
|
||||
.published-status {
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.schedule-button,
|
||||
.edit-button {
|
||||
font-size: 11px;
|
||||
padding: 3px 15px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.datepair .date,
|
||||
.datepair .time {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
@include box-shadow(none);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.datepair .date {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.datepair .time {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
&.collapsed .subsection-list,
|
||||
.collapsed .subsection-list,
|
||||
.collapsed > ol {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
header {
|
||||
min-height: 75px;
|
||||
@include clearfix();
|
||||
|
||||
.item-details, .section-published-date {
|
||||
|
||||
}
|
||||
|
||||
.item-details {
|
||||
display: inline-block;
|
||||
padding: 20px 0 10px 0;
|
||||
@include clearfix();
|
||||
|
||||
.section-name {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 350px;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.section-name-span {
|
||||
cursor: pointer;
|
||||
@include transition(color .15s);
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
|
||||
.section-name-edit {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
background: $white;
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
padding: 7px 20px 7px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
padding: 7px 20px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-published-date {
|
||||
float: right;
|
||||
width: 265px;
|
||||
margin-right: 220px;
|
||||
@include border-radius(3px);
|
||||
background: $lightGrey;
|
||||
|
||||
.published-status {
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.schedule-button,
|
||||
.edit-button {
|
||||
font-size: 11px;
|
||||
padding: 3px 15px 5px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 70px;
|
||||
width: 145px;
|
||||
|
||||
.status-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
display: none;
|
||||
width: 100px;
|
||||
padding: 10px 35px 10px 10px;
|
||||
@include border-radius(3px);
|
||||
background: $lightGrey;
|
||||
color: $lightGrey;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
color: $lightGrey;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
display: none;
|
||||
opacity: 0.0;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 2px;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
@include transition(display .15s);
|
||||
|
||||
|
||||
li {
|
||||
width: 115px;
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
color: $darkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
float: left;
|
||||
padding: 21px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
margin-top: 21px;
|
||||
margin-right: 12px;
|
||||
|
||||
.edit-button,
|
||||
.delete-button {
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-collapse-icon {
|
||||
float: left;
|
||||
margin: 29px 6px 16px 16px;
|
||||
@include transition(none);
|
||||
|
||||
&.expand {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
margin-left: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.section-name-span {
|
||||
cursor: pointer;
|
||||
@include transition(color .15s);
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
|
||||
.section-name-form {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-name-edit {
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
padding: 7px 20px 7px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
padding: 7px 20px 7px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
color: #878e9d;
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.list-header {
|
||||
@include linear-gradient(top, transparent, rgba(0, 0, 0, .1));
|
||||
background-color: #ced2db;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.subsection-list {
|
||||
margin: 0 12px;
|
||||
|
||||
> ol {
|
||||
@include tree-view;
|
||||
border-top-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.new-section {
|
||||
|
||||
header {
|
||||
height: auto;
|
||||
@include clearfix();
|
||||
}
|
||||
|
||||
.expand-collapse-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
padding: 25px 0 0 0;
|
||||
|
||||
.section-name {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-button-sections {
|
||||
display: none;
|
||||
position: relative;
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
|
||||
font-size: 13px;
|
||||
color: $darkGrey;
|
||||
|
||||
&.is-shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ss-icon {
|
||||
@include border-radius(20px);
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
line-height: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.new-section-name,
|
||||
.new-subsection-name-input {
|
||||
width: 515px;
|
||||
}
|
||||
|
||||
.new-section-name-save,
|
||||
.new-subsection-name-save {
|
||||
@include blue-button;
|
||||
padding: 4px 20px 7px;
|
||||
margin: 0 5px;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.new-section-name-cancel,
|
||||
.new-subsection-name-cancel {
|
||||
@include white-button;
|
||||
padding: 4px 20px 7px;
|
||||
color: #8891a1 !important;
|
||||
}
|
||||
|
||||
.dummy-calendar {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 110px;
|
||||
z-index: 9999;
|
||||
border: 1px solid #3C3C3C;
|
||||
@include box-shadow(0 1px 15px rgba(0, 0, 0, .2));
|
||||
}
|
||||
|
||||
.preview {
|
||||
background: url(../img/preview.jpg) center top no-repeat;
|
||||
}
|
||||
|
||||
.edit-subsection-publish-settings {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
z-index: 99999;
|
||||
width: 600px;
|
||||
margin-left: -300px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
|
||||
.settings {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 34px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.picker {
|
||||
margin: 30px 0 65px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.start-date,
|
||||
.start-time {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@include blue-button;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.save-button,
|
||||
.cancel-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-all-button {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: $darkGrey;
|
||||
}
|
||||
|
||||
// sort/drag and drop
|
||||
.ui-droppable {
|
||||
@include transition (padding 0.5s ease-in-out 0s);
|
||||
min-height: 20px;
|
||||
padding: 0;
|
||||
|
||||
&.dropover {
|
||||
padding: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-draggable-dragging {
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .3));
|
||||
border: 1px solid $darkGrey;
|
||||
opacity : 0.2;
|
||||
&:hover {
|
||||
opacity : 1.0;
|
||||
.section-item {
|
||||
background: $yellow !important;
|
||||
}
|
||||
}
|
||||
|
||||
// hiding unit button - temporary fix until this semantically corrected
|
||||
.new-unit-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ol.ui-droppable .branch:first-child .section-item {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Studio - Course Settings
|
||||
// studio - views - course settings
|
||||
// ====================
|
||||
|
||||
body.course.settings {
|
||||
|
||||
.content-primary, .content-supplementary {
|
||||
@@ -1,4 +1,8 @@
|
||||
.static-pages {
|
||||
// studio - views - course static pages
|
||||
// ====================
|
||||
|
||||
body.course.static-pages {
|
||||
|
||||
.new-static-page-button {
|
||||
@include grey-button;
|
||||
display: block;
|
||||
@@ -16,6 +20,51 @@
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-component-editor {
|
||||
z-index: 9999;
|
||||
position: relative;
|
||||
background: $lightBluishGrey2;
|
||||
}
|
||||
|
||||
.component-editor {
|
||||
@include edit-box;
|
||||
@include box-shadow(none);
|
||||
display: none;
|
||||
padding: 20px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
|
||||
.metadata_edit {
|
||||
margin-bottom: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
margin-top: 10px;
|
||||
margin: 15px 8px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-editor {
|
||||
@@ -35,6 +84,7 @@
|
||||
}
|
||||
|
||||
.component {
|
||||
position: relative;
|
||||
border: 1px solid $mediumGrey;
|
||||
border-top: none;
|
||||
|
||||
@@ -56,10 +106,13 @@
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 11;
|
||||
width: 35px;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: url(../img/drag-handles.png) center no-repeat #fff;
|
||||
|
||||
@@ -69,6 +122,7 @@
|
||||
}
|
||||
|
||||
.component-actions {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
right: 44px;
|
||||
}
|
||||
372
cms/static/sass/views/_subsection.scss
Normal file
372
cms/static/sass/views/_subsection.scss
Normal file
@@ -0,0 +1,372 @@
|
||||
// studio - views - course subsection
|
||||
// ====================
|
||||
|
||||
body.course.subsection {
|
||||
|
||||
.unit-settings {
|
||||
.window-contents {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.unit-actions {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
border: 1px solid #edbd3c;
|
||||
border-radius: 3px;
|
||||
background: #fbf6e1;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
|
||||
div {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-button, .view-button {
|
||||
@include white-button;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.publish-button {
|
||||
@include orange-button;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.delete-draft {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.delete-button,
|
||||
.preview-button,
|
||||
.publish-button,
|
||||
.view-button {
|
||||
font-size: 11px;
|
||||
margin-top: 10px;
|
||||
padding: 6px 15px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.unit-history {
|
||||
&.collapsed {
|
||||
h4 {
|
||||
border-bottom: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.window-contents {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
border: 1px solid #ced2db;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 6px 8px 8px 10px;
|
||||
background: #edf1f5;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fffcf1;
|
||||
|
||||
.item-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: #d1dae3;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-location {
|
||||
.url {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.draft-tag,
|
||||
.hidden-tag,
|
||||
.private-tag,
|
||||
.has-new-draft-tag {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.window-contents > ol {
|
||||
@include tree-view;
|
||||
|
||||
.section-item {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
ol {
|
||||
.section-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
ol ol {
|
||||
.section-item {
|
||||
padding-left: 34px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin: 0 0 10px 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subsection-body {
|
||||
padding: 32px 40px;
|
||||
@include clearfix;
|
||||
|
||||
> div {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sortable-unit-list {
|
||||
ol {
|
||||
@include tree-view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subsection-name-input {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.scheduled-date-input,
|
||||
.due-date-input {
|
||||
@include clearfix;
|
||||
|
||||
.date-input,
|
||||
.time-input {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.inherits-check {
|
||||
label {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.due-date-input {
|
||||
label {
|
||||
display: inline-block !important;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.date-setter {
|
||||
@include clearfix;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.remove-date {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.row.visibility {
|
||||
label {
|
||||
display: inline-block !important;
|
||||
margin-right: 10px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
height: 31px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 31px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.large-toggle {
|
||||
width: 41px;
|
||||
background: url(../img/large-toggles.png) no-repeat;
|
||||
background-position: 0 -50px;
|
||||
|
||||
.hidden {
|
||||
background-position: 0 -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gradable {
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.gradable-status {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
width: 65%;
|
||||
|
||||
.status-label {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: $blue;
|
||||
border: none;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
|
||||
&:hover, &.is-active {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -7px;
|
||||
display: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
opacity: 0.0;
|
||||
background: $white;
|
||||
border: 1px solid $mediumGrey;
|
||||
font-size: 12px;
|
||||
@include border-radius(4px);
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
|
||||
@include transition(opacity .15s);
|
||||
|
||||
|
||||
li {
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown state
|
||||
&.is-active {
|
||||
|
||||
.menu {
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// set state
|
||||
&.is-set {
|
||||
|
||||
.menu-toggle {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
display: block;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
681
cms/static/sass/views/_unit.scss
Normal file
681
cms/static/sass/views/_unit.scss
Normal file
@@ -0,0 +1,681 @@
|
||||
// studio - views - unit
|
||||
// ====================
|
||||
|
||||
body.course.unit {
|
||||
|
||||
.unit .main-wrapper {
|
||||
@include clearfix();
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
//Problem Selector tab menu requirements
|
||||
.js .tabs .tab {
|
||||
display: none;
|
||||
}
|
||||
//end problem selector reqs
|
||||
|
||||
.main-column {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.unit-body.published {
|
||||
.components > li {
|
||||
border: none;
|
||||
|
||||
.rendered-component {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-body {
|
||||
|
||||
.unit-name-input {
|
||||
padding: 20px 40px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-bottom: 1px solid #cbd1db;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0) 70%);
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .7) inset);
|
||||
@include clearfix;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
a,
|
||||
.current-page {
|
||||
display: block;
|
||||
padding: 15px 35px 15px 30px;
|
||||
font-size: 14px;
|
||||
background: url(../img/breadcrumb-arrow.png) no-repeat right center;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 30px 40px 30px 0;
|
||||
color: #646464;
|
||||
font-size: 19px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.components {
|
||||
|
||||
> li {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
margin: 20px 40px;
|
||||
|
||||
|
||||
|
||||
.title {
|
||||
margin: 0 0 15px 0;
|
||||
color: $mediumGrey;
|
||||
|
||||
.value {
|
||||
}
|
||||
}
|
||||
|
||||
&.new-component-item {
|
||||
margin: 20px 0px;
|
||||
border-top: 1px solid $mediumGrey;
|
||||
box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
|
||||
background-color: $lightGrey;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.new-component-button {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #edf1f5;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 20px 0px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.rendered-component {
|
||||
display: none;
|
||||
background: #fff;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.new-component-type {
|
||||
|
||||
a,
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid $mediumGrey;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: #fff;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
|
||||
|
||||
.name {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-templates {
|
||||
display: none;
|
||||
margin: 20px 40px 20px 40px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $mediumGrey;
|
||||
background-color: #fff;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
|
||||
@include clearfix;
|
||||
|
||||
.cancel-button {
|
||||
margin: 20px 0px 10px 10px;
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// specific menu types
|
||||
&.new-component-problem {
|
||||
padding-bottom:10px;
|
||||
|
||||
.ss-icon, .editor-indicator {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-type,
|
||||
.new-component-template {
|
||||
@include clearfix;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
border: 1px solid $darkGreen;
|
||||
background: tint($green,20%);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: $brightGreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.problem-type-tabs {
|
||||
list-style-type: none;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
|
||||
background-color: $lightBluishGrey;
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
|
||||
|
||||
li:first-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
float:left;
|
||||
display:inline-block;
|
||||
text-align:center;
|
||||
width: auto;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
|
||||
background-color: tint($lightBluishGrey, 10%);
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
|
||||
opacity:.8;
|
||||
|
||||
&:hover {
|
||||
opacity:1;
|
||||
background-color: tint($lightBluishGrey, 20%);
|
||||
}
|
||||
|
||||
&.ui-state-active {
|
||||
border: 0px;
|
||||
@include active;
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
a{
|
||||
display: block;
|
||||
padding: 15px 25px;
|
||||
font-size: 15px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
color: #3c3c3c;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.new-component-template {
|
||||
|
||||
a {
|
||||
background: #fff;
|
||||
border: 0px;
|
||||
color: #3c3c3c;
|
||||
@include transition (none);
|
||||
|
||||
&:hover {
|
||||
background: tint($green,30%);
|
||||
color: #fff;
|
||||
@include transition(background-color .15s);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
border:none;
|
||||
border-bottom: 1px dashed $lightGrey;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
a {
|
||||
border-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
li:nth-child(2) {
|
||||
a {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include clearfix();
|
||||
display: block;
|
||||
padding: 7px 20px;
|
||||
border-bottom: none;
|
||||
font-weight: 500;
|
||||
|
||||
.name {
|
||||
float: left;
|
||||
|
||||
.ss-icon {
|
||||
@include transition(opacity .15s);
|
||||
display: inline-block;
|
||||
top: 1px;
|
||||
margin-right: 5px;
|
||||
opacity: 0.5;
|
||||
width: 17;
|
||||
height: 21px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-indicator {
|
||||
@include transition(opacity .15s);
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 12px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.ss-icon, .editor-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
||||
.ss-icon {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.editor-indicator {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// specific editor types
|
||||
.empty {
|
||||
|
||||
a {
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
background: #fff;
|
||||
color: #3c3c3c;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: tint($green,30%);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-component {
|
||||
text-align: center;
|
||||
|
||||
h5 {
|
||||
color: $darkGreen;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component {
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
@include transition(none);
|
||||
|
||||
&:hover {
|
||||
border-color: #6696d7;
|
||||
|
||||
.drag-handle {
|
||||
background-color: $blue;
|
||||
border-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
z-index: auto;
|
||||
|
||||
.drag-handle,
|
||||
.component-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.component-placeholder {
|
||||
border-color: #6696d7;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: -1px;
|
||||
right: -16px;
|
||||
z-index: 10;
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
border-radius: 0 3px 3px 0;
|
||||
border: 1px solid $lightBluishGrey2;
|
||||
background: url(../img/white-drag-handles.png) center no-repeat $lightBluishGrey2;
|
||||
cursor: move;
|
||||
@include transition(none);
|
||||
}
|
||||
}
|
||||
|
||||
.xmodule_display {
|
||||
padding: 40px 20px 20px;
|
||||
overflow-x: auto;
|
||||
|
||||
h1 {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-component-editor {
|
||||
z-index: 9999;
|
||||
position: relative;
|
||||
background: $lightBluishGrey2;
|
||||
}
|
||||
|
||||
.component-editor {
|
||||
@include edit-box;
|
||||
@include box-shadow(none);
|
||||
display: none;
|
||||
padding: 20px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
|
||||
.metadata_edit {
|
||||
margin-bottom: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
margin-top: 10px;
|
||||
margin: 15px 8px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-settings {
|
||||
.window-contents {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.unit-actions {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
border: 1px solid #edbd3c;
|
||||
border-radius: 3px;
|
||||
background: #fbf6e1;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
|
||||
div {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-button, .view-button {
|
||||
@include white-button;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.publish-button {
|
||||
@include orange-button;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
@include blue-button;
|
||||
}
|
||||
|
||||
.delete-draft {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.delete-button,
|
||||
.preview-button,
|
||||
.publish-button,
|
||||
.view-button {
|
||||
font-size: 11px;
|
||||
margin-top: 10px;
|
||||
padding: 6px 15px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.unit-history {
|
||||
&.collapsed {
|
||||
h4 {
|
||||
border-bottom: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.window-contents {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
border: 1px solid #ced2db;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 6px 8px 8px 10px;
|
||||
background: #edf1f5;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fffcf1;
|
||||
|
||||
.item-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: #d1dae3;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-location {
|
||||
.url {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
.draft-tag,
|
||||
.hidden-tag,
|
||||
.private-tag,
|
||||
.has-new-draft-tag {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.window-contents > ol {
|
||||
@include tree-view;
|
||||
|
||||
.section-item {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
ol {
|
||||
.section-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
ol ol {
|
||||
.section-item {
|
||||
padding-left: 34px;
|
||||
}
|
||||
|
||||
.new-unit-item {
|
||||
margin: 0 0 10px 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-draft {
|
||||
.visibility,
|
||||
|
||||
.edit-draft-message,
|
||||
.view-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-public {
|
||||
.delete-draft,
|
||||
.component-actions,
|
||||
.new-component-item,
|
||||
.editing-draft-alert,
|
||||
.publish-draft-message,
|
||||
.preview-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.published-alert {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-private {
|
||||
.delete-draft,
|
||||
.publish-draft,
|
||||
.editing-draft-alert,
|
||||
.create-draft,
|
||||
.view-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// editing units from courseware
|
||||
body.unit {
|
||||
|
||||
.component {
|
||||
padding-top: 30px;
|
||||
|
||||
.component-actions {
|
||||
@include box-sizing(border-box);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-bottom: 1px solid $lightBluishGrey2;
|
||||
background: $lightGrey;
|
||||
}
|
||||
|
||||
&.editing {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
.course-info {
|
||||
// studio - views - course updates
|
||||
// ====================
|
||||
|
||||
body.course.updates {
|
||||
|
||||
h2 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 22px;
|
||||
@@ -1,4 +1,8 @@
|
||||
.users {
|
||||
// studio - views - course users
|
||||
// ====================
|
||||
|
||||
body.course.users {
|
||||
|
||||
.new-user-form {
|
||||
display: none;
|
||||
padding: 15px 20px;
|
||||
14
cms/templates/404.html
Normal file
14
cms/templates/404.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Page Not Found</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
|
||||
<h1>Page not found</h1>
|
||||
<p>The page that you were looking for was not found. Go back to the <a href="/">homepage</a> or let us know about any pages that may have been moved at <a href="mailto:technical@edx.org">technical@edx.org</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</%block>
|
||||
13
cms/templates/500.html
Normal file
13
cms/templates/500.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Server Error</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<h1>Currently the <em>edX</em> servers are down</h1>
|
||||
<p>Our staff is currently working to get the site back up as soon as possible. Please email us at <a href="mailto:technical@edx.org">technical@edx.org</a> to report any problems or downtime.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</%block>
|
||||
@@ -1,7 +1,7 @@
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="bodyclass">is-signedin course uploads</%block>
|
||||
<%block name="title">Uploads & Files</%block>
|
||||
<%block name="title">Files & Uploads</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.tablednd.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.form.js')}"></script>
|
||||
<script src="${static.url('js/vendor/jquery.smooth-scroll.min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/CodeMirror/htmlmixed.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/CodeMirror/css.js')}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
74
cms/templates/checklists.html
Normal file
74
cms/templates/checklists.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Checklists</%block>
|
||||
<%block name="bodyclass">is-signedin course uxdesign checklists</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%block name="jsextra">
|
||||
|
||||
<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 () {
|
||||
var checklistCollection = new CMS.Models.ChecklistCollection();
|
||||
checklistCollection.url = "${reverse('checklists_updates', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}";
|
||||
|
||||
var editor = new CMS.Views.Checklists({
|
||||
el: $('.course-checklists'),
|
||||
collection: checklistCollection
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Tools</span>
|
||||
<h1 class="title-1">Course Checklists</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<form id="course-checklists" class="course-checklists" method="post" action="">
|
||||
<h2 class="title title-3 sr">Current Checklists</h2>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<div class="bit">
|
||||
<h3 class="title title-3">What are checklists?</h3>
|
||||
<p>
|
||||
Running a course on edX is a complex undertaking. Course checklists are designed to help you understand and keep track of all the steps necessary to get your course ready for students.
|
||||
</p>
|
||||
<p>
|
||||
These checklists are shared among your course team, and any changes you make are immediately visible to other members of the team and saved automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title title-3">Studio checklists</h3>
|
||||
<nav class="nav-page checklists-current">
|
||||
<ol>
|
||||
% for checklist in checklists:
|
||||
<li class="nav-item">
|
||||
<a rel="view" href="${'#course-checklist' + str(loop.index)}">${checklist['short_description']}</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -2,7 +2,7 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<!-- TODO decode course # from context_course into title -->
|
||||
<%block name="title">Updates</%block>
|
||||
<%block name="title">Course Updates</%block>
|
||||
<%block name="bodyclass">is-signedin course course-info updates</%block>
|
||||
|
||||
|
||||
|
||||
@@ -31,18 +31,6 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div id="policy-to-delete" style="display:none">
|
||||
</div>
|
||||
|
||||
<div id="add-new-policy-element-template" style="display:none">
|
||||
<li class="policy-list-element new-policy-list-element">
|
||||
<input type="text" class="policy-list-name" autocomplete="off" size="15"/>: <input type="text" class="policy-list-value" size=40 autocomplete="off"/>
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
<a href="#" class="delete-icon remove-policy-data"></a>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="unit-settings window id-holder" data-id="${subsection.location}">
|
||||
<h4 class="header">Subsection Settings</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Export Course</%block>
|
||||
<%block name="title">Course Export</%block>
|
||||
<%block name="bodyclass">is-signedin course tools export</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
<figcaption class="description">Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
@@ -164,7 +164,7 @@
|
||||
<figcaption class="description">Quickly create videos, text snippets, inline discussions, and a variety of problem types.</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
@@ -177,7 +177,7 @@
|
||||
<figcaption class="description">Simply set the date of a section or subsection, and Studio will publish it to your students for you.</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Import Course</%block>
|
||||
<%block name="title">Course Import</%block>
|
||||
<%block name="bodyclass">is-signedin course tools import</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="title">Courses</%block>
|
||||
<%block name="title">My Courses</%block>
|
||||
<%block name="bodyclass">is-signedin index dashboard</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Staff Manager</%block>
|
||||
<%block name="title">Course Team Settings</%block>
|
||||
<%block name="bodyclass">is-signedin course users settings team</%block>
|
||||
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if section.lms.format is not None else 'Not Graded'}">
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if subsection.lms.format is not None else 'Not Graded'}">
|
||||
</div>
|
||||
|
||||
<div class="item-actions">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user