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
2ff863cb
Commit
2ff863cb
authored
Feb 20, 2015
by
Anton Stupak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6778 from edx/jmclaus/edxnotes-add-a11y
TNL 713: Added accessibility plugin.
parents
ca8bfa84
7cb495a1
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
862 additions
and
71 deletions
+862
-71
common/templates/edxnotes_wrapper.html
+1
-0
lms/static/js/edxnotes/plugins/accessibility.js
+320
-0
lms/static/js/edxnotes/plugins/scroller.js
+9
-4
lms/static/js/edxnotes/views/notes_factory.js
+2
-2
lms/static/js/edxnotes/views/shim.js
+101
-32
lms/static/js/edxnotes/views/toggle_notes_factory.js
+18
-5
lms/static/js/fixtures/edxnotes/toggle_notes.html
+1
-1
lms/static/js/spec/edxnotes/plugins/accessibility_spec.js
+318
-0
lms/static/js/spec/edxnotes/views/shim_spec.js
+14
-0
lms/static/js/spec/edxnotes/views/toggle_notes_factory_spec.js
+10
-5
lms/static/js/spec/main.js
+1
-0
lms/static/sass/course/modules/_student-notes.scss
+65
-18
lms/templates/courseware/courseware.html
+1
-3
lms/templates/edxnotes/toggle_notes.html
+1
-1
No files found.
common/templates/edxnotes_wrapper.html
View file @
2ff863cb
<
%!
import
json
%
>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
student
.
models
import
anonymous_id_for_user
%
>
<
%
if
user:
...
...
lms/static/js/edxnotes/plugins/accessibility.js
0 → 100644
View file @
2ff863cb
;(
function
(
define
,
undefined
)
{
'use strict'
;
define
([
'jquery'
,
'underscore'
,
'annotator_1.2.9'
],
function
(
$
,
_
,
Annotator
)
{
/**
* Adds the Accessibility Plugin
**/
Annotator
.
Plugin
.
Accessibility
=
function
()
{
_
.
bindAll
(
this
,
'addAriaAttributes'
,
'onHighlightKeyDown'
,
'onViewerKeyDown'
,
'onEditorKeyDown'
,
'addDescriptions'
,
'removeDescription'
,
'saveCurrentHighlight'
,
'focusOnGrabber'
,
'showViewer'
,
'onClose'
);
// Call the Annotator.Plugin constructor this sets up the element and
// options properties.
Annotator
.
Plugin
.
apply
(
this
,
arguments
);
};
$
.
extend
(
Annotator
.
Plugin
.
Accessibility
.
prototype
,
new
Annotator
.
Plugin
(),
{
pluginInit
:
function
()
{
this
.
annotator
.
subscribe
(
'annotationViewerTextField'
,
this
.
addAriaAttributes
);
this
.
annotator
.
subscribe
(
'annotationsLoaded'
,
this
.
addDescriptions
);
this
.
annotator
.
subscribe
(
'annotationCreated'
,
this
.
addDescriptions
);
this
.
annotator
.
subscribe
(
'annotationDeleted'
,
this
.
removeDescription
);
this
.
annotator
.
element
.
on
(
'keydown.accessibility.hl'
,
'.annotator-hl'
,
this
.
onHighlightKeyDown
);
this
.
annotator
.
element
.
on
(
'keydown.accessibility.viewer'
,
'.annotator-viewer'
,
this
.
onViewerKeyDown
);
this
.
annotator
.
element
.
on
(
'keydown.accessibility.editor'
,
'.annotator-editor'
,
this
.
onEditorKeyDown
);
this
.
addFocusGrabber
();
this
.
addTabIndex
();
},
destroy
:
function
()
{
this
.
annotator
.
unsubscribe
(
'annotationViewerTextField'
,
this
.
addAriaAttributes
);
this
.
annotator
.
unsubscribe
(
'annotationsLoaded'
,
this
.
addDescriptions
);
this
.
annotator
.
unsubscribe
(
'annotationCreated'
,
this
.
addDescriptions
);
this
.
annotator
.
unsubscribe
(
'annotationDeleted'
,
this
.
removeDescription
);
this
.
annotator
.
element
.
off
(
'.accessibility'
);
this
.
removeFocusGrabber
();
this
.
savedHighlights
=
null
;
},
addTabIndex
:
function
()
{
this
.
annotator
.
element
.
find
(
'.annotator-edit, .annotator-delete'
)
.
attr
(
'tabindex'
,
0
);
},
addFocusGrabber
:
function
()
{
this
.
focusGrabber
=
$
(
'<span />'
,
{
'class'
:
'sr edx-notes-focus-grabber'
,
'tabindex'
:
'-1'
,
'text'
:
gettext
(
'Focus grabber'
)
});
this
.
annotator
.
wrapper
.
before
(
this
.
focusGrabber
);
},
removeFocusGrabber
:
function
()
{
if
(
this
.
focusGrabber
)
{
this
.
focusGrabber
.
remove
();
this
.
focusGrabber
=
null
;
}
},
focusOnGrabber
:
function
()
{
this
.
annotator
.
wrapper
.
siblings
(
'.edx-notes-focus-grabber'
).
focus
();
},
addDescriptions
:
function
(
annotations
)
{
if
(
!
_
.
isArray
(
annotations
))
{
annotations
=
[
annotations
];
}
_
.
each
(
annotations
,
function
(
annotation
)
{
var
id
=
annotation
.
id
||
_
.
uniqueId
();
this
.
annotator
.
wrapper
.
after
(
$
(
'<div />'
,
{
'class'
:
'aria-note-description sr'
,
'id'
:
'aria-note-description-'
+
id
,
'text'
:
Annotator
.
Util
.
escape
(
annotation
.
text
)
}));
$
(
annotation
.
highlights
).
attr
({
'aria-describedby'
:
'aria-note-description-'
+
id
});
},
this
);
},
removeDescription
:
function
(
annotation
)
{
var
id
=
$
(
annotation
.
highlights
).
attr
(
'aria-describedby'
);
$
(
'#'
+
id
).
remove
();
},
addAriaAttributes
:
function
(
field
,
annotation
)
{
// Add ARIA attributes to associated note ie <div>My note</div>
$
(
field
).
attr
({
'tabindex'
:
-
1
,
'role'
:
'note'
,
'class'
:
'annotator-note'
});
},
saveCurrentHighlight
:
function
(
annotation
)
{
if
(
annotation
&&
annotation
.
highlights
)
{
this
.
savedHighlights
=
annotation
.
highlights
[
0
];
}
},
focusOnHighlightedText
:
function
()
{
if
(
this
.
savedHighlights
)
{
this
.
savedHighlights
.
focus
();
this
.
savedHighlights
=
null
;
}
},
getViewerTabControls
:
function
()
{
var
viewer
,
note
,
viewerControls
,
editButton
,
delButton
,
closeButton
,
tabControls
=
[];
// Viewer elements
viewer
=
this
.
annotator
.
element
.
find
(
'.annotator-viewer'
);
note
=
viewer
.
find
(
'.annotator-note'
);
viewerControls
=
viewer
.
find
(
'.annotator-controls'
);
editButton
=
viewerControls
.
find
(
'.annotator-edit'
);
delButton
=
viewerControls
.
find
(
'.annotator-delete'
);
closeButton
=
viewerControls
.
find
(
'.annotator-close'
);
tabControls
.
push
(
note
,
editButton
,
delButton
,
closeButton
);
return
tabControls
;
},
getEditorTabControls
:
function
()
{
var
editor
,
editorControls
,
textArea
,
saveButton
,
cancelButton
,
tabControls
=
[];
// 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'
);
saveButton
=
editorControls
.
find
(
'.annotator-save'
);
cancelButton
=
editorControls
.
find
(
'.annotator-cancel'
);
tabControls
.
push
(
textArea
,
saveButton
,
cancelButton
);
return
tabControls
;
},
focusOnNextTabControl
:
function
(
tabControls
,
tabControl
)
{
var
nextIndex
;
_
.
each
(
tabControls
,
function
(
element
,
index
)
{
if
(
element
.
is
(
tabControl
))
{
nextIndex
=
index
===
tabControls
.
length
-
1
?
0
:
index
+
1
;
tabControls
[
nextIndex
].
focus
();
}
});
},
focusOnPreviousTabControl
:
function
(
tabControls
,
tabControl
)
{
var
previousIndex
;
_
.
each
(
tabControls
,
function
(
element
,
index
)
{
if
(
element
.
is
(
tabControl
))
{
previousIndex
=
index
===
0
?
tabControls
.
length
-
1
:
index
-
1
;
tabControls
[
previousIndex
].
focus
();
}
});
},
showViewer
:
function
(
position
,
annotation
)
{
annotation
=
$
.
makeArray
(
annotation
);
this
.
saveCurrentHighlight
(
annotation
[
0
]);
this
.
annotator
.
showViewer
(
annotation
,
position
);
this
.
annotator
.
element
.
find
(
'.annotator-listing'
).
focus
();
this
.
annotator
.
subscribe
(
'annotationDeleted'
,
this
.
focusOnGrabber
);
},
onClose
:
function
()
{
this
.
focusOnHighlightedText
();
this
.
annotator
.
unsubscribe
(
'annotationDeleted'
,
this
.
focusOnGrabber
);
},
onHighlightKeyDown
:
function
(
event
)
{
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
,
target
=
$
(
event
.
currentTarget
),
annotation
,
position
;
switch
(
keyCode
)
{
case
KEY
.
TAB
:
// This happens only when coming from notes page
if
(
this
.
annotator
.
viewer
.
isShown
())
{
this
.
annotator
.
element
.
find
(
'.annotator-listing'
).
focus
();
}
break
;
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
if
(
!
this
.
annotator
.
viewer
.
isShown
())
{
position
=
target
.
position
();
this
.
showViewer
(
position
,
target
.
data
(
'annotation'
));
}
break
;
case
KEY
.
ESCAPE
:
this
.
annotator
.
viewer
.
hide
();
break
;
}
// We do not stop propagation and default behavior on a TAB keypress
if
(
event
.
keyCode
!==
KEY
.
TAB
||
(
event
.
keyCode
===
KEY
.
TAB
&&
this
.
annotator
.
viewer
.
isShown
()))
{
event
.
preventDefault
();
event
.
stopPropagation
();
}
},
onViewerKeyDown
:
function
(
event
)
{
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
,
target
=
$
(
event
.
target
),
listing
=
this
.
annotator
.
element
.
find
(
'.annotator-listing'
),
tabControls
;
switch
(
keyCode
)
{
case
KEY
.
TAB
:
tabControls
=
this
.
getViewerTabControls
();
if
(
event
.
shiftKey
)
{
// Tabbing backwards
if
(
target
.
is
(
listing
))
{
_
.
last
(
tabControls
).
focus
();
}
else
{
this
.
focusOnPreviousTabControl
(
tabControls
,
target
);
}
}
else
{
// Tabbing forward
if
(
target
.
is
(
listing
))
{
_
.
first
(
tabControls
).
focus
();
}
else
{
this
.
focusOnNextTabControl
(
tabControls
,
target
);
}
}
event
.
preventDefault
();
event
.
stopPropagation
();
break
;
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
if
(
target
.
hasClass
(
'annotator-close'
))
{
this
.
annotator
.
viewer
.
hide
();
this
.
onClose
();
event
.
preventDefault
();
}
break
;
case
KEY
.
ESCAPE
:
this
.
annotator
.
viewer
.
hide
();
this
.
onClose
();
event
.
preventDefault
();
break
;
}
},
onEditorKeyDown
:
function
(
event
)
{
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
,
target
=
$
(
event
.
target
),
editor
,
form
,
editorControls
,
save
,
cancel
,
tabControls
;
editor
=
this
.
annotator
.
element
.
find
(
'.annotator-editor'
);
form
=
editor
.
find
(
'.annotator-widget'
);
editorControls
=
editor
.
find
(
'.annotator-controls'
);
save
=
editorControls
.
find
(
'.annotator-save'
);
cancel
=
editorControls
.
find
(
'.annotator-cancel'
);
switch
(
keyCode
)
{
case
KEY
.
TAB
:
tabControls
=
this
.
getEditorTabControls
();
if
(
event
.
shiftKey
)
{
// Tabbing backwards
if
(
target
.
is
(
form
))
{
_
.
last
(
tabControls
).
focus
();
}
else
{
this
.
focusOnPreviousTabControl
(
tabControls
,
target
);
}
}
else
{
// Tabbing forward
if
(
target
.
is
(
form
))
{
_
.
first
(
tabControls
).
focus
();
}
else
{
this
.
focusOnNextTabControl
(
tabControls
,
target
);
}
}
event
.
preventDefault
();
event
.
stopPropagation
();
break
;
case
KEY
.
ENTER
:
if
(
target
.
is
(
save
)
||
event
.
metaKey
||
event
.
ctrlKey
)
{
this
.
annotator
.
editor
.
submit
();
}
else
if
(
target
.
is
(
cancel
))
{
this
.
annotator
.
editor
.
hide
();
}
else
{
break
;
}
this
.
onClose
();
event
.
preventDefault
();
break
;
case
KEY
.
SPACE
:
if
(
target
.
is
(
save
))
{
this
.
annotator
.
editor
.
submit
();
}
else
if
(
target
.
is
(
cancel
))
{
this
.
annotator
.
editor
.
hide
();
}
else
{
break
;
}
this
.
onClose
();
event
.
preventDefault
();
break
;
case
KEY
.
ESCAPE
:
this
.
annotator
.
editor
.
hide
();
this
.
onClose
();
event
.
preventDefault
();
break
;
}
}
});
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/static/js/edxnotes/plugins/scroller.js
View file @
2ff863cb
...
...
@@ -47,10 +47,15 @@ define(['jquery', 'underscore', 'annotator_1.2.9'], function ($, _, Annotator) {
highlight
=
$
(
note
.
highlights
[
0
]);
offset
=
highlight
.
position
();
// Open the note
this
.
annotator
.
showFrozenViewer
([
note
],
{
top
:
offset
.
top
+
0.5
*
highlight
.
height
(),
left
:
offset
.
left
+
0.5
*
highlight
.
width
()
});
this
.
annotator
.
plugins
.
Accessibility
.
showViewer
(
{
top
:
offset
.
top
+
0.5
*
highlight
.
height
(),
left
:
offset
.
left
+
0.5
*
highlight
.
width
()
},
note
);
// Freeze the viewer
this
.
annotator
.
freezeAll
();
// Scroll to highlight
this
.
scrollIntoView
(
highlight
);
}
...
...
lms/static/js/edxnotes/views/notes_factory.js
View file @
2ff863cb
...
...
@@ -3,9 +3,9 @@
define
([
'jquery'
,
'underscore'
,
'annotator_1.2.9'
,
'js/edxnotes/utils/logger'
,
'js/edxnotes/views/shim'
,
'js/edxnotes/plugins/scroller'
,
'js/edxnotes/plugins/events'
'js/edxnotes/plugins/events'
,
'js/edxnotes/plugins/accessibility'
],
function
(
$
,
_
,
Annotator
,
NotesLogger
)
{
var
plugins
=
[
'Auth'
,
'Store'
,
'Scroller'
,
'Events'
],
var
plugins
=
[
'Auth'
,
'Store'
,
'Scroller'
,
'Events'
,
'Accessibility'
],
getOptions
,
setupPlugins
,
updateHeaders
,
getAnnotator
;
/**
...
...
lms/static/js/edxnotes/views/shim.js
View file @
2ff863cb
...
...
@@ -59,13 +59,17 @@ define([
};
/**
* Modifies Annotator.highlightRange to add a "tabindex=0" attribute
* to the <span class="annotator-hl"> markup that encloses the note.
* These are then focusable via the TAB key.
* Modifies Annotator.highlightRange to add "tabindex=0" and role="link"
* attributes to the <span class="annotator-hl"> markup that encloses the
* note. These are then focusable via the TAB key and are accessible to
* screen readers.
**/
Annotator
.
prototype
.
highlightRange
=
_
.
compose
(
function
(
results
)
{
$
(
'.annotator-hl'
,
this
.
wrapper
).
attr
(
'tabindex'
,
0
);
$
(
'.annotator-hl'
,
this
.
wrapper
).
attr
({
'tabindex'
:
0
,
'role'
:
'link'
});
return
results
;
},
Annotator
.
prototype
.
highlightRange
...
...
@@ -98,24 +102,37 @@ define([
);
/**
* Modifies Annotator.Viewer.html.item template to add an i18n for the
* buttons.
**/
Annotator
.
Viewer
.
prototype
.
html
.
item
=
[
'<li class="annotator-annotation annotator-item">'
,
'<span class="annotator-controls">'
,
'<a href="#" title="'
,
_t
(
'View as webpage'
),
'" class="annotator-link">'
,
_t
(
'View as webpage'
),
'</a>'
,
'<button title="'
,
_t
(
'Edit'
),
'" class="annotator-edit">'
,
_t
(
'Edit'
),
'</button>'
,
'<button title="'
,
_t
(
'Delete'
),
'" class="annotator-delete">'
,
_t
(
'Delete'
),
'</button>'
,
'</span>'
,
'</li>'
].
join
(
''
);
* Modifies Annotator.Viewer.html template to make viewer div focusable.
* Also adds a close button and necessary i18n attributes to all buttons.
**/
Annotator
.
Viewer
.
prototype
.
html
=
{
element
:
[
'<div class="annotator-outer annotator-viewer">'
,
'<ul class="annotator-widget annotator-listing" tabindex="-1"></ul>'
,
'</div>'
].
join
(
''
),
item
:
[
'<li class="annotator-annotation annotator-item">'
,
'<span class="annotator-controls">'
,
'<a href="#" title="'
,
_t
(
'View as webpage'
),
'" class="annotator-link">'
,
_t
(
'View as webpage'
),
'</a>'
,
'<button class="annotator-edit">'
,
_t
(
'Edit'
),
'<span class="sr">'
,
_t
(
'Note'
),
'</span>'
,
'</button>'
,
'<button class="annotator-delete">'
,
_t
(
'Delete'
),
'<span class="sr">'
,
_t
(
'Note'
),
'</span>'
,
'</button>'
,
'<button class="annotator-close">'
,
_t
(
'Close'
),
'<span class="sr">'
,
_t
(
'Note'
),
'</span>'
,
'</button>'
,
'</span>'
,
'</li>'
].
join
(
''
)
};
/**
* Overrides Annotator._setupViewer to add a "click" event on viewer and to
...
...
@@ -134,8 +151,8 @@ define([
$
(
field
).
html
(
Utils
.
nl2br
(
Annotator
.
Util
.
escape
(
annotation
.
text
)));
}
else
{
$
(
field
).
html
(
'<i>'
+
_t
(
'No Comment'
)
+
'</i>'
);
self
.
publish
(
'annotationViewerTextField'
,
[
field
,
annotation
]);
}
return
self
.
publish
(
'annotationViewerTextField'
,
[
field
,
annotation
]);
}
})
.
element
.
appendTo
(
this
.
wrapper
).
bind
({
...
...
@@ -148,6 +165,62 @@ define([
Annotator
.
Editor
.
prototype
.
isShown
=
Annotator
.
Viewer
.
prototype
.
isShown
;
/**
* Modifies Annotator.Editor.html template to add tabindex = -1 to
* form.annotator-widget and reverse order of Save and Cancel buttons.
**/
Annotator
.
Editor
.
prototype
.
html
=
[
'<div class="annotator-outer annotator-editor">'
,
'<form class="annotator-widget" tabindex="-1">'
,
'<ul class="annotator-listing"></ul>'
,
'<div class="annotator-controls">'
,
'<button class="annotator-save">'
,
_t
(
'Save'
),
'<span class="sr">'
,
_t
(
'Note'
),
'</span>'
,
'</button>'
,
'<button class="annotator-cancel">'
,
_t
(
'Cancel'
),
'<span class="sr">'
,
_t
(
'Note'
),
'</span>'
,
'</button>'
,
'</div>'
,
'</form>'
,
'</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.
**/
Annotator
.
Editor
.
prototype
.
show
=
_
.
compose
(
function
(
event
)
{
if
(
event
.
type
===
'keydown'
)
{
this
.
element
.
find
(
'.annotator-save'
).
removeClass
(
this
.
classes
.
focus
);
this
.
element
.
find
(
'form.annotator-widget'
).
focus
();
}
},
Annotator
.
Editor
.
prototype
.
show
);
/**
* Removes the textarea keydown event handler as it triggers 'processKeypress'
* which hides the viewer on ESC and saves on ENTER. We will define different
* behaviors for these in /plugins/accessibility.js
**/
delete
Annotator
.
Editor
.
prototype
.
events
[
"textarea keydown"
];
/**
* Modifies Annotator.onHighlightMouseover to avoid showing the viewer if the
* editor is opened.
**/
...
...
@@ -174,8 +247,6 @@ define([
Annotator
.
prototype
.
_setupWrapper
);
Annotator
.
Editor
.
prototype
.
isShown
=
Annotator
.
Viewer
.
prototype
.
isShown
;
$
.
extend
(
true
,
Annotator
.
prototype
,
{
isFrozen
:
false
,
uid
:
_
.
uniqueId
(),
...
...
@@ -191,11 +262,15 @@ define([
},
onNoteClick
:
function
(
event
)
{
var
target
=
$
(
event
.
target
);
event
.
stopPropagation
();
Annotator
.
Util
.
preventEventDefault
(
event
);
if
(
!
$
(
event
.
target
).
is
(
'.annotator-delete'
))
{
if
(
!
(
target
.
is
(
'.annotator-delete'
)
||
target
.
is
(
'.annotator-close'
)))
{
Annotator
.
frozenSrc
=
this
;
this
.
freezeAll
();
}
else
if
(
target
.
is
(
'.annotator-close'
))
{
this
.
viewer
.
hide
();
}
},
...
...
@@ -235,12 +310,6 @@ define([
unfreezeAll
:
function
()
{
_
.
invoke
(
Annotator
.
_instances
,
'unfreeze'
);
return
this
;
},
showFrozenViewer
:
function
(
annotations
,
location
)
{
this
.
showViewer
(
annotations
,
location
);
this
.
freezeAll
();
return
this
;
}
});
});
...
...
lms/static/js/edxnotes/views/toggle_notes_factory.js
View file @
2ff863cb
...
...
@@ -12,7 +12,7 @@ define([
errorMessage
:
gettext
(
"An error has occurred. Make sure that you are connected to the Internet, and then try refreshing the page."
),
initialize
:
function
(
options
)
{
_
.
bindAll
(
this
,
'onSuccess'
,
'onError'
);
_
.
bindAll
(
this
,
'onSuccess'
,
'onError'
,
'keyDownToggleHandler'
);
this
.
visibility
=
options
.
visibility
;
this
.
visibilityUrl
=
options
.
visibilityUrl
;
this
.
label
=
this
.
$
(
'.utility-control-label'
);
...
...
@@ -20,6 +20,12 @@ define([
this
.
actionLink
.
removeClass
(
'is-disabled'
);
this
.
actionToggleMessage
=
this
.
$
(
'.action-toggle-message'
);
this
.
notification
=
new
Annotator
.
Notification
();
$
(
document
).
on
(
'keydown.edxnotes:togglenotes'
,
this
.
keyDownToggleHandler
);
},
remove
:
function
()
{
$
(
document
).
off
(
'keydown.edxnotes:togglenotes'
);
Backbone
.
View
.
prototype
.
remove
.
call
(
this
);
},
toggleHandler
:
function
(
event
)
{
...
...
@@ -29,6 +35,13 @@ define([
this
.
toggleNotes
(
this
.
visibility
);
},
keyDownToggleHandler
:
function
(
event
)
{
// Character '[' has keyCode 219
if
(
event
.
keyCode
===
219
&&
event
.
ctrlKey
&&
event
.
shiftKey
)
{
this
.
toggleHandler
(
event
);
}
},
toggleNotes
:
function
(
visibility
)
{
if
(
visibility
)
{
this
.
enableNotes
();
...
...
@@ -47,16 +60,16 @@ define([
enableNotes
:
function
()
{
_
.
each
(
$
(
'.edx-notes-wrapper'
),
EdxnotesVisibilityDecorator
.
enableNote
);
this
.
actionLink
.
addClass
(
'is-active'
)
.
attr
(
'aria-pressed'
,
true
)
;
this
.
actionLink
.
addClass
(
'is-active'
);
this
.
label
.
text
(
gettext
(
'Hide notes'
));
this
.
actionToggleMessage
.
text
(
gettext
(
'
Showing notes
'
));
this
.
actionToggleMessage
.
text
(
gettext
(
'
Notes visible
'
));
},
disableNotes
:
function
()
{
EdxnotesVisibilityDecorator
.
disableNotes
();
this
.
actionLink
.
removeClass
(
'is-active'
)
.
attr
(
'aria-pressed'
,
false
)
;
this
.
actionLink
.
removeClass
(
'is-active'
);
this
.
label
.
text
(
gettext
(
'Show notes'
));
this
.
actionToggleMessage
.
text
(
gettext
(
'
Hiding notes
'
));
this
.
actionToggleMessage
.
text
(
gettext
(
'
Notes hidden
'
));
},
hideErrorMessage
:
function
()
{
...
...
lms/static/js/fixtures/edxnotes/toggle_notes.html
View file @
2ff863cb
<div
class=
"wrapper-utility edx-notes-visibility"
>
<span
class=
"action-toggle-message"
>
Hiding notes
</span>
<span
class=
"action-toggle-message"
>
Notes visible
</span>
<button
class=
"utility-control utility-control-button action-toggle-notes is-disabled is-active"
aria-pressed=
"true"
>
<i
class=
"icon fa fa-pencil"
></i>
<span
class=
"utility-control-label sr"
>
Hide notes
</span>
...
...
lms/static/js/spec/edxnotes/plugins/accessibility_spec.js
0 → 100644
View file @
2ff863cb
define
([
'jquery'
,
'underscore'
,
'annotator_1.2.9'
,
'logger'
,
'js/edxnotes/views/notes_factory'
,
'js/spec/edxnotes/custom_matchers'
],
function
(
$
,
_
,
Annotator
,
Logger
,
NotesFactory
,
customMatchers
)
{
'use strict'
;
describe
(
'EdxNotes Accessibility Plugin'
,
function
()
{
function
keyDownEvent
(
key
)
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
key
});
}
function
tabBackwardEvent
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
$
.
ui
.
keyCode
.
TAB
,
shiftKey
:
true
});
}
function
tabForwardEvent
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
$
.
ui
.
keyCode
.
TAB
,
shiftKey
:
false
});
}
function
enterMetaKeyEvent
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
$
.
ui
.
keyCode
.
ENTER
,
metaKey
:
true
});
}
function
enterControlKeyEvent
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
$
.
ui
.
keyCode
.
ENTER
,
ctrlKey
:
true
});
}
beforeEach
(
function
()
{
this
.
KEY
=
$
.
ui
.
keyCode
;
customMatchers
(
this
);
loadFixtures
(
'js/fixtures/edxnotes/edxnotes_wrapper.html'
);
this
.
annotator
=
NotesFactory
.
factory
(
$
(
'div#edx-notes-wrapper-123'
).
get
(
0
),
{
endpoint
:
'http://example.com/'
}
);
this
.
plugin
=
this
.
annotator
.
plugins
.
Accessibility
;
spyOn
(
Logger
,
'log'
);
});
afterEach
(
function
()
{
_
.
invoke
(
Annotator
.
_instances
,
'destroy'
);
});
describe
(
'destroy'
,
function
()
{
it
(
'should unbind all events'
,
function
()
{
spyOn
(
$
.
fn
,
'off'
);
spyOn
(
this
.
annotator
,
'unsubscribe'
);
this
.
plugin
.
destroy
();
expect
(
this
.
annotator
.
unsubscribe
).
toHaveBeenCalledWith
(
'annotationViewerTextField'
,
this
.
plugin
.
addAriaAttributes
);
expect
(
this
.
annotator
.
unsubscribe
).
toHaveBeenCalledWith
(
'annotationsLoaded'
,
this
.
plugin
.
addDescriptions
);
expect
(
this
.
annotator
.
unsubscribe
).
toHaveBeenCalledWith
(
'annotationCreated'
,
this
.
plugin
.
addDescriptions
);
expect
(
this
.
annotator
.
unsubscribe
).
toHaveBeenCalledWith
(
'annotationDeleted'
,
this
.
plugin
.
removeDescription
);
expect
(
$
.
fn
.
off
).
toHaveBeenCalledWith
(
'.accessibility'
);
});
});
describe
(
'a11y attributes'
,
function
()
{
var
highlight
,
annotation
,
note
;
beforeEach
(
function
()
{
highlight
=
$
(
'<span class="annotator-hl" tabindex="0"/>'
).
appendTo
(
this
.
annotator
.
element
);
annotation
=
{
id
:
'01'
,
text
:
'Test text'
,
highlights
:
[
highlight
.
get
(
0
)]
};
});
it
(
'should be added to highlighted text and associated note'
,
function
()
{
this
.
annotator
.
viewer
.
load
([
annotation
]);
note
=
$
(
'.annotator-note'
);
expect
(
note
).
toExist
();
expect
(
note
).
toHaveAttr
(
'tabindex'
,
-
1
);
expect
(
note
).
toHaveAttr
(
'role'
,
'note'
);
expect
(
note
).
toHaveAttr
(
'class'
,
'annotator-note'
);
});
it
(
'should create aria-descriptions when annotations are loaded'
,
function
()
{
this
.
annotator
.
publish
(
'annotationsLoaded'
,
[[
annotation
]]);
expect
(
highlight
).
toHaveAttr
(
'aria-describedby'
,
'aria-note-description-01'
);
expect
(
$
(
'#aria-note-description-01'
)).
toContainText
(
'Test text'
);
});
it
(
'should create aria-description when new annotation is created'
,
function
()
{
this
.
annotator
.
publish
(
'annotationCreated'
,
[
annotation
]);
expect
(
highlight
).
toHaveAttr
(
'aria-describedby'
,
'aria-note-description-01'
);
expect
(
$
(
'#aria-note-description-01'
)).
toContainText
(
'Test text'
);
});
it
(
'should remove aria-description when the annotation is removed'
,
function
()
{
this
.
annotator
.
publish
(
'annotationDeleted'
,
[
annotation
]);
expect
(
$
(
'#aria-note-description-01'
)).
not
.
toExist
();
});
});
describe
(
'keydown events on highlighted text'
,
function
()
{
var
highlight
,
annotation
,
note
;
beforeEach
(
function
()
{
highlight
=
$
(
'<span class="annotator-hl" tabindex="0"/>'
).
appendTo
(
this
.
annotator
.
element
);
annotation
=
{
id
:
'01'
,
text
:
'Test text'
,
highlights
:
[
highlight
.
get
(
0
)]
};
highlight
.
data
(
'annotation'
,
annotation
);
spyOn
(
this
.
annotator
,
'showViewer'
).
andCallThrough
();
spyOn
(
this
.
annotator
.
viewer
,
'hide'
).
andCallThrough
();
spyOn
(
this
.
plugin
,
'focusOnGrabber'
).
andCallThrough
();
});
it
(
'should open the viewer on SPACE keydown and focus on note'
,
function
()
{
highlight
.
trigger
(
keyDownEvent
(
this
.
KEY
.
SPACE
));
expect
(
this
.
annotator
.
showViewer
).
toHaveBeenCalled
();
});
it
(
'should open the viewer on ENTER keydown and focus on note'
,
function
()
{
highlight
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ENTER
));
expect
(
this
.
annotator
.
showViewer
).
toHaveBeenCalled
();
});
// This happens only when coming from notes page
it
(
'should open focus on viewer on TAB keydown if viewer is opened'
,
function
()
{
this
.
annotator
.
viewer
.
load
([
annotation
]);
highlight
.
trigger
(
keyDownEvent
(
this
.
KEY
.
TAB
));
expect
(
this
.
annotator
.
element
.
find
(
'.annotator-listing'
)).
toBeFocused
();
});
it
(
'should focus highlighted text after closing'
,
function
()
{
var
note
;
highlight
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ENTER
));
expect
(
this
.
plugin
.
savedHighlights
).
toBeDefined
();
note
=
this
.
annotator
.
element
.
find
(
'.annotator-edit'
);
note
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ESCAPE
));
expect
(
highlight
).
toBeFocused
();
expect
(
this
.
plugin
.
savedHighlights
).
toBeNull
();
});
it
(
'should focus on grabber after being deleted'
,
function
()
{
highlight
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ENTER
));
this
.
annotator
.
publish
(
'annotationDeleted'
,
{});
expect
(
this
.
plugin
.
focusGrabber
).
toBeFocused
();
});
it
(
'should not focus on grabber when the viewer is hidden'
,
function
()
{
this
.
annotator
.
publish
(
'annotationDeleted'
,
{});
expect
(
this
.
plugin
.
focusGrabber
).
not
.
toBeFocused
();
});
});
describe
(
'keydown events on viewer'
,
function
()
{
var
highlight
,
annotation
,
listing
,
note
,
edit
,
del
,
close
;
beforeEach
(
function
()
{
highlight
=
$
(
'<span class="annotator-hl" tabindex="0"/>'
).
appendTo
(
this
.
annotator
.
element
);
annotation
=
{
id
:
'01'
,
text
:
"Test text"
,
highlights
:
[
highlight
.
get
(
0
)]
};
highlight
.
data
(
'annotation'
,
annotation
);
this
.
annotator
.
viewer
.
load
([
annotation
]);
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
();;
});
it
(
'should give focus to Note on Listing TAB keydown'
,
function
()
{
listing
.
focus
();
listing
.
trigger
(
tabForwardEvent
());
expect
(
note
).
toBeFocused
();
});
it
(
'should give focus to Close on Listing SHIFT + TAB keydown'
,
function
()
{
listing
.
focus
();
listing
.
trigger
(
tabBackwardEvent
());
expect
(
close
).
toBeFocused
();
});
it
(
'should cycle forward through Note, Edit, Delete, and Close on TAB keydown'
,
function
()
{
note
.
focus
();
note
.
trigger
(
tabForwardEvent
());
expect
(
edit
).
toBeFocused
();
edit
.
trigger
(
tabForwardEvent
());
expect
(
del
).
toBeFocused
();
del
.
trigger
(
tabForwardEvent
());
expect
(
close
).
toBeFocused
();
close
.
trigger
(
tabForwardEvent
());
expect
(
note
).
toBeFocused
();
});
it
(
'should cycle backward through Note, Edit, Delete, and Close on SHIFT + TAB keydown'
,
function
()
{
note
.
focus
();
note
.
trigger
(
tabBackwardEvent
());
expect
(
close
).
toBeFocused
();
close
.
trigger
(
tabBackwardEvent
());
expect
(
del
).
toBeFocused
();
del
.
trigger
(
tabBackwardEvent
());
expect
(
edit
).
toBeFocused
();
edit
.
trigger
(
tabBackwardEvent
());
expect
(
note
).
toBeFocused
();
});
it
(
'should hide on ESCAPE keydown'
,
function
()
{
var
tabControls
=
[
listing
,
note
,
edit
,
del
,
close
];
_
.
each
(
tabControls
,
function
(
control
)
{
control
.
focus
();
control
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ESCAPE
));
},
this
);
expect
(
this
.
annotator
.
viewer
.
hide
.
callCount
).
toBe
(
5
);
});
});
describe
(
'keydown events on editor'
,
function
()
{
var
highlight
,
annotation
,
form
,
textArea
,
save
,
cancel
;
beforeEach
(
function
()
{
highlight
=
$
(
'<span class="annotator-hl" tabindex="0"/>'
).
appendTo
(
this
.
annotator
.
element
);
annotation
=
{
id
:
'01'
,
text
:
"Test text"
,
highlights
:
[
highlight
.
get
(
0
)]
};
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'
);
save
=
this
.
annotator
.
element
.
find
(
'.annotator-save'
);
cancel
=
this
.
annotator
.
element
.
find
(
'.annotator-cancel'
);
spyOn
(
this
.
annotator
.
editor
,
'submit'
).
andCallThrough
();
spyOn
(
this
.
annotator
.
editor
,
'hide'
).
andCallThrough
();
});
it
(
'should give focus to TextArea on Form TAB keydown'
,
function
()
{
form
.
focus
();
form
.
trigger
(
tabForwardEvent
());
expect
(
textArea
).
toBeFocused
();
});
it
(
'should give focus to Cancel on Form SHIFT + TAB keydown'
,
function
()
{
form
.
focus
();
form
.
trigger
(
tabBackwardEvent
());
expect
(
cancel
).
toBeFocused
();
});
it
(
'should cycle forward through texarea, save, and cancel on TAB keydown'
,
function
()
{
textArea
.
focus
();
textArea
.
trigger
(
tabForwardEvent
());
expect
(
save
).
toBeFocused
();
save
.
trigger
(
tabForwardEvent
());
expect
(
cancel
).
toBeFocused
();
cancel
.
trigger
(
tabForwardEvent
());
expect
(
textArea
).
toBeFocused
();
});
it
(
'should cycle back through texarea, 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
(
textArea
).
toBeFocused
();
});
it
(
'should submit if target is Save on ENTER or SPACE keydown'
,
function
()
{
save
.
focus
();
save
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ENTER
));
expect
(
this
.
annotator
.
editor
.
submit
).
toHaveBeenCalled
();
this
.
annotator
.
editor
.
submit
.
reset
();
save
.
focus
();
save
.
trigger
(
keyDownEvent
(
this
.
KEY
.
SPACE
));
expect
(
this
.
annotator
.
editor
.
submit
).
toHaveBeenCalled
();
});
it
(
'should submit on META or CONTROL + ENTER keydown'
,
function
()
{
textArea
.
focus
();
textArea
.
trigger
(
enterMetaKeyEvent
());
expect
(
this
.
annotator
.
editor
.
submit
).
toHaveBeenCalled
();
this
.
annotator
.
editor
.
submit
.
reset
();
textArea
.
focus
();
textArea
.
trigger
(
enterControlKeyEvent
());
expect
(
this
.
annotator
.
editor
.
submit
).
toHaveBeenCalled
();
});
it
(
'should hide if target is Cancel on ENTER or SPACE keydown'
,
function
()
{
cancel
.
focus
();
cancel
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ENTER
));
expect
(
this
.
annotator
.
editor
.
hide
).
toHaveBeenCalled
();
this
.
annotator
.
editor
.
hide
.
reset
();
cancel
.
focus
();
save
.
trigger
(
keyDownEvent
(
this
.
KEY
.
SPACE
));
expect
(
this
.
annotator
.
editor
.
hide
).
toHaveBeenCalled
();
});
it
(
'should hide on ESCAPE keydown'
,
function
()
{
var
tabControls
=
[
textArea
,
save
,
cancel
];
_
.
each
(
tabControls
,
function
(
control
)
{
control
.
focus
();
control
.
trigger
(
keyDownEvent
(
this
.
KEY
.
ESCAPE
));
},
this
);
expect
(
this
.
annotator
.
editor
.
hide
.
callCount
).
toBe
(
3
);
});
});
});
});
lms/static/js/spec/edxnotes/views/shim_spec.js
View file @
2ff863cb
...
...
@@ -137,6 +137,20 @@ define([
);
});
it
(
'should hide viewer when close button is clicked'
,
function
()
{
var
close
,
annotation
=
{
id
:
'01'
,
text
:
"Test text"
,
highlights
:
[
highlights
[
0
].
get
(
0
)]
};
annotators
[
0
].
viewer
.
load
([
annotation
]);
close
=
annotators
[
0
].
viewer
.
element
.
find
(
'.annotator-close'
);
close
.
click
();
expect
(
$
(
'#edx-notes-wrapper-123 .annotator-viewer'
)).
toHaveClass
(
'annotator-hide'
);
});
describe
(
'_setupViewer'
,
function
()
{
var
mockViewer
=
null
;
...
...
lms/static/js/spec/edxnotes/views/toggle_notes_factory_spec.js
View file @
2ff863cb
...
...
@@ -33,6 +33,7 @@ define([
this
.
button
=
$
(
'.action-toggle-notes'
);
this
.
label
=
this
.
button
.
find
(
'.utility-control-label'
);
this
.
toggleMessage
=
$
(
'.action-toggle-message'
);
spyOn
(
this
.
toggleNotes
,
'toggleHandler'
).
andCallThrough
();
});
afterEach
(
function
()
{
...
...
@@ -49,14 +50,13 @@ define([
expect
(
this
.
button
).
toHaveClass
(
'is-active'
);
expect
(
this
.
button
).
toHaveAttr
(
'aria-pressed'
,
'true'
);
expect
(
this
.
toggleMessage
).
not
.
toHaveClass
(
'is-fleeting'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Hiding notes
'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Notes visible
'
);
this
.
button
.
click
();
expect
(
this
.
label
).
toContainText
(
'Show notes'
);
expect
(
this
.
button
).
not
.
toHaveClass
(
'is-active'
);
expect
(
this
.
button
).
toHaveAttr
(
'aria-pressed'
,
'false'
);
expect
(
this
.
toggleMessage
).
toHaveClass
(
'is-fleeting'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Hiding notes
'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Notes hidden
'
);
expect
(
Annotator
.
_instances
).
toHaveLength
(
0
);
AjaxHelpers
.
expectJsonRequest
(
requests
,
'PUT'
,
'/test_url'
,
{
...
...
@@ -67,9 +67,8 @@ define([
this
.
button
.
click
();
expect
(
this
.
label
).
toContainText
(
'Hide notes'
);
expect
(
this
.
button
).
toHaveClass
(
'is-active'
);
expect
(
this
.
button
).
toHaveAttr
(
'aria-pressed'
,
'true'
);
expect
(
this
.
toggleMessage
).
toHaveClass
(
'is-fleeting'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Showing notes
'
);
expect
(
this
.
toggleMessage
).
toContainText
(
'
Notes visible
'
);
expect
(
Annotator
.
_instances
).
toHaveLength
(
2
);
AjaxHelpers
.
expectJsonRequest
(
requests
,
'PUT'
,
'/test_url'
,
{
...
...
@@ -95,5 +94,11 @@ define([
AjaxHelpers
.
respondWithJson
(
requests
,
{});
expect
(
errorContainer
).
not
.
toHaveClass
(
'annotator-notice-show'
);
});
it
(
'toggles notes when CTRL + SHIFT + [ keydown on document'
,
function
()
{
// Character '[' has keyCode 219
$
(
document
).
trigger
(
$
.
Event
(
'keydown'
,
{
keyCode
:
219
,
ctrlKey
:
true
,
shiftKey
:
true
}));
expect
(
this
.
toggleNotes
.
toggleHandler
).
toHaveBeenCalled
();
});
});
});
lms/static/js/spec/main.js
View file @
2ff863cb
...
...
@@ -569,6 +569,7 @@
'lms/include/js/spec/edxnotes/views/toggle_notes_factory_spec.js'
,
'lms/include/js/spec/edxnotes/models/tab_spec.js'
,
'lms/include/js/spec/edxnotes/models/note_spec.js'
,
'lms/include/js/spec/edxnotes/plugins/accessibility_spec.js'
,
'lms/include/js/spec/edxnotes/plugins/events_spec.js'
,
'lms/include/js/spec/edxnotes/plugins/scroller_spec.js'
,
'lms/include/js/spec/edxnotes/collections/notes_spec.js'
,
...
...
lms/static/sass/course/modules/_student-notes.scss
View file @
2ff863cb
...
...
@@ -3,6 +3,7 @@
// in this document:
// --------------------
// +extends
// +notes
// +local variables/utilities
// +toggling notes
...
...
@@ -11,6 +12,22 @@
// +listing notes
// +necessary, but ugly overrides
// +extends:
// --------------------
%bubble
{
@include
transform
(
rotate
(
45deg
));
@include
left
(
12px
);
position
:
absolute
;
bottom
:
-
(
$baseline
/
2
);
display
:
block
;
width
:
16px
;
height
:
16px
;
content
:
" "
;
background
:
$white
;
// Set a default
border-bottom
:
1px
solid
$gray-l2
;
border-right
:
1px
solid
$gray-l2
;
}
// +notes:
// --------------------
// this Sass partial contains all of the styling needed for the in-line student notes UI.
...
...
@@ -101,6 +118,10 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
border-color
:
$error-color
;
}
.edx-notes-focus-grabber
{
outline
:
none
;
}
.edx-notes-wrapper
{
// +individual note (in context)
// --------------------
...
...
@@ -117,6 +138,21 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
.annotator-listing
{
padding
:
0
!
important
;
margin
:
0
!
important
;
.annotator-widget
{
&
:after
{
@extend
%bubble
;
}
}
}
.annotator-editor
{
.annotator-widget
{
&
:after
{
@extend
%bubble
;
background
:
$gray-l5
;
}
}
}
.annotator-item
{
...
...
@@ -148,15 +184,9 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
// using annotatorJS triangle styling for adder
&
:before
{
position
:
absolute
;
@include
left
(
8px
);
bottom
:
-
(
$baseline
/
2
);
display
:
block
;
width
:
18px
;
height
:
(
$baseline
/
2
);
content
:
""
;
background-image
:
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAEiCAYAAAD0w4JOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDY0MTMzNTM2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDY0MTMzNTQ2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2ODkwQjlFQzZBRDExMUUxQTZEQkREODBBMzc2ODk1NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpENjQxMzM1MjZBRDMxMUUxQTZEQkREODBBMzc2ODk1NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkijPpwAABBRSURBVHja7JsJVBRX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)
;
background-position
:
0
0
;
@extend
%bubble
;
@include
left
(
10px
);
background
:
whitesmoke
;
}
}
}
...
...
@@ -167,20 +197,22 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
.annotator-controls
{
@include
text-align
(
left
);
@include
clearfix
();
background
:
$notes-annotator-background-med
!
important
;
//matches annotator JS editing bubble triangle color
font-family
:
$f-sans-serif
!
important
;
@extend
%ui-depth1
;
position
:
relative
;
padding
:
8px
;
border
:
none
!
important
;
border-radius
:
0
!
important
;
background
:
$gray-l5
!
important
;
font-family
:
$f-sans-serif
!
important
;
// actions
.annotator-save
,
.annotator-cancel
{
@extend
%notes-reset-background
;
font-family
:
$f-sans-serif
!
important
;
font-size
:
14px
!
important
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
)
!
important
;
border
:
none
;
box-shadow
:
none
;
font-family
:
$f-sans-serif
!
important
;
font-size
:
14px
!
important
;
text-shadow
:
none
!
important
;
// removing vendor icons
...
...
@@ -255,6 +287,10 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
// content
.annotator-viewer
{
.annotator-widget.annotator-listing
{
outline
:
none
;
}
// poorly scoped selector for content of a note's comment
div
:first-of-type
{
@extend
%notes-reset-font
;
...
...
@@ -266,13 +302,14 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
// controls
.annotator-controls
{
opacity
:
1
;
// RTL support
@include
right
(
0
);
top
:
0
;
@include
float
(
right
);
@include
padding-left
(
$baseline
/
4
);
.annotator-
delete
,
.annotator-edit
{
.annotator-
edit
,
.annotator-delete
,
.annotator-close
{
position
:
relative
;
display
:
inline-block
;
vertical-align
:
middle
;
...
...
@@ -291,16 +328,26 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
&
:before
{
top
:
0
;
@include
left
(
0
);
content
:
"\f04
4"
;
content
:
"\f04
0"
;
// .fa-pencil
}
}
.annotator-delete
{
@include
margin-right
(
$baseline
/
3
);
&
:before
{
top
:
-
(
$baseline
/
20
);
@include
left
(
0
);
content
:
"\f1f8"
;
// .fa-trash
}
}
.annotator-close
{
&
:before
{
top
:
-
(
$baseline
/
20
);
@include
left
(
0
);
content
:
"\f00d"
;
content
:
"\f00d"
;
// .fa-close
}
}
}
...
...
@@ -313,12 +360,12 @@ $notes-annotator-background-dark: rgba(122,122,122,0.6); // taken from annotator
opacity
:
1
.0
;
}
.edx-notes-wrapper
.annotator-wrapper
.annotator-editor.annotator-outer
a
.annotator-save
{
.edx-notes-wrapper
.annotator-wrapper
.annotator-editor.annotator-outer
button
.annotator-save
{
@extend
%btn-inherited-primary
;
@extend
%t-action2
;
}
.edx-notes-wrapper
.annotator-wrapper
.annotator-editor.annotator-outer
a
.annotator-cancel
{
.edx-notes-wrapper
.annotator-wrapper
.annotator-editor.annotator-outer
button
.annotator-cancel
{
@extend
%shame-link-base
;
@extend
%t-action2
;
@extend
%t-regular
;
...
...
lms/templates/courseware/courseware.html
View file @
2ff863cb
...
...
@@ -243,9 +243,7 @@ ${fragment.foot_html()}
</div>
</div>
<nav
class=
"nav-utilities ${"
has-utility-calculator
"
if
course
.
show_calculator
else
""}"
>
<h2
class=
"sr nav-utilities-title"
>
${_('Course Utilities Navigation')}
</h2>
<nav
class=
"nav-utilities ${"
has-utility-calculator
"
if
course
.
show_calculator
else
""}"
area-label=
"${_('Course Utilities')}"
>
## Utility: Chat
% if show_chat:
<
%
include
file=
"/chat/toggle_chat.html"
/>
...
...
lms/templates/edxnotes/toggle_notes.html
View file @
2ff863cb
...
...
@@ -9,7 +9,7 @@
%
>
<div
class=
"wrapper-utility edx-notes-visibility"
>
<span
class=
"action-toggle-message"
aria-live=
"polite"
></span>
<button
class=
"utility-control utility-control-button action-toggle-notes is-disabled ${"
is-active
"
if
edxnotes_visibility
else
""}"
aria-pressed=
"${"
true
"
if
edxnotes_visibility
else
"
false
"}"
>
<button
class=
"utility-control utility-control-button action-toggle-notes is-disabled ${"
is-active
"
if
edxnotes_visibility
else
""}"
>
<i
class=
"icon fa fa-pencil"
></i>
% if edxnotes_visibility:
<span
class=
"utility-control-label sr"
>
${_("Hide notes")}
</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