From e86b4a12dc4719558e3ce6203239c132f3fd47ba Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Fri, 28 Feb 2014 14:57:42 -0500 Subject: [PATCH] Changes for viewing built-in tabs in studio Changed "Status Page" -> "Page". UX: support for displaying built-in tabs restored drag and drop on Studio Pages additional styling for fixed state on Studio Pages add a new page action added to bottom of Studio Pages Dev changes for viewing tabs in studio, refactored the tab code, decoupled the code from django layer. is_hideable flag on tabs get_discussion method is needed to continue to support external_discussion links for now since used by 6.00x course. override the __eq__ operator to support comparing with dict-type tabs. Test moved test code to common, added acceptance test for built-in pages added additional unit tests for tabs. changed test_split_modulestore test to support serializing objects that are fields in a Course. Env: updated environment configuration settings so they are consistent for both cms and lms. --- CHANGELOG.rst | 2 + .../{static-pages.feature => pages.feature} | 19 +- .../features/{static-pages.py => pages.py} | 35 +- cms/djangoapps/contentstore/views/course.py | 17 +- cms/djangoapps/contentstore/views/tabs.py | 60 +- .../contentstore/views/tests/test_tabs.py | 12 +- cms/envs/acceptance.py | 4 + cms/envs/aws.py | 2 + cms/envs/common.py | 6 +- cms/envs/dev.py | 3 + cms/envs/test.py | 7 + cms/static/coffee/src/views/tabs.coffee | 7 +- cms/static/sass/views/_static-pages.scss | 179 ++-- cms/static/sass/views/_unit.scss | 2 +- cms/templates/edit-tabs.html | 60 +- cms/templates/export.html | 2 +- cms/templates/widgets/header.html | 2 +- common/lib/xmodule/xmodule/course_module.py | 39 +- common/lib/xmodule/xmodule/html_module.py | 2 +- .../xmodule/xmodule/modulestore/mongo/base.py | 26 +- .../tests/test_split_modulestore.py | 9 +- common/lib/xmodule/xmodule/modulestore/xml.py | 7 +- common/lib/xmodule/xmodule/tabs.py | 835 ++++++++++++++++++ common/lib/xmodule/xmodule/tests/test_tabs.py | 586 ++++++++++++ .../test/acceptance/pages/studio/edit_tabs.py | 6 +- common/test/acceptance/pages/studio/unit.py | 2 +- common/test/acceptance/tests/test_studio.py | 4 +- lms/djangoapps/courseware/tabs.py | 454 ---------- lms/djangoapps/courseware/tests/test_tabs.py | 374 +------- lms/djangoapps/courseware/views.py | 163 ++-- lms/djangoapps/instructor/views/legacy.py | 2 +- lms/envs/acceptance.py | 2 + lms/envs/aws.py | 3 - lms/envs/common.py | 3 + lms/envs/test.py | 3 +- .../courseware/course_navigation.html | 24 +- lms/templates/courseware/welcome-back.html | 28 - .../dashboard/_dashboard_course_listing.html | 5 +- lms/templates/help_modal.html | 6 +- 39 files changed, 1853 insertions(+), 1149 deletions(-) rename cms/djangoapps/contentstore/features/{static-pages.feature => pages.feature} (64%) rename cms/djangoapps/contentstore/features/{static-pages.py => pages.py} (71%) create mode 100644 common/lib/xmodule/xmodule/tabs.py create mode 100644 common/lib/xmodule/xmodule/tests/test_tabs.py delete mode 100644 lms/djangoapps/courseware/tabs.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3e67502877..b0ee9eecec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Studio: Support for viewing built-in tabs on the Pages page. STUD-1193 + Blades: Fixed bug when image mapped input's Show Answer multiplies rectangles on many inputtypes. BLD-810. diff --git a/cms/djangoapps/contentstore/features/static-pages.feature b/cms/djangoapps/contentstore/features/pages.feature similarity index 64% rename from cms/djangoapps/contentstore/features/static-pages.feature rename to cms/djangoapps/contentstore/features/pages.feature index 73b8a32095..22865e8ece 100644 --- a/cms/djangoapps/contentstore/features/static-pages.feature +++ b/cms/djangoapps/contentstore/features/pages.feature @@ -1,12 +1,11 @@ @shard_2 -Feature: CMS.Static Pages - As a course author, I want to be able to add static pages +Feature: CMS.Pages + As a course author, I want to be able to add pages Scenario: Users can add static pages - Given I have opened a new course in Studio - And I go to the static pages page + Given I have opened the pages page in a new course Then I should not see any static pages - When I add a new page + When I add a new static page Then I should see a static page named "Empty" Scenario: Users can delete static pages @@ -16,6 +15,10 @@ Feature: CMS.Static Pages When I confirm the prompt Then I should not see any static pages + Scenario: Users can see built-in pages + Given I have opened the pages page in a new course + Then I should see the default built-in pages + # Safari won't update the name properly @skip_safari Scenario: Users can edit static pages @@ -28,7 +31,7 @@ Feature: CMS.Static Pages @skip_safari Scenario: Users can reorder static pages Given I have created two different static pages - When I reorder the tabs - Then the tabs are in the reverse order + When I reorder the static tabs + Then the static tabs are in the reverse order And I reload the page - Then the tabs are in the reverse order + Then the static tabs are in the reverse order diff --git a/cms/djangoapps/contentstore/features/static-pages.py b/cms/djangoapps/contentstore/features/pages.py similarity index 71% rename from cms/djangoapps/contentstore/features/static-pages.py rename to cms/djangoapps/contentstore/features/pages.py index 091df51367..f4de3455da 100644 --- a/cms/djangoapps/contentstore/features/static-pages.py +++ b/cms/djangoapps/contentstore/features/pages.py @@ -1,11 +1,12 @@ # pylint: disable=C0111 # pylint: disable=W0621 +# pylint: disable=W0613 from lettuce import world, step from nose.tools import assert_equal # pylint: disable=E0611 -@step(u'I go to the static pages page$') +@step(u'I go to the pages page$') def go_to_static(step): menu_css = 'li.nav-course-courseware' static_css = 'li.nav-course-courseware-pages a' @@ -13,7 +14,7 @@ def go_to_static(step): world.css_click(static_css) -@step(u'I add a new page$') +@step(u'I add a new static page$') def add_page(step): button_css = 'a.new-button' world.css_click(button_css) @@ -32,6 +33,15 @@ def not_see_any_static_pages(step): assert (world.is_css_not_present(pages_css, wait_time=30)) +@step(u'I should see the default built-in pages') +def see_default_built_in_pages(step): + expected_pages = ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress'] + pages = world.css_find("div.course-nav-tab-header h3.title") + assert_equal(len(expected_pages), len(pages)) + for i, page_name in enumerate(expected_pages): + assert_equal(pages[i].text, page_name) + + @step(u'I "(edit|delete)" the static page$') def click_edit_or_delete(step, edit_or_delete): button_css = 'ul.component-actions a.%s-button' % edit_or_delete @@ -50,22 +60,27 @@ def change_name(step, new_name): world.css_click(save_button) -@step(u'I reorder the tabs') +@step(u'I reorder the static tabs') def reorder_tabs(_step): # For some reason, the drag_and_drop method did not work in this case. - draggables = world.css_find('.drag-handle') + draggables = world.css_find('.component .drag-handle') source = draggables.first target = draggables.last - source.action_chains.click_and_hold(source._element).perform() - source.action_chains.move_to_element_with_offset(target._element, 0, 50).perform() + source.action_chains.click_and_hold(source._element).perform() # pylint: disable=protected-access + source.action_chains.move_to_element_with_offset(target._element, 0, 50).perform() # pylint: disable=protected-access source.action_chains.release().perform() @step(u'I have created a static page') def create_static_page(step): + step.given('I have opened the pages page in a new course') + step.given('I add a new static page') + + +@step(u'I have opened the pages page in a new course') +def open_pages_page_in_new_course(step): step.given('I have opened a new course in Studio') - step.given('I go to the static pages page') - step.given('I add a new page') + step.given('I go to the pages page') @step(u'I have created two different static pages') @@ -73,12 +88,12 @@ def create_two_pages(step): step.given('I have created a static page') step.given('I "edit" the static page') step.given('I change the name to "First"') - step.given('I add a new page') + step.given('I add a new static page') # Verify order of tabs _verify_tab_names('First', 'Empty') -@step(u'the tabs are in the reverse order') +@step(u'the static tabs are in the reverse order') def tabs_in_reverse_order(step): _verify_tab_names('Empty', 'First') diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index cce91c60c5..b50cc7ee55 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -22,6 +22,7 @@ from edxmako.shortcuts import render_to_response from xmodule.error_module import ErrorDescriptor from xmodule.modulestore.django import modulestore, loc_mapper from xmodule.contentstore.content import StaticContent +from xmodule.tabs import PDFTextbookTabs from xmodule.modulestore.exceptions import ( ItemNotFoundError, InvalidLocationError) @@ -39,7 +40,6 @@ from util.json_request import expect_json from util.string_utils import _has_non_ascii_characters from .access import has_course_access -from .tabs import initialize_course_tabs from .component import ( OPEN_ENDED_COMPONENT_TYPES, NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY) @@ -411,8 +411,6 @@ def create_new_course(request): definition_data=overview_template.get('data') ) - initialize_course_tabs(new_course, request.user) - new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True) # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course # however, we can assume that b/c this user had authority to create the course, the user can add themselves @@ -657,8 +655,7 @@ def _config_course_advanced_components(request, course_module): 'open_ended': OPEN_ENDED_COMPONENT_TYPES, 'notes': NOTE_COMPONENT_TYPES, } - # Check to see if the user instantiated any notes or open ended - # components + # Check to see if the user instantiated any notes or open ended components for tab_type in tab_component_map.keys(): component_types = tab_component_map.get(tab_type) found_ac_type = False @@ -841,8 +838,8 @@ def textbooks_list_handler(request, tag=None, package_id=None, branch=None, vers textbook["id"] = tid tids.add(tid) - if not any(tab['type'] == 'pdf_textbooks' for tab in course.tabs): - course.tabs.append({"type": "pdf_textbooks"}) + if not any(tab['type'] == PDFTextbookTabs.type for tab in course.tabs): + course.tabs.append(PDFTextbookTabs()) course.pdf_textbooks = textbooks store.update_item(course, request.user.id) return JsonResponse(course.pdf_textbooks) @@ -858,10 +855,8 @@ def textbooks_list_handler(request, tag=None, package_id=None, branch=None, vers existing = course.pdf_textbooks existing.append(textbook) course.pdf_textbooks = existing - if not any(tab['type'] == 'pdf_textbooks' for tab in course.tabs): - tabs = course.tabs - tabs.append({"type": "pdf_textbooks"}) - course.tabs = tabs + if not any(tab['type'] == PDFTextbookTabs.type for tab in course.tabs): + course.tabs.append(PDFTextbookTabs()) store.update_item(course, request.user.id) resp = JsonResponse(textbook, status=201) resp["Location"] = locator.url_reverse('textbooks', textbook["id"]) diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index aa479e2e35..69da777bcf 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -5,6 +5,7 @@ from access import has_course_access from util.json_request import expect_json, JsonResponse from django.http import HttpResponseNotFound +from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django_future.csrf import ensure_csrf_cookie @@ -13,6 +14,7 @@ from edxmako.shortcuts import render_to_response from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import loc_mapper from xmodule.modulestore.locator import BlockUsageLocator +from xmodule.tabs import CourseTabList, StaticTab, CourseTab from ..utils import get_modulestore @@ -20,33 +22,6 @@ from django.utils.translation import ugettext as _ __all__ = ['tabs_handler'] - -def initialize_course_tabs(course, user): - """ - set up the default tabs - I've added this because when we add static tabs, the LMS either expects a None for the tabs list or - 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 - """ - - # This logic is repeated in xmodule/modulestore/tests/factories.py - # so if you change anything here, you need to also change it there. - course.tabs = [ - # Translators: "Courseware" is the title of the page where you access a course's videos and problems. - {"type": "courseware", "name": _("Courseware")}, - # Translators: "Course Info" is the name of the course's information and updates page - {"type": "course_info", "name": _("Course Info")}, - # Translators: "Discussion" is the title of the course forum page - {"type": "discussion", "name": _("Discussion")}, - # Translators: "Wiki" is the title of the course's wiki page - {"type": "wiki", "name": _("Wiki")}, - # Translators: "Progress" is the title of the student's grade information page - {"type": "progress", "name": _("Progress")}, - ] - - modulestore('direct').update_item(course, user.id) - @expect_json @login_required @ensure_csrf_cookie @@ -108,12 +83,12 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N reordered_tabs = [] static_tab_idx = 0 for tab in course_item.tabs: - if tab['type'] == 'static_tab': + if isinstance(tab, StaticTab): reordered_tabs.append( - {'type': 'static_tab', - 'name': tab_items[static_tab_idx].display_name, - 'url_slug': tab_items[static_tab_idx].location.name, - } + StaticTab( + name=tab_items[static_tab_idx].display_name, + url_slug=tab_items[static_tab_idx].location.name, + ) ) static_tab_idx += 1 else: @@ -126,19 +101,19 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N else: raise NotImplementedError('Creating or changing tab content is not supported.') elif request.method == 'GET': # assume html - # see tabs have been uninitialized (e.g. supporting courses created before tab support in studio) - if course_item.tabs is None or len(course_item.tabs) == 0: - initialize_course_tabs(course_item, request.user) - - # first get all static tabs from the tabs list + # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs # we do this because this is also the order in which items are displayed in the LMS - static_tabs_refs = [t for t in course_item.tabs if t['type'] == 'static_tab'] static_tabs = [] - for static_tab_ref in static_tabs_refs: - static_tab_loc = old_location.replace(category='static_tab', name=static_tab_ref['url_slug']) - static_tabs.append(modulestore('direct').get_item(static_tab_loc)) + built_in_tabs = [] + for tab in CourseTabList.iterate_displayable(course_item, settings, include_instructor_tab=False): + if isinstance(tab, StaticTab): + static_tab_loc = old_location.replace(category='static_tab', name=tab.url_slug) + static_tabs.append(modulestore('direct').get_item(static_tab_loc)) + else: + built_in_tabs.append(tab) + # create a list of components for each static tab components = [ loc_mapper().translate_location( course_item.location.course_id, static_tab.location, False, True @@ -149,6 +124,7 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N return render_to_response('edit-tabs.html', { 'context_course': course_item, + 'built_in_tabs': built_in_tabs, 'components': components, 'course_locator': locator }) @@ -183,7 +159,7 @@ def primitive_delete(course, num): def primitive_insert(course, num, tab_type, name): "Inserts a new tab at the given number (0 based)." validate_args(num, tab_type) - new_tab = {u'type': unicode(tab_type), u'name': unicode(name)} + new_tab = CourseTab.from_json({u'type': unicode(tab_type), u'name': unicode(name)}) tabs = course.tabs tabs.insert(num, new_tab) modulestore('direct').update_item(course, '**replace_user**') diff --git a/cms/djangoapps/contentstore/views/tests/test_tabs.py b/cms/djangoapps/contentstore/views/tests/test_tabs.py index f1cf8ddfa5..0a8bb1198b 100644 --- a/cms/djangoapps/contentstore/views/tests/test_tabs.py +++ b/cms/djangoapps/contentstore/views/tests/test_tabs.py @@ -20,22 +20,22 @@ class PrimitiveTabEdit(TestCase): tabs.primitive_delete(course, 6) tabs.primitive_delete(course, 2) self.assertFalse({u'type': u'textbooks'} in course.tabs) - # Check that discussion has shifted down + # Check that discussion has shifted up self.assertEquals(course.tabs[2], {'type': 'discussion', 'name': 'Discussion'}) def test_insert(self): """Test primitive tab insertion.""" course = CourseFactory.create(org='edX', course='999') - tabs.primitive_insert(course, 2, 'atype', 'aname') - self.assertEquals(course.tabs[2], {'type': 'atype', 'name': 'aname'}) + tabs.primitive_insert(course, 2, 'notes', 'aname') + self.assertEquals(course.tabs[2], {'type': 'notes', 'name': 'aname'}) with self.assertRaises(ValueError): - tabs.primitive_insert(course, 0, 'atype', 'aname') + tabs.primitive_insert(course, 0, 'notes', 'aname') with self.assertRaises(ValueError): tabs.primitive_insert(course, 3, 'static_tab', 'aname') def test_save(self): """Test course saving.""" course = CourseFactory.create(org='edX', course='999') - tabs.primitive_insert(course, 3, 'atype', 'aname') + tabs.primitive_insert(course, 3, 'notes', 'aname') course2 = get_course_by_id(course.id) - self.assertEquals(course2.tabs[3], {'type': 'atype', 'name': 'aname'}) + self.assertEquals(course2.tabs[3], {'type': 'notes', 'name': 'aname'}) diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index b47596c9c5..fda0125fa1 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -89,6 +89,10 @@ STATICFILES_FINDERS += ('pipeline.finders.PipelineFinder', ) # Use the auto_auth workflow for creating users and logging them in FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True +# For consistency in user-experience, keep the value of this setting in sync with +# the one in lms/envs/acceptance.py +FEATURES['ENABLE_DISCUSSION_SERVICE'] = True + # HACK # Setting this flag to false causes imports to not load correctly in the lettuce python files # We do not yet understand why this occurs. Setting this to true is a stopgap measure diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 383d6925e0..9cd435b7e0 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -168,6 +168,8 @@ ENV_FEATURES = ENV_TOKENS.get('FEATURES', ENV_TOKENS.get('MITX_FEATURES', {})) for feature, value in ENV_FEATURES.items(): FEATURES[feature] = value +WIKI_ENABLED = ENV_TOKENS.get('WIKI_ENABLED', WIKI_ENABLED) + LOGGING = get_logger_config(LOG_DIR, logging_env=ENV_TOKENS['LOGGING_ENV'], syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514), diff --git a/cms/envs/common.py b/cms/envs/common.py index abf6210bec..3b6adcb9e2 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -28,7 +28,7 @@ import imp import sys import lms.envs.common from lms.envs.common import ( - USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, ALL_LANGUAGES + USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, ALL_LANGUAGES, WIKI_ENABLED ) from path import path @@ -43,7 +43,9 @@ FEATURES = { 'GITHUB_PUSH': False, - 'ENABLE_DISCUSSION_SERVICE': False, + # for consistency in user-experience, keep the value of this setting in sync with the + # one in lms/envs/common.py + 'ENABLE_DISCUSSION_SERVICE': True, 'AUTH_USE_CERTIFICATES': False, diff --git a/cms/envs/dev.py b/cms/envs/dev.py index cd3e2067f4..c417a96ee8 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -8,6 +8,9 @@ This config file runs the simplest dev environment""" from .common import * from logsettings import get_logger_config +# import settings from LMS for consistent behavior with CMS +from lms.envs.dev import (WIKI_ENABLED) + DEBUG = True TEMPLATE_DEBUG = DEBUG LOGGING = get_logger_config(ENV_ROOT / "log", diff --git a/cms/envs/test.py b/cms/envs/test.py index 64887e312c..4171dc303f 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -17,6 +17,9 @@ import os from path import path from warnings import filterwarnings +# import settings from LMS for consistent behavior with CMS +from lms.envs.test import (WIKI_ENABLED) + # Nose Test Runner TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' @@ -222,3 +225,7 @@ MICROSITE_CONFIGURATION = { } MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_microsites' FEATURES['USE_MICROSITES'] = True + +# For consistency in user-experience, keep the value of this setting in sync with +# the one in lms/envs/test.py +FEATURES['ENABLE_DISCUSSION_SERVICE'] = False diff --git a/cms/static/coffee/src/views/tabs.coffee b/cms/static/coffee/src/views/tabs.coffee index 83ca7dc2fe..202ea6c6c7 100644 --- a/cms/static/coffee/src/views/tabs.coffee +++ b/cms/static/coffee/src/views/tabs.coffee @@ -17,6 +17,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views ) @options.mast.find('.new-tab').on('click', @addNewTab) + $('.add-pages .new-tab').on('click', @addNewTab) @$('.components').sortable( handle: '.drag-handle' update: @tabMoved @@ -34,7 +35,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views tabs.push($(element).data('locator')) ) - analytics.track "Reordered Static Pages", + analytics.track "Reordered Pages", course: course_location_analytics saving = new NotificationView.Mini({title: gettext("Saving…")}) @@ -68,7 +69,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views {category: 'static_tab'} ) - analytics.track "Added Static Page", + analytics.track "Added Page", course: course_location_analytics deleteTab: (event) => @@ -82,7 +83,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views view.hide() $component = $(event.currentTarget).parents('.component') - analytics.track "Deleted Static Page", + analytics.track "Deleted Page", course: course_location_analytics id: $component.data('locator') deleting = new NotificationView.Mini diff --git a/cms/static/sass/views/_static-pages.scss b/cms/static/sass/views/_static-pages.scss index 2d7c458323..cbe71bb973 100644 --- a/cms/static/sass/views/_static-pages.scss +++ b/cms/static/sass/views/_static-pages.scss @@ -1,23 +1,24 @@ -// studio - views - course static pages +// studio - views - course pages // ==================== .view-static-pages { - .new-static-page-button { - @include grey-button; - display: block; - text-align: center; - padding: 12px 0; + // page structure + .content-primary, + .content-supplementary { + @include box-sizing(border-box); + float: left; } .content-primary { width: flex-grid(9, 12); margin-right: flex-gutter(); - .no-pages-content { + .add-pages { @extend %ui-well; - padding: ($baseline*2); + margin: ($baseline*1.5) 0; background-color: $gray-l4; + padding: ($baseline*2); text-align: center; color: $gray; @@ -30,90 +31,96 @@ } } } + } - .actions-list-wrap { - top: 6px; + .content-supplementary { + width: flex-grid(3, 12); + } - .actions-list { + .wrapper-actions-list { + top: 6px; - .action-item { - position: relative; + .actions-list { + + .action-item { + position: relative; + display: inline-block; + min-width: ($baseline*1.5); + margin: 0; + text-align: center; + + .action-button, + .toggle-actions-view { + @include transition(all $tmg-f2 ease-in-out 0s); display: inline-block; - margin: 0; - text-align: center; + border: 0; + background: none; + color: $gray-l3; - .action-button, - .toggle-actions-view { - @include transition(all $tmg-f2 ease-in-out 0s); - display: inline-block; - border: 0; - background: none; - color: $gray-l3; - - &:hover { - background-color: $blue; - color: $gray-l6; - } - } - - &.action-visible { - position: relative; - } - - &.action-visible label { - position: absolute; - top: 0; - right: 0; - height: 30px; - width: 30px; - - &:hover { - background-color: $blue; - } - } - - &.action-visible .toggle-checkbox { - position: absolute; - top: 0; - right: 0; - height: 30px; - width: 30px; - opacity: 0; - } - - &.action-visible .toggle-checkbox:hover ~ .action-button { + &:hover { background-color: $blue; color: $gray-l6; } + } - &.action-visible .toggle-checkbox ~ .action-button { - .icon-eye-open { - display: inline-block; - } + &.action-visible { + position: relative; + } - .icon-eye-close { - display: none; - } + &.action-visible label { + position: absolute; + top: 0; + right: 0; + height: 30px; + width: 30px; + + &:hover { + background-color: $blue; + } + } + + &.action-visible .toggle-checkbox { + position: absolute; + top: 0; + right: 0; + height: 30px; + width: 30px; + opacity: 0; + } + + &.action-visible .toggle-checkbox:hover ~ .action-button, + &.action-visible .toggle-checkbox:checked:hover ~ .action-button { + background-color: $blue; + color: $gray-l6; + } + + &.action-visible .toggle-checkbox ~ .action-button { + .icon-eye-open { + display: inline-block; } - &.action-visible .toggle-checkbox:checked ~ .action-button { - background-color: $gray; - color: $white; + .icon-eye-close { + display: none; + } + } - .icon-eye-open { - display: none; - } + &.action-visible .toggle-checkbox:checked ~ .action-button { + background-color: $gray; + color: $white; - .icon-eye-close { - display: inline-block; - } + .icon-eye-open { + display: none; + } + + .icon-eye-close { + display: inline-block; } } } } - } + .unit-body { padding: 0; @@ -209,6 +216,12 @@ &:hover { background: url(../img/drag-handles.png) center no-repeat #fff; } + + &.is-fixed { + cursor: default; + width: ($baseline*1.5); + background: $gray-l4 none; + } } // uses similar styling as assets.scss, unit.scss @@ -229,14 +242,14 @@ .course-nav-tab-actions { display: inline-block; float: right; - margin-right: $baseline*2; + margin-right: ($baseline*2); padding: 8px 0px; vertical-align: middle; text-align: center; .action-item { display: inline-block; - margin: ($baseline/4) 0 ($baseline/4) ($baseline/4); + margin: ($baseline/4) 0 ($baseline/4) ($baseline/2); .action-button { @include transition(all $tmg-f2 ease-in-out 0s); @@ -275,27 +288,33 @@ } } - // basic course nav items + // basic course nav items - overrides from above .course-nav-tab { - padding: ($baseline*.75) $baseline; + padding: ($baseline*.75) ($baseline/4) ($baseline*.75) $baseline; - &.locked { - background-color: $gray-l6; + &.fixed { + @include transition(opacity $tmg-f2 ease-in-out 0s); + opacity: .7; + + &:hover { + opacity: 1; + } } .course-nav-tab-header { display: inline-block; - max-width: 80%; + width:80%; .title { @extend %t-title4; font-weight: 300; + color: $gray; } } .course-nav-tab-actions { + display: inline-block; padding: ($baseline/10); - margin-right: ($baseline*1.5); } } diff --git a/cms/static/sass/views/_unit.scss b/cms/static/sass/views/_unit.scss index 0a315e6799..f96a674638 100644 --- a/cms/static/sass/views/_unit.scss +++ b/cms/static/sass/views/_unit.scss @@ -487,7 +487,7 @@ body.course.unit,.view-unit { margin-bottom: 0px; } - // Module Actions, also used for Static Pages + // Module Actions, also used for Pages .module-actions { box-shadow: inset 0 1px 2px $shadow; border-top: 1px solid $gray-l1; diff --git a/cms/templates/edit-tabs.html b/cms/templates/edit-tabs.html index 9cf9fc6b3d..3bf242929d 100644 --- a/cms/templates/edit-tabs.html +++ b/cms/templates/edit-tabs.html @@ -1,10 +1,10 @@ +<%inherit file="base.html" /> <%namespace name='static' file='static_content.html'/> <%! from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse %> -<%inherit file="base.html" /> -<%block name="title">Pages +<%block name="title">${_("Pages")} <%block name="bodyclass">is-signedin course view-static-pages <%block name="jsextra"> @@ -31,6 +31,7 @@

${_("Content")} + ## Translators: Pages refer to the tabs that appear in the top navigation of each course. > ${_("Pages")}

@@ -53,27 +54,34 @@
-
    +
      - -
    1. -
      -

      Wiki

      -
      -
      -
        -
      • - - -
        -
      • -
      -
      -
    2. - + % for tab in built_in_tabs: +
    3. +
      +

      ${_(tab.name)}

      +
      +
      +
        + + % if tab.is_hideable: +
      • + + +
        +
      • + %endif + +
      +
      +
      + ${_("Fixed page")} +
      +
    4. + % endfor % for locator in components: -
    5. +
    6. % endfor
    7. @@ -81,6 +89,10 @@
+ +
+

${_("You can add additional custom pages to your course.")} ${_("Add a New Page")}

+
@@ -88,7 +100,7 @@
<% -discussion_link = get_discussion_link(course) if course else None + discussion_tab = CourseTabList.get_discussion(course) if course else None + discussion_link = discussion_tab.link_func(course, reverse) if (discussion_tab and discussion_tab.can_display(course, settings, True, True)) else None %> % if discussion_link: