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
5fe384ca
Commit
5fe384ca
authored
Apr 27, 2015
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add tags plugin.
TNL-1924
parent
ced607d5
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
229 additions
and
30 deletions
+229
-30
cms/envs/bok_choy.py
+3
-0
common/test/acceptance/pages/lms/edxnotes.py
+46
-2
common/test/acceptance/tests/lms/test_lms_edxnotes.py
+78
-2
lms/envs/bok_choy.py
+3
-0
lms/static/js/edxnotes/plugins/accessibility.js
+14
-6
lms/static/js/edxnotes/views/notes_factory.js
+1
-1
lms/static/js/edxnotes/views/shim.js
+39
-12
lms/static/js/spec/edxnotes/plugins/accessibility_spec.js
+12
-6
lms/static/js/spec/edxnotes/views/shim_spec.js
+32
-0
lms/static/sass/course/_student-notes.scss
+1
-1
No files found.
cms/envs/bok_choy.py
View file @
5fe384ca
...
...
@@ -65,6 +65,9 @@ FEATURES['MILESTONES_APP'] = True
# Enable pre-requisite course
FEATURES
[
'ENABLE_PREREQUISITE_COURSES'
]
=
True
# Enable student notes
FEATURES
[
'ENABLE_EDXNOTES'
]
=
True
########################### Entrance Exams #################################
FEATURES
[
'ENTRANCE_EXAMS'
]
=
True
...
...
common/test/acceptance/pages/lms/edxnotes.py
View file @
5fe384ca
from
bok_choy.page_object
import
PageObject
,
PageLoadError
,
unguarded
from
bok_choy.promise
import
BrokenPromise
from
bok_choy.promise
import
BrokenPromise
,
EmptyPromise
from
.course_page
import
CoursePage
from
...tests.helpers
import
disable_animations
from
selenium.webdriver.common.action_chains
import
ActionChains
...
...
@@ -523,7 +523,7 @@ class EdxNoteHighlight(NoteChild):
Returns text of the note.
"""
self
.
show
()
element
=
self
.
q
(
css
=
self
.
_bounded_selector
(
".annotator-annotation > div"
))
element
=
self
.
q
(
css
=
self
.
_bounded_selector
(
".annotator-annotation > div
.annotator-note
"
))
if
element
:
text
=
element
.
text
[
0
]
.
strip
()
else
:
...
...
@@ -538,3 +538,47 @@ class EdxNoteHighlight(NoteChild):
Sets text for the note.
"""
self
.
q
(
css
=
self
.
_bounded_selector
(
".annotator-item textarea"
))
.
first
.
fill
(
value
)
@property
def
tags
(
self
):
"""
Returns the tags associated with the note.
Tags are returned as a list of strings, with each tag as an individual string.
"""
tag_text
=
[]
self
.
show
()
tags
=
self
.
q
(
css
=
self
.
_bounded_selector
(
".annotator-annotation > div.annotator-tags > span.annotator-tag"
))
if
tags
:
for
tag
in
tags
:
tag_text
.
append
(
tag
.
text
)
self
.
q
(
css
=
"body"
)
.
first
.
click
()
self
.
wait_for_notes_invisibility
()
return
tag_text
@tags.setter
def
tags
(
self
,
tags
):
"""
Sets tags for the note. Tags should be supplied as a list of strings, with each tag as an individual string.
"""
self
.
q
(
css
=
self
.
_bounded_selector
(
".annotator-item input"
))
.
first
.
fill
(
" "
.
join
(
tags
))
def
has_sr_label
(
self
,
sr_index
,
field_index
,
expected_text
):
"""
Returns true iff a screen reader label (of index sr_index) exists for the annotator field with
the specified field_index and text.
"""
label_exists
=
False
EmptyPromise
(
lambda
:
len
(
self
.
q
(
css
=
self
.
_bounded_selector
(
"li.annotator-item > label.sr"
)))
>
sr_index
,
"Expected more than '{}' sr labels"
.
format
(
sr_index
)
)
.
fulfill
()
annotator_field_label
=
self
.
q
(
css
=
self
.
_bounded_selector
(
"li.annotator-item > label.sr"
))[
sr_index
]
for_attrib_correct
=
annotator_field_label
.
get_attribute
(
"for"
)
==
"annotator-field-"
+
str
(
field_index
)
if
for_attrib_correct
and
(
annotator_field_label
.
text
==
expected_text
):
label_exists
=
True
self
.
q
(
css
=
"body"
)
.
first
.
click
()
self
.
wait_for_notes_invisibility
()
return
label_exists
common/test/acceptance/tests/lms/test_lms_edxnotes.py
View file @
5fe384ca
import
os
from
uuid
import
uuid4
from
datetime
import
datetime
from
unittest
import
skipUnless
from
..helpers
import
UniqueCourseTest
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...pages.lms.auto_auth
import
AutoAuthPage
...
...
@@ -11,7 +10,6 @@ from ...pages.lms.edxnotes import EdxNotesUnitPage, EdxNotesPage
from
...fixtures.edxnotes
import
EdxNotesFixture
,
Note
,
Range
@skipUnless
(
os
.
environ
.
get
(
"FEATURE_EDXNOTES"
),
"Requires Student Notes feature to be enabled"
)
class
EdxNotesTestMixin
(
UniqueCourseTest
):
"""
Creates a course with initial data and contains useful helper methods.
...
...
@@ -140,6 +138,16 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
note
.
text
=
"TEST TEXT {}"
.
format
(
index
)
index
+=
1
def
edit_tags_in_notes
(
self
,
components
,
tags
):
self
.
assertGreater
(
len
(
components
),
0
)
index
=
0
for
component
in
components
:
self
.
assertGreater
(
len
(
component
.
notes
),
0
)
for
note
in
component
.
edit_note
():
note
.
tags
=
tags
[
index
]
index
+=
1
self
.
assertEqual
(
index
,
len
(
tags
),
"Number of supplied tags did not match components"
)
def
remove_notes
(
self
,
components
):
self
.
assertGreater
(
len
(
components
),
0
)
for
component
in
components
:
...
...
@@ -155,6 +163,11 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
expected
=
[
"TEST TEXT {}"
.
format
(
i
)
for
i
in
xrange
(
len
(
notes
))]
self
.
assertEqual
(
expected
,
actual
)
def
assert_tags_in_notes
(
self
,
notes
,
expected_tags
):
actual
=
[
note
.
tags
for
note
in
notes
]
expected
=
[
expected_tags
[
i
]
for
i
in
xrange
(
len
(
notes
))]
self
.
assertEqual
(
expected
,
actual
)
def
test_can_create_notes
(
self
):
"""
Scenario: User can create notes.
...
...
@@ -255,6 +268,69 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
components
=
self
.
note_unit_page
.
components
self
.
assert_notes_are_removed
(
components
)
def
test_can_create_note_with_tags
(
self
):
"""
Scenario: a user of notes can define one with tags
Given I have a course with 3 annotatable components
And I open the unit with 2 annotatable components
When I add a note with tags for the first component
And I refresh the page
Then I see that note was correctly stored with its tags
"""
self
.
note_unit_page
.
visit
()
components
=
self
.
note_unit_page
.
components
for
note
in
components
[
0
]
.
create_note
(
".{}"
.
format
(
self
.
selector
)):
note
.
tags
=
[
"fruit"
,
"tasty"
]
self
.
note_unit_page
.
refresh
()
self
.
assertEqual
([
"fruit"
,
"tasty"
],
self
.
note_unit_page
.
notes
[
0
]
.
tags
)
def
test_can_change_tags
(
self
):
"""
Scenario: a user of notes can edit tags on notes
Given I have a course with 3 components with notes
When I open the unit with 2 annotatable components
And I edit tags on the notes for the 2 annotatable components
Then I see that the tags were correctly changed
And I again edit tags on the notes for the 2 annotatable components
And I refresh the page
Then I see that the tags were correctly changed
"""
self
.
_add_notes
()
self
.
note_unit_page
.
visit
()
components
=
self
.
note_unit_page
.
components
self
.
edit_tags_in_notes
(
components
,
[[
"hard"
],
[
"apple"
,
"pear"
]])
self
.
assert_tags_in_notes
(
self
.
note_unit_page
.
notes
,
[[
"hard"
],
[
"apple"
,
"pear"
]])
self
.
edit_tags_in_notes
(
components
,
[[],
[
"avocado"
]])
self
.
assert_tags_in_notes
(
self
.
note_unit_page
.
notes
,
[[],
[
"avocado"
]])
self
.
note_unit_page
.
refresh
()
self
.
assert_tags_in_notes
(
self
.
note_unit_page
.
notes
,
[[],
[
"avocado"
]])
def
test_sr_labels
(
self
):
"""
Scenario: screen reader labels exist for text and tags fields
Given I have a course with 3 components with notes
When I open the unit with 2 annotatable components
And I open the editor for each note
Then the text and tags fields both have screen reader labels
"""
self
.
_add_notes
()
self
.
note_unit_page
.
visit
()
# First note is in the first annotatable component, will have field indexes 0 and 1.
for
note
in
self
.
note_unit_page
.
components
[
0
]
.
edit_note
():
self
.
assertTrue
(
note
.
has_sr_label
(
0
,
0
,
"Note"
))
self
.
assertTrue
(
note
.
has_sr_label
(
1
,
1
,
"Tags (space-separated)"
))
# Second note is in the second annotatable component, will have field indexes 2 and 3.
for
note
in
self
.
note_unit_page
.
components
[
1
]
.
edit_note
():
self
.
assertTrue
(
note
.
has_sr_label
(
0
,
2
,
"Note"
))
self
.
assertTrue
(
note
.
has_sr_label
(
1
,
3
,
"Tags (space-separated)"
))
class
EdxNotesPageTest
(
EdxNotesTestMixin
):
"""
...
...
lms/envs/bok_choy.py
View file @
5fe384ca
...
...
@@ -94,6 +94,9 @@ FEATURES['MILESTONES_APP'] = True
# Enable pre-requisite course
FEATURES
[
'ENABLE_PREREQUISITE_COURSES'
]
=
True
# Enable student notes
FEATURES
[
'ENABLE_EDXNOTES'
]
=
True
# Unfortunately, we need to use debug mode to serve staticfiles
DEBUG
=
True
...
...
lms/static/js/edxnotes/plugins/accessibility.js
View file @
5fe384ca
...
...
@@ -131,19 +131,27 @@ define(['jquery', 'underscore', 'annotator_1.2.9'], function ($, _, Annotator) {
},
getEditorTabControls
:
function
()
{
var
editor
,
editorControls
,
textArea
,
saveButton
,
cancelButton
,
tabControls
=
[];
var
editor
,
editorControls
,
textArea
,
saveButton
,
cancelButton
,
tabControls
=
[],
annotatorItems
,
tagInput
=
null
;
// Editor elements
editor
=
this
.
annotator
.
element
.
find
(
'.annotator-editor'
);
editorControls
=
editor
.
find
(
'.annotator-controls'
);
textArea
=
editor
.
find
(
'.annotator-listing'
)
.
find
(
'.annotator-item'
)
.
first
()
.
children
(
'textarea'
);
annotatorItems
=
editor
.
find
(
'.annotator-listing'
).
find
(
'.annotator-item'
);
textArea
=
annotatorItems
.
first
().
children
(
'textarea'
);
saveButton
=
editorControls
.
find
(
'.annotator-save'
);
cancelButton
=
editorControls
.
find
(
'.annotator-cancel'
);
tabControls
.
push
(
textArea
,
saveButton
,
cancelButton
);
// If the tags plugin is enabled, add the ability to tab into it.
if
(
annotatorItems
.
length
>
1
)
{
tagInput
=
annotatorItems
.
first
().
next
().
children
(
'input'
);
}
tabControls
.
push
(
textArea
);
if
(
tagInput
){
tabControls
.
push
(
tagInput
);
}
tabControls
.
push
(
saveButton
,
cancelButton
);
return
tabControls
;
},
...
...
lms/static/js/edxnotes/views/notes_factory.js
View file @
5fe384ca
...
...
@@ -6,7 +6,7 @@ define([
'js/edxnotes/plugins/events'
,
'js/edxnotes/plugins/accessibility'
,
'js/edxnotes/plugins/caret_navigation'
],
function
(
$
,
_
,
Annotator
,
NotesLogger
)
{
var
plugins
=
[
'Auth'
,
'Store'
,
'Scroller'
,
'Events'
,
'Accessibility'
,
'CaretNavigation'
],
var
plugins
=
[
'Auth'
,
'Store'
,
'Scroller'
,
'Events'
,
'Accessibility'
,
'CaretNavigation'
,
'Tags'
],
getOptions
,
setupPlugins
,
updateHeaders
,
getAnnotator
;
/**
...
...
lms/static/js/edxnotes/views/shim.js
View file @
5fe384ca
...
...
@@ -58,6 +58,34 @@ define([
return
(
timeToExpiry
>
0
)
?
timeToExpiry
:
0
;
};
Annotator
.
Plugin
.
Tags
.
prototype
.
updateField
=
_
.
compose
(
function
()
{
// Add screen reader label for edit mode. Note that the id of the tags element will not always be "1".
// It depends on the number of annotatable components on the page.
var
tagsField
=
$
(
"li.annotator-item >input"
,
this
.
annotator
.
editor
.
element
).
attr
(
'id'
);
if
(
$
(
"label.sr[for='"
+
tagsField
+
"']"
,
this
.
annotator
.
editor
.
element
).
length
===
0
)
{
$
(
'<label class="sr" for='
+
tagsField
+
'>'
+
_t
(
'Tags (space-separated)'
)
+
'</label>'
).
insertBefore
(
$
(
'#'
+
tagsField
,
this
.
annotator
.
editor
.
element
)
);
}
return
this
;
},
Annotator
.
Plugin
.
Tags
.
prototype
.
updateField
);
Annotator
.
Plugin
.
Tags
.
prototype
.
updateViewer
=
_
.
compose
(
function
()
{
// Add ARIA information for viewing mode.
$
(
'div.annotator-tags'
,
this
.
wrapper
).
attr
({
'role'
:
'region'
,
'aria-label'
:
'tags'
});
return
this
;
},
Annotator
.
Plugin
.
Tags
.
prototype
.
updateViewer
);
/**
* Modifies Annotator.highlightRange to add "tabindex=0" and role="link"
* attributes to the <span class="annotator-hl"> markup that encloses the
...
...
@@ -186,25 +214,24 @@ define([
'</div>'
].
join
(
''
);
/**
* Modifies Annotator._setupEditor to add a label for textarea#annotator-field-0.
**/
Annotator
.
prototype
.
_setupEditor
=
_
.
compose
(
function
()
{
$
(
'<label class="sr" for="annotator-field-0">Edit note</label>'
).
insertBefore
(
$
(
'#annotator-field-0'
,
this
.
wrapper
)
);
return
this
;
},
Annotator
.
prototype
.
_setupEditor
);
/**
* Modifies Annotator.Editor.show, in the case of a keydown event, to remove
* focus from Save button and put it on form.annotator-widget instead.
*
* Also add a sr label for note textarea.
**/
Annotator
.
Editor
.
prototype
.
show
=
_
.
compose
(
function
(
event
)
{
// Add screen reader label for the note area. Note that the id of the tags element will not always be "0".
// It depends on the number of annotatable components on the page.
var
noteField
=
$
(
"li.annotator-item >textarea"
,
this
.
element
).
attr
(
'id'
);
if
(
$
(
"label.sr[for='"
+
noteField
+
"']"
,
this
.
element
).
length
===
0
)
{
$
(
'<label class="sr" for='
+
noteField
+
'>'
+
_t
(
'Note'
)
+
'</label>'
).
insertBefore
(
$
(
'#'
+
noteField
,
this
.
element
)
);
}
if
(
event
.
type
===
'keydown'
)
{
this
.
element
.
find
(
'.annotator-save'
).
removeClass
(
this
.
classes
.
focus
);
this
.
element
.
find
(
'form.annotator-widget'
).
focus
();
...
...
lms/static/js/spec/edxnotes/plugins/accessibility_spec.js
View file @
5fe384ca
...
...
@@ -168,12 +168,12 @@ define([
};
highlight
.
data
(
'annotation'
,
annotation
);
this
.
annotator
.
viewer
.
load
([
annotation
]);
listing
=
this
.
annotator
.
element
.
find
(
'.annotator-listing'
).
first
()
,
listing
=
this
.
annotator
.
element
.
find
(
'.annotator-listing'
).
first
()
;
note
=
this
.
annotator
.
element
.
find
(
'.annotator-note'
).
first
();
edit
=
this
.
annotator
.
element
.
find
(
'.annotator-edit'
).
first
();
del
=
this
.
annotator
.
element
.
find
(
'.annotator-delete'
).
first
();
close
=
this
.
annotator
.
element
.
find
(
'.annotator-close'
).
first
();
spyOn
(
this
.
annotator
.
viewer
,
'hide'
).
andCallThrough
();
;
spyOn
(
this
.
annotator
.
viewer
,
'hide'
).
andCallThrough
();
});
it
(
'should give focus to Note on Listing TAB keydown'
,
function
()
{
...
...
@@ -224,7 +224,7 @@ define([
});
describe
(
'keydown events on editor'
,
function
()
{
var
highlight
,
annotation
,
form
,
textArea
,
save
,
cancel
;
var
highlight
,
annotation
,
form
,
annotatorItems
,
textArea
,
tags
,
save
,
cancel
;
beforeEach
(
function
()
{
highlight
=
$
(
'<span class="annotator-hl" tabindex="0"/>'
).
appendTo
(
this
.
annotator
.
element
);
...
...
@@ -236,7 +236,9 @@ define([
highlight
.
data
(
'annotation'
,
annotation
);
this
.
annotator
.
editor
.
show
(
annotation
,
{
'left'
:
0
,
'top'
:
0
});
form
=
this
.
annotator
.
element
.
find
(
'form.annotator-widget'
);
textArea
=
this
.
annotator
.
element
.
find
(
'.annotator-item'
).
first
().
children
(
'textarea'
);
annotatorItems
=
this
.
annotator
.
element
.
find
(
'.annotator-item'
);
textArea
=
annotatorItems
.
first
().
children
(
'textarea'
);
tags
=
annotatorItems
.
first
().
next
().
children
(
'input'
);
save
=
this
.
annotator
.
element
.
find
(
'.annotator-save'
);
cancel
=
this
.
annotator
.
element
.
find
(
'.annotator-cancel'
);
spyOn
(
this
.
annotator
.
editor
,
'submit'
).
andCallThrough
();
...
...
@@ -255,9 +257,11 @@ define([
expect
(
cancel
).
toBeFocused
();
});
it
(
'should cycle forward through tex
area
, save, and cancel on TAB keydown'
,
function
()
{
it
(
'should cycle forward through tex
tarea, tags
, save, and cancel on TAB keydown'
,
function
()
{
textArea
.
focus
();
textArea
.
trigger
(
tabForwardEvent
());
expect
(
tags
).
toBeFocused
();
tags
.
trigger
(
tabForwardEvent
());
expect
(
save
).
toBeFocused
();
save
.
trigger
(
tabForwardEvent
());
expect
(
cancel
).
toBeFocused
();
...
...
@@ -265,13 +269,15 @@ define([
expect
(
textArea
).
toBeFocused
();
});
it
(
'should cycle back through tex
area
, save, and cancel on SHIFT + TAB keydown'
,
function
()
{
it
(
'should cycle back through tex
tarea, tags
, save, and cancel on SHIFT + TAB keydown'
,
function
()
{
textArea
.
focus
();
textArea
.
trigger
(
tabBackwardEvent
());
expect
(
cancel
).
toBeFocused
();
cancel
.
trigger
(
tabBackwardEvent
());
expect
(
save
).
toBeFocused
();
save
.
trigger
(
tabBackwardEvent
());
expect
(
tags
).
toBeFocused
();
tags
.
trigger
(
tabBackwardEvent
());
expect
(
textArea
).
toBeFocused
();
});
...
...
lms/static/js/spec/edxnotes/views/shim_spec.js
View file @
5fe384ca
...
...
@@ -233,5 +233,37 @@ define([
expect
(
mockViewer
.
element
.
appendTo
).
toHaveBeenCalledWith
(
annotators
[
0
].
wrapper
);
});
});
describe
(
'TagsPlugin'
,
function
()
{
it
(
'should add ARIA label information to the viewer'
,
function
()
{
var
tagDiv
,
annotation
=
{
id
:
'01'
,
text
:
"Test text"
,
tags
:
[
"tag1"
,
"tag2"
,
"tag3"
],
highlights
:
[
highlights
[
0
].
get
(
0
)]
};
annotators
[
0
].
viewer
.
load
([
annotation
]);
tagDiv
=
annotators
[
0
].
viewer
.
element
.
find
(
'.annotator-tags'
);
expect
(
$
(
tagDiv
).
attr
(
'role'
)).
toEqual
(
'region'
);
expect
(
$
(
tagDiv
).
attr
(
'aria-label'
)).
toEqual
(
'tags'
);
// Three children for the individual tags.
expect
(
$
(
tagDiv
).
children
().
length
).
toEqual
(
3
);
});
it
(
'should add screen reader label to the editor'
,
function
()
{
var
srLabel
,
editor
,
inputId
;
// We don't know exactly what the input ID will be (depends on number of annotatable components shown),
// but the sr label "for" attribute should match the ID of the element immediately following it.
annotators
[
0
].
showEditor
({},
{});
editor
=
annotators
[
0
].
editor
;
srLabel
=
editor
.
element
.
find
(
"label.sr"
);
inputId
=
srLabel
.
next
().
attr
(
'id'
);
expect
(
srLabel
.
attr
(
'for'
)).
toEqual
(
inputId
);
});
});
});
});
lms/static/sass/course/_student-notes.scss
View file @
5fe384ca
...
...
@@ -212,7 +212,7 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
.reference-title
{
@extend
%t-title8
;
@extend
%t-weight3
;
margin-top
:
$baseline
;
margin-top
:
(
$baseline
/
2
)
;
text-transform
:
uppercase
;
letter-spacing
:
(
$baseline
/
20
);
color
:
$gray-l2
;
...
...
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