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
b5726a68
Commit
b5726a68
authored
Jan 16, 2014
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Front-end work for duplicating components on the unit page.
STUD-1186
parent
58e6f60e
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
262 additions
and
15 deletions
+262
-15
CHANGELOG.rst
+2
-0
cms/djangoapps/contentstore/features/component.feature
+11
-0
cms/djangoapps/contentstore/features/component.py
+16
-0
cms/djangoapps/contentstore/tests/test_item.py
+2
-2
cms/djangoapps/contentstore/views/item.py
+4
-1
cms/static/coffee/spec/main.coffee
+2
-0
cms/static/coffee/src/views/module_edit.coffee
+3
-3
cms/static/coffee/src/views/unit.coffee
+40
-7
cms/static/js/spec/views/unit_spec.js
+166
-0
cms/static/sass/_base.scss
+4
-2
cms/static/sass/assets/_graphics.scss
+7
-0
cms/static/sass/views/_static-pages.scss
+4
-0
cms/templates/component.html
+1
-0
No files found.
CHANGELOG.rst
View file @
b5726a68
...
...
@@ -9,6 +9,8 @@ Blades: Video player start-end time range is now shown even before Play is
clicked. Video player VCR time shows correct non-zero total time for YouTube
videos even before Play is clicked. BLD-529.
Studio: Add ability to duplicate components on the unit page.
Blades: Adds CookieStorage utility for video player that provides convenient
way to work with cookies.
...
...
cms/djangoapps/contentstore/features/component.feature
View file @
b5726a68
...
...
@@ -101,3 +101,14 @@ Feature: CMS.Component Adding
And
I add a
"Blank Advanced Problem"
"Advanced Problem"
component
And
I delete all components
Then
I see no components
Scenario
:
I
can duplicate a component
Given
I am in Studio editing a new unit
And
I add a
"Blank Common Problem"
"Problem"
component
And
I add a
"Multiple Choice"
"Problem"
component
And
I duplicate the
"0"
component
Then
I see a Problem component with display name
"Duplicate of 'Blank Common Problem'"
in position
"1"
And
I reload the page
Then
I see a Problem component with display name
"Blank Common Problem"
in position
"0"
And
I see a Problem component with display name
"Duplicate of 'Blank Common Problem'"
in position
"1"
And
I see a Problem component with display name
"Multiple Choice"
in position
"2"
cms/djangoapps/contentstore/features/component.py
View file @
b5726a68
...
...
@@ -132,3 +132,19 @@ def delete_one_component(step):
def
edit_and_save_component
(
step
):
world
.
css_click
(
'.edit-button'
)
world
.
css_click
(
'.save-button'
)
@step
(
u'I duplicate the "([^"]*)" component$'
)
def
duplicated_component
(
step
,
index
):
duplicate_btn_css
=
'a.duplicate-button'
world
.
css_click
(
duplicate_btn_css
,
int
(
index
))
@step
(
u'I see a Problem component with display name "([^"]*)" in position "([^"]*)"$'
)
def
see_component_in_position
(
step
,
display_name
,
index
):
component_css
=
'section.xmodule_CapaModule'
def
find_problem
(
_driver
):
return
world
.
css_text
(
component_css
,
int
(
index
))
.
startswith
(
display_name
.
upper
())
world
.
wait_for
(
find_problem
,
timeout_msg
=
'Did not find the duplicated problem'
)
cms/djangoapps/contentstore/tests/test_item.py
View file @
b5726a68
...
...
@@ -288,8 +288,8 @@ class TestDuplicateItem(ItemTest):
# Uses default display_name of 'Text' from HTML component.
verify_name
(
self
.
html_locator
,
self
.
seq_locator
,
"Duplicate of 'Text'"
)
# The sequence does not have a display_name set, so
None gets included as the string 'None'
.
verify_name
(
self
.
seq_locator
,
self
.
chapter_locator
,
"Duplicate of
'None'
"
)
# The sequence does not have a display_name set, so
category is shown
.
verify_name
(
self
.
seq_locator
,
self
.
chapter_locator
,
"Duplicate of
sequential
"
)
# Now send a custom display name for the duplicate.
verify_name
(
self
.
seq_locator
,
self
.
chapter_locator
,
"customized name"
,
display_name
=
"customized name"
)
...
...
cms/djangoapps/contentstore/views/item.py
View file @
b5726a68
...
...
@@ -321,7 +321,10 @@ def _duplicate_item(parent_location, duplicate_source_location, display_name=Non
if
display_name
is
not
None
:
duplicate_metadata
[
'display_name'
]
=
display_name
else
:
duplicate_metadata
[
'display_name'
]
=
_
(
"Duplicate of '{0}'"
)
.
format
(
source_item
.
display_name
)
if
source_item
.
display_name
is
None
:
duplicate_metadata
[
'display_name'
]
=
_
(
"Duplicate of {0}"
)
.
format
(
source_item
.
category
)
else
:
duplicate_metadata
[
'display_name'
]
=
_
(
"Duplicate of '{0}'"
)
.
format
(
source_item
.
display_name
)
get_modulestore
(
category
)
.
create_and_save_xmodule
(
dest_location
,
...
...
cms/static/coffee/spec/main.coffee
View file @
b5726a68
...
...
@@ -214,6 +214,8 @@ define([
"js/spec/views/baseview_spec"
,
"js/spec/views/paging_spec"
,
"js/spec/views/unit_spec"
# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js
# "coffee/spec/views/assets_spec"
...
...
cms/static/coffee/src/views/module_edit.coffee
View file @
b5726a68
define
[
"backbone"
,
"jquery"
,
"underscore"
,
"gettext"
,
"xblock/runtime.v1"
,
"js/views/feedback_notification"
,
"js/views/metadata"
,
"js/collections/metadata"
"js/utils/modal"
,
"jquery.inputnumber"
,
"xmodule"
],
"js/utils/modal"
,
"jquery.inputnumber"
,
"xmodule"
,
"coffee/src/main"
],
(
Backbone
,
$
,
_
,
gettext
,
XBlock
,
NotificationView
,
MetadataView
,
MetadataCollection
,
ModalUtils
)
->
class
ModuleEdit
extends
Backbone
.
View
tagName
:
'li'
...
...
@@ -62,7 +62,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
changedMetadata
:
->
return
_
.
extend
(
@
metadataEditor
.
getModifiedMetadataValues
(),
@
customMetadata
())
createItem
:
(
parent
,
payload
)
->
createItem
:
(
parent
,
payload
,
callback
=->
)
->
payload
.
parent_locator
=
parent
$
.
postJSON
(
@
model
.
urlRoot
...
...
@@ -71,7 +71,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
@
model
.
set
(
id
:
data
.
locator
)
@
$el
.
data
(
'locator'
,
data
.
locator
)
@
render
()
)
)
.
success
(
callback
)
render
:
->
if
@
model
.
id
...
...
cms/static/coffee/src/views/unit.coffee
View file @
b5726a68
...
...
@@ -13,6 +13,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
'click .create-draft'
:
'createDraft'
'click .publish-draft'
:
'publishDraft'
'change .visibility-select'
:
'setVisibility'
"click .component-actions .duplicate-button"
:
'duplicateComponent'
initialize
:
=>
@
visibilityView
=
new
UnitEditView
.
Visibility
(
...
...
@@ -86,7 +87,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@
$newComponentItem
.
removeClass
(
'adding'
)
@
$newComponentItem
.
find
(
'.rendered-component'
).
remove
()
saveNewComponent
:
(
event
)
=>
createComponent
:
(
event
,
data
,
message
,
success_callback
)
=>
event
.
preventDefault
()
editor
=
new
ModuleEditView
(
...
...
@@ -94,20 +95,52 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
model
:
new
ModuleModel
()
)
@
$newComponentItem
.
before
(
editor
.
$el
)
notification
=
new
NotificationView
.
Mini
title
:
gettext
(
message
)
+
'…'
notification
.
show
()
callback
=
->
notification
.
hide
()
success_callback
()
analytics
.
track
message
,
course
:
course_location_analytics
unit_id
:
unit_location_analytics
type
:
editor
.
$el
.
data
(
'locator'
)
editor
.
createItem
(
@
$el
.
data
(
'locator'
),
$
(
event
.
currentTarget
).
data
()
data
,
callback
)
analytics
.
track
"Added a Component"
,
course
:
course_location_analytics
unit_id
:
unit_location_analytics
type
:
$
(
event
.
currentTarget
).
data
(
'location'
)
return
editor
saveNewComponent
:
(
event
)
=>
success_callback
=
=>
@
$newComponentItem
.
before
(
editor
.
$el
)
editor
=
@
createComponent
(
event
,
$
(
event
.
currentTarget
).
data
(),
"Adding"
,
success_callback
)
@
closeNewComponent
(
event
)
duplicateComponent
:
(
event
)
=>
$component
=
$
(
event
.
currentTarget
).
parents
(
'.component'
)
source_locator
=
$component
.
data
(
'locator'
)
success_callback
=
->
$component
.
after
(
editor
.
$el
)
$
(
'html, body'
).
animate
({
scrollTop
:
editor
.
$el
.
offset
().
top
},
500
)
editor
=
@
createComponent
(
event
,
{
duplicate_source_locator
:
source_locator
},
"Duplicating"
,
success_callback
)
components
:
=>
@
$
(
'.component'
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'locator'
)).
get
()
wait
:
(
value
)
=>
...
...
cms/static/js/spec/views/unit_spec.js
0 → 100644
View file @
b5726a68
define
([
"coffee/src/views/unit"
,
"js/models/module_info"
,
"js/spec/create_sinon"
,
"js/views/feedback_notification"
,
"jasmine-stealth"
],
function
(
UnitEditView
,
ModuleModel
,
create_sinon
,
NotificationView
)
{
var
verifyJSON
=
function
(
requests
,
json
)
{
var
request
=
requests
[
requests
.
length
-
1
];
expect
(
request
.
url
).
toEqual
(
"/xblock"
);
expect
(
request
.
method
).
toEqual
(
"POST"
);
expect
(
request
.
requestBody
).
toEqual
(
json
);
};
var
verifyComponents
=
function
(
unit
,
locators
)
{
var
components
=
unit
.
$
(
".component"
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
for
(
var
i
=
0
;
i
<
locators
.
length
;
i
++
)
{
expect
(
$
(
components
[
i
]).
data
(
'locator'
)).
toBe
(
locators
[
i
]);
}
};
var
verifyNotification
=
function
(
notificationSpy
,
text
,
requests
)
{
expect
(
notificationSpy
.
constructor
).
toHaveBeenCalled
();
expect
(
notificationSpy
.
show
).
toHaveBeenCalled
();
expect
(
notificationSpy
.
hide
).
not
.
toHaveBeenCalled
();
var
options
=
notificationSpy
.
constructor
.
mostRecentCall
.
args
[
0
];
expect
(
options
.
title
).
toMatch
(
text
);
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
expect
(
notificationSpy
.
hide
).
toHaveBeenCalled
();
};
describe
(
'duplicateComponent '
,
function
()
{
var
unit
;
var
clickDuplicate
=
function
(
index
)
{
unit
.
$
(
".duplicate-button"
)[
index
].
click
();
};
beforeEach
(
function
()
{
setFixtures
(
'<div class="main-wrapper edit-state-draft" data-locator="unit_locator">
\
<ol class="components">
\
<li class="component" data-locator="loc_1">
\
<div class="wrapper wrapper-component-editor">
\
</div>
\
<div class="component-actions">
\
<a href="#" class="duplicate-button standard"><span class="duplicate-icon icon-copy"></span>Duplicate</a>
\
</div>
\
</li>
\
<li class="component" data-locator="loc_2">
\
<div class="wrapper wrapper-component-editor">
\
</div>
\
<div class="component-actions">
\
<a href="#" class="duplicate-button standard"><span class="duplicate-icon icon-copy"></span>Duplicate</a>
\
</div>
\
</li>
\
</ol>
\
'
);
unit
=
new
UnitEditView
({
el
:
$
(
'.main-wrapper'
),
model
:
new
ModuleModel
({
id
:
'unit_locator'
,
state
:
'draft'
})
});
});
it
(
'sends the correct JSON to the server'
,
function
()
{
var
requests
=
create_sinon
.
requests
(
this
);
clickDuplicate
(
0
);
verifyJSON
(
requests
,
'{"duplicate_source_locator":"loc_1","parent_locator":"unit_locator"}'
);
});
it
(
'inserts duplicated component immediately after source upon success and shows notification'
,
function
()
{
var
requests
=
create_sinon
.
requests
(
this
);
clickDuplicate
(
0
);
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"duplicated_item"
});
verifyComponents
(
unit
,
[
'loc_1'
,
'duplicated_item'
,
'loc_2'
]);
});
it
(
'inserts duplicated component at end if last duplicated'
,
function
()
{
var
requests
=
create_sinon
.
requests
(
this
);
clickDuplicate
(
1
);
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"duplicated_item"
});
verifyComponents
(
unit
,
[
'loc_1'
,
'loc_2'
,
'duplicated_item'
]);
});
it
(
'shows a notification while duplicating'
,
function
()
{
var
notificationSpy
=
spyOnConstructor
(
NotificationView
,
"Mini"
,
[
"show"
,
"hide"
]);
notificationSpy
.
show
.
andReturn
(
notificationSpy
);
var
requests
=
create_sinon
.
requests
(
this
);
clickDuplicate
(
0
);
verifyNotification
(
notificationSpy
,
/Duplicating/
,
requests
);
});
it
(
'does not insert duplicated component upon failure'
,
function
()
{
var
server
=
create_sinon
.
server
(
500
,
this
);
clickDuplicate
(
0
);
server
.
respond
();
verifyComponents
(
unit
,
[
'loc_1'
,
'loc_2'
]);
});
});
describe
(
'saveNewComponent '
,
function
()
{
var
unit
;
var
clickNewComponent
=
function
()
{
unit
.
$
(
".new-component .new-component-type a.single-template"
).
click
();
};
beforeEach
(
function
()
{
setFixtures
(
'<div class="main-wrapper edit-state-draft" data-locator="unit_locator">
\
<ol class="components">
\
<li class="component" data-locator="loc_1">
\
<div class="wrapper wrapper-component-editor">
\
</div>
\
</li>
\
<li class="component" data-locator="loc_2">
\
<div class="wrapper wrapper-component-editor">
\
</div>
\
</li>
\
<li class="new-component-item adding">
\
<div class="new-component">
\
<ul class="new-component-type">
\
<li>
\
<a href="#" class="single-template" data-type="discussion" data-category="discussion"/>
\
</li>
\
</ul>
\
</div>
\
</li>
\
</ol>
\
'
);
unit
=
new
UnitEditView
({
el
:
$
(
'.main-wrapper'
),
model
:
new
ModuleModel
({
id
:
'unit_locator'
,
state
:
'draft'
})
});
});
it
(
'sends the correct JSON to the server'
,
function
()
{
var
requests
=
create_sinon
.
requests
(
this
);
clickNewComponent
();
verifyJSON
(
requests
,
'{"category":"discussion","type":"discussion","parent_locator":"unit_locator"}'
);
});
it
(
'inserts new component at end'
,
function
()
{
var
requests
=
create_sinon
.
requests
(
this
);
clickNewComponent
();
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
verifyComponents
(
unit
,
[
'loc_1'
,
'loc_2'
,
'new_item'
]);
});
it
(
'shows a notification while creating'
,
function
()
{
var
notificationSpy
=
spyOnConstructor
(
NotificationView
,
"Mini"
,
[
"show"
,
"hide"
]);
notificationSpy
.
show
.
andReturn
(
notificationSpy
);
var
requests
=
create_sinon
.
requests
(
this
);
clickNewComponent
();
verifyNotification
(
notificationSpy
,
/Adding/
,
requests
);
});
it
(
'does not insert duplicated component upon failure'
,
function
()
{
var
server
=
create_sinon
.
server
(
500
,
this
);
clickNewComponent
();
server
.
respond
();
verifyComponents
(
unit
,
[
'loc_1'
,
'loc_2'
]);
});
});
}
);
cms/static/sass/_base.scss
View file @
b5726a68
...
...
@@ -689,7 +689,8 @@ hr.divide {
}
.edit-button.standard
,
.delete-button.standard
{
.delete-button.standard
,
.duplicate-button.standard
{
@extend
%t-action4
;
@include
white-button
;
float
:
left
;
...
...
@@ -698,7 +699,8 @@ hr.divide {
font-weight
:
400
;
.edit-icon
,
.delete-icon
{
.delete-icon
,
.duplicate-icon
{
margin-right
:
4px
;
}
}
...
...
cms/static/sass/assets/_graphics.scss
View file @
b5726a68
...
...
@@ -105,6 +105,13 @@
}
}
.duplicate-icon
{
display
:
inline-block
;
width
:
12px
;
height
:
12px
;
margin-right
:
2px
;
}
.visibility-toggle
{
.toggle-icon
{
display
:
inline-block
;
...
...
cms/static/sass/views/_static-pages.scss
View file @
b5726a68
...
...
@@ -163,6 +163,10 @@
margin-right
:
12px
;
}
}
.duplicate-button.standard
{
display
:
none
;
}
}
.edit-static-page
{
...
...
cms/templates/component.html
View file @
b5726a68
...
...
@@ -29,6 +29,7 @@
<div
class=
"component-actions"
>
<a
href=
"#"
class=
"edit-button standard"
><span
class=
"edit-icon"
></span>
${_("Edit")}
</a>
<a
href=
"#"
class=
"duplicate-button standard"
><span
class=
"duplicate-icon icon-copy"
></span>
${_("Duplicate")}
</a>
<a
href=
"#"
class=
"delete-button standard"
><span
class=
"delete-icon"
></span>
${_("Delete")}
</a>
</div>
<span
data-tooltip=
"${_("
Drag
to
reorder
")}"
class=
"drag-handle"
></span>
...
...
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