Commit a3590653 by Andy Armstrong Committed by cahrens

Integrate visual styling into the course outline

parent c027f90d
......@@ -203,8 +203,8 @@ def create_a_course():
def add_section():
world.css_click('.course-outline .add-button')
assert_true(world.is_css_present('.outline-item-section .xblock-field-value'))
world.css_click('.outline .button-new')
assert_true(world.is_css_present('.outline-section .xblock-field-value'))
def set_date_and_time(date_css, desired_date, time_css, desired_time, key=None):
......@@ -241,7 +241,7 @@ def create_unit_from_course_outline():
The end result is the page where the user is editing the new unit.
"""
css_selectors = [
'.outline-item-subsection .expand-collapse', '.outline-item-subsection .add-button'
'.outline-subsection .expand-collapse', '.outline-subsection .button-new'
]
for selector in css_selectors:
world.css_click(selector)
......
......@@ -69,7 +69,7 @@ def i_add_a_section(step):
@step(u'I press the "section" delete icon')
def i_press_the_section_delete_icon(step):
delete_locator = 'section .outline-item-section > .wrapper-xblock-header a.delete-button'
delete_locator = 'section .outline-section > .wrapper-xblock-header a.delete-button'
world.css_click(delete_locator)
......@@ -110,9 +110,9 @@ def i_click_the_collapse_expand_all_span(step, text):
@step(u'I ([^"]*) the first section$')
def i_collapse_expand_a_section(step, text):
if text == "collapse":
locator = 'section .outline-item-section .ui-toggle-expansion'
locator = 'section .outline-section .ui-toggle-expansion'
elif text == "expand":
locator = 'section .outline-item-section .ui-toggle-expansion'
locator = 'section .outline-section .ui-toggle-expansion'
world.css_click(locator)
......
......@@ -66,5 +66,5 @@ def i_am_on_tab(step, tab_name):
@step('I see a link for adding a new section$')
def i_see_new_section_link(step):
link_css = '.course-outline .add-button'
link_css = '.outline .button-new'
assert world.css_has_text(link_css, 'New Section')
......@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase):
resp = self._show_course_overview(course.id)
self.assertContains(
resp,
'<article class="course-outline" data-locator="{locator}" data-course-key="{course_key}">'.format(
'<article class="outline" data-locator="{locator}" data-course-key="{course_key}">'.format(
locator='i4x://MITx/999/course/Robot_Super_Course',
course_key='MITx/999/Robot_Super_Course',
),
......
......@@ -9,7 +9,7 @@ from django.test.client import Client
from django.contrib.auth.models import User
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import PublishState, ModuleStoreEnum
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum, mongo
from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -151,16 +151,16 @@ class CourseTestCase(ModuleStoreTestCase):
# create a Draft vertical
vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1)
draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id)
self.assertEqual(self.store.compute_publish_state(draft_vertical), PublishState.draft)
self.assertEqual(self.store.compute_publish_state(draft_vertical), LegacyPublishState.draft)
# create a Private (draft only) vertical
private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL)
self.assertEqual(self.store.compute_publish_state(private_vertical), PublishState.private)
self.assertEqual(self.store.compute_publish_state(private_vertical), LegacyPublishState.private)
# create a Published (no draft) vertical
public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL)
public_vertical = self.store.publish(public_vertical.location, self.user.id)
self.assertEqual(self.store.compute_publish_state(public_vertical), PublishState.public)
self.assertEqual(self.store.compute_publish_state(public_vertical), LegacyPublishState.public)
# add the new private and new public as children of the sequential
sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL))
......@@ -197,7 +197,7 @@ class CourseTestCase(ModuleStoreTestCase):
def verify_item_publish_state(item, publish_state):
"""Verifies the publish state of the item is as expected."""
if publish_state in (PublishState.private, PublishState.draft):
if publish_state in (LegacyPublishState.private, LegacyPublishState.draft):
self.assertTrue(getattr(item, 'is_draft', False))
else:
self.assertFalse(getattr(item, 'is_draft', False))
......@@ -210,18 +210,18 @@ class CourseTestCase(ModuleStoreTestCase):
return item
# verify that the draft vertical is draft
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, PublishState.draft)
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, LegacyPublishState.draft)
for child in vertical.get_children():
verify_item_publish_state(child, PublishState.draft)
verify_item_publish_state(child, LegacyPublishState.draft)
# make sure that we don't have a sequential that is not in draft mode
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, PublishState.public)
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, LegacyPublishState.public)
# verify that we have the private vertical
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, PublishState.private)
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, LegacyPublishState.private)
# verify that we have the public vertical
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, PublishState.public)
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, LegacyPublishState.public)
# verify verticals are children of sequential
for vert in [vertical, private_vertical, public_vertical]:
......@@ -332,7 +332,7 @@ class CourseTestCase(ModuleStoreTestCase):
it'll return public in that case
"""
supposed_state = self.store.compute_publish_state(item)
if supposed_state == PublishState.draft and isinstance(item.runtime.modulestore, DraftModuleStore):
if supposed_state == LegacyPublishState.draft and isinstance(item.runtime.modulestore, DraftModuleStore):
# see if the draft differs from the published
published = self.store.get_item(item.location, revision=ModuleStoreEnum.RevisionOption.published_only)
if item.get_explicitly_set_fields_by_scope() != published.get_explicitly_set_fields_by_scope():
......@@ -345,13 +345,13 @@ class CourseTestCase(ModuleStoreTestCase):
# checking children: if published differs from item, return draft
return supposed_state
# published == item in all respects, so return public
return PublishState.public
elif supposed_state == PublishState.public and item.location.category in DIRECT_ONLY_CATEGORIES:
return LegacyPublishState.public
elif supposed_state == LegacyPublishState.public and item.location.category in DIRECT_ONLY_CATEGORIES:
if not all([
self.store.has_item(child_loc, revision=ModuleStoreEnum.RevisionOption.draft_only)
for child_loc in item.children
]):
return PublishState.draft
return LegacyPublishState.draft
else:
return supposed_state
else:
......
......@@ -155,10 +155,10 @@ def compute_publish_state(xblock):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
LegacyPublishState.public - content is locked and deployed to LMS
LegacyPublishState.private - content is editable and not deployed to LMS
"""
return modulestore().compute_publish_state(xblock)
......
......@@ -12,7 +12,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from edxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import PublishState
from xmodule.modulestore import LegacyPublishState
from xblock.core import XBlock
from xblock.django.request import webob_to_django_response, django_to_webob_request
......@@ -101,7 +101,7 @@ def subsection_handler(request, usage_key_string):
subsection_units = item.get_children()
for unit in subsection_units:
state = compute_publish_state(unit)
if state in (PublishState.public, PublishState.draft):
if state in (LegacyPublishState.public, LegacyPublishState.draft):
can_view_live = True
break
......
......@@ -404,7 +404,7 @@ def course_outline_initial_state(locator_to_show, course_structure):
"""
if xblock_info['id'] == locator:
return xblock_info
children = xblock_info['child_info']['children'] if xblock_info['child_info'] else None
children = xblock_info['child_info']['children'] if xblock_info.get('child_info', None) else None
if children:
for child_xblock_info in children:
result = find_xblock_info(child_xblock_info, locator)
......@@ -417,7 +417,7 @@ def course_outline_initial_state(locator_to_show, course_structure):
Collect all the locators for an xblock and its children.
"""
locators.append(xblock_info['id'])
children = xblock_info['child_info']['children'] if xblock_info['child_info'] else None
children = xblock_info['child_info']['children'] if xblock_info.get('child_info', None) else None
if children:
for child_xblock_info in children:
collect_all_locators(locators, child_xblock_info)
......
......@@ -601,10 +601,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
In addition, an optional include_children_predicate argument can be provided to define whether or
not a particular xblock should have its children included.
"""
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
def safe_get_username(user_id):
"""
......@@ -623,11 +619,23 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
return None
# Compute the child info first so it can be included in aggregate information for the parent
if include_child_info and xblock.has_children:
child_info = _create_xblock_child_info(
xblock, include_children_predicate=include_children_predicate
)
else:
child_info = None
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
currently_visible_to_students = is_currently_visible_to_students(xblock)
xblock_info = {
"id": unicode(xblock.location),
"display_name": xblock.display_name_with_default,
"category": xblock.category,
"has_changes": modulestore().has_changes(xblock.location),
"published": published,
"edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None,
"edited_by": safe_get_username(xblock.subtree_edited_by),
......@@ -637,8 +645,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"released_to_students": datetime.now(UTC) > xblock.start,
"release_date": release_date,
"release_date_from": _get_release_date_from(xblock) if release_date else None,
"visible_to_staff_only": xblock.visible_to_staff_only,
"currently_visible_to_students": is_currently_visible_to_students(xblock),
"currently_visible_to_students": currently_visible_to_students,
"publish_state": _compute_publish_state(xblock, child_info) if not xblock.category == 'course' else None
}
if data is not None:
xblock_info["data"] = data
......@@ -646,13 +654,70 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info["metadata"] = metadata
if include_ancestor_info:
xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock)
if include_child_info and xblock.has_children:
xblock_info['child_info'] = _create_xblock_child_info(
xblock, include_children_predicate=include_children_predicate
)
if child_info:
xblock_info['child_info'] = child_info
return xblock_info
class PublishState(object):
"""
Represents the possible publish states for an xblock:
live - the block and all of its children are live to students (except for staff only items)
ready - the block and all of its children are ready to go live in the future
unscheduled - the block and all of its children are unscheduled
has_unpublished_content - the block or its children have unpublished content that is not staff only
staff_only - all of the block's content is to be shown to staff only
"""
live = 'live'
ready = 'ready'
unscheduled = 'unscheduled'
has_unpublished_content = 'has_unpublished_content'
staff_only = 'staff_only'
def _compute_publish_state(xblock, child_info):
"""
Returns the current publish state for the specified xblock and its children
"""
if xblock.visible_to_staff_only:
return PublishState.staff_only
elif is_unit(xblock) and modulestore().has_changes(xblock.location):
return PublishState.has_unpublished_content
is_unscheduled = xblock.start == DEFAULT_START_DATE
children = child_info and child_info['children']
if children and len(children) > 0:
all_staff_only = True
all_unscheduled = True
all_live = True
for child in child_info['children']:
child_state = child['publish_state']
if child_state == PublishState.has_unpublished_content:
return child_state
elif not child_state == PublishState.staff_only:
all_staff_only = False
if not child_state == PublishState.unscheduled:
all_unscheduled = False
if not child_state == PublishState.live:
all_live = False
if all_staff_only:
return PublishState.staff_only
elif all_unscheduled:
if not is_unscheduled:
return PublishState.has_unpublished_content
else:
return PublishState.unscheduled
elif all_live:
return PublishState.live
else:
return PublishState.ready
if is_unscheduled:
return PublishState.unscheduled
elif datetime.now(UTC) > xblock.start:
return PublishState.live
else:
return PublishState.ready
def _create_xblock_ancestor_info(xblock):
"""
Returns information about the ancestors of an xblock. Note that the direct parent will also return
......
......@@ -10,6 +10,7 @@ from contentstore.views.access import has_course_access
from contentstore.views.course import course_outline_initial_state
from course_action_state.models import CourseRerunState
from contentstore.views.item import create_xblock_info
from contentstore.views.item import create_xblock_info, PublishState
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from opaque_keys.edx.locator import CourseLocator
......@@ -229,7 +230,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(json_response['category'], 'course')
self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(json_response['display_name'], 'Robot Super Course')
self.assertTrue(json_response['published'])
self.assertIsNone(json_response['publish_state'])
# Now verify the first child
children = json_response['child_info']['children']
......@@ -238,7 +239,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(first_child_response['category'], 'chapter')
self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(first_child_response['display_name'], 'Week 1')
self.assertTrue(first_child_response['published'])
self.assertEqual(first_child_response['publish_state'], PublishState.unscheduled)
self.assertTrue(len(first_child_response['child_info']['children']) > 0)
# Finally, validate the entire response for consistency
......@@ -251,7 +252,6 @@ class TestCourseOutline(CourseTestCase):
self.assertIsNotNone(json_response['display_name'])
self.assertIsNotNone(json_response['id'])
self.assertIsNotNone(json_response['category'])
self.assertIsNotNone(json_response['published'])
if json_response.get('child_info', None):
for child_response in json_response['child_info']['children']:
self.assert_correct_json_response(child_response)
......
......@@ -25,21 +25,6 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"ancestor_info": null,
/**
* True iff:
* 1) Edits have been made to the xblock and no published version exists.
* 2) Edits have been made to the xblock since the last published version.
*/
"has_changes": null,
/**
* True iff a published version of the xblock exists.
*/
"published": null,
/**
* If true, only course staff can see the xblock regardless of publish status or
* release date status.
*/
"visible_to_staff_only": null,
/**
* Date of the last edit to this xblock or any of its descendants.
*/
"edited_on":null,
......@@ -48,6 +33,10 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"edited_by":null,
/**
* True iff a published version of the xblock exists.
*/
"published": null,
/**
* Date of the last publish of this xblock, or null if never published.
*/
"published_on": null,
......@@ -56,6 +45,15 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"published_by": null,
/**
* Represents the possible publish states for an xblock:
* is_live - the block and all of its children are live to students (except for staff only items)
* is_ready - the block and all of its children are ready to go live in the future
* unscheduled - the block and all of its children are unscheduled
* has_unpublished_content - the block or its children have unpublished content that is not staff only
* is_staff_only - all of the block's content is to be shown to staff only
*/
"publish_state": null,
/**
* True iff the release date of the xblock is in the past.
*/
"released_to_students": null,
......
......@@ -25,12 +25,14 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
category: 'vertical',
display_name: displayName,
studio_url: '/container/mock-unit',
publish_state: 'unscheduled',
ancestor_info: {
ancestors: [{
id: 'mock-subsection',
category: 'sequential',
display_name: 'Mock Subsection',
studio_url: '/course/mock-course?show=mock-subsection',
publish_state: 'unscheduled',
child_info: {
category: 'vertical',
display_name: 'Unit',
......@@ -38,24 +40,28 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
id: 'mock-unit',
category: 'vertical',
display_name: displayName,
studio_url: '/container/mock-unit'
studio_url: '/container/mock-unit',
publish_state: 'unscheduled'
}, {
id: 'mock-unit-2',
category: 'vertical',
display_name: 'Mock Unit 2',
studio_url: '/container/mock-unit-2'
studio_url: '/container/mock-unit-2',
publish_state: 'unscheduled'
}]
}
}, {
id: 'mock-section',
category: 'chapter',
display_name: 'Section',
studio_url: '/course/slashes:mock-course?show=mock-section'
studio_url: '/course/slashes:mock-course?show=mock-section',
publish_state: 'unscheduled'
}, {
id: 'mock-course',
category: 'course',
display_name: 'Mock Course',
studio_url: '/course/mock-course'
studio_url: '/course/mock-course',
publish_state: 'unscheduled'
}]
},
metadata: {
......@@ -77,16 +83,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it('can render itself', function() {
createUnitOutlineView(this, createMockXBlockInfo('Mock Unit'));
expect(unitOutlineView.$('.sortable-course-list')).toExist();
expect(unitOutlineView.$('.sortable-section-list')).toExist();
expect(unitOutlineView.$('.sortable-subsection-list')).toExist();
expect(unitOutlineView.$('.list-sections')).toExist();
expect(unitOutlineView.$('.list-subsections')).toExist();
expect(unitOutlineView.$('.list-units')).toExist();
});
it('can add a unit', function() {
var redirectSpy;
createUnitOutlineView(this, createMockXBlockInfo('Mock Unit'));
redirectSpy = spyOn(ViewUtils, 'redirect');
unitOutlineView.$('.outline-item-subsection > .add-xblock-component .add-button').click();
unitOutlineView.$('.outline-subsection > .outline-content > .add-unit .button-new').click();
create_sinon.expectJsonRequest(requests, 'POST', '/xblock/', {
category: 'vertical',
display_name: 'Unit',
......@@ -106,8 +112,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/mock-unit');
create_sinon.respondWithJson(requests,
createMockXBlockInfo(updatedDisplayName));
unitHeader = unitOutlineView.$('.outline-item-unit .wrapper-xblock-header');
expect(unitHeader.find('.xblock-title').first().text().trim()).toBe(updatedDisplayName);
expect(unitOutlineView.$('.outline-unit .unit-title').first().text().trim()).toBe(updatedDisplayName);
});
});
});
......@@ -16,6 +16,10 @@ define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_b
"click .ui-toggle-expansion": "toggleExpandCollapse"
},
options: {
collapsedClass: 'collapsed'
},
//override the constructor function
constructor: function(options) {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
......@@ -48,7 +52,7 @@ define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_b
// this element, e.g. clicking on the element of a child view container in a parent.
event.stopPropagation();
event.preventDefault();
ViewUtils.toggleExpandCollapse(target);
ViewUtils.toggleExpandCollapse(target, this.options.collapsedClass);
},
/**
......
......@@ -12,6 +12,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
var XBlockContainerPage = BasePage.extend({
// takes XBlockInfo as a model
options: {
collapsedClass: 'is-collapsed'
},
view: 'container_preview',
initialize: function(options) {
......
......@@ -7,7 +7,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var disabledCss = "is-disabled";
/**
* A view that calls render when "has_changes" or "published" values in XBlockInfo have changed
* A view that refreshes the view when certain values in the XBlockInfo have changed
* after a server sync operation.
*/
var ContainerStateListenerView = BaseView.extend({
......@@ -53,19 +53,20 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
*/
var PreviewActionController = ContainerStateListenerView.extend({
shouldRefresh: function(model) {
return ViewUtils.hasChangedAttributes(model, ['has_changes', 'published']);
return ViewUtils.hasChangedAttributes(model, ['edited_on', 'published_on', 'publish_state']);
},
render: function() {
var previewAction = this.$el.find('.button-preview'),
viewLiveAction = this.$el.find('.button-view');
if (this.model.get('published')) {
viewLiveAction = this.$el.find('.button-view'),
publishState = this.model.get('publish_state');
if (publishState !== 'unscheduled') {
viewLiveAction.removeClass(disabledCss);
}
else {
viewLiveAction.addClass(disabledCss);
}
if (this.model.get('has_changes') || !this.model.get('published')) {
if (publishState !== 'live' && publishState !== 'ready') {
previewAction.removeClass(disabledCss);
}
else {
......@@ -98,25 +99,22 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync: function(model) {
if (ViewUtils.hasChangedAttributes(model, [
'has_changes', 'published', 'edited_on', 'edited_by', 'visible_to_staff_only'
])) {
if (ViewUtils.hasChangedAttributes(model, [ 'edited_on', 'published_on', 'publish_state' ])) {
this.render();
}
},
render: function () {
this.$el.html(this.template({
hasChanges: this.model.get('has_changes'),
published: this.model.get('published'),
publishState: this.model.get('publish_state'),
editedOn: this.model.get('edited_on'),
editedBy: this.model.get('edited_by'),
published: this.model.get('published'),
publishedOn: this.model.get('published_on'),
publishedBy: this.model.get('published_by'),
releasedToStudents: this.model.get('released_to_students'),
releaseDate: this.model.get('release_date'),
releaseDateFrom: this.model.get('release_date_from'),
visibleToStaffOnly: this.model.get('visible_to_staff_only')
releaseDateFrom: this.model.get('release_date_from')
}));
return this;
......@@ -138,7 +136,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
discardChanges: function (e) {
var xblockInfo = this.model, that=this, renderPage = this.renderPage;
var xblockInfo = this.model, renderPage = this.renderPage;
if (e && e.preventDefault) {
e.preventDefault();
}
......@@ -164,7 +162,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
if (e && e.preventDefault) {
e.preventDefault();
}
enableStaffLock = !xblockInfo.get('visible_to_staff_only');
enableStaffLock = xblockInfo.get('publish_state') !== 'staff_only';
revertCheckBox = function() {
self.checkStaffLock(!enableStaffLock);
......@@ -223,7 +221,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync: function(model) {
if (ViewUtils.hasChangedAttributes(model, ['published', 'published_on', 'published_by'])) {
if (ViewUtils.hasChangedAttributes(model, ['published_on'])) {
this.render();
}
},
......
......@@ -8,14 +8,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// takes XBlockInfo as a model
events: {
"click .toggle-button-expand-collapse": "toggleExpandCollapse"
"click .button-toggle-expand-collapse": "toggleExpandCollapse"
},
options: {
collapsedClass: 'is-collapsed'
},
initialize: function() {
var self = this;
this.initialState = this.options.initialState;
BasePage.prototype.initialize.call(this);
this.$('.add-button').click(function(event) {
this.$('.button-new').click(function(event) {
self.outlineView.handleAddEvent(event);
});
this.model.on('change', this.setCollapseExpandVisibility, this);
......@@ -23,19 +27,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
setCollapseExpandVisibility: function() {
var has_content = this.hasContent(),
collapseExpandButton = $('.toggle-button-expand-collapse');
collapseExpandButton = $('.button-toggle-expand-collapse');
if (has_content) {
collapseExpandButton.show();
collapseExpandButton.removeClass('is-hidden');
} else {
collapseExpandButton.hide();
collapseExpandButton.addClass('is-hidden');
}
},
renderPage: function() {
var locatorToShow;
this.setCollapseExpandVisibility();
this.outlineView = new CourseOutlineView({
el: this.$('.course-outline'),
el: this.$('.outline'),
model: this.model,
isRoot: true,
initialState: this.initialState
......@@ -50,19 +53,16 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
},
toggleExpandCollapse: function(event) {
var toggleButton = this.$('.toggle-button-expand-collapse'),
var toggleButton = this.$('.button-toggle-expand-collapse'),
collapse = toggleButton.hasClass('collapse-all');
event.preventDefault();
toggleButton.toggleClass('collapse-all expand-all');
this.$('.course-outline > ol > li').each(function(index, domElement) {
var element = $(domElement),
expandCollapseElement = element.find('.expand-collapse').first();
this.$('.list-sections > li').each(function(index, domElement) {
var element = $(domElement);
if (collapse) {
expandCollapseElement.removeClass('expand').addClass('collapse');
element.addClass('collapsed');
element.addClass('is-collapsed');
} else {
expandCollapseElement.addClass('expand').removeClass('collapse');
element.removeClass('collapsed');
element.removeClass('is-collapsed');
}
});
}
......
......@@ -10,6 +10,7 @@ define(['js/views/xblock_outline'],
// takes XBlockInfo as a model
templateName: 'unit-outline',
className: 'group-configurations-list',
render: function() {
XBlockOutlineView.prototype.render.call(this);
......@@ -23,7 +24,7 @@ define(['js/views/xblock_outline'],
previousAncestor = null;
if (this.model.get('ancestor_info')) {
ancestors = this.model.get('ancestor_info').ancestors;
listElement = this.$('.sortable-list');
listElement = this.getListElement();
// Note: the ancestors are processed in reverse order because the tree wants to
// start at the root, but the ancestors are ordered by closeness to the unit,
// i.e. subsection and then section.
......@@ -33,7 +34,7 @@ define(['js/views/xblock_outline'],
ancestorView.render();
listElement.append(ancestorView.$el);
previousAncestor = ancestor;
listElement = ancestorView.$('.sortable-list');
listElement = ancestorView.getListElement();
}
}
return ancestorView;
......
......@@ -10,9 +10,13 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
/**
* Toggles the expanded state of the current element.
*/
toggleExpandCollapse = function(target) {
toggleExpandCollapse = function(target, collapsedClass) {
// Support the old 'collapsed' option until fully switched over to is-collapsed
if (!collapsedClass) {
collapsedClass = 'collapsed';
}
target.closest('.expand-collapse').toggleClass('expand collapse');
target.closest('.is-collapsible, .window').toggleClass('collapsed');
target.closest('.is-collapsible, .window').toggleClass(collapsedClass);
target.closest('.is-collapsible').children('article').slideToggle();
};
......
......@@ -21,6 +21,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var XBlockOutlineView = BaseView.extend({
// takes XBlockInfo as a model
options: {
collapsedClass: 'is-collapsed'
},
templateName: 'xblock-outline',
initialize: function() {
......@@ -94,8 +98,12 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
this.renderedChildren = true;
},
getListElement: function() {
return this.$('> .outline-content > ol');
},
addChildView: function(childView) {
this.$('> .sortable-list').append(childView.$el);
this.getListElement().append(childView.$el);
},
addNameEditor: function() {
......@@ -136,7 +144,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
addButtonActions: function(element) {
var self = this;
element.find('.delete-button').click(_.bind(this.handleDeleteEvent, this));
element.find('.add-button').click(_.bind(this.handleAddEvent, this));
element.find('.button-new').click(_.bind(this.handleAddEvent, this));
},
shouldRenderChildren: function() {
......@@ -163,7 +171,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
xblockType = 'section';
} else if (category === 'sequential') {
xblockType = 'subsection';
} else if (category === 'vertical' && parentInfo && parentInfo.get('category') === 'sequential') {
} else if (category === 'vertical' && (!parentInfo || parentInfo.get('category') === 'sequential')) {
xblockType = 'unit';
}
return xblockType;
......
......@@ -318,7 +318,7 @@ $outline-indent-width: $baseline;
border-left-color: $color-live;
}
// CASE: has staff-only content
// CASE: is presented for staff only
&.is-staff-only {
border-left-color: $color-staff-only;
}
......
......@@ -25,14 +25,15 @@
font-weight: 600;
}
// TODO: abstract out
.is-editable {
.incontext-editor-input {
@extend %t-title4;
background: none repeat scroll 0 0 white;
@extend %t-strong;
background: none repeat scroll 0 0 $white;
border: 0;
box-shadow: 0 0 2px 2px $shadow inset;
font-weight: 600;
}
}
}
......
......@@ -6,6 +6,37 @@
%outline-item-header {
@include clearfix();
line-height: 0;
// CASE: is-editable
// TODO: abstract out
.is-editable {
.incontext-editor-open-action {
@include transition(opacity $tmg-f1 ease-in-out 0);
opacity: 0.0;
}
.incontext-editor-form {
width: 100%;
}
.incontext-editor-input {
@extend %t-title5;
@extend %t-strong;
width: 100%;
background: none repeat scroll 0 0 $white;
border: 0;
box-shadow: 0 0 2px 2px $shadow-l1 inset;
}
// STATE: hover/focus
&:hover, &:focus {
.incontext-editor-open-action {
opacity: 1.0;
}
}
}
}
%outline-item-content-hidden {
......
......@@ -10,7 +10,6 @@ else:
<%!
import json
from xmodule.modulestore import PublishState
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
from django.utils.translation import ugettext as _
%>
......
......@@ -47,13 +47,13 @@ from contentstore.utils import reverse_usage_url
<h3 class="sr">${_("Page Actions")}</h3>
<ul>
<li class="nav-item">
<a href="#" class="toggle-button toggle-button-expand-collapse collapse-all is-hidden">
<a href="#" class="button button-toggle button-toggle-expand-collapse collapse-all is-hidden">
<span class="collapse-all"><i class="icon-arrow-up"></i> <span class="label">${_("Collapse All Sections")}</span></span>
<span class="expand-all"><i class="icon-arrow-down"></i> <span class="label">${_("Expand All Sections")}</span></span>
</a>
</li>
<li class="nav-item">
<a href="#" class="button view-button add-button" data-category="chapter" data-parent="${context_course.location}" data-default-name="Section">
<a href="#" class="button button-new" data-category="chapter" data-parent="${context_course.location}" data-default-name="Section">
<i class="icon-plus"></i>${_('New Section')}
</a>
</li>
......@@ -72,7 +72,7 @@ from contentstore.utils import reverse_usage_url
<%
course_locator = context_course.location
%>
<article class="course-outline" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
<article class="outline" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
</article>
</div>
<div class="ui-loading">
......
<%
var category = xblockInfo.get('category');
var releasedToStudents = xblockInfo.get('released_to_students');
var publishState = xblockInfo.get('publish_state');
var publishClass = '';
if (publishState === 'staff_only') {
publishClass = 'is-staff-only';
} else if (publishState === 'live') {
publishClass = 'is-live';
} else if (publishState === 'ready') {
publishClass = 'is-ready';
} else if (publishState === 'has_unpublished_content') {
publishClass = 'has-warnings';
}
var listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
var statusMessage = null;
var statusType = null;
if (publishState === 'is_staff_only') {
statusType = 'staff-only';
statusMessage = 'Contains staff only content';
} else if (publishState === 'has_unpublished_content') {
if (category === 'vertical') {
statusType = 'warning';
if (releasedToStudents) {
statusMessage = 'Unpublished changes to live content';
} else {
statusMessage = 'Unpublished units will not be released';
}
}
}
var statusIconClass = '';
if (statusType === 'warning') {
statusIconClass = 'icon-file-alt';
} else if (statusType === 'error') {
statusIconClass = 'icon-warning-sign';
} else if (statusType === 'staff-only') {
statusIconClass = 'icon-lock';
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? 'collapsed' : '' %>"
<li class="outline-item outline-<%= xblockType %> <%= publishClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-collapsed' : '' %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="wrapper-xblock-header">
<div class="wrapper-xblock-header-primary">
<% if (includesChildren) { %>
<h3 class="xblock-title expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %>" title="<%= gettext('Collapse/Expand this Checklist') %>">
<i class="icon-caret-down ui-toggle-expansion"></i>
<% } else { %>
<h3 class="xblock-title">
<% } %>
<div class="<%= xblockType %>-header">
<% if (includesChildren) { %>
<h3 class="<%= xblockType %>-header-details expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion" title="<%= gettext('Collapse/Expand this Checklist') %>">
<i class="icon-caret-down icon"></i>
<% } else { %>
<h3 class="<%= xblockType %>-header-details">
<% } %>
<% if (xblockInfo.get('category') === 'vertical') { %>
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<% if (category === 'vertical') { %>
<span class="unit-title item-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
</span>
<% } else { %>
<span class="wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="xblock-field-value incontext-editor-value"><%= xblockInfo.get('display_name') %></span>
<span class="wrapper-<%= xblockType %>-title wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="<%= xblockType %>-title item-title xblock-field-value incontext-editor-value"><%= xblockInfo.get('display_name') %></span>
</span>
<% } %>
</h3>
<div class="item-actions">
<ul class="actions-list">
<li class="action-item action-delete">
<a href="#" data-tooltip="<%= gettext('Delete') %>" class="delete-button action-button">
<i class="icon-remove"></i>
<span class="sr"><%= gettext('Delete') %></span>
</a>
</li>
</ul>
</div>
<div class="<%= xblockType %>-header-actions">
<ul class="actions-list">
<li class="action-item action-delete">
<a href="#" data-tooltip="<%= gettext('Delete') %>" class="delete-button action-button">
<i class="icon icon-trash"></i>
<span class="sr action-button-text"><%= gettext('Delete') %></span>
</a>
</li>
</ul>
</div>
<div class="wrapper-xblock-header-secondary">
<% if (xblockInfo.get('edited_on')) { %>
<div class="meta-info">
<% if (xblockInfo.get('published')) { %>
<i class="icon-check"></i>
<%= gettext('Released:') %> Dec 31, 2015 at 21:00 UTC
<% } else { %>
<i class="icon-time"></i>
<%= gettext('Scheduled:') %> Dec 31, 2015 at 21:00 UTC
<% } %>
</div>
<% if (statusMessage) { %>
<div class="<%= xblockType %>-status">
<% if (category !== 'vertical') { %>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>
<span class="status-release-value">
<% if (xblockInfo.get('released_to_students')) { %>
<i class="icon icon-check-sign"></i>
<%= gettext('Released:') %>
<% } else if (xblockInfo.get('release_date')) { %>
<i class="icon icon-time"></i>
<%= gettext('Scheduled:') %>
<% } else { %>
<i class="icon icon-file-alt"></i>
<%= gettext('Unscheduled') %>
<% } %>
<% if (xblockInfo.get('release_date')) { %>
<%= xblockInfo.get('release_date') %>
<% } %>
</span>
</p>
</div>
<% } %>
<div class="item-actions">
<ul class="actions-list">
</ul>
<div class="status-message">
<i class="icon <%= statusIconClass %>"></i>
<p class="status-message-copy"><%- statusMessage %></p>
</div>
</div>
</div>
<% } %>
<% } %>
<% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
<div class="no-content add-xblock-component">
<div class="no-content add-section">
<p><%= gettext("You haven't added any content to this course yet.") %>
<a href="#" class="add-button" data-category="<%= childCategory %>"
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
<i class="icon icon-plus"></i><%= addChildLabel %>
</a>
</p>
</div>
<% } else { %>
<ol class="sortable-list sortable-<%= xblockType %>-list">
</ol>
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="add-button" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
<div class="outline-content <%= xblockType %>-content">
<ol class="<%= listType %> is-sortable">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
</div>
<% } %>
<% if (parentInfo) { %>
......
......@@ -11,13 +11,13 @@
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="toggle-button toggle-button-expand-collapse collapse-all">
<a href="#" class="button button-toggle button-toggle-expand-collapse collapse-all is-hidden">
<span class="collapse-all"><i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span></span>
<span class="expand-all"><i class="icon-arrow-down"></i> <span class="label">Expand All Sections</span></span>
</a>
</li>
<li class="nav-item">
<a href="#" class="button view-button add-button" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<a href="#" class="button button-new" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<i class="icon-plus"></i>New Section
</a>
</li>
......@@ -33,10 +33,10 @@
<section class="content">
<article class="content-primary" role="main">
<div class="wrapper-dnd">
<article class="course-outline" data-locator="mock-course" data-course-key="slashes:MockCourse">
<article class="outline" data-locator="mock-course" data-course-key="slashes:MockCourse">
<div class="no-content add-xblock-component">
<p>You haven't added any content to this course yet.
<a href="#" class="add-button" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<a href="#" class="button button-new" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<i class="icon-plus"></i>Add Section
</a>
</p>
......
<%
var copy = gettext("Never published");
if (published_on && published_by) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s");
copy = interpolate(message, {
last_published_date: '<span class="date">' + published_on + '</span>',
publish_username: '<span class="user">' + published_by + '</span>'
}, true);
} else if (published) {
copy = gettext("Previously published");
}
%>
<div class="wrapper-last-publish">
<p class="copy">
<% if (published) {
if (published_on && published_by) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
<%= interpolate(message, {
last_published_date: '<span class="date">' + published_on + '</span>',
publish_username: '<span class="user">' + published_by + '</span>' }, true) %>
<% } else { %>
<%= gettext("Previously published") %>
<% } %>
<% } else { %>
<%= gettext("Never published") %>
<% } %>
</p>
</div>
\ No newline at end of file
<p class="copy"><%= copy %></p>
</div>
<%
var publishClasses = "";
var title = gettext("Draft (Never published)");
if (published) {
if (published && hasChanges) {
publishClasses = publishClasses + " is-draft";
title = gettext("Draft (Unpublished changes)");
} else {
publishClasses = publishClasses + " is-published";
title = gettext("Published");
}
}
if (releaseDate) {
publishClasses = publishClasses + " is-scheduled";
var publishClass = '';
if (publishState === 'staff_only') {
publishClass = 'is-staff-only';
} else if (publishState === 'live') {
publishClass = 'is-live is-published is-released';
} else if (publishState === 'ready') {
publishClass = 'is-ready is-published';
} else if (publishState === 'has_unpublished_content') {
publishClass = 'has-warnings is-draft';
}
if (visibleToStaffOnly) {
publishClasses = publishClasses + " is-staff-only";
var title = gettext("Draft (Never published)");
if (publishState === 'staff_only') {
title = gettext("Unpublished (Staff only)");
} else if (publishState === 'live') {
title = gettext("Published and Live");
} else if (publishState === 'ready') {
title = gettext("Published");
} else if (publishState === 'has_unpublished_content') {
title = gettext("Draft (Unpublished changes)");
}
var releaseLabel = gettext("Release:");
if (publishState === 'live') {
releaseLabel = gettext("Released:");
} else if (publishState === 'ready') {
releaseLabel = gettext("Scheduled:");
}
var canPublish = publishState !== 'ready' && publishState !== 'live';
var canDiscardChanges = publishState === 'has_unpublished_content';
var visibleToStaffOnly = publishState === 'staff_only';
%>
<div class="bit-publishing <%= publishClasses %>">
<div class="bit-publishing <%= publishClass %>">
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
<%= title %>
</h3>
<div class="wrapper-last-draft bar-mod-content">
<p class="copy meta">
<% if (hasChanges && editedOn && editedBy) {
<% if (publishState === 'has_unpublished_content' && editedOn && editedBy) {
var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
<%= interpolate(message, {
last_saved_date: '<span class="date">' + editedOn + '</span>',
......@@ -42,17 +56,7 @@ if (visibleToStaffOnly) {
</div>
<div class="wrapper-release bar-mod-content">
<h5 class="title">
<% if (published && releaseDate) {
if (releasedToStudents) { %>
<%= gettext("Released:") %>
<% } else { %>
<%= gettext("Scheduled:") %>
<% }
} else { %>
<%= gettext("Release:") %>
<% } %>
</h5>
<h5 class="title"><%= releaseLabel %></h5>
<p class="copy">
<% if (releaseDate) { %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
......@@ -87,12 +91,12 @@ if (visibleToStaffOnly) {
<div class="wrapper-pub-actions bar-mod-actions">
<ul class="action-list">
<li class="action-item">
<a class="action-publish action-primary <% if (published && !hasChanges) { %>is-disabled<% } %>"
<a class="action-publish action-primary <% if (!canPublish) { %>is-disabled<% } %>"
href=""><%= gettext("Publish") %>
</a>
</li>
<li class="action-item">
<a class="action-discard action-secondary <% if (!published || !hasChanges) { %>is-disabled<% } %>"
<a class="action-discard action-secondary <% if (!canDiscardChanges) { %>is-disabled<% } %>"
href=""><%= gettext("Discard Changes") %>
</a>
</li>
......
<%
var publishState = xblockInfo.get('publish_state');
var publishClass = '';
if (publishState === 'staff_only') {
publishClass = 'is-staff-only';
} else if (publishState === 'live') {
publishClass = 'is-live';
} else if (publishState === 'ready') {
publishClass = 'is-ready';
} else if (publishState === 'has_unpublished_content') {
publishClass = 'has_warnings';
}
var listType = 'list-for-' + xblockType;
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-item-<%= xblockType %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="wrapper-xblock-header">
<div class="wrapper-xblock-header-primary">
<h3 class="xblock-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<li class="outline-item outline-<%= xblockType %> <%= publishClass %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="<%= xblockType %>-header">
<h3 class="<%= xblockType %>-header-details">
<span class="unit-title item-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
</span>
</h3>
</div>
</div>
<% } %>
<ol class="sortable-list sortable-<%= xblockType %>-list">
</ol>
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="add-button" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
<div class="<%= xblockType %>-content outline-content">
<ol class="<%= listType %>">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
</div>
<% if (parentInfo) { %>
</li>
</li>
<% } %>
<% if (parentInfo) { %>
<li class="outline-item outline-item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? 'collapsed' : '' %>"
<li class="outline-item outline-item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? 'is-collapsed' : '' %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<span class="draggable-drop-indicator draggable-drop-indicator-before"><i class="icon-caret-right"></i></span>
......@@ -56,7 +56,7 @@
<% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
<div class="no-content add-xblock-component">
<p><%= gettext("You haven't added any content to this course yet.") %>
<a href="#" class="add-button" data-category="<%= childCategory %>"
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
......@@ -68,7 +68,7 @@
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="add-button" data-category="<%= childCategory %>"
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
......
......@@ -89,11 +89,11 @@ class ModuleStoreEnum(object):
# user ID to use for tests that do not have a django user available
test = -3
class PublishState(object):
"""
The publish state for a given xblock-- either 'draft', 'private', or 'public'.
Currently in CMS, an xblock can only be in 'draft' or 'private' if it is at or below the Unit level.
class LegacyPublishState(object):
"""
The legacy publish state for a given xblock-- either 'draft', 'private', or 'public'. These states
are no longer used in Studio directly, but are still referenced in a few places.
"""
draft = 'draft'
private = 'private'
......@@ -301,10 +301,10 @@ class ModuleStoreRead(object):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
LegacyPublishState.public - content is locked and deployed to LMS
LegacyPublishState.private - content is editable and not deployed to LMS
"""
pass
......@@ -522,7 +522,7 @@ class ModuleStoreReadBase(ModuleStoreRead):
"""
Returns PublishState.public since this is a read-only store.
"""
return PublishState.public
return LegacyPublishState.public
def heartbeat(self):
"""
......
......@@ -439,10 +439,10 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
LegacyPublishState.public - content is locked and deployed to LMS
LegacyPublishState.private - content is editable and not deployed to LMS
"""
course_id = xblock.scope_ids.usage_id.course_key
store = self._get_modulestore_for_courseid(course_id)
......
......@@ -11,7 +11,7 @@ import logging
from opaque_keys.edx.locations import Location
from xmodule.exceptions import InvalidVersionError
from xmodule.modulestore import PublishState, ModuleStoreEnum
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum
from xmodule.modulestore.exceptions import (
ItemNotFoundError, DuplicateItemError, InvalidBranchSetting, DuplicateCourseError
)
......@@ -613,7 +613,7 @@ class DraftModuleStore(MongoModuleStore):
return False
# don't check children if this block has changes (is not public)
if self.compute_publish_state(item) != PublishState.public:
if self.compute_publish_state(item) != LegacyPublishState.public:
return True
# if this block doesn't have changes, then check its children
elif item.has_children:
......@@ -792,10 +792,10 @@ class DraftModuleStore(MongoModuleStore):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
LegacyPublishState.public - content is locked and deployed to LMS
LegacyPublishState.private - content is editable and not deployed to LMS
"""
if getattr(xblock, 'is_draft', False):
published_xblock_location = as_published(xblock.location)
......@@ -803,11 +803,11 @@ class DraftModuleStore(MongoModuleStore):
{'_id': published_xblock_location.to_deprecated_son()}
)
if published_item is None:
return PublishState.private
return LegacyPublishState.private
else:
return PublishState.draft
return LegacyPublishState.draft
else:
return PublishState.public
return LegacyPublishState.public
def _verify_branch_setting(self, expected_branch_setting):
"""
......
......@@ -4,7 +4,7 @@ Module for the dual-branch fall-back Draft->Published Versioning ModuleStore
from ..exceptions import ItemNotFoundError
from split import SplitMongoModuleStore, EXCLUDE_ALL
from xmodule.modulestore import ModuleStoreEnum, PublishState
from xmodule.modulestore import ModuleStoreEnum, LegacyPublishState
from xmodule.modulestore.exceptions import InsufficientSpecificationError
from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished, DIRECT_ONLY_CATEGORIES, UnsupportedRevisionError
......@@ -251,10 +251,11 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - published exists and is different from draft
PublishState.public - published exists and is the same as draft
PublishState.private - no published version exists
LegacyPublishState.draft - published exists and is different from draft
LegacyPublishState.public - published exists and is the same as draft
LegacyPublishState.private - no published version exists
"""
# TODO figure out what to say if xblock is not from the HEAD of its branch
def get_head(branch):
course_structure = self._lookup_course(xblock.location.course_key.for_branch(branch))['structure']
return self._get_block_from_structure(course_structure, xblock.location.block_id)
......@@ -271,13 +272,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
if not published_head:
# published version does not exist
return PublishState.private
return LegacyPublishState.private
elif get_version(draft_head) == get_version(published_head):
# published and draft versions are equal
return PublishState.public
return LegacyPublishState.public
else:
# published and draft versions differ
return PublishState.draft
return LegacyPublishState.draft
def convert_to_draft(self, location, user_id):
"""
......
......@@ -9,7 +9,7 @@ from pytz import UTC
from xmodule.tests import DATA_DIR
from opaque_keys.edx.locations import Location
from xmodule.modulestore import ModuleStoreEnum, PublishState
from xmodule.modulestore import ModuleStoreEnum, LegacyPublishState
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.exceptions import InvalidVersionError
......@@ -991,22 +991,22 @@ class TestMixedModuleStore(unittest.TestCase):
item_location = item.location.version_agnostic()
mongo_store = self.store._get_modulestore_for_courseid(self._course_key_from_string(self.MONGO_COURSEID))
with check_mongo_calls(mongo_store, max_find, max_send):
self.assertEquals(self.store.compute_publish_state(item), PublishState.private)
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.private)
# Private -> Public
self.store.publish(item_location, self.user_id)
item = self.store.get_item(item_location)
self.assertEquals(self.store.compute_publish_state(item), PublishState.public)
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.public)
# Public -> Private
self.store.unpublish(item_location, self.user_id)
item = self.store.get_item(item_location)
self.assertEquals(self.store.compute_publish_state(item), PublishState.private)
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.private)
# Private -> Public
self.store.publish(item_location, self.user_id)
item = self.store.get_item(item_location)
self.assertEquals(self.store.compute_publish_state(item), PublishState.public)
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.public)
# Public -> Draft with NO changes
# Note: This is where Split and Mongo differ
......@@ -1014,14 +1014,14 @@ class TestMixedModuleStore(unittest.TestCase):
item = self.store.get_item(item_location)
self.assertEquals(
self.store.compute_publish_state(item),
PublishState.draft if default_ms == 'draft' else PublishState.public
LegacyPublishState.draft if default_ms == 'draft' else LegacyPublishState.public
)
# Draft WITH changes
item.display_name = 'new name'
item = self.store.update_item(item, self.user_id)
self.assertTrue(self.store.has_changes(item.location))
self.assertEquals(self.store.compute_publish_state(item), PublishState.draft)
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.draft)
@ddt.data('draft', 'split')
def test_auto_publish(self, default_ms):
......
......@@ -113,7 +113,7 @@ class CourseOutlineContainer(CourseOutlineItem):
"""
click_css(
self,
self._bounded_selector(".add-xblock-component a.add-button"),
self._bounded_selector(".add-item a.button-new"),
require_notification=require_notification,
)
......@@ -125,7 +125,7 @@ class CourseOutlineContainer(CourseOutlineItem):
self.browser.execute_script("jQuery.fx.off = true;")
def subsection_expanded():
add_button = self.q(css=self._bounded_selector('> .add-xblock-component a.add-button')).first.results
add_button = self.q(css=self._bounded_selector('> .outline-content > .add-item a.button-new')).first.results
return add_button and add_button[0].is_displayed()
currently_expanded = subsection_expanded()
......@@ -171,8 +171,8 @@ class CourseOutlineUnit(CourseOutlineChild):
PageObject that wraps a unit link on the Studio Course Outline page.
"""
url = None
BODY_SELECTOR = '.outline-item-unit'
NAME_SELECTOR = '.xblock-title a'
BODY_SELECTOR = '.outline-unit'
NAME_SELECTOR = '.unit-title a'
def go_to(self):
"""
......@@ -191,7 +191,9 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
"""
url = None
BODY_SELECTOR = '.outline-item-subsection'
BODY_SELECTOR = '.outline-subsection'
NAME_SELECTOR = '.subsection-title'
NAME_FIELD_WRAPPER_SELECTOR = '.subsection-header .wrapper-xblock-field'
CHILD_CLASS = CourseOutlineUnit
def unit(self, title):
......@@ -224,7 +226,9 @@ class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
:class`.PageObject` that wraps a section block on the Studio Course Outline page.
"""
url = None
BODY_SELECTOR = '.outline-item-section'
BODY_SELECTOR = '.outline-section'
NAME_SELECTOR = '.section-title'
NAME_FIELD_WRAPPER_SELECTOR = '.section-header .wrapper-xblock-field'
CHILD_CLASS = CourseOutlineSubsection
def subsection(self, title):
......@@ -268,7 +272,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
url_path = "course"
CHILD_CLASS = CourseOutlineSection
EXPAND_COLLAPSE_CSS = '.toggle-button-expand-collapse'
BOTTOM_ADD_SECTION_BUTTON = '.course-outline > .add-xblock-component .add-button'
BOTTOM_ADD_SECTION_BUTTON = '.outline > .add-section .button-new'
def is_browser_on_page(self):
return self.q(css='body.view-outline').present
......@@ -337,7 +341,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
Clicks the button for adding a section which resides at the top of the screen.
"""
click_css(self, '.wrapper-mast nav.nav-actions .add-button')
click_css(self, '.wrapper-mast nav.nav-actions .button-new')
def add_section_from_bottom_button(self):
"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment