Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
138cd459
Commit
138cd459
authored
May 29, 2014
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3819 from edx/andya/edit-containers
Allow editing of container xblocks/xmodules
parents
172c6d15
6b3e100c
Show whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
839 additions
and
337 deletions
+839
-337
CHANGELOG.rst
+2
-0
cms/djangoapps/contentstore/views/item.py
+8
-20
cms/djangoapps/contentstore/views/preview.py
+1
-6
cms/djangoapps/contentstore/views/tests/test_container_page.py
+1
-5
cms/djangoapps/contentstore/views/tests/test_unit_page.py
+2
-2
cms/static/coffee/src/views/module_edit.coffee
+5
-1
cms/static/js/models/xblock_info.js
+1
-1
cms/static/js/spec/views/container_spec.js
+1
-1
cms/static/js/spec/views/pages/container_spec.js
+71
-9
cms/static/js/spec/views/unit_spec.js
+95
-3
cms/static/js/spec_helpers/edit_helpers.js
+8
-2
cms/static/js/spec_helpers/modal_helpers.js
+12
-5
cms/static/js/views/container.js
+3
-2
cms/static/js/views/modals/edit_xblock.js
+6
-7
cms/static/js/views/pages/container.js
+10
-4
cms/static/sass/_base.scss
+1
-0
cms/static/sass/elements/_controls.scss
+1
-0
cms/static/sass/elements/_xblocks.scss
+63
-10
cms/static/sass/views/_container.scss
+40
-3
cms/static/sass/views/_unit.scss
+46
-6
cms/templates/container.html
+11
-2
cms/templates/container_xblock_component.html
+27
-10
cms/templates/js/mock/mock-container-page.underscore
+10
-3
cms/templates/js/mock/mock-container-xblock.underscore
+79
-63
cms/templates/js/mock/mock-unit-page-child-container.underscore
+37
-0
cms/templates/js/mock/mock-updated-container-xblock.underscore
+18
-0
cms/templates/studio_container_wrapper.html
+0
-39
cms/templates/studio_xblock_wrapper.html
+34
-10
cms/templates/widgets/sequence-edit.html
+0
-51
common/lib/xmodule/xmodule/js/src/sequence/edit.coffee
+0
-7
common/lib/xmodule/xmodule/js/src/vertical/edit.coffee
+0
-7
common/lib/xmodule/xmodule/js/src/wrapper/edit.coffee
+0
-10
common/lib/xmodule/xmodule/split_test_module.py
+22
-17
common/lib/xmodule/xmodule/studio_editable.py
+15
-6
common/lib/xmodule/xmodule/tests/test_split_module.py
+39
-2
common/lib/xmodule/xmodule/tests/test_vertical.py
+11
-0
common/lib/xmodule/xmodule/vertical_module.py
+12
-1
common/lib/xmodule/xmodule/wrapper_module.py
+0
-4
common/static/sass/_mixins.scss
+8
-8
common/test/acceptance/pages/studio/component_editor.py
+66
-0
common/test/acceptance/pages/studio/container.py
+21
-4
common/test/acceptance/pages/studio/utils.py
+1
-1
common/test/acceptance/tests/test_studio_container.py
+44
-2
lms/templates/studio_render_children_view.html
+7
-3
No files found.
CHANGELOG.rst
View file @
138cd459
...
@@ -14,6 +14,8 @@ Blades: Remove Video player outline. BLD-975.
...
@@ -14,6 +14,8 @@ Blades: Remove Video player outline. BLD-975.
Blades: Fix Youtube regular expression in video player editor. BLD-967.
Blades: Fix Youtube regular expression in video player editor. BLD-967.
Studio: Support editing of containers. STUD-1312.
Blades: Fix displaying transcripts on touch devices. BLD-1033.
Blades: Fix displaying transcripts on touch devices. BLD-1033.
Blades: Tolerance expressed in percentage now computes correctly. BLD-522.
Blades: Tolerance expressed in percentage now computes correctly. BLD-522.
...
...
cms/djangoapps/contentstore/views/item.py
View file @
138cd459
...
@@ -199,25 +199,6 @@ def xblock_view_handler(request, usage_key_string, view_name):
...
@@ -199,25 +199,6 @@ def xblock_view_handler(request, usage_key_string, view_name):
# change not authored by requestor but by xblocks.
# change not authored by requestor but by xblocks.
store
.
update_item
(
xblock
,
None
)
store
.
update_item
(
xblock
,
None
)
elif
view_name
==
'student_view'
and
xblock_has_own_studio_page
(
xblock
):
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
False
,
'read_only'
:
is_read_only
,
'root_xblock'
:
xblock
,
}
# For non-leaf xblocks on the unit page, show the special rendering
# which links to the new container page.
html
=
render_to_string
(
'container_xblock_component.html'
,
{
'xblock_context'
:
context
,
'xblock'
:
xblock
,
'locator'
:
usage_key
,
})
return
JsonResponse
({
'html'
:
html
,
'resources'
:
[],
})
elif
view_name
in
(
unit_views
+
container_views
):
elif
view_name
in
(
unit_views
+
container_views
):
is_container_view
=
(
view_name
in
container_views
)
is_container_view
=
(
view_name
in
container_views
)
...
@@ -245,8 +226,15 @@ def xblock_view_handler(request, usage_key_string, view_name):
...
@@ -245,8 +226,15 @@ def xblock_view_handler(request, usage_key_string, view_name):
# the component div. Note that the container view recursively adds headers
# the component div. Note that the container view recursively adds headers
# into the preview fragment, so we don't want to add another header here.
# into the preview fragment, so we don't want to add another header here.
if
not
is_container_view
:
if
not
is_container_view
:
fragment
.
content
=
render_to_string
(
'component.html'
,
{
# For non-leaf xblocks, show the special rendering which links to the new container page.
if
xblock_has_own_studio_page
(
xblock
):
template
=
'container_xblock_component.html'
else
:
template
=
'component.html'
fragment
.
content
=
render_to_string
(
template
,
{
'xblock_context'
:
context
,
'xblock_context'
:
context
,
'xblock'
:
xblock
,
'locator'
:
usage_key
,
'preview'
:
fragment
.
content
,
'preview'
:
fragment
.
content
,
'label'
:
xblock
.
display_name
or
xblock
.
scope_ids
.
block_type
,
'label'
:
xblock
.
display_name
or
xblock
.
scope_ids
.
block_type
,
})
})
...
...
cms/djangoapps/contentstore/views/preview.py
View file @
138cd459
...
@@ -180,12 +180,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
...
@@ -180,12 +180,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_root'
:
is_root
,
'is_root'
:
is_root
,
'is_reorderable'
:
is_reorderable
,
'is_reorderable'
:
is_reorderable
,
}
}
# For child xblocks with their own page, render a link to the page
html
=
render_to_string
(
'studio_xblock_wrapper.html'
,
template_context
)
if
xblock_has_own_studio_page
(
xblock
)
and
not
is_root
:
template
=
'studio_container_wrapper.html'
else
:
template
=
'studio_xblock_wrapper.html'
html
=
render_to_string
(
template
,
template_context
)
frag
=
wrap_fragment
(
frag
,
html
)
frag
=
wrap_fragment
(
frag
,
html
)
return
frag
return
frag
...
...
cms/djangoapps/contentstore/views/tests/test_container_page.py
View file @
138cd459
...
@@ -137,8 +137,6 @@ class ContainerPageTestCase(StudioPageTestCase):
...
@@ -137,8 +137,6 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
"""
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
category
=
'split_test'
,
display_name
=
'Split Test'
)
category
=
'split_test'
,
display_name
=
'Split Test'
)
ItemFactory
.
create
(
parent_location
=
empty_child_container
.
location
,
category
=
'html'
,
display_name
=
'Split Child'
)
self
.
validate_preview_html
(
empty_child_container
,
self
.
reorderable_child_view
,
self
.
validate_preview_html
(
empty_child_container
,
self
.
reorderable_child_view
,
can_reorder
=
False
,
can_edit
=
False
,
can_add
=
False
)
can_reorder
=
False
,
can_edit
=
False
,
can_add
=
False
)
...
@@ -148,9 +146,7 @@ class ContainerPageTestCase(StudioPageTestCase):
...
@@ -148,9 +146,7 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
"""
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
category
=
'split_test'
,
display_name
=
'Split Test'
)
category
=
'split_test'
,
display_name
=
'Split Test'
)
ItemFactory
.
create
(
parent_location
=
empty_child_container
.
location
,
category
=
'html'
,
display_name
=
'Split Child'
)
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
draft_empty_child_container
=
modulestore
(
'draft'
)
.
convert_to_draft
(
empty_child_container
.
location
)
draft_empty_child_container
=
modulestore
(
'draft'
)
.
convert_to_draft
(
empty_child_container
.
location
)
self
.
validate_preview_html
(
draft_empty_child_container
,
self
.
reorderable_child_view
,
self
.
validate_preview_html
(
draft_empty_child_container
,
self
.
reorderable_child_view
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
cms/djangoapps/contentstore/views/tests/test_unit_page.py
View file @
138cd459
...
@@ -60,7 +60,7 @@ class UnitPageTestCase(StudioPageTestCase):
...
@@ -60,7 +60,7 @@ class UnitPageTestCase(StudioPageTestCase):
ItemFactory
.
create
(
parent_location
=
child_container
.
location
,
ItemFactory
.
create
(
parent_location
=
child_container
.
location
,
category
=
'html'
,
display_name
=
'grandchild'
)
category
=
'html'
,
display_name
=
'grandchild'
)
self
.
validate_preview_html
(
child_container
,
'student_view'
,
self
.
validate_preview_html
(
child_container
,
'student_view'
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
def
test_draft_child_container_preview_html
(
self
):
def
test_draft_child_container_preview_html
(
self
):
"""
"""
...
@@ -74,4 +74,4 @@ class UnitPageTestCase(StudioPageTestCase):
...
@@ -74,4 +74,4 @@ class UnitPageTestCase(StudioPageTestCase):
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
draft_child_container
=
modulestore
(
'draft'
)
.
get_item
(
child_container
.
location
)
draft_child_container
=
modulestore
(
'draft'
)
.
get_item
(
child_container
.
location
)
self
.
validate_preview_html
(
draft_child_container
,
'student_view'
,
self
.
validate_preview_html
(
draft_child_container
,
'student_view'
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
cms/static/coffee/src/views/module_edit.coffee
View file @
138cd459
...
@@ -15,7 +15,11 @@ define ["jquery", "underscore", "gettext", "xblock/runtime.v1",
...
@@ -15,7 +15,11 @@ define ["jquery", "underscore", "gettext", "xblock/runtime.v1",
@
render
()
@
render
()
loadDisplay
:
->
loadDisplay
:
->
XBlock
.
initializeBlock
(
@
$el
.
find
(
'.xblock-student_view'
))
# Not all components render an inline student view, e.g. child containers which
# instead render a link to a separate container page.
xblockElement
=
@
$el
.
find
(
'.xblock-student_view'
)
if
xblockElement
.
length
>
0
XBlock
.
initializeBlock
(
xblockElement
)
createItem
:
(
parent
,
payload
,
callback
=->
)
->
createItem
:
(
parent
,
payload
,
callback
=->
)
->
payload
.
parent_locator
=
parent
payload
.
parent_locator
=
parent
...
...
cms/static/js/models/xblock_info.js
View file @
138cd459
...
@@ -11,7 +11,7 @@ define(["backbone", "js/utils/module"], function(Backbone, ModuleUtils) {
...
@@ -11,7 +11,7 @@ define(["backbone", "js/utils/module"], function(Backbone, ModuleUtils) {
"is_container"
:
null
,
"is_container"
:
null
,
"data"
:
null
,
"data"
:
null
,
"metadata"
:
null
,
"metadata"
:
null
,
"children"
:
[]
"children"
:
null
}
}
});
});
return
XBlockInfo
;
return
XBlockInfo
;
...
...
cms/static/js/spec/views/container_spec.js
View file @
138cd459
...
@@ -11,7 +11,7 @@ define([ "jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers
...
@@ -11,7 +11,7 @@ define([ "jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers
getDragHandle
,
dragComponentVertically
,
dragComponentAbove
,
getDragHandle
,
dragComponentVertically
,
dragComponentAbove
,
verifyRequest
,
verifyNumReorderCalls
,
respondToRequest
,
notificationSpy
,
verifyRequest
,
verifyNumReorderCalls
,
respondToRequest
,
notificationSpy
,
rootLocator
=
'
testCourse/branch/draft/split_test/splitFFF
'
,
rootLocator
=
'
locator-container
'
,
containerTestUrl
=
'/xblock/'
+
rootLocator
,
containerTestUrl
=
'/xblock/'
+
rootLocator
,
groupAUrl
=
"/xblock/locator-group-A"
,
groupAUrl
=
"/xblock/locator-group-A"
,
...
...
cms/static/js/spec/views/pages/container_spec.js
View file @
138cd459
...
@@ -7,6 +7,7 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -7,6 +7,7 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
model
,
containerPage
,
requests
,
model
,
containerPage
,
requests
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-container-xblock.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-container-xblock.underscore'
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
beforeEach
(
function
()
{
beforeEach
(
function
()
{
...
@@ -14,8 +15,8 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -14,8 +15,8 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
appendSetFixtures
(
mockContainerPage
);
appendSetFixtures
(
mockContainerPage
);
model
=
new
XBlockInfo
({
model
=
new
XBlockInfo
({
id
:
'
testCourse/branch/draft/block/verticalFFF
'
,
id
:
'
locator-container
'
,
display_name
:
'Test
Unit
'
,
display_name
:
'Test
Container
'
,
category
:
'vertical'
category
:
'vertical'
});
});
containerPage
=
new
ContainerPage
({
containerPage
=
new
ContainerPage
({
...
@@ -51,13 +52,14 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -51,13 +52,14 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
});
});
};
};
describe
(
"
Basic
display"
,
function
()
{
describe
(
"
Initial
display"
,
function
()
{
it
(
'can render itself'
,
function
()
{
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
expect
(
containerPage
.
$el
.
select
(
'.xblock-header'
)).
toBeTruthy
();
expect
(
containerPage
.
$el
.
select
(
'.xblock-header'
)).
toBeTruthy
();
expect
(
containerPage
.
$
(
'.wrapper-xblock'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.wrapper-xblock'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.no-container-content'
)).
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.no-container-content'
)).
toHaveClass
(
'is-hidden'
);
});
});
it
(
'shows a loading indicator'
,
function
()
{
it
(
'shows a loading indicator'
,
function
()
{
requests
=
create_sinon
.
requests
(
this
);
requests
=
create_sinon
.
requests
(
this
);
containerPage
.
render
();
containerPage
.
render
();
...
@@ -67,6 +69,66 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -67,6 +69,66 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
});
});
});
});
describe
(
"Editing the container"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXBlock
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can edit itself'
,
function
()
{
var
editButtons
,
modalElement
,
updatedTitle
=
'Updated Test Container'
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
// Click the root edit button
editButtons
=
containerPage
.
$
(
'.nav-actions .edit-button'
);
editButtons
.
first
().
click
();
modalElement
=
edit_helpers
.
getModalElement
();
// Expect a request to be made to show the studio view for the container
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-container/studio_view'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockContainerXBlockHtml
,
resources
:
[]
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeTruthy
();
// Expect the correct title to be shown
expect
(
modalElement
.
find
(
'.modal-window-title'
).
text
()).
toBe
(
'Editing: Test Container'
);
// Press the save button and respond with a success message to the save
edit_helpers
.
pressModalButton
(
'.action-save'
);
create_sinon
.
respondWithJson
(
requests
,
{
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeFalsy
();
// Expect the last request be to refresh the container page
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-container/container_preview'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockUpdatedContainerXBlockHtml
,
resources
:
[]
});
// Expect the title and breadcrumb to be updated
expect
(
containerPage
.
$
(
'.page-header-title'
).
text
().
trim
()).
toBe
(
updatedTitle
);
expect
(
containerPage
.
$
(
'.page-header .subtitle a'
).
last
().
text
().
trim
()).
toBe
(
updatedTitle
);
});
});
describe
(
"Editing an xblock"
,
function
()
{
describe
(
"Editing an xblock"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
var
newDisplayName
=
'New Display Name'
;
...
@@ -87,10 +149,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -87,10 +149,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
it
(
'can show an edit modal for a child xblock'
,
function
()
{
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
var
editButtons
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
editButtons
=
containerPage
.
$
(
'.edit-button'
);
editButtons
=
containerPage
.
$
(
'.
wrapper-xblock .
edit-button'
);
// The container
renders six mock xblocks, so there should be an equal number of edit button
s
// The container
should have rendered six mock xblock
s
expect
(
editButtons
.
length
).
toBe
(
6
);
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
.
first
()
.
click
();
editButtons
[
0
]
.
click
();
// Make sure that the correct xblock is requested to be edited
// Make sure that the correct xblock is requested to be edited
expect
(
lastRequest
().
url
).
toBe
(
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-component-A1/studio_view'
'/xblock/locator-component-A1/studio_view'
...
@@ -125,10 +187,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -125,10 +187,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
editButtons
=
containerPage
.
$
(
'.edit-button'
);
editButtons
=
containerPage
.
$
(
'.
wrapper-xblock .
edit-button'
);
// The container
renders six mock xblocks, so there should be an equal number of edit button
s
// The container
should have rendered six mock xblock
s
expect
(
editButtons
.
length
).
toBe
(
6
);
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
.
first
()
.
click
();
editButtons
[
0
]
.
click
();
create_sinon
.
respondWithJson
(
requests
,
{
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
html
:
mockXModuleEditor
,
resources
:
[]
resources
:
[]
...
...
cms/static/js/spec/views/unit_spec.js
View file @
138cd459
define
([
"jquery"
,
"underscore"
,
"jasmine"
,
"coffee/src/views/unit"
,
"js/models/module_info"
,
define
([
"jquery"
,
"underscore"
,
"jasmine"
,
"coffee/src/views/unit"
,
"js/models/module_info"
,
"js/spec_helpers/create_sinon"
,
"js/spec_helpers/edit_helpers"
,
"jasmine-stealth"
],
"js/spec_helpers/create_sinon"
,
"js/spec_helpers/edit_helpers"
,
"jasmine-stealth"
],
function
(
$
,
_
,
jasmine
,
UnitEditView
,
ModuleModel
,
create_sinon
,
edit_helpers
)
{
function
(
$
,
_
,
jasmine
,
UnitEditView
,
ModuleModel
,
create_sinon
,
edit_helpers
)
{
var
requests
,
unitView
,
initialize
,
respondWithHtml
,
verifyComponents
,
i
;
var
requests
,
unitView
,
initialize
,
lastRequest
,
respondWithHtml
,
verifyComponents
,
i
,
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
respondWithHtml
=
function
(
html
,
requestIndex
)
{
respondWithHtml
=
function
(
html
,
requestIndex
)
{
create_sinon
.
respondWithJson
(
create_sinon
.
respondWithJson
(
...
@@ -13,6 +14,7 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
...
@@ -13,6 +14,7 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
initialize
=
function
(
test
)
{
initialize
=
function
(
test
)
{
var
mockXBlockHtml
=
readFixtures
(
'mock/mock-unit-page-xblock.underscore'
),
var
mockXBlockHtml
=
readFixtures
(
'mock/mock-unit-page-xblock.underscore'
),
mockChildContainerHtml
=
readFixtures
(
'mock/mock-unit-page-child-container.underscore'
),
model
;
model
;
requests
=
create_sinon
.
requests
(
test
);
requests
=
create_sinon
.
requests
(
test
);
model
=
new
ModuleModel
({
model
=
new
ModuleModel
({
...
@@ -25,11 +27,13 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
...
@@ -25,11 +27,13 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
model
:
model
model
:
model
});
});
// Respond with renderings for the two xblocks in the unit
// Respond with renderings for the two xblocks in the unit
(the second is itself a child container)
respondWithHtml
(
mockXBlockHtml
,
0
);
respondWithHtml
(
mockXBlockHtml
,
0
);
respondWithHtml
(
mock
XBlock
Html
,
1
);
respondWithHtml
(
mock
ChildContainer
Html
,
1
);
};
};
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
verifyComponents
=
function
(
unit
,
locators
)
{
verifyComponents
=
function
(
unit
,
locators
)
{
var
components
=
unit
.
$
(
".component"
);
var
components
=
unit
.
$
(
".component"
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
...
@@ -174,5 +178,93 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
...
@@ -174,5 +178,93 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
test_link_disabled_during_ajax_call
(
draft_states
[
i
]);
test_link_disabled_during_ajax_call
(
draft_states
[
i
]);
}
}
});
});
describe
(
"Editing an xblock"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXBlock
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
initialize
(
this
);
editButtons
=
unitView
.
$
(
'.edit-button'
);
// The container renders two mock xblocks
expect
(
editButtons
.
length
).
toBe
(
2
);
editButtons
[
1
].
click
();
// Make sure that the correct xblock is requested to be edited
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/loc_2/studio_view'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeTruthy
();
});
});
describe
(
"Editing an xmodule"
,
function
()
{
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXModule
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXModule
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can save changes to settings'
,
function
()
{
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
initialize
(
this
);
editButtons
=
unitView
.
$
(
'.edit-button'
);
// The container renders two mock xblocks
expect
(
editButtons
.
length
).
toBe
(
2
);
editButtons
[
1
].
click
();
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
resources
:
[]
});
modal
=
$
(
'.edit-xblock-modal'
);
// Click on the settings tab
modal
.
find
(
'.settings-button'
).
click
();
// Change the display name's text
modal
.
find
(
'.setting-input'
).
text
(
"Mock Update"
);
// Press the save button
modal
.
find
(
'.action-save'
).
click
();
// Respond to the save
create_sinon
.
respondWithJson
(
requests
,
{
id
:
'mock-id'
});
// Respond to the request to refresh
respondWithHtml
(
mockUpdatedXBlockHtml
);
// Verify that the xblock was updated
expect
(
unitView
.
$
(
'.mock-updated-content'
).
text
()).
toBe
(
'Mock Update'
);
});
});
});
});
});
});
cms/static/js/spec_helpers/edit_helpers.js
View file @
138cd459
...
@@ -9,11 +9,17 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
...
@@ -9,11 +9,17 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
var
installMockXBlock
,
uninstallMockXBlock
,
installMockXModule
,
uninstallMockXModule
,
var
installMockXBlock
,
uninstallMockXBlock
,
installMockXModule
,
uninstallMockXModule
,
mockComponentTemplates
,
installEditTemplates
,
showEditModal
,
verifyXBlockRequest
;
mockComponentTemplates
,
installEditTemplates
,
showEditModal
,
verifyXBlockRequest
;
installMockXBlock
=
function
()
{
installMockXBlock
=
function
(
mockResult
)
{
window
.
MockXBlock
=
function
(
runtime
,
element
)
{
window
.
MockXBlock
=
function
(
runtime
,
element
)
{
return
{
var
block
=
{
runtime
:
runtime
runtime
:
runtime
};
};
if
(
mockResult
)
{
block
.
save
=
function
()
{
return
mockResult
;
};
}
return
block
;
};
};
};
};
...
...
cms/static/js/spec_helpers/modal_helpers.js
View file @
138cd459
...
@@ -7,6 +7,7 @@ define(["jquery", "js/spec_helpers/view_helpers"],
...
@@ -7,6 +7,7 @@ define(["jquery", "js/spec_helpers/view_helpers"],
getModalElement
,
getModalElement
,
isShowingModal
,
isShowingModal
,
hideModalIfShowing
,
hideModalIfShowing
,
pressModalButton
,
cancelModal
,
cancelModal
,
cancelModalIfShowing
;
cancelModalIfShowing
;
...
@@ -37,12 +38,16 @@ define(["jquery", "js/spec_helpers/view_helpers"],
...
@@ -37,12 +38,16 @@ define(["jquery", "js/spec_helpers/view_helpers"],
}
}
};
};
cancelModal
=
function
(
modal
)
{
pressModalButton
=
function
(
selector
,
modal
)
{
var
modalElement
,
cancelB
utton
;
var
modalElement
,
b
utton
;
modalElement
=
getModalElement
(
modal
);
modalElement
=
getModalElement
(
modal
);
cancelButton
=
modalElement
.
find
(
'.action-cancel:visible'
);
button
=
modalElement
.
find
(
selector
+
':visible'
);
expect
(
cancelButton
.
length
).
toBe
(
1
);
expect
(
button
.
length
).
toBe
(
1
);
cancelButton
.
click
();
button
.
click
();
};
cancelModal
=
function
(
modal
)
{
pressModalButton
(
'.action-cancel'
,
modal
);
};
};
cancelModalIfShowing
=
function
(
modal
)
{
cancelModalIfShowing
=
function
(
modal
)
{
...
@@ -52,9 +57,11 @@ define(["jquery", "js/spec_helpers/view_helpers"],
...
@@ -52,9 +57,11 @@ define(["jquery", "js/spec_helpers/view_helpers"],
};
};
return
$
.
extend
(
view_helpers
,
{
return
$
.
extend
(
view_helpers
,
{
'getModalElement'
:
getModalElement
,
'installModalTemplates'
:
installModalTemplates
,
'installModalTemplates'
:
installModalTemplates
,
'isShowingModal'
:
isShowingModal
,
'isShowingModal'
:
isShowingModal
,
'hideModalIfShowing'
:
hideModalIfShowing
,
'hideModalIfShowing'
:
hideModalIfShowing
,
'pressModalButton'
:
pressModalButton
,
'cancelModal'
:
cancelModal
,
'cancelModal'
:
cancelModal
,
'cancelModalIfShowing'
:
cancelModalIfShowing
'cancelModalIfShowing'
:
cancelModalIfShowing
});
});
...
...
cms/static/js/views/container.js
View file @
138cd459
define
([
"jquery"
,
"underscore"
,
"js/views/xblock"
,
"js/utils/module"
,
"gettext"
,
"js/views/feedback_notification"
],
define
([
"jquery"
,
"underscore"
,
"js/views/xblock"
,
"js/utils/module"
,
"gettext"
,
"js/views/feedback_notification"
],
function
(
$
,
_
,
XBlockView
,
ModuleUtils
,
gettext
,
NotificationView
)
{
function
(
$
,
_
,
XBlockView
,
ModuleUtils
,
gettext
,
NotificationView
)
{
var
reorderableClass
=
'.reorderable-container'
,
var
reorderableClass
=
'.reorderable-container'
,
sortableInitializedClass
=
'.ui-sortable'
,
studioXBlockWrapperClass
=
'.studio-xblock-wrapper'
;
studioXBlockWrapperClass
=
'.studio-xblock-wrapper'
;
var
ContainerView
=
XBlockView
.
extend
({
var
ContainerView
=
XBlockView
.
extend
({
...
@@ -8,7 +9,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
...
@@ -8,7 +9,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
xblockReady
:
function
()
{
xblockReady
:
function
()
{
XBlockView
.
prototype
.
xblockReady
.
call
(
this
);
XBlockView
.
prototype
.
xblockReady
.
call
(
this
);
var
reorderableContainer
=
this
.
$
(
reorderableClass
),
var
reorderableContainer
=
this
.
$
(
reorderableClass
),
alreadySortable
=
this
.
$
(
'.ui-sortable'
),
alreadySortable
=
this
.
$
(
sortableInitializedClass
),
newParent
,
newParent
,
oldParent
,
oldParent
,
self
=
this
;
self
=
this
;
...
@@ -113,7 +114,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
...
@@ -113,7 +114,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
},
},
refresh
:
function
()
{
refresh
:
function
()
{
this
.
$
(
reorderable
Class
).
sortable
(
'refresh'
);
this
.
$
(
sortableInitialized
Class
).
sortable
(
'refresh'
);
}
}
});
});
...
...
cms/static/js/views/modals/edit_xblock.js
View file @
138cd459
...
@@ -111,14 +111,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
...
@@ -111,14 +111,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
},
},
getTitle
:
function
()
{
getTitle
:
function
()
{
var
displayName
=
this
.
xblockElement
.
find
(
'.xblock-header .header-details'
).
text
().
trim
();
var
displayName
=
this
.
xblockInfo
.
get
(
'display_name'
);
// If not found, try the old unit page style rendering
if
(
!
displayName
)
{
displayName
=
this
.
xblockElement
.
find
(
'.component-header'
).
text
().
trim
();
if
(
!
displayName
)
{
if
(
!
displayName
)
{
displayName
=
gettext
(
'Component'
);
displayName
=
gettext
(
'Component'
);
}
}
}
return
interpolate
(
gettext
(
"Editing: %(title)s"
),
{
title
:
displayName
},
true
);
return
interpolate
(
gettext
(
"Editing: %(title)s"
),
{
title
:
displayName
},
true
);
},
},
...
@@ -180,13 +176,16 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
...
@@ -180,13 +176,16 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
findXBlockInfo
:
function
(
xblockWrapperElement
,
defaultXBlockInfo
)
{
findXBlockInfo
:
function
(
xblockWrapperElement
,
defaultXBlockInfo
)
{
var
xblockInfo
=
defaultXBlockInfo
,
var
xblockInfo
=
defaultXBlockInfo
,
xblockElement
;
xblockElement
,
displayName
;
if
(
xblockWrapperElement
.
length
>
0
)
{
if
(
xblockWrapperElement
.
length
>
0
)
{
xblockElement
=
xblockWrapperElement
.
find
(
'.xblock'
);
xblockElement
=
xblockWrapperElement
.
find
(
'.xblock'
);
displayName
=
xblockWrapperElement
.
find
(
'.xblock-header .header-details'
).
text
().
trim
();
xblockInfo
=
new
XBlockInfo
({
xblockInfo
=
new
XBlockInfo
({
id
:
xblockWrapperElement
.
data
(
'locator'
),
id
:
xblockWrapperElement
.
data
(
'locator'
),
courseKey
:
xblockWrapperElement
.
data
(
'course-key'
),
courseKey
:
xblockWrapperElement
.
data
(
'course-key'
),
category
:
xblockElement
.
data
(
'block-type'
)
category
:
xblockElement
.
data
(
'block-type'
),
display_name
:
displayName
});
});
}
}
return
xblockInfo
;
return
xblockInfo
;
...
...
cms/static/js/views/pages/container.js
View file @
138cd459
...
@@ -46,6 +46,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
...
@@ -46,6 +46,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
}
else
{
}
else
{
noContentElement
.
removeClass
(
'is-hidden'
);
noContentElement
.
removeClass
(
'is-hidden'
);
}
}
self
.
refreshTitle
();
loadingElement
.
addClass
(
'is-hidden'
);
loadingElement
.
addClass
(
'is-hidden'
);
self
.
delegateEvents
();
self
.
delegateEvents
();
}
}
...
@@ -60,6 +61,12 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
...
@@ -60,6 +61,12 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
return
this
.
xblockView
.
model
.
urlRoot
;
return
this
.
xblockView
.
model
.
urlRoot
;
},
},
refreshTitle
:
function
()
{
var
title
=
this
.
$
(
'.xblock-header .header-details span'
).
first
().
text
().
trim
();
this
.
$
(
'.page-header-title'
).
text
(
title
);
this
.
$
(
'.page-header .subtitle a'
).
last
().
text
(
title
);
},
onXBlockRefresh
:
function
(
xblockView
)
{
onXBlockRefresh
:
function
(
xblockView
)
{
this
.
addButtonActions
(
xblockView
.
$el
);
this
.
addButtonActions
(
xblockView
.
$el
);
this
.
xblockView
.
refresh
();
this
.
xblockView
.
refresh
();
...
@@ -177,10 +184,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
...
@@ -177,10 +184,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
*/
*/
refreshXBlock
:
function
(
xblockElement
)
{
refreshXBlock
:
function
(
xblockElement
)
{
var
parentElement
=
xblockElement
.
parent
(),
var
parentElement
=
xblockElement
.
parent
(),
rootLocator
=
this
.
xblockView
.
model
.
id
,
rootLocator
=
this
.
xblockView
.
model
.
id
;
xblockLocator
=
xblockElement
.
data
(
'locator'
);
if
(
xblockElement
.
length
===
0
||
xblockElement
.
data
(
'locator'
)
===
rootLocator
)
{
if
(
xblockLocator
===
rootLocator
)
{
this
.
render
({
});
this
.
render
();
}
else
if
(
parentElement
.
hasClass
(
'reorderable-container'
))
{
}
else
if
(
parentElement
.
hasClass
(
'reorderable-container'
))
{
this
.
refreshChildXBlock
(
xblockElement
);
this
.
refreshChildXBlock
(
xblockElement
);
}
else
{
}
else
{
...
...
cms/static/sass/_base.scss
View file @
138cd459
...
@@ -334,6 +334,7 @@ p, ul, ol, dl {
...
@@ -334,6 +334,7 @@ p, ul, ol, dl {
.navigation-link
{
.navigation-link
{
@extend
%cont-truncated
;
@extend
%cont-truncated
;
display
:
inline-block
;
display
:
inline-block
;
vertical-align
:
bottom
;
// correct for extra padding in FF
max-width
:
250px
;
max-width
:
250px
;
&
.navigation-current
{
&
.navigation-current
{
...
...
cms/static/sass/elements/_controls.scss
View file @
138cd459
...
@@ -230,6 +230,7 @@
...
@@ -230,6 +230,7 @@
vertical-align
:
middle
;
vertical-align
:
middle
;
.action-button
{
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
display
:
block
;
border-radius
:
3px
;
border-radius
:
3px
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
...
...
cms/static/sass/elements/_xblocks.scss
View file @
138cd459
...
@@ -15,11 +15,8 @@
...
@@ -15,11 +15,8 @@
}
}
// UI: xblock header
// UI: xblock header
.xblock-header
{
.xblock-header
-primary
{
@include
box-sizing
(
border-box
);
@include
box-sizing
(
border-box
);
@include
ui-flexbox
();
@extend
%ui-align-center-flex
;
justify-content
:
space-between
;
border-bottom
:
1px
solid
$gray-l4
;
border-bottom
:
1px
solid
$gray-l4
;
border-radius
:
(
$baseline
/
5
)
(
$baseline
/
5
)
0
0
;
border-radius
:
(
$baseline
/
5
)
(
$baseline
/
5
)
0
0
;
min-height
:
(
$baseline
*
2
.5
);
min-height
:
(
$baseline
*
2
.5
);
...
@@ -28,17 +25,73 @@
...
@@ -28,17 +25,73 @@
.header-details
{
.header-details
{
@extend
%cont-truncated
;
@extend
%cont-truncated
;
@extend
%ui-justify-left-flex
;
display
:
inline-block
;
@include
ui-flexbox
();
width
:
50%
;
width
:
flex-grid
(
6
,
12
);
vertical-align
:
middle
;
vertical-align
:
middle
;
}
}
.header-actions
{
.header-actions
{
@include
ui-flexbox
();
display
:
inline-block
;
@extend
%ui-justify-right-flex
;
width
:
49%
;
width
:
flex-grid
(
6
,
12
);
vertical-align
:
middle
;
vertical-align
:
middle
;
text-align
:
right
;
}
}
.xblock-header-secondary
{
overflow
:
hidden
;
border-top
:
1px
solid
$gray-l3
;
background-color
:
$gray-l5
;
padding
:
(
$baseline
/
2
)
$baseline
;
.meta-info
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
65%
;
font-style
:
italic
;
color
:
$gray
;
}
.actions-list
{
width
:
34%
;
display
:
inline-block
;
vertical-align
:
middle
;
text-align
:
right
;
.action-item
{
display
:
inline-block
;
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
width
:
auto
;
height
:
(
$baseline
*
1
.5
);
border-radius
:
3px
;
padding
:
3px
(
$baseline
/
2
)
0
(
$baseline
/
2
);
color
:
$gray-l1
;
&
:hover
{
background-color
:
$blue
;
color
:
$gray-l6
;
}
.action-button-text
{
display
:
inline-block
;
vertical-align
:
middle
;
padding
:
0
1px
;
text-transform
:
uppercase
;
}
&
.delete-button
:hover
{
background-color
:
$gray-l1
;
}
}
[
class
^=
"icon-"
]
{
display
:
inline-block
;
vertical-align
:
middle
;
}
}
}
}
}
}
}
}
...
...
cms/static/sass/views/_container.scss
View file @
138cd459
...
@@ -12,13 +12,42 @@
...
@@ -12,13 +12,42 @@
.mast
{
.mast
{
border-bottom
:
none
;
border-bottom
:
none
;
padding-bottom
:
0
;
padding-bottom
:
0
;
.page-header
{
@extend
%t-title
;
@include
font-size
(
28
);
@include
line-height
(
32
);
.subtitle
.navigation-link
{
color
:
$gray
;
&
:hover
{
color
:
$blue
;
}
}
}
}
}
.wrapper-mast
.mast.has-navigation
.nav-actions
{
.wrapper-mast
.mast.has-navigation
.nav-actions
{
bottom
:
-
(
$baseline
*.
75
);
.nav-item
.button
{
.nav-item
{
.edit-button
{
@include
blue-button
;
@extend
%t-action4
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
text-align
:
center
;
.action-button-text
{
display
:
inline-block
;
vertical-align
:
middle
;
}
[
class
^=
"icon-"
]
{
display
:
inline-block
;
vertical-align
:
middle
;
}
}
}
}
}
}
...
@@ -140,6 +169,10 @@ body.view-container .content-primary {
...
@@ -140,6 +169,10 @@ body.view-container .content-primary {
}
}
.xblock-header
{
.xblock-header
{
display
:
block
;
}
.xblock-header-primary
{
@include
ui-flexbox
();
@include
ui-flexbox
();
margin-bottom
:
0
;
margin-bottom
:
0
;
border-bottom
:
none
;
border-bottom
:
none
;
...
@@ -168,6 +201,10 @@ body.view-container .content-primary {
...
@@ -168,6 +201,10 @@ body.view-container .content-primary {
}
}
.xblock-header
{
.xblock-header
{
display
:
block
;
}
.xblock-header-primary
{
display
:
flex
;
display
:
flex
;
margin-bottom
:
0
;
margin-bottom
:
0
;
border-bottom
:
1px
solid
$gray-l4
;
border-bottom
:
1px
solid
$gray-l4
;
...
@@ -183,7 +220,7 @@ body.view-container .content-primary {
...
@@ -183,7 +220,7 @@ body.view-container .content-primary {
// STATE: xBlock containers styled as rows.
// STATE: xBlock containers styled as rows.
&
.xblock-type-container
{
&
.xblock-type-container
{
.xblock-header
{
.xblock-header
-primary
{
margin-bottom
:
0
;
margin-bottom
:
0
;
border-bottom
:
0
;
border-bottom
:
0
;
border-radius
:
(
$baseline
/
5
);
border-radius
:
(
$baseline
/
5
);
...
...
cms/static/sass/views/_unit.scss
View file @
138cd459
...
@@ -750,13 +750,15 @@ body.unit {
...
@@ -750,13 +750,15 @@ body.unit {
body
.unit
{
body
.unit
{
.component-actions
{
.component-actions
,
.xblock-header-secondary
.actions-list
{
.action-item
{
.action-item
{
display
:
inline-block
;
display
:
inline-block
;
margin
:
(
$baseline
/
4
)
0
(
$baseline
/
4
)
(
$baseline
/
4
);
margin
:
(
$baseline
/
4
)
0
(
$baseline
/
4
)
(
$baseline
/
4
);
.action-button
{
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
display
:
block
;
padding
:
0
$baseline
/
2
;
padding
:
0
$baseline
/
2
;
width
:
auto
;
width
:
auto
;
...
@@ -770,10 +772,10 @@ body.unit {
...
@@ -770,10 +772,10 @@ body.unit {
}
}
.action-button-text
{
.action-button-text
{
padding-left
:
1px
;
display
:
inline-block
;
vertical-align
:
bottom
;
vertical-align
:
middle
;
padding
:
0
1px
;
text-transform
:
uppercase
;
text-transform
:
uppercase
;
line-height
:
17px
;
}
}
&
.delete-button
:hover
{
&
.delete-button
:hover
{
...
@@ -783,7 +785,7 @@ body.unit {
...
@@ -783,7 +785,7 @@ body.unit {
[
class
^=
"icon-"
]
{
[
class
^=
"icon-"
]
{
display
:
inline-block
;
display
:
inline-block
;
vertical-align
:
bottom
;
vertical-align
:
middle
;
}
}
}
}
}
}
...
@@ -1128,7 +1130,7 @@ body.unit .xblock-type-container {
...
@@ -1128,7 +1130,7 @@ body.unit .xblock-type-container {
}
}
}
}
.xblock-header
{
.xblock-header
-primary
{
border-bottom
:
0
;
border-bottom
:
0
;
border-radius
:
(
$baseline
/
5
);
border-radius
:
(
$baseline
/
5
);
...
@@ -1178,6 +1180,44 @@ body.unit .component.editing {
...
@@ -1178,6 +1180,44 @@ body.unit .component.editing {
}
}
}
}
// UI: special case xblock with no preview, ex. experiment blocks
body
.unit
.component
{
.wrapper-component-action-header.nopreview
{
position
:
relative
;
border-bottom
:
0
;
}
.xblock-header-secondary
{
overflow
:
hidden
;
border-top
:
1px
solid
$gray-l3
;
background-color
:
$gray-l5
;
padding
:
(
$baseline
/
2
);
.meta-info
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
65%
;
padding-left
:
(
$baseline
/
4
);
font-style
:
italic
;
color
:
$gray
;
}
.actions-list
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
33%
;
text-align
:
right
;
.action-item
{
margin
:
0
;
}
}
}
}
body
.view-unit
.main-column
.unit-body
,
body
.view-unit
.main-column
.unit-body
,
body
.view-container
{
body
.view-container
{
...
...
cms/templates/container.html
View file @
138cd459
...
@@ -54,9 +54,9 @@ main_xblock_info = {
...
@@ -54,9 +54,9 @@ main_xblock_info = {
<div
class=
"wrapper-mast wrapper"
data-location=
""
data-display-name=
""
data-category=
""
>
<div
class=
"wrapper-mast wrapper"
data-location=
""
data-display-name=
""
data-category=
""
>
<header
class=
"mast has-actions has-navigation"
>
<header
class=
"mast has-actions has-navigation
has-subtitle
"
>
<h1
class=
"page-header"
>
<h1
class=
"page-header"
>
<small
class=
"navigation navigation-parents"
>
<small
class=
"navigation navigation-parents
subtitle
"
>
% for ancestor in ancestor_xblocks:
% for ancestor in ancestor_xblocks:
<
%
<
%
ancestor_url =
xblock_studio_url(ancestor)
ancestor_url =
xblock_studio_url(ancestor)
...
@@ -68,11 +68,20 @@ main_xblock_info = {
...
@@ -68,11 +68,20 @@ main_xblock_info = {
% endfor
% endfor
<a
href=
"#"
class=
"navigation-link navigation-current"
>
${xblock.display_name_with_default | h}
</a>
<a
href=
"#"
class=
"navigation-link navigation-current"
>
${xblock.display_name_with_default | h}
</a>
</small>
</small>
<span
class=
"page-header-title"
>
${xblock.display_name_with_default | h}
</span>
</h1>
</h1>
<nav
class=
"nav-actions"
>
<nav
class=
"nav-actions"
>
<h3
class=
"sr"
>
${_("Page Actions")}
</h3>
<h3
class=
"sr"
>
${_("Page Actions")}
</h3>
<ul>
<ul>
% if not unit_publish_state == 'public':
<li
class=
"action-item action-edit nav-item"
>
<a
href=
"#"
class=
"button edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
% endif
</ul>
</ul>
</nav>
</nav>
</header>
</header>
...
...
cms/templates/container_xblock_component.html
View file @
138cd459
...
@@ -4,12 +4,33 @@ from contentstore.views.helpers import xblock_studio_url
...
@@ -4,12 +4,33 @@ from contentstore.views.helpers import xblock_studio_url
%
>
%
>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<section
class=
"wrapper-xblock xblock-type-container level-element"
data-locator=
"${xblock.location}"
data-course-key=
"${xblock.location.course_key}"
>
<section
class=
"wrapper wrapper-xblock wrapper-component-action-header nopreview"
data-locator=
"${locator}"
data-course-key=
"${xblock.location.course_key}"
>
<header
class=
"xblock-header"
>
<div
class=
"component-header"
>
<div
class=
"header-details"
>
${xblock.display_name_with_default}
${xblock.display_name_with_default}
</div>
</div>
<div
class=
"header-actions"
>
<ul
class=
"component-actions"
>
<li
class=
"action-item action-edit"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
<li
class=
"action-item action-duplicate"
>
<a
href=
"#"
data-tooltip=
"${_("
Duplicate
")}"
class=
"duplicate-button action-button"
>
<i
class=
"icon-copy"
></i>
<span
class=
"sr"
>
${_("Duplicate")}
</span>
</a>
</li>
<li
class=
"action-item action-delete"
>
<a
href=
"#"
data-tooltip=
"${_("
Delete
")}"
class=
"delete-button action-button"
>
<i
class=
"icon-trash"
></i>
<span
class=
"sr"
>
${_("Delete")}
</span>
</a>
</li>
</ul>
</section>
<div
class=
"xblock-header-secondary"
>
<div
class=
"meta-info"
>
${_('This block contains multiple components.')}
</div>
<ul
class=
"actions-list"
>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
...
@@ -18,10 +39,6 @@ from contentstore.views.helpers import xblock_studio_url
...
@@ -18,10 +39,6 @@ from contentstore.views.helpers import xblock_studio_url
<i
class=
"icon-arrow-right"
></i>
<i
class=
"icon-arrow-right"
></i>
</a>
</a>
</li>
</li>
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
</ul>
</ul>
</div>
</div>
</header>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</section>
cms/templates/js/mock/mock-container-page.underscore
View file @
138cd459
...
@@ -3,15 +3,22 @@
...
@@ -3,15 +3,22 @@
<div class="wrapper-mast wrapper" data-location="" data-display-name="" data-category="">
<div class="wrapper-mast wrapper" data-location="" data-display-name="" data-category="">
<header class="mast has-actions has-navigation">
<header class="mast has-actions has-navigation">
<h1 class="page-header">
<h1 class="page-header">
<small class="navigation navigation-parents">
<small class="navigation navigation-parents
subtitle
">
<a href="/unit/TestCourse/branch/draft/block/vertical8eb" class="navigation-link navigation-parent">Unit 1</a>
<a href="/unit/TestCourse/branch/draft/block/vertical8eb" class="navigation-link navigation-parent">Unit 1</a>
<a href="#" class="navigation-link navigation-current">
Nested Vertical Test
</a>
<a href="#" class="navigation-link navigation-current">
Test Container
</a>
</small>
</small>
<span class="page-header-title">Test Container</span>
</h1>
</h1>
<nav class="nav-actions">
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<h3 class="sr">Page Actions</h3>
<ul>
<ul>
<li class="action-item action-edit nav-item">
<a href="#" class="button edit-button action-button">
<i class="icon-pencil"></i>
<span class="action-button-text">${_("Edit")}</span>
</a>
</li>
</ul>
</ul>
</nav>
</nav>
</header>
</header>
...
@@ -22,7 +29,7 @@
...
@@ -22,7 +29,7 @@
<section class="content-area">
<section class="content-area">
<article class="content-primary window">
<article class="content-primary window">
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="
TestCourse/branch/draft/block/vertical131
">
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="
locator-container
">
</section>
</section>
<div class="no-container-content is-hidden">
<div class="no-container-content is-hidden">
<p>This page has no content yet.</p>
<p>This page has no content yet.</p>
...
...
cms/templates/js/mock/mock-container-xblock.underscore
View file @
138cd459
<header class="xblock-header"></header>
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span>Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<article class="xblock-render">
<div class="xblock" data-block-type="vertical" data-locator="locator-container">
<div class="xblock" data-block-type="vertical" data-locator="locator-container"
<ol class="reorderable-container">
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<li class="studio-xblock-wrapper is-draggable" data-locator="testCourse/branch/draft/split_test/splitFFF">
<div class="xblock" data-block-type="vertical">
<ol class="reorderable-container">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-A">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-A">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span>Group A</span>
</div>
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-drag">
<li class="action-item action-drag">
...
@@ -16,6 +31,7 @@
...
@@ -16,6 +31,7 @@
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render">
<article class="xblock-render">
...
@@ -25,25 +41,24 @@
...
@@ -25,25 +41,24 @@
<section class="wrapper-xblock level-element"
<section class="wrapper-xblock level-element"
data-locator="locator-component-A1">
data-locator="locator-component-A1">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -54,24 +69,23 @@
...
@@ -54,24 +69,23 @@
<header class="xblock-header">
<header class="xblock-header">
<div class="header-actions">
<div class="header-actions">
<div class="xblock-header-primary">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -80,25 +94,24 @@
...
@@ -80,25 +94,24 @@
<section class="wrapper-xblock level-element"
<section class="wrapper-xblock level-element"
data-locator="locator-component-A3">
data-locator="locator-component-A3">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -112,6 +125,14 @@
...
@@ -112,6 +125,14 @@
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-B">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-B">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span>Group B</span>
</div>
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-drag">
<li class="action-item action-drag">
...
@@ -119,6 +140,7 @@
...
@@ -119,6 +140,7 @@
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render">
<article class="xblock-render">
...
@@ -129,25 +151,24 @@
...
@@ -129,25 +151,24 @@
data-locator="locator-component-B1">
data-locator="locator-component-B1">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -157,25 +178,24 @@
...
@@ -157,25 +178,24 @@
data-locator="locator-component-B2">
data-locator="locator-component-B2">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -185,25 +205,24 @@
...
@@ -185,25 +205,24 @@
data-locator="locator-component-B3">
data-locator="locator-component-B3">
<header class="xblock-header">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<div class="header-actions">
<ul class="actions-list">
<ul class="actions-list">
<li class="action-item action-edit"><a
<li class="action-item action-edit">
href="#"
<a href="#" class="edit-button action-button"></a>
class="edit-button action-button"></a>
</li>
</li>
<li class="action-item action-duplicate"><a
<li class="action-item action-duplicate">
href="#"
<a href="#" class="duplicate-button action-button"></a>
class="duplicate-button action-button"></a>
</li>
</li>
<li class="action-item action-delete"><a
<li class="action-item action-delete">
href="#"
<a href="#" class="delete-button action-button"></a>
class="delete-button action-button"></a>
</li>
</li>
<li class="action-item action-drag">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</li>
</ul>
</ul>
</div>
</div>
</div>
</header>
</header>
<article class="xblock-render"></article>
<article class="xblock-render"></article>
</section>
</section>
...
@@ -216,7 +235,4 @@
...
@@ -216,7 +235,4 @@
</li>
</li>
</ol>
</ol>
</div>
</div>
</li>
</ol>
</div>
</article>
</article>
cms/templates/js/mock/mock-unit-page-child-container.underscore
0 → 100644
View file @
138cd459
<section class="wrapper wrapper-xblock wrapper-component-action-header nopreview" data-locator="locator-child-container">
<div class="component-header">
Test Child Container
</div>
<ul class="component-actions">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button">
<i class="icon-pencil"></i>
<span class="action-button-text">Edit</span>
</a>
</li>
<li class="action-item action-duplicate">
<a href="#" data-tooltip="Duplicate" class="duplicate-button action-button">
<i class="icon-copy"></i>
<span class="sr">Duplicate</span>
</a>
</li>
<li class="action-item action-delete">
<a href="#" data-tooltip="Delete" class="delete-button action-button">
<i class="icon-trash"></i>
<span class="sr">Delete</span>
</a>
</li>
</ul>
</section>
<div class="xblock-header-secondary">
<div class="meta-info">This block contains multiple components.</div>
<ul class="actions-list">
<li class="action-item action-view">
<a href="/container/locator-child-container" class="action-button">
<span class="action-button-text">View</span>
<i class="icon-arrow-right"></i>
</a>
</li>
</ul>
</div>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
cms/templates/js/mock/mock-updated-container-xblock.underscore
0 → 100644
View file @
138cd459
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span>Updated Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-block-type="vertical" data-locator="locator-container"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<ol class="reorderable-container">
</ol>
</div>
</article>
cms/templates/studio_container_wrapper.html
deleted
100644 → 0
View file @
172c6d15
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
contentstore
.
views
.
helpers
import
xblock_studio_url
%
>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
% if is_reorderable:
<li
class=
"studio-xblock-wrapper is-draggable"
data-locator=
"${xblock.location}"
>
% else:
<div
class=
"studio-xblock-wrapper"
>
% endif
<section
class=
"wrapper-xblock xblock-type-container level-element"
data-locator=
"${xblock.location}"
>
<header
class=
"xblock-header"
>
<div
class=
"header-details"
>
${xblock.display_name_with_default}
</div>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
% if not xblock_context['read_only'] and is_reorderable:
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
% endif
</ul>
</div>
</header>
</section>
% if is_reorderable:
</li>
% else:
</div>
% endif
cms/templates/studio_xblock_wrapper.html
View file @
138cd459
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
contentstore
.
views
.
helpers
import
xblock_studio_url
%
>
<
%
xblock_url =
xblock_studio_url(xblock)
show_inline =
xblock.has_children
and
not
xblock_url
section_class =
"level-nesting"
if
show_inline
else
"
level-element
"
collapsible_class =
"is-collapsible"
if
xblock
.
has_children
else
""
%
>
% if not is_root:
% if not is_root:
% if is_reorderable:
% if is_reorderable:
...
@@ -7,16 +17,13 @@
...
@@ -7,16 +17,13 @@
<div
class=
"studio-xblock-wrapper"
data-locator=
"${xblock.location}"
>
<div
class=
"studio-xblock-wrapper"
data-locator=
"${xblock.location}"
>
% endif
% endif
<
%
section_class =
"level-nesting"
if
xblock
.
has_children
else
"
level-element
"
collapsible_class =
"is-collapsible"
if
xblock
.
has_children
else
""
%
>
<section
class=
"wrapper-xblock ${section_class} ${collapsible_class}"
data-course-key=
"${xblock.location.course_key}"
>
<section
class=
"wrapper-xblock ${section_class} ${collapsible_class}"
data-course-key=
"${xblock.location.course_key}"
>
% endif
% endif
<header
class=
"xblock-header"
>
<header
class=
"xblock-header"
>
<div
class=
"xblock-header-primary"
>
<div
class=
"header-details"
>
<div
class=
"header-details"
>
% if
xblock.has_children
:
% if
show_inline
:
<a
href=
"#"
data-tooltip=
"${_('Expand or Collapse')}"
class=
"action expand-collapse collapse"
>
<a
href=
"#"
data-tooltip=
"${_('Expand or Collapse')}"
class=
"action expand-collapse collapse"
>
<i
class=
"icon-caret-down ui-toggle-expansion"
></i>
<i
class=
"icon-caret-down ui-toggle-expansion"
></i>
<span
class=
"sr"
>
${_('Expand or Collapse')}
</span>
<span
class=
"sr"
>
${_('Expand or Collapse')}
</span>
...
@@ -26,8 +33,8 @@
...
@@ -26,8 +33,8 @@
</div>
</div>
<div
class=
"header-actions"
>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
<ul
class=
"actions-list"
>
% if not xblock_context['read_only']:
% if not xblock_context['read_only']
and not is_root
:
% if not
xblock.has_children
:
% if not
show_inline
:
<li
class=
"action-item action-edit"
>
<li
class=
"action-item action-edit"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<i
class=
"icon-pencil"
></i>
...
@@ -47,7 +54,7 @@
...
@@ -47,7 +54,7 @@
</a>
</a>
</li>
</li>
% endif
% endif
% if
not is_root and
is_reorderable:
% if is_reorderable:
<li
class=
"action-item action-drag"
>
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
</li>
...
@@ -55,10 +62,27 @@
...
@@ -55,10 +62,27 @@
% endif
% endif
</ul>
</ul>
</div>
</div>
</div>
% if xblock_url and not is_root:
<div
class=
"xblock-header-secondary"
>
<div
class=
"meta-info"
>
${_('This block contains multiple components.')}
</div>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_url}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
</ul>
</div>
% endif
</header>
</header>
% if is_root or not xblock_url:
<article
class=
"xblock-render"
>
<article
class=
"xblock-render"
>
${content}
${content}
</article>
</article>
% endif
% if not is_root:
% if not is_root:
</section>
</section>
...
...
cms/templates/widgets/sequence-edit.html
View file @
138cd459
<section
class=
"sequence-edit"
>
<section
class=
"sequence-edit"
>
<section
class=
"filters wip"
>
<ul>
<li>
<h2>
Sort:
</h2>
<select>
<option
value=
""
>
Linear Order
</option>
<option
value=
""
>
Recently Modified
</option>
<option
value=
""
>
Type
</option>
<option
value=
""
>
Alphabetically
</option>
</select>
</li>
<li>
<h2>
Filter:
</h2>
<select>
<option
value=
""
>
All content
</option>
<option
value=
""
>
Videos
</option>
<option
value=
""
>
Problems
</option>
<option
value=
""
>
Labs
</option>
<option
value=
""
>
Tutorials
</option>
<option
value=
""
>
HTML
</option>
</select>
<a
href=
"#"
class=
"more"
>
More
</a>
</li>
<li
class=
"search"
>
<input
type=
"search"
name=
""
id=
""
value=
""
placeholder=
"Search"
/>
</li>
</ul>
</section>
<div
class=
"content"
>
<section
class=
"modules"
>
<ol>
<li>
<ol
id=
"sortable"
>
% for child in module.get_children():
<li
class=
"${module.scope_ids.block_type}"
>
<a
href=
"#"
class=
"module-edit"
data-id=
"${child.location}"
data-type=
"${child.js_module_name}"
data-preview-type=
"${child.module_class.js_module_name}"
>
${child.display_name_with_default}
</a>
<a
href=
"#"
class=
"draggable"
>
handle
</a>
</li>
%endfor
</ol>
</li>
</ol>
</section>
</div>
<
%
include
file=
"metadata-edit.html"
/>
<
%
include
file=
"metadata-edit.html"
/>
</section>
</section>
common/lib/xmodule/xmodule/js/src/sequence/edit.coffee
View file @
138cd459
class
@
SequenceDescriptor
extends
XModule
.
Descriptor
class
@
SequenceDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
@
$tabs
=
$
(
@
element
).
find
(
"#sequence-list"
)
@
$tabs
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'#sequence-list li a'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/js/src/vertical/edit.coffee
View file @
138cd459
class
@
VerticalDescriptor
extends
XModule
.
Descriptor
class
@
VerticalDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
@
$items
=
$
(
@
element
).
find
(
".vert-mod"
)
@
$items
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'.vert-mod div'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/js/src/wrapper/edit.coffee
deleted
100644 → 0
View file @
172c6d15
class
@
WrapperDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
console
.
log
'WrapperDescriptor'
@
$items
=
$
(
@
element
).
find
(
".vert-mod"
)
@
$items
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'.vert-mod div'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/split_test_module.py
View file @
138cd459
...
@@ -8,12 +8,13 @@ from webob import Response
...
@@ -8,12 +8,13 @@ from webob import Response
from
xmodule.progress
import
Progress
from
xmodule.progress
import
Progress
from
xmodule.seq_module
import
SequenceDescriptor
from
xmodule.seq_module
import
SequenceDescriptor
from
xmodule.studio_editable
import
StudioEditableModule
from
xmodule.x_module
import
XModule
,
module_attr
from
xmodule.x_module
import
XModule
,
module_attr
from
lxml
import
etree
from
lxml
import
etree
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Integer
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
Integer
,
String
,
ReferenceValueDict
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
log
=
logging
.
getLogger
(
'edx.'
+
__name__
)
log
=
logging
.
getLogger
(
'edx.'
+
__name__
)
...
@@ -23,6 +24,13 @@ class SplitTestFields(object):
...
@@ -23,6 +24,13 @@ class SplitTestFields(object):
"""Fields needed for split test module"""
"""Fields needed for split test module"""
has_children
=
True
has_children
=
True
display_name
=
String
(
display_name
=
"Display Name"
,
help
=
"This name appears in the horizontal navigation at the top of the page."
,
scope
=
Scope
.
settings
,
default
=
"Experiment Block"
)
user_partition_id
=
Integer
(
user_partition_id
=
Integer
(
help
=
"Which user partition is used for this test"
,
help
=
"Which user partition is used for this test"
,
scope
=
Scope
.
content
scope
=
Scope
.
content
...
@@ -45,7 +53,7 @@ class SplitTestFields(object):
...
@@ -45,7 +53,7 @@ class SplitTestFields(object):
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.wants
(
'partitions'
)
@XBlock.wants
(
'partitions'
)
class
SplitTestModule
(
SplitTestFields
,
XModule
):
class
SplitTestModule
(
SplitTestFields
,
XModule
,
StudioEditableModule
):
"""
"""
Show the user the appropriate child. Uses the ExperimentState
Show the user the appropriate child. Uses the ExperimentState
API to figure out which child to show.
API to figure out which child to show.
...
@@ -177,21 +185,10 @@ class SplitTestModule(SplitTestFields, XModule):
...
@@ -177,21 +185,10 @@ class SplitTestModule(SplitTestFields, XModule):
Renders the Studio preview by rendering each child so that they can all be seen and edited.
Renders the Studio preview by rendering each child so that they can all be seen and edited.
"""
"""
fragment
=
Fragment
()
fragment
=
Fragment
()
contents
=
[]
# Only render the children when this block is being shown as the container
root_xblock
=
context
.
get
(
'root_xblock'
)
for
child
in
self
.
descriptor
.
get_children
():
if
root_xblock
and
root_xblock
.
location
==
self
.
location
:
rendered_child
=
self
.
runtime
.
get_module
(
child
)
.
render
(
'student_view'
,
context
)
self
.
render_children
(
context
,
fragment
,
can_reorder
=
False
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
'id'
:
child
.
location
.
to_deprecated_string
(),
'content'
:
rendered_child
.
content
})
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
'items'
:
contents
}))
return
fragment
return
fragment
def
student_view
(
self
,
context
):
def
student_view
(
self
,
context
):
...
@@ -296,3 +293,11 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor):
...
@@ -296,3 +293,11 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor):
makes it use module.get_child_descriptors().
makes it use module.get_child_descriptors().
"""
"""
return
True
return
True
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
SplitTestDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
extend
([
SplitTestDescriptor
.
due
,
])
return
non_editable_fields
common/lib/xmodule/xmodule/studio_editable.py
View file @
138cd459
...
@@ -5,25 +5,34 @@ Mixin to support editing in Studio.
...
@@ -5,25 +5,34 @@ Mixin to support editing in Studio.
class
StudioEditableModule
(
object
):
class
StudioEditableModule
(
object
):
"""
"""
Helper methods for supporting Studio editing of xblocks.
Helper methods for supporting Studio editing of xblocks/xmodules.
This class is only intended to be used with an XModule, as it assumes the existence of
self.descriptor and self.system.
"""
"""
def
render_
reorderable_children
(
self
,
context
,
fragment
):
def
render_
children
(
self
,
context
,
fragment
,
can_reorder
=
False
,
can_add
=
False
,
view_name
=
'student_view'
):
"""
"""
Renders children with the appropriate HTML structure for drag and drop.
Renders the children of the module with HTML appropriate for Studio. If can_reorder is True,
then the children will be rendered to support drag and drop.
"""
"""
contents
=
[]
contents
=
[]
for
child
in
self
.
get_display_items
():
for
child
in
self
.
descriptor
.
get_children
():
# pylint: disable=E1101
if
can_reorder
:
context
[
'reorderable_items'
]
.
add
(
child
.
location
)
context
[
'reorderable_items'
]
.
add
(
child
.
location
)
rendered_child
=
child
.
render
(
'student_view'
,
context
)
child_module
=
self
.
system
.
get_module
(
child
)
# pylint: disable=E1101
rendered_child
=
child_module
.
render
(
view_name
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
contents
.
append
({
'id'
:
child
.
location
.
to_deprecated_string
(),
'content'
:
rendered_child
.
content
'content'
:
rendered_child
.
content
})
})
fragment
.
add_content
(
self
.
system
.
render_template
(
"studio_render_children_view.html"
,
{
fragment
.
add_content
(
self
.
system
.
render_template
(
"studio_render_children_view.html"
,
{
# pylint: disable=E1101
'items'
:
contents
,
'items'
:
contents
,
'xblock_context'
:
context
,
'xblock_context'
:
context
,
'can_add'
:
can_add
,
'can_reorder'
:
can_reorder
,
}))
}))
common/lib/xmodule/xmodule/tests/test_split_module.py
View file @
138cd459
...
@@ -43,7 +43,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
...
@@ -43,7 +43,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond1'
,
text
=
'HTML FOR GROUP 1'
)
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond1'
,
text
=
'HTML FOR GROUP 1'
)
self
.
course
=
self
.
process_xml
(
course
)
self
.
course
=
self
.
process_xml
(
course
)
course_seq
=
self
.
course
.
get_children
()[
0
]
self
.
course_sequence
=
self
.
course
.
get_children
()[
0
]
self
.
module_system
=
get_test_system
()
self
.
module_system
=
get_test_system
()
def
get_module
(
descriptor
):
def
get_module
(
descriptor
):
...
@@ -71,7 +71,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
...
@@ -71,7 +71,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
)
)
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
# pylint: disable=protected-access
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
# pylint: disable=protected-access
self
.
split_test_module
=
course_seq
.
get_children
()[
0
]
self
.
split_test_module
=
self
.
course_sequence
.
get_children
()[
0
]
self
.
split_test_module
.
bind_for_student
(
self
.
module_system
,
self
.
split_test_module
.
_field_data
)
# pylint: disable=protected-access
self
.
split_test_module
.
bind_for_student
(
self
.
module_system
,
self
.
split_test_module
.
_field_data
)
# pylint: disable=protected-access
@ddt.data
((
'0'
,
'split_test_cond0'
),
(
'1'
,
'split_test_cond1'
))
@ddt.data
((
'0'
,
'split_test_cond0'
),
(
'1'
,
'split_test_cond1'
))
...
@@ -147,3 +147,40 @@ class SplitTestModuleTest(XModuleXmlImportTest):
...
@@ -147,3 +147,40 @@ class SplitTestModuleTest(XModuleXmlImportTest):
self
.
assertEquals
(
fields
.
get
(
'user_partition_id'
),
'0'
)
self
.
assertEquals
(
fields
.
get
(
'user_partition_id'
),
'0'
)
self
.
assertIsNotNone
(
fields
.
get
(
'group_id_to_child'
))
self
.
assertIsNotNone
(
fields
.
get
(
'group_id_to_child'
))
self
.
assertEquals
(
len
(
children
),
2
)
self
.
assertEquals
(
len
(
children
),
2
)
def
test_render_studio_view
(
self
):
"""
Test the rendering of the Studio view.
"""
# The split_test module should render both its groups when it is the root
reorderable_items
=
set
()
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
'root_xblock'
:
self
.
split_test_module
,
}
html
=
self
.
module_system
.
render
(
self
.
split_test_module
,
'student_view'
,
context
)
.
content
self
.
assertIn
(
'HTML FOR GROUP 0'
,
html
)
self
.
assertIn
(
'HTML FOR GROUP 1'
,
html
)
# When rendering as a child, it shouldn't render either of its groups
reorderable_items
=
set
()
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
'root_xblock'
:
self
.
course_sequence
,
}
html
=
self
.
module_system
.
render
(
self
.
split_test_module
,
'student_view'
,
context
)
.
content
self
.
assertNotIn
(
'HTML FOR GROUP 0'
,
html
)
self
.
assertNotIn
(
'HTML FOR GROUP 1'
,
html
)
def
test_settings
(
self
):
"""
Test the settings configuration.
"""
non_editable_metadata_fields
=
self
.
split_test_module
.
non_editable_metadata_fields
self
.
assertIn
(
SplitTestDescriptor
.
due
,
non_editable_metadata_fields
)
self
.
assertNotIn
(
SplitTestDescriptor
.
display_name
,
non_editable_metadata_fields
)
common/lib/xmodule/xmodule/tests/test_vertical.py
View file @
138cd459
...
@@ -54,9 +54,20 @@ class VerticalModuleTestCase(BaseVerticalModuleTest):
...
@@ -54,9 +54,20 @@ class VerticalModuleTestCase(BaseVerticalModuleTest):
"""
"""
Test the rendering of the Studio view
Test the rendering of the Studio view
"""
"""
# Vertical shouldn't render children on the unit page
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
False
,
}
html
=
self
.
module_system
.
render
(
self
.
vertical
,
'student_view'
,
context
)
.
content
self
.
assertNotIn
(
self
.
test_html_1
,
html
)
self
.
assertNotIn
(
self
.
test_html_2
,
html
)
# Vertical should render reorderable children on the container page
reorderable_items
=
set
()
reorderable_items
=
set
()
context
=
{
context
=
{
'runtime_type'
:
'studio'
,
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
'reorderable_items'
:
reorderable_items
,
}
}
html
=
self
.
module_system
.
render
(
self
.
vertical
,
'student_view'
,
context
)
.
content
html
=
self
.
module_system
.
render
(
self
.
vertical
,
'student_view'
,
context
)
.
content
...
...
common/lib/xmodule/xmodule/vertical_module.py
View file @
138cd459
...
@@ -30,7 +30,10 @@ class VerticalModule(VerticalFields, XModule, StudioEditableModule):
...
@@ -30,7 +30,10 @@ class VerticalModule(VerticalFields, XModule, StudioEditableModule):
Renders the Studio preview view, which supports drag and drop.
Renders the Studio preview view, which supports drag and drop.
"""
"""
fragment
=
Fragment
()
fragment
=
Fragment
()
self
.
render_reorderable_children
(
context
,
fragment
)
# For the container page we want the full drag-and-drop, but for unit pages we want
# a more concise version that appears alongside the "View =>" link.
if
context
.
get
(
'container_view'
):
self
.
render_children
(
context
,
fragment
,
can_reorder
=
True
,
can_add
=
True
)
return
fragment
return
fragment
def
render_view
(
self
,
context
,
template_name
):
def
render_view
(
self
,
context
,
template_name
):
...
@@ -82,3 +85,11 @@ class VerticalDescriptor(VerticalFields, SequenceDescriptor):
...
@@ -82,3 +85,11 @@ class VerticalDescriptor(VerticalFields, SequenceDescriptor):
# TODO (victor): Does this need its own definition_to_xml method? Otherwise it looks
# TODO (victor): Does this need its own definition_to_xml method? Otherwise it looks
# like verticals will get exported as sequentials...
# like verticals will get exported as sequentials...
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
VerticalDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
extend
([
VerticalDescriptor
.
due
,
])
return
non_editable_fields
common/lib/xmodule/xmodule/wrapper_module.py
View file @
138cd459
...
@@ -20,7 +20,3 @@ class WrapperDescriptor(VerticalDescriptor):
...
@@ -20,7 +20,3 @@ class WrapperDescriptor(VerticalDescriptor):
module_class
=
WrapperModule
module_class
=
WrapperModule
has_children
=
True
has_children
=
True
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/vertical/edit.coffee'
)]}
js_module_name
=
"VerticalDescriptor"
common/static/sass/_mixins.scss
View file @
138cd459
...
@@ -54,19 +54,19 @@
...
@@ -54,19 +54,19 @@
// extends - justify-content right for display:flex alignment in older browsers
// extends - justify-content right for display:flex alignment in older browsers
%ui-justify-right-flex
{
%ui-justify-right-flex
{
-webkit-box-pack
:
end
;
-webkit-box-pack
:
flex-
end
;
-moz-box-pack
:
end
;
-moz-box-pack
:
flex-
end
;
-ms-flex-pack
:
end
;
-ms-flex-pack
:
flex-
end
;
-webkit-justify-content
:
end
;
-webkit-justify-content
:
flex-
end
;
justify-content
:
flex-end
;
justify-content
:
flex-end
;
}
}
// extends - justify-content left for display:flex alignment in older browsers
// extends - justify-content left for display:flex alignment in older browsers
%ui-justify-left-flex
{
%ui-justify-left-flex
{
-webkit-box-pack
:
start
;
-webkit-box-pack
:
flex-
start
;
-moz-box-pack
:
start
;
-moz-box-pack
:
flex-
start
;
-ms-flex-pack
:
start
;
-ms-flex-pack
:
flex-
start
;
-webkit-justify-content
:
start
;
-webkit-justify-content
:
flex-
start
;
justify-content
:
flex-start
;
justify-content
:
flex-start
;
}
}
...
...
common/test/acceptance/pages/studio/component_editor.py
0 → 100644
View file @
138cd459
from
bok_choy.page_object
import
PageObject
from
selenium.webdriver.common.keys
import
Keys
from
selenium.webdriver.common.action_chains
import
ActionChains
from
utils
import
click_css
class
ComponentEditorView
(
PageObject
):
"""
A :class:`.PageObject` representing the rendered view of a component editor.
This class assumes that the editor is our default editor as displayed for xmodules.
"""
BODY_SELECTOR
=
'.xblock-editor'
def
__init__
(
self
,
browser
,
locator
):
"""
Args:
browser (selenium.webdriver): The Selenium-controlled browser that this page is loaded in.
locator (str): The locator that identifies which xblock this :class:`.xblock-editor` relates to.
"""
super
(
ComponentEditorView
,
self
)
.
__init__
(
browser
)
self
.
locator
=
locator
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
.
present
def
_bounded_selector
(
self
,
selector
):
"""
Return `selector`, but limited to this particular `ComponentEditorView` context
"""
return
'{}[data-locator="{}"] {}'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
,
selector
)
def
url
(
self
):
"""
Returns None because this is not directly accessible via URL.
"""
return
None
def
get_setting_entry_index
(
self
,
label
):
"""
Returns the index of the setting entry with given label (display name) within the Settings modal.
"""
# TODO: will need to handle tabbed "Settings" in future (current usage is in vertical, only shows Settings.
setting_labels
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.metadata_edit .wrapper-comp-setting .setting-label'
))
for
index
,
setting
in
enumerate
(
setting_labels
):
if
setting
.
text
==
label
:
return
index
return
None
def
set_field_value_and_save
(
self
,
label
,
value
):
"""
Set the field with given label (display name) to the specified value, and presses Save.
"""
index
=
self
.
get_setting_entry_index
(
label
)
elem
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.metadata_edit div.wrapper-comp-setting input.setting-input'
))[
index
]
# Click in the field, delete the value there.
action
=
ActionChains
(
self
.
browser
)
.
click
(
elem
)
for
_x
in
range
(
0
,
len
(
elem
.
get_attribute
(
'value'
))):
action
=
action
.
send_keys
(
Keys
.
BACKSPACE
)
# Send the new text, then Tab to move to the next field (so change event is triggered).
action
.
send_keys
(
value
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
click_css
(
self
,
'a.action-save'
)
common/test/acceptance/pages/studio/container.py
View file @
138cd459
...
@@ -3,7 +3,7 @@ Container page in Studio
...
@@ -3,7 +3,7 @@ Container page in Studio
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
bok_choy.promise
import
Promise
,
EmptyPromise
from
.
import
BASE_URL
from
.
import
BASE_URL
from
selenium.webdriver.common.action_chains
import
ActionChains
from
selenium.webdriver.common.action_chains
import
ActionChains
...
@@ -15,15 +15,24 @@ class ContainerPage(PageObject):
...
@@ -15,15 +15,24 @@ class ContainerPage(PageObject):
"""
"""
Container page in Studio
Container page in Studio
"""
"""
NAME_SELECTOR
=
'a.navigation-current'
def
__init__
(
self
,
browser
,
unit_
locator
):
def
__init__
(
self
,
browser
,
locator
):
super
(
ContainerPage
,
self
)
.
__init__
(
browser
)
super
(
ContainerPage
,
self
)
.
__init__
(
browser
)
self
.
unit_locator
=
unit_
locator
self
.
locator
=
locator
@property
@property
def
url
(
self
):
def
url
(
self
):
"""URL to the container page for an xblock."""
"""URL to the container page for an xblock."""
return
"{}/container/{}"
.
format
(
BASE_URL
,
self
.
unit_locator
)
return
"{}/container/{}"
.
format
(
BASE_URL
,
self
.
locator
)
@property
def
name
(
self
):
titles
=
self
.
q
(
css
=
self
.
NAME_SELECTOR
)
.
text
if
titles
:
return
titles
[
0
]
else
:
return
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
...
@@ -91,6 +100,14 @@ class ContainerPage(PageObject):
...
@@ -91,6 +100,14 @@ class ContainerPage(PageObject):
# Click the confirmation dialog button
# Click the confirmation dialog button
click_css
(
self
,
'a.button.action-primary'
,
0
)
click_css
(
self
,
'a.button.action-primary'
,
0
)
def
edit
(
self
):
self
.
q
(
css
=
'.edit-button'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.xblock-studio_view'
)
.
present
,
'Wait for the Studio editor to be present'
)
.
fulfill
()
return
self
class
XBlockWrapper
(
PageObject
):
class
XBlockWrapper
(
PageObject
):
...
...
common/test/acceptance/pages/studio/utils.py
View file @
138cd459
...
@@ -5,7 +5,7 @@ from bok_choy.promise import Promise
...
@@ -5,7 +5,7 @@ from bok_choy.promise import Promise
from
selenium.webdriver.common.action_chains
import
ActionChains
from
selenium.webdriver.common.action_chains
import
ActionChains
def
click_css
(
page
,
css
,
source_index
,
require_notification
=
True
):
def
click_css
(
page
,
css
,
source_index
=
0
,
require_notification
=
True
):
"""
"""
Click the button/link with the given css and index on the specified page (subclass of PageObject).
Click the button/link with the given css and index on the specified page (subclass of PageObject).
...
...
common/test/acceptance/tests/test_studio_container.py
View file @
138cd459
"""
"""
Acceptance tests for Studio related to the container page.
Acceptance tests for Studio related to the container page.
"""
"""
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.studio.overview
import
CourseOutlinePage
from
..pages.studio.overview
import
CourseOutlinePage
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
.helpers
import
UniqueCourseTest
from
.helpers
import
UniqueCourseTest
from
..pages.studio.component_editor
import
ComponentEditorView
from
unittest
import
skip
class
ContainerBase
(
UniqueCourseTest
):
class
ContainerBase
(
UniqueCourseTest
):
...
@@ -85,13 +89,17 @@ class ContainerBase(UniqueCourseTest):
...
@@ -85,13 +89,17 @@ class ContainerBase(UniqueCourseTest):
)
.
install
()
)
.
install
()
def
go_to_container_page
(
self
,
make_draft
=
False
):
def
go_to_container_page
(
self
,
make_draft
=
False
):
unit
=
self
.
go_to_unit_page
(
make_draft
)
container
=
unit
.
components
[
0
]
.
go_to_container
()
return
container
def
go_to_unit_page
(
self
,
make_draft
=
False
):
self
.
outline
.
visit
()
self
.
outline
.
visit
()
subsection
=
self
.
outline
.
section
(
'Test Section'
)
.
subsection
(
'Test Subsection'
)
subsection
=
self
.
outline
.
section
(
'Test Section'
)
.
subsection
(
'Test Subsection'
)
unit
=
subsection
.
toggle_expand
()
.
unit
(
'Test Unit'
)
.
go_to
()
unit
=
subsection
.
toggle_expand
()
.
unit
(
'Test Unit'
)
.
go_to
()
if
make_draft
:
if
make_draft
:
unit
.
edit_draft
()
unit
.
edit_draft
()
container
=
unit
.
components
[
0
]
.
go_to_container
()
return
unit
return
container
def
verify_ordering
(
self
,
container
,
expected_orderings
):
def
verify_ordering
(
self
,
container
,
expected_orderings
):
xblocks
=
container
.
xblocks
xblocks
=
container
.
xblocks
...
@@ -131,6 +139,7 @@ class DragAndDropTest(ContainerBase):
...
@@ -131,6 +139,7 @@ class DragAndDropTest(ContainerBase):
expected_ordering
expected_ordering
)
)
@skip
(
"Sporadically drags outside of the Group."
)
def
test_reorder_in_group
(
self
):
def
test_reorder_in_group
(
self
):
"""
"""
Drag Group A Item 2 before Group A Item 1.
Drag Group A Item 2 before Group A Item 1.
...
@@ -303,3 +312,36 @@ class DeleteComponentTest(ContainerBase):
...
@@ -303,3 +312,36 @@ class DeleteComponentTest(ContainerBase):
{
self
.
group_b
:
[
self
.
group_b_item_1
,
self
.
group_b_item_2
]},
{
self
.
group_b
:
[
self
.
group_b_item_1
,
self
.
group_b_item_2
]},
{
self
.
group_empty
:
[]}]
{
self
.
group_empty
:
[]}]
self
.
delete_and_verify
(
self
.
group_a_item_1_action_index
,
expected_ordering
)
self
.
delete_and_verify
(
self
.
group_a_item_1_action_index
,
expected_ordering
)
class
EditContainerTest
(
ContainerBase
):
"""
Tests of editing a container.
"""
__test__
=
True
def
modify_display_name_and_verify
(
self
,
component
):
"""
Helper method for changing a display name.
"""
modified_name
=
'modified'
self
.
assertNotEqual
(
component
.
name
,
modified_name
)
component
.
edit
()
component_editor
=
ComponentEditorView
(
self
.
browser
,
component
.
locator
)
component_editor
.
set_field_value_and_save
(
'Display Name'
,
modified_name
)
self
.
assertEqual
(
component
.
name
,
modified_name
)
def
test_edit_container_on_unit_page
(
self
):
"""
Test the "edit" button on a container appearing on the unit page.
"""
unit
=
self
.
go_to_unit_page
(
make_draft
=
True
)
component
=
unit
.
components
[
0
]
self
.
modify_display_name_and_verify
(
component
)
def
test_edit_container_on_container_page
(
self
):
"""
Test the "edit" button on a container appearing on the container page.
"""
container
=
self
.
go_to_container_page
(
make_draft
=
True
)
self
.
modify_display_name_and_verify
(
container
)
lms/templates/studio_render_children_view.html
View file @
138cd459
<ol
class=
"reorderable-container"
>
% if can_reorder:
<ol
class=
"reorderable-container"
>
% endif
% for item in items:
% for item in items:
${item['content']}
${item['content']}
% endfor
% endfor
</ol>
% if can_reorder:
% if not xblock_context['read_only']:
</ol>
% endif
% if can_add and not xblock_context['read_only']:
<div
class=
"add-xblock-component new-component-item adding"
></div>
<div
class=
"add-xblock-component new-component-item adding"
></div>
% endif
% endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment