diff --git a/cms/.coveragerc b/cms/.coveragerc
index 9b1e59d670..b7ae181e99 100644
--- a/cms/.coveragerc
+++ b/cms/.coveragerc
@@ -1,7 +1,7 @@
# .coveragerc for cms
[run]
data_file = reports/cms/.coverage
-source = cms
+source = cms,common/djangoapps
omit = cms/envs/*, cms/manage.py
[report]
diff --git a/cms/djangoapps/contentstore/module_info_model.py b/cms/djangoapps/contentstore/module_info_model.py
index 0017010885..3b783c8815 100644
--- a/cms/djangoapps/contentstore/module_info_model.py
+++ b/cms/djangoapps/contentstore/module_info_model.py
@@ -1,5 +1,5 @@
import logging
-from static_replace import replace_urls
+from static_replace import replace_static_urls
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
@@ -18,7 +18,17 @@ def get_module_info(store, location, parent_location = None, rewrite_static_link
data = module.definition['data']
if rewrite_static_links:
- data = replace_urls(module.definition['data'], course_namespace = Location([module.location.tag, module.location.org, module.location.course, None, None]))
+ data = replace_static_urls(
+ module.definition['data'],
+ None,
+ course_namespace=Location([
+ module.location.tag,
+ module.location.org,
+ module.location.course,
+ None,
+ None
+ ])
+ )
return {
'id': module.location.url(),
@@ -47,7 +57,7 @@ def set_module_info(store, location, post_data):
if post_data.get('data') is not None:
data = post_data['data']
store.update_item(location, data)
-
+
# cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 80909dad7a..085ecebff1 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -515,6 +515,9 @@ class ContentStoreTest(TestCase):
# 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_missing_static_content(self):
+ resp = self.client.get("/c4x/asd/asd/asd/asd")
+ self.assertEqual(resp.status_code, 404)
def test_capa_module(self):
"""Test that a problem treats markdown specially."""
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index d8090cf68b..bd0dc2fb48 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -31,7 +31,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationErr
from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor
from xmodule.errortracker import exc_info_to_str
-from static_replace import replace_urls
+import static_replace
from external_auth.views import ssl_login_shortcut
from mitxmako.shortcuts import render_to_response, render_to_string
@@ -134,7 +134,7 @@ def has_access(user, location, role=STAFF_ROLE_NAME):
Return True if user allowed to access this piece of data
Note that the CMS permissions model is with respect to courses
There is a super-admin permissions if user.is_staff is set
- Also, since we're unifying the user database between LMS and CAS,
+ Also, since we're unifying the user database between LMS and CAS,
I'm presuming that the course instructor (formally known as admin)
will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our queries here as INSTRUCTOR
has all the rights that STAFF do
@@ -156,7 +156,7 @@ 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()
@@ -215,7 +215,7 @@ def edit_subsection(request, location):
# remove all metadata from the generic dictionary that is presented in a more normalized UI
- policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
+ policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields)
can_view_live = False
@@ -263,7 +263,6 @@ def edit_unit(request, location):
break
lms_link = get_lms_link_for_item(item.location)
- preview_lms_link = get_lms_link_for_item(item.location, preview=True)
component_templates = defaultdict(list)
@@ -294,7 +293,7 @@ def edit_unit(request, location):
containing_section = modulestore().get_item(containing_section_locs[0])
# cdodge hack. We're having trouble previewing drafts via jump_to redirect
- # so let's generate the link url here
+ # so let's generate the link url here
# need to figure out where this item is in the list of children as the preview will need this
index =1
@@ -305,12 +304,12 @@ def edit_unit(request, location):
preview_lms_link = '//{preview}{lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'.format(
preview='preview.',
- lms_base=settings.LMS_BASE,
+ lms_base=settings.LMS_BASE,
org=course.location.org,
- course=course.location.course,
- course_name=course.location.name,
- section=containing_section.location.name,
- subsection=containing_subsection.location.name,
+ course=course.location.course,
+ course_name=course.location.name,
+ section=containing_section.location.name,
+ subsection=containing_subsection.location.name,
index=index)
unit_state = compute_unit_state(item)
@@ -361,14 +360,14 @@ def assignment_type_update(request, org, course, category, name):
location = Location(['i4x', org, course, category, name])
if not has_access(request.user, location):
raise HttpResponseForbidden()
-
+
if request.method == 'GET':
- return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)),
+ return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)),
mimetype="application/json")
elif request.method == 'POST': # post or put, doesn't matter.
- return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)),
+ return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)),
mimetype="application/json")
-
+
def user_author_string(user):
'''Get an author string for commits by this user. Format:
@@ -476,7 +475,7 @@ def preview_module_system(request, preview_id, descriptor):
get_module=partial(get_preview_module, request, preview_id),
render_template=render_from_lms,
debug=True,
- replace_urls=replace_urls,
+ replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_namespace=descriptor.location),
user=request.user,
)
@@ -513,20 +512,20 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
error_msg=exc_info_to_str(sys.exc_info())
).xmodule_constructor(system)(None, None)
- # cdodge: Special case
+ # cdodge: Special case
if module.location.category == 'static_tab':
module.get_html = wrap_xmodule(
module.get_html,
module,
"xmodule_tab_display.html",
)
- else:
+ else:
module.get_html = wrap_xmodule(
module.get_html,
module,
"xmodule_display.html",
)
-
+
module.get_html = replace_static_urls(
module.get_html,
module.metadata.get('data_dir', module.location.course),
@@ -557,7 +556,7 @@ def _xmodule_recurse(item, action):
_xmodule_recurse(child, action)
action(item)
-
+
@login_required
@expect_json
@@ -592,7 +591,7 @@ def delete_item(request):
# delete_item on a vertical tries to delete the draft version leaving the
# requested delete to never occur
if item.location.revision is None and item.location.category=='vertical' and delete_all_versions:
- modulestore('direct').delete_item(item.location)
+ modulestore('direct').delete_item(item.location)
return HttpResponse()
@@ -611,7 +610,7 @@ def save_item(request):
if request.POST.get('data') is not None:
data = request.POST['data']
store.update_item(item_location, data)
-
+
# cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
@@ -701,7 +700,7 @@ def unpublish_unit(request):
def clone_item(request):
parent_location = Location(request.POST['parent_location'])
template = Location(request.POST['template'])
-
+
display_name = request.POST.get('display_name')
if not has_access(request.user, parent_location):
@@ -741,9 +740,9 @@ def upload_asset(request, org, course, coursename):
location = ['i4x', org, course, 'course', coursename]
if not has_access(request.user, location):
return HttpResponseForbidden()
-
+
# Does the course actually exist?!? Get anything from it to prove its existance
-
+
try:
item = modulestore().get_item(location)
except:
@@ -777,9 +776,9 @@ def upload_asset(request, org, course, coursename):
# readback the saved content - we need the database timestamp
readback = contentstore().find(content.location)
-
- response_payload = {'displayname' : content.name,
- 'uploadDate' : get_date_display(readback.last_modified_at),
+
+ response_payload = {'displayname' : content.name,
+ 'uploadDate' : get_date_display(readback.last_modified_at),
'url' : StaticContent.get_url_path_from_location(content.location),
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg' : 'Upload completed'
@@ -795,7 +794,7 @@ This view will return all CMS users who are editors for the specified course
@login_required
@ensure_csrf_cookie
def manage_users(request, location):
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME):
raise PermissionDenied()
@@ -811,7 +810,7 @@ def manage_users(request, location):
'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
'request_user_id' : request.user.id
})
-
+
def create_json_response(errmsg = None):
if errmsg is not None:
@@ -833,13 +832,13 @@ def add_user(request, location):
if email=='':
return create_json_response('Please specify an email address.')
-
+
# check that logged in user has admin permissions to this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied()
-
+
user = get_user_by_email(email)
-
+
# user doesn't exist?!? Return error.
if user is None:
return create_json_response('Could not find user by email address \'{0}\'.'.format(email))
@@ -862,7 +861,7 @@ the specified course
@ensure_csrf_cookie
def remove_user(request, location):
email = request.POST["email"]
-
+
# check that logged in user has admin permissions on this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied()
@@ -889,7 +888,7 @@ def landing(request, org, course, coursename):
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()
@@ -918,7 +917,7 @@ def reorder_static_tabs(request):
# get list of existing static tabs in course
# make sure they are the same lengths (i.e. the number of passed in tabs equals the number
# that we know about) otherwise we can drop some!
-
+
existing_static_tabs = [t for t in course.tabs if t['type'] == 'static_tab']
if len(existing_static_tabs) != len(tabs):
return HttpResponseBadRequest()
@@ -937,15 +936,15 @@ def reorder_static_tabs(request):
static_tab_idx = 0
for tab in course.tabs:
if tab['type'] == 'static_tab':
- reordered_tabs.append({'type': 'static_tab',
- 'name' : tab_items[static_tab_idx].metadata.get('display_name'),
+ reordered_tabs.append({'type': 'static_tab',
+ 'name' : tab_items[static_tab_idx].metadata.get('display_name'),
'url_slug' : tab_items[static_tab_idx].location.name})
static_tab_idx += 1
else:
reordered_tabs.append(tab)
- # OK, re-assemble the static tabs in the new order
+ # OK, re-assemble the static tabs in the new order
course.tabs = reordered_tabs
modulestore('direct').update_metadata(course.location, course.metadata)
return HttpResponse()
@@ -954,7 +953,7 @@ def reorder_static_tabs(request):
@login_required
@ensure_csrf_cookie
def edit_tabs(request, org, course, coursename):
- location = ['i4x', org, course, 'course', coursename]
+ location = ['i4x', org, course, 'course', coursename]
course_item = modulestore().get_item(location)
static_tabs_loc = Location('i4x', org, course, 'static_tab', None)
@@ -983,7 +982,7 @@ def edit_tabs(request, org, course, coursename):
return render_to_response('edit-tabs.html', {
'active_tab': 'pages',
- 'context_course':course_item,
+ 'context_course':course_item,
'components': components
})
@@ -1004,13 +1003,13 @@ 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()
-
+
course_module = modulestore().get_item(location)
-
+
# get current updates
location = ['i4x', org, course, 'course_info', "updates"]
@@ -1021,7 +1020,7 @@ def course_info(request, org, course, name, provided_id=None):
'course_updates' : json.dumps(get_course_updates(location)),
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
})
-
+
@expect_json
@login_required
@ensure_csrf_cookie
@@ -1035,7 +1034,7 @@ def course_info_updates(request, org, course, provided_id=None):
# ??? No way to check for access permission afaik
# get current updates
location = ['i4x', org, course, 'course_info', "updates"]
-
+
# Hmmm, provided_id is coming as empty string on create whereas I believe it used to be None :-(
# Possibly due to my removing the seemingly redundant pattern in urls.py
if provided_id == '':
@@ -1050,7 +1049,7 @@ def course_info_updates(request, org, course, provided_id=None):
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
-
+
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
@@ -1067,7 +1066,7 @@ def course_info_updates(request, org, course, provided_id=None):
@ensure_csrf_cookie
def module_info(request, module_location):
location = Location(module_location)
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
@@ -1080,10 +1079,10 @@ def module_info(request, module_location):
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))
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
- raise PermissionDenied()
+ raise PermissionDenied()
if real_method == 'GET':
return HttpResponse(json.dumps(get_module_info(get_modulestore(location), location, rewrite_static_links=rewrite_static_links)), mimetype="application/json")
@@ -1101,20 +1100,20 @@ 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()
-
+
course_module = modulestore().get_item(location)
course_details = CourseDetails.fetch(location)
-
+
return render_to_response('settings.html', {
- 'active_tab': 'settings',
+ 'active_tab': 'settings',
'context_course': course_module,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
-
+
@expect_json
@login_required
@ensure_csrf_cookie
@@ -1137,13 +1136,13 @@ def course_settings_updates(request, org, course, name, section):
elif section == 'grading':
manager = CourseGradingModel
else: return
-
+
if request.method == 'GET':
# 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),
+ 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.
- return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
+ return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
mimetype="application/json")
@expect_json
@@ -1156,7 +1155,7 @@ 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]
# check that logged in user has permissions to this item
@@ -1167,13 +1166,13 @@ def course_grader_updates(request, org, course, name, grader_index=None):
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
-
+
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(['i4x', org, course, 'course',name]), grader_index)),
mimetype="application/json")
elif real_method == "DELETE":
- # ??? Shoudl this return anything? Perhaps success fail?
+ # ??? Shoudl this return anything? Perhaps success fail?
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course',name]), grader_index)
return HttpResponse()
elif request.method == 'POST': # post or put, doesn't matter.
@@ -1190,7 +1189,7 @@ 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()
@@ -1203,7 +1202,7 @@ def asset_index(request, org, course, name):
})
course_module = modulestore().get_item(location)
-
+
course_reference = StaticContent.compute_location(org, course, name)
assets = contentstore().get_all_content_for_course(course_reference)
@@ -1217,15 +1216,15 @@ def asset_index(request, org, course, name):
display_info = {}
display_info['displayname'] = asset['displayname']
display_info['uploadDate'] = get_date_display(asset['uploadDate'])
-
+
asset_location = StaticContent.compute_location(id['org'], id['course'], id['name'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
-
+
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
_thumbnail_location = asset.get('thumbnail_location', None)
thumbnail_location = Location(_thumbnail_location) if _thumbnail_location is not None else None
display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None
-
+
asset_display.append(display_info)
return render_to_response('asset_index.html', {
@@ -1244,9 +1243,9 @@ def edge(request):
@expect_json
def create_new_course(request):
template = Location(request.POST['template'])
- org = request.POST.get('org')
- number = request.POST.get('number')
- display_name = request.POST.get('display_name')
+ org = request.POST.get('org')
+ number = request.POST.get('number')
+ display_name = request.POST.get('display_name')
try:
dest_location = Location('i4x', org, number, 'course', Location.clean(display_name))
@@ -1292,13 +1291,13 @@ def initialize_course_tabs(course):
# at least a list populated with the minimal times
# @TODO: I don't like the fact that the presentation tier is away of these data related constraints, let's find a better
# place for this. Also rather than using a simple list of dictionaries a nice class model would be helpful here
- course.tabs = [{"type": "courseware"},
- {"type": "course_info", "name": "Course Info"},
+ course.tabs = [{"type": "courseware"},
+ {"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
- modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
+ modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
@ensure_csrf_cookie
@login_required
@@ -1391,7 +1390,7 @@ def generate_export_course(request, org, course, name):
root_dir = path(mkdtemp())
# export out to a tempdir
-
+
logging.debug('root = {0}'.format(root_dir))
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name)
@@ -1403,7 +1402,7 @@ def generate_export_course(request, org, course, name):
tf.close()
# remove temp dir
- shutil.rmtree(root_dir/name)
+ shutil.rmtree(root_dir/name)
wrapper = FileWrapper(export_file)
response = HttpResponse(wrapper, content_type='application/x-tgz')
@@ -1433,4 +1432,4 @@ def event(request):
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
console logs don't get distracted :-)
'''
- return HttpResponse(True)
\ No newline at end of file
+ return HttpResponse(True)
diff --git a/cms/envs/aws.py b/cms/envs/aws.py
index 48cfa3cf9a..a147f84531 100644
--- a/cms/envs/aws.py
+++ b/cms/envs/aws.py
@@ -52,10 +52,6 @@ LOGGING = get_logger_config(LOG_DIR,
debug=False,
service_variant=SERVICE_VARIANT)
-with open(ENV_ROOT / "repos.json") as repos_file:
- REPOS = json.load(repos_file)
-
-
################ SECURE AUTH ITEMS ###############################
# Secret things: passwords, access keys, etc.
with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file:
diff --git a/cms/envs/common.py b/cms/envs/common.py
index f2d47dfdc6..3ea532d70d 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -285,4 +285,5 @@ INSTALLED_APPS = (
# For asset pipelining
'pipeline',
'staticfiles',
+ 'static_replace',
)
diff --git a/cms/static/js/base.js b/cms/static/js/base.js
index 7c7e0f2bfd..776d42d1cf 100644
--- a/cms/static/js/base.js
+++ b/cms/static/js/base.js
@@ -95,64 +95,6 @@ $(document).ready(function() {
$('.import .file-input').click();
});
- // making the unit list draggable. Note: sortable didn't work b/c it considered
- // drop points which the user hovered over as destinations and proactively changed
- // the dom; so, if the user subsequently dropped at an illegal spot, the reversion
- // point was the last dom change.
- $('.unit').draggable({
- axis: 'y',
- handle: '.drag-handle',
- zIndex: 999,
- start: initiateHesitate,
- drag: checkHoverState,
- stop: removeHesitate,
- revert: "invalid"
- });
-
- // Subsection reordering
- $('.id-holder').draggable({
- axis: 'y',
- handle: '.section-item .drag-handle',
- zIndex: 999,
- start: initiateHesitate,
- drag: checkHoverState,
- stop: removeHesitate,
- revert: "invalid"
- });
-
- // Section reordering
- $('.courseware-section').draggable({
- axis: 'y',
- handle: 'header .drag-handle',
- stack: '.courseware-section',
- revert: "invalid"
- });
-
-
- $('.sortable-unit-list').droppable({
- accept : '.unit',
- greedy: true,
- tolerance: "pointer",
- hoverClass: "dropover",
- drop: onUnitReordered
- });
- $('.subsection-list > ol').droppable({
- // why don't we have a more useful class for subsections than id-holder?
- accept : '.id-holder', // '.unit, .id-holder',
- tolerance: "pointer",
- hoverClass: "dropover",
- drop: onSubsectionReordered,
- greedy: true
- });
-
- // Section reordering
- $('.courseware-overview').droppable({
- accept : '.courseware-section',
- tolerance: "pointer",
- drop: onSectionReordered,
- greedy: true
- });
-
$('.new-course-button').bind('click', addNewCourse);
// section name editing
@@ -294,136 +236,6 @@ function removePolicyMetadata(e) {
saveSubsection()
}
-CMS.HesitateEvent.toggleXpandHesitation = null;
-function initiateHesitate(event, ui) {
- CMS.HesitateEvent.toggleXpandHesitation = new CMS.HesitateEvent(expandSection, 'dragLeave', true);
- $('.collapsed').on('dragEnter', CMS.HesitateEvent.toggleXpandHesitation, CMS.HesitateEvent.toggleXpandHesitation.trigger);
- $('.collapsed').each(function() {
- this.proportions = {width : this.offsetWidth, height : this.offsetHeight };
- // reset b/c these were holding values from aborts
- this.isover = false;
- });
-}
-function checkHoverState(event, ui) {
- // copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect
- var draggable = $(this).data("ui-draggable"),
- x1 = (draggable.positionAbs || draggable.position.absolute).left + (draggable.helperProportions.width / 2),
- y1 = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2);
- $('.collapsed').each(function() {
- // don't expand the thing being carried
- if (ui.helper.is(this)) {
- return;
- }
-
- $.extend(this, {offset : $(this).offset()});
-
- var droppable = this,
- l = droppable.offset.left,
- r = l + droppable.proportions.width,
- t = droppable.offset.top,
- b = t + droppable.proportions.height;
-
- if (l === r) {
- // probably wrong values b/c invisible at the time of caching
- droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight };
- r = l + droppable.proportions.width;
- b = t + droppable.proportions.height;
- }
- // equivalent to the intersects test
- var intersects = (l < x1 && // Right Half
- x1 < r && // Left Half
- t < y1 && // Bottom Half
- y1 < b ), // Top Half
-
- c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
-
- if(!c) {
- return;
- }
-
- this[c] = true;
- this[c === "isout" ? "isover" : "isout"] = false;
- $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave");
- });
-}
-function removeHesitate(event, ui) {
- $('.collapsed').off('dragEnter', CMS.HesitateEvent.toggleXpandHesitation.trigger);
- CMS.HesitateEvent.toggleXpandHesitation = null;
-}
-
-function expandSection(event) {
- $(event.delegateTarget).removeClass('collapsed', 400);
- // don't descend to icon's on children (which aren't under first child) only to this element's icon
- $(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse');
-}
-
-function onUnitReordered(event, ui) {
- // a unit's been dropped on this subsection,
- // figure out where it came from and where it slots in.
- _handleReorder(event, ui, 'subsection-id', 'li:.leaf');
-}
-
-function onSubsectionReordered(event, ui) {
- // a subsection has been dropped on this section,
- // figure out where it came from and where it slots in.
- _handleReorder(event, ui, 'section-id', 'li:.branch');
-}
-
-function onSectionReordered(event, ui) {
- // a section moved w/in the overall (cannot change course via this, so no parentage change possible, just order)
- _handleReorder(event, ui, 'course-id', '.courseware-section');
-}
-
-function _handleReorder(event, ui, parentIdField, childrenSelector) {
- // figure out where it came from and where it slots in.
- var subsection_id = $(event.target).data(parentIdField);
- var _els = $(event.target).children(childrenSelector);
- var children = _els.map(function(idx, el) { return $(el).data('id'); }).get();
- // if new to this parent, figure out which parent to remove it from and do so
- if (!_.contains(children, ui.draggable.data('id'))) {
- var old_parent = ui.draggable.parent();
- var old_children = old_parent.children(childrenSelector).map(function(idx, el) { return $(el).data('id'); }).get();
- old_children = _.without(old_children, ui.draggable.data('id'));
- $.ajax({
- url: "/save_item",
- type: "POST",
- dataType: "json",
- contentType: "application/json",
- data:JSON.stringify({ 'id' : old_parent.data(parentIdField), 'children' : old_children})
- });
- }
- else {
- // staying in same parent
- // remove so that the replacement in the right place doesn't double it
- children = _.without(children, ui.draggable.data('id'));
- }
- // add to this parent (figure out where)
- for (var i = 0; i < _els.length; i++) {
- if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) {
- // insert at i in children and _els
- ui.draggable.insertBefore($(_els[i]));
- // TODO figure out correct way to have it remove the style: top:n; setting (and similar line below)
- ui.draggable.attr("style", "position:relative;");
- children.splice(i, 0, ui.draggable.data('id'));
- break;
- }
- }
- // see if it goes at end (the above loop didn't insert it)
- if (!_.contains(children, ui.draggable.data('id'))) {
- $(event.target).append(ui.draggable);
- ui.draggable.attr("style", "position:relative;"); // STYLE hack too
- children.push(ui.draggable.data('id'));
- }
- $.ajax({
- url: "/save_item",
- type: "POST",
- dataType: "json",
- contentType: "application/json",
- data:JSON.stringify({ 'id' : subsection_id, 'children' : children})
- });
-
-}
-
function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
var edxTimeStr = null;
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
new file mode 100644
index 0000000000..8cbae177a8
--- /dev/null
+++ b/cms/static/js/views/overview.js
@@ -0,0 +1,231 @@
+$(document).ready(function() {
+ // making the unit list draggable. Note: sortable didn't work b/c it considered
+ // drop points which the user hovered over as destinations and proactively changed
+ // the dom; so, if the user subsequently dropped at an illegal spot, the reversion
+ // point was the last dom change.
+ $('.unit').draggable({
+ axis: 'y',
+ handle: '.drag-handle',
+ zIndex: 999,
+ start: initiateHesitate,
+ // left 2nd arg in as inert selector b/c i was uncertain whether we'd try to get the shove up/down
+ // to work in the future
+ drag: generateCheckHoverState('.collapsed', ''),
+ stop: removeHesitate,
+ revert: "invalid"
+ });
+
+ // Subsection reordering
+ $('.id-holder').draggable({
+ axis: 'y',
+ handle: '.section-item .drag-handle',
+ zIndex: 999,
+ start: initiateHesitate,
+ drag: generateCheckHoverState('.courseware-section.collapsed', ''),
+ stop: removeHesitate,
+ revert: "invalid"
+ });
+
+ // Section reordering
+ $('.courseware-section').draggable({
+ axis: 'y',
+ handle: 'header .drag-handle',
+ stack: '.courseware-section',
+ revert: "invalid"
+ });
+
+
+ $('.sortable-unit-list').droppable({
+ accept : '.unit',
+ greedy: true,
+ tolerance: "pointer",
+ hoverClass: "dropover",
+ drop: onUnitReordered
+ });
+ $('.subsection-list > ol').droppable({
+ // why don't we have a more useful class for subsections than id-holder?
+ accept : '.id-holder', // '.unit, .id-holder',
+ tolerance: "pointer",
+ hoverClass: "dropover",
+ drop: onSubsectionReordered,
+ greedy: true
+ });
+
+ // Section reordering
+ $('.courseware-overview').droppable({
+ accept : '.courseware-section',
+ tolerance: "pointer",
+ drop: onSectionReordered,
+ greedy: true
+ });
+
+});
+
+CMS.HesitateEvent.toggleXpandHesitation = null;
+function initiateHesitate(event, ui) {
+ CMS.HesitateEvent.toggleXpandHesitation = new CMS.HesitateEvent(expandSection, 'dragLeave', true);
+ $('.collapsed').on('dragEnter', CMS.HesitateEvent.toggleXpandHesitation, CMS.HesitateEvent.toggleXpandHesitation.trigger);
+ $('.collapsed, .unit, .id-holder').each(function() {
+ this.proportions = {width : this.offsetWidth, height : this.offsetHeight };
+ // reset b/c these were holding values from aborts
+ this.isover = false;
+ });
+}
+
+function computeIntersection(droppable, uiHelper, y) {
+ /*
+ * Test whether y falls within the bounds of the droppable on the Y axis
+ */
+ // NOTE: this only judges y axis intersection b/c that's all we're doing right now
+ // don't expand the thing being carried
+ if (uiHelper.is(droppable)) {
+ return null;
+ }
+
+ $.extend(droppable, {offset : $(droppable).offset()});
+
+ var t = droppable.offset.top,
+ b = t + droppable.proportions.height;
+
+ if (t === b) {
+ // probably wrong values b/c invisible at the time of caching
+ droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight };
+ b = t + droppable.proportions.height;
+ }
+ // equivalent to the intersects test
+ return (t < y && // Bottom Half
+ y < b ); // Top Half
+}
+
+// NOTE: selectorsToShove is not currently being used but I left this code as it did work but not well
+function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
+ return function(event, ui) {
+ // copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect
+ var draggable = $(this).data("ui-draggable"),
+ centerY = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2);
+ $(selectorsToOpen).each(function() {
+ var intersects = computeIntersection(this, ui.helper, centerY),
+ c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
+
+ if(!c) {
+ return;
+ }
+
+ this[c] = true;
+ this[c === "isout" ? "isover" : "isout"] = false;
+ $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave");
+ });
+
+ $(selectorsToShove).each(function() {
+ var intersectsBottom = computeIntersection(this, ui.helper, (draggable.positionAbs || draggable.position.absolute).top);
+
+ if ($(this).hasClass('ui-dragging-pushup')) {
+ if (!intersectsBottom) {
+ console.log('not up', $(this).data('id'));
+ $(this).removeClass('ui-dragging-pushup');
+ }
+ }
+ else if (intersectsBottom) {
+ console.log('up', $(this).data('id'));
+ $(this).addClass('ui-dragging-pushup');
+ }
+
+ var intersectsTop = computeIntersection(this, ui.helper,
+ (draggable.positionAbs || draggable.position.absolute).top + draggable.helperProportions.height);
+
+ if ($(this).hasClass('ui-dragging-pushdown')) {
+ if (!intersectsTop) {
+ console.log('not down', $(this).data('id'));
+ $(this).removeClass('ui-dragging-pushdown');
+ }
+ }
+ else if (intersectsTop) {
+ console.log('down', $(this).data('id'));
+ $(this).addClass('ui-dragging-pushdown');
+ }
+
+ });
+ }
+}
+
+function removeHesitate(event, ui) {
+ $('.collapsed').off('dragEnter', CMS.HesitateEvent.toggleXpandHesitation.trigger);
+ $('.ui-dragging-pushdown').removeClass('ui-dragging-pushdown');
+ $('.ui-dragging-pushup').removeClass('ui-dragging-pushup');
+ CMS.HesitateEvent.toggleXpandHesitation = null;
+}
+
+function expandSection(event) {
+ $(event.delegateTarget).removeClass('collapsed', 400);
+ // don't descend to icon's on children (which aren't under first child) only to this element's icon
+ $(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse');
+}
+
+function onUnitReordered(event, ui) {
+ // a unit's been dropped on this subsection,
+ // figure out where it came from and where it slots in.
+ _handleReorder(event, ui, 'subsection-id', 'li:.leaf');
+}
+
+function onSubsectionReordered(event, ui) {
+ // a subsection has been dropped on this section,
+ // figure out where it came from and where it slots in.
+ _handleReorder(event, ui, 'section-id', 'li:.branch');
+}
+
+function onSectionReordered(event, ui) {
+ // a section moved w/in the overall (cannot change course via this, so no parentage change possible, just order)
+ _handleReorder(event, ui, 'course-id', '.courseware-section');
+}
+
+function _handleReorder(event, ui, parentIdField, childrenSelector) {
+ // figure out where it came from and where it slots in.
+ var subsection_id = $(event.target).data(parentIdField);
+ var _els = $(event.target).children(childrenSelector);
+ var children = _els.map(function(idx, el) { return $(el).data('id'); }).get();
+ // if new to this parent, figure out which parent to remove it from and do so
+ if (!_.contains(children, ui.draggable.data('id'))) {
+ var old_parent = ui.draggable.parent();
+ var old_children = old_parent.children(childrenSelector).map(function(idx, el) { return $(el).data('id'); }).get();
+ old_children = _.without(old_children, ui.draggable.data('id'));
+ $.ajax({
+ url: "/save_item",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ data:JSON.stringify({ 'id' : old_parent.data(parentIdField), 'children' : old_children})
+ });
+ }
+ else {
+ // staying in same parent
+ // remove so that the replacement in the right place doesn't double it
+ children = _.without(children, ui.draggable.data('id'));
+ }
+ // add to this parent (figure out where)
+ for (var i = 0; i < _els.length; i++) {
+ if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) {
+ // insert at i in children and _els
+ ui.draggable.insertBefore($(_els[i]));
+ // TODO figure out correct way to have it remove the style: top:n; setting (and similar line below)
+ ui.draggable.attr("style", "position:relative;");
+ children.splice(i, 0, ui.draggable.data('id'));
+ break;
+ }
+ }
+ // see if it goes at end (the above loop didn't insert it)
+ if (!_.contains(children, ui.draggable.data('id'))) {
+ $(event.target).append(ui.draggable);
+ ui.draggable.attr("style", "position:relative;"); // STYLE hack too
+ children.push(ui.draggable.data('id'));
+ }
+ $.ajax({
+ url: "/save_item",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ data:JSON.stringify({ 'id' : subsection_id, 'children' : children})
+ });
+
+}
+
+
diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss
index 4ea110f4c8..f2bd25c601 100644
--- a/cms/static/sass/_courseware.scss
+++ b/cms/static/sass/_courseware.scss
@@ -1,90 +1,90 @@
input.courseware-unit-search-input {
- float: left;
- width: 260px;
- background-color: #fff;
+ float: left;
+ width: 260px;
+ background-color: #fff;
}
.branch {
- .section-item {
- @include clearfix();
+ .section-item {
+ @include clearfix();
- .details {
- display: block;
- float: left;
- margin-bottom: 0;
- width: 650px;
- }
+ .details {
+ display: block;
+ float: left;
+ margin-bottom: 0;
+ width: 650px;
+ }
- .gradable-status {
- float: right;
- position: relative;
- top: -4px;
- right: 50px;
- width: 145px;
+ .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;
- }
+ .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;
+ .menu-toggle {
+ z-index: 10;
+ position: absolute;
+ top: 0;
+ right: 5px;
+ padding: 5px;
+ color: $mediumGrey;
- &:hover, &.is-active {
- color: $blue;
- }
- }
+ &: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);
+ .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;
+ li {
+ width: 115px;
+ margin-bottom: 3px;
+ padding-bottom: 3px;
+ border-bottom: 1px solid $lightGrey;
- &:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- border: none;
+ &:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border: none;
- a {
- color: $darkGrey;
- }
- }
- }
+ a {
+ color: $darkGrey;
+ }
+ }
+ }
a {
color: $blue;
@@ -127,262 +127,262 @@ input.courseware-unit-search-input {
.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));
+ 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;
- }
+ &:first-child {
+ margin-top: 0;
+ }
- &.collapsed {
- padding-bottom: 0;
- }
+ &.collapsed {
+ padding-bottom: 0;
+ }
- label {
- float: left;
- line-height: 29px;
- }
+ label {
+ float: left;
+ line-height: 29px;
+ }
- .datepair {
- float: left;
- margin-left: 10px;
- }
+ .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;
+ .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;
+ .published-status {
+ font-size: 12px;
+ margin-right: 15px;
- strong {
- font-weight: bold;
- }
- }
+ strong {
+ font-weight: bold;
+ }
+ }
- .schedule-button {
- @include blue-button;
- }
+ .schedule-button {
+ @include blue-button;
+ }
- .edit-button {
- @include blue-button;
- }
+ .edit-button {
+ @include blue-button;
+ }
- .schedule-button,
- .edit-button {
- font-size: 11px;
- padding: 3px 15px 5px;
- }
- }
+ .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,
+ .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 .date {
+ width: 80px;
+ }
- .datepair .time {
- width: 65px;
- }
+ .datepair .time {
+ width: 65px;
+ }
- &.collapsed .subsection-list,
- .collapsed .subsection-list,
- .collapsed > ol {
- display: none !important;
- }
+ &.collapsed .subsection-list,
+ .collapsed .subsection-list,
+ .collapsed > ol {
+ display: none !important;
+ }
- header {
- min-height: 75px;
- @include clearfix();
+ header {
+ min-height: 75px;
+ @include clearfix();
- .item-details, .section-published-date {
+ .item-details, .section-published-date {
- }
+ }
- .item-details {
- display: inline-block;
- padding: 20px 0 10px 0;
- @include clearfix();
+ .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 {
+ float: left;
+ margin-right: 10px;
+ width: 350px;
+ font-size: 19px;
+ font-weight: bold;
+ color: $blue;
+ }
- .section-name-span {
- cursor: pointer;
- @include transition(color .15s);
+ .section-name-span {
+ cursor: pointer;
+ @include transition(color .15s);
- &:hover {
- color: $orange;
- }
- }
+ &:hover {
+ color: $orange;
+ }
+ }
- .section-name-edit {
- position: relative;
- width: 400px;
- background: $white;
+ .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;
- }
+ 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;
- }
- }
+ .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;
+ .section-published-date {
+ float: right;
+ width: 265px;
+ margin-right: 220px;
+ @include border-radius(3px);
+ background: $lightGrey;
- .published-status {
- font-size: 12px;
- margin-right: 15px;
+ .published-status {
+ font-size: 12px;
+ margin-right: 15px;
- strong {
- font-weight: bold;
- }
- }
+ strong {
+ font-weight: bold;
+ }
+ }
- .schedule-button {
- @include blue-button;
- }
+ .schedule-button {
+ @include blue-button;
+ }
- .edit-button {
- @include blue-button;
- }
+ .edit-button {
+ @include blue-button;
+ }
- .schedule-button,
- .edit-button {
- font-size: 11px;
- padding: 3px 15px 5px;
-
- }
- }
+ .schedule-button,
+ .edit-button {
+ font-size: 11px;
+ padding: 3px 15px 5px;
+
+ }
+ }
- .gradable-status {
- position: absolute;
- top: 20px;
- right: 70px;
- width: 145px;
+ .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;
- }
+ .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;
+ .menu-toggle {
+ z-index: 10;
+ position: absolute;
+ top: 2px;
+ right: 5px;
+ padding: 5px;
+ color: $lightGrey;
- &:hover, &.is-active {
- color: $blue;
- }
- }
+ &: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);
+ .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;
+ li {
+ width: 115px;
+ margin-bottom: 3px;
+ padding-bottom: 3px;
+ border-bottom: 1px solid $lightGrey;
- &:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- border: none;
+ &:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border: none;
- a {
- color: $darkGrey;
- }
- }
- }
+ a {
+ color: $darkGrey;
+ }
+ }
+ }
- a {
+ a {
- &.is-selected {
- font-weight: bold;
- }
- }
- }
+ &.is-selected {
+ font-weight: bold;
+ }
+ }
+ }
- // dropdown state
- &.is-active {
+ // dropdown state
+ &.is-active {
- .menu {
- z-index: 1000;
- display: block;
- opacity: 1.0;
- }
+ .menu {
+ z-index: 1000;
+ display: block;
+ opacity: 1.0;
+ }
.menu-toggle {
@@ -408,256 +408,272 @@ input.courseware-unit-search-input {
}
}
- .item-actions {
- margin-top: 21px;
- margin-right: 12px;
+ .item-actions {
+ margin-top: 21px;
+ margin-right: 12px;
- .edit-button,
- .delete-button {
- margin-top: -3px;
- }
- }
+ .edit-button,
+ .delete-button {
+ margin-top: -3px;
+ }
+ }
- .expand-collapse-icon {
- float: left;
- margin: 29px 6px 16px 16px;
- @include transition(none);
+ .expand-collapse-icon {
+ float: left;
+ margin: 29px 6px 16px 16px;
+ @include transition(none);
- &.expand {
- background-position: 0 0;
- }
+ &.expand {
+ background-position: 0 0;
+ }
- &.collapsed {
-
- }
- }
+ &.collapsed {
+
+ }
+ }
- .drag-handle {
- margin-left: 11px;
- }
- }
+ .drag-handle {
+ margin-left: 11px;
+ }
+ }
- h3 {
- font-size: 19px;
- font-weight: 700;
- color: $blue;
- }
+ h3 {
+ font-size: 19px;
+ font-weight: 700;
+ color: $blue;
+ }
- .section-name-span {
- cursor: pointer;
- @include transition(color .15s);
+ .section-name-span {
+ cursor: pointer;
+ @include transition(color .15s);
- &:hover {
- color: $orange;
- }
- }
+ &:hover {
+ color: $orange;
+ }
+ }
- .section-name-form {
- margin-bottom: 15px;
- }
+ .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;
- }
+ .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;
- }
- }
+ .cancel-button {
+ @include white-button;
+ padding: 7px 20px 7px;
+ }
+ }
- h4 {
- font-size: 12px;
- color: #878e9d;
+ h4 {
+ font-size: 12px;
+ color: #878e9d;
- strong {
- font-weight: bold;
- }
- }
+ 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;
- }
+ .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;
+ .subsection-list {
+ margin: 0 12px;
- > ol {
- @include tree-view;
- border-top-width: 0;
- }
- }
+ > ol {
+ @include tree-view;
+ border-top-width: 0;
+ }
+ }
- &.new-section {
- header {
- height: auto;
- @include clearfix();
- }
+ &.new-section {
+ header {
+ height: auto;
+ @include clearfix();
+ }
- .expand-collapse-icon {
- visibility: hidden;
- }
- }
+ .expand-collapse-icon {
+ visibility: hidden;
+ }
+ }
}
.toggle-button-sections {
- display: none;
- position: relative;
- float: right;
- margin-top: 10px;
+ display: none;
+ position: relative;
+ float: right;
+ margin-top: 10px;
- font-size: 13px;
- color: $darkGrey;
+ font-size: 13px;
+ color: $darkGrey;
- &.is-shown {
- display: block;
- }
+ &.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;
- }
+ .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;
- }
+ .label {
+ display: inline-block;
+ }
}
.new-section-name,
.new-subsection-name-input {
- width: 515px;
+ width: 515px;
}
.new-section-name-save,
.new-subsection-name-save {
- @include blue-button;
- padding: 4px 20px 7px;
- margin: 0 5px;
- color: #fff !important;
+ @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;
+ @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));
+ 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;
+ padding: 20px 40px;
- label {
- display: block;
- }
+ label {
+ display: block;
+ }
- input {
- width: 100%;
- font-size: 20px;
- }
+ input {
+ width: 100%;
+ font-size: 20px;
+ }
}
.preview {
- background: url(../img/preview.jpg) center top no-repeat;
+ 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;
+ display: none;
+ position: fixed;
+ top: 100px;
+ left: 50%;
+ z-index: 99999;
+ width: 600px;
+ margin-left: -300px;
+ background: #fff;
+ text-align: center;
- .settings {
- padding: 40px;
- }
+ .settings {
+ padding: 40px;
+ }
- h3 {
- font-size: 34px;
- font-weight: 300;
- }
+ h3 {
+ font-size: 34px;
+ font-weight: 300;
+ }
- .picker {
- margin: 30px 0 65px;
- }
+ .picker {
+ margin: 30px 0 65px;
+ }
- .description {
- margin-top: 30px;
- font-size: 14px;
- line-height: 20px;
- }
+ .description {
+ margin-top: 30px;
+ font-size: 14px;
+ line-height: 20px;
+ }
- strong {
- font-weight: 700;
- }
+ strong {
+ font-weight: 700;
+ }
- .start-date,
- .start-time {
- font-size: 19px;
- }
+ .start-date,
+ .start-time {
+ font-size: 19px;
+ }
- .save-button {
- @include blue-button;
- margin-right: 10px;
- }
+ .save-button {
+ @include blue-button;
+ margin-right: 10px;
+ }
- .cancel-button {
- @include white-button;
- }
+ .cancel-button {
+ @include white-button;
+ }
- .save-button,
- .cancel-button {
- font-size: 16px;
- }
+ .save-button,
+ .cancel-button {
+ font-size: 16px;
+ }
}
.collapse-all-button {
- float: right;
- margin-top: 10px;
- font-size: 13px;
- color: $darkGrey;
+ float: right;
+ margin-top: 10px;
+ font-size: 13px;
+ color: $darkGrey;
}
// sort/drag and drop
.ui-droppable {
- min-height: 20px;
+ @include transition (padding 0.5s ease-in-out 0s);
+ min-height: 20px;
+ padding: 0;
- &.dropover {
- padding-top: 10px;
- padding-bottom: 10px;
- }
+ &.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;
+ border-top: none;
}
-
-
diff --git a/cms/static/sass/_unit.scss b/cms/static/sass/_unit.scss
index 5199c92a0e..66a0dfaaa8 100644
--- a/cms/static/sass/_unit.scss
+++ b/cms/static/sass/_unit.scss
@@ -305,6 +305,7 @@
.wrapper-component-editor {
z-index: 9999;
position: relative;
+ background: $lightBluishGrey2;
}
.component-editor {
diff --git a/cms/templates/overview.html b/cms/templates/overview.html
index 522f52a1a6..f0a32bfda4 100644
--- a/cms/templates/overview.html
+++ b/cms/templates/overview.html
@@ -19,6 +19,7 @@
+