Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
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
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
a4ce9479
Commit
a4ce9479
authored
Jan 19, 2015
by
gergely-ujvari
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1866 from hypothesis/vanilla-annotator
Replace Annotator with dist build
parents
16f4d24a
a14c60a1
Changes
17
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
2578 additions
and
3038 deletions
+2578
-3038
annotator.coffee
h/static/scripts/annotator/annotator.coffee
+0
-798
class.coffee
h/static/scripts/annotator/class.coffee
+0
-251
console.coffee
h/static/scripts/annotator/console.coffee
+0
-33
editor.coffee
h/static/scripts/annotator/editor.coffee
+0
-382
monkey.coffee
h/static/scripts/annotator/monkey.coffee
+107
-0
auth.coffee
h/static/scripts/annotator/plugin/auth.coffee
+0
-249
document.coffee
h/static/scripts/annotator/plugin/document.coffee
+0
-160
range.coffee
h/static/scripts/annotator/range.coffee
+0
-457
range_monkey.coffee
h/static/scripts/annotator/range_monkey.coffee
+0
-99
util.coffee
h/static/scripts/annotator/util.coffee
+0
-180
viewer.coffee
h/static/scripts/annotator/viewer.coffee
+0
-250
widget.coffee
h/static/scripts/annotator/widget.coffee
+0
-99
xpath.coffee
h/static/scripts/annotator/xpath.coffee
+0
-78
annotator.auth.js
h/static/scripts/vendor/annotator.auth.js
+232
-0
annotator.document.js
h/static/scripts/vendor/annotator.document.js
+285
-0
annotator.js
h/static/scripts/vendor/annotator.js
+1951
-0
karma.config.js
karma.config.js
+3
-2
No files found.
h/static/scripts/annotator/annotator.coffee
deleted
100644 → 0
View file @
16f4d24a
# Selection and range creation reference for the following code:
# http://www.quirksmode.org/dom/range_intro.html
#
# I've removed any support for IE TextRange (see commit d7085bf2 for code)
# for the moment, having no means of testing it.
# Store a reference to the current Annotator object.
_Annotator
=
this
.
Annotator
class
Annotator
extends
Delegator
# Events to be bound on Annotator#element.
events
:
".annotator-adder button click"
:
"onAdderClick"
".annotator-adder button mousedown"
:
"onAdderMousedown"
".annotator-hl mouseover"
:
"onHighlightMouseover"
".annotator-hl mouseout"
:
"startViewerHideTimer"
html
:
adder
:
'<div class="annotator-adder"><button>'
+
_t
(
'Annotate'
)
+
'</button></div>'
wrapper
:
'<div class="annotator-wrapper"></div>'
options
:
# Configuration options
readOnly
:
false
# Start Annotator in read-only mode. No controls will be shown.
plugins
:
{}
editor
:
null
viewer
:
null
selectedRanges
:
null
mouseIsDown
:
false
ignoreMouseup
:
false
viewerHideTimer
:
null
# Public: Creates an instance of the Annotator. Requires a DOM Element in
# which to watch for annotations as well as any options.
#
# NOTE: If the Annotator is not supported by the current browser it will not
# perform any setup and simply return a basic object. This allows plugins
# to still be loaded but will not function as expected. It is reccomended
# to call Annotator.supported() before creating the instance or using the
# Unsupported plugin which will notify users that the Annotator will not work.
#
# element - A DOM Element in which to annotate.
# options - An options Object. NOTE: There are currently no user options.
#
# Examples
#
# annotator = new Annotator(document.body)
#
# # Example of checking for support.
# if Annotator.supported()
# annotator = new Annotator(document.body)
# else
# # Fallback for unsupported browsers.
#
# Returns a new instance of the Annotator.
constructor
:
(
element
,
options
)
->
super
@
plugins
=
{}
# Return early if the annotator is not supported.
return
this
unless
Annotator
.
supported
()
this
.
_setupDocumentEvents
()
unless
@
options
.
readOnly
this
.
_setupWrapper
().
_setupViewer
().
_setupEditor
()
this
.
_setupDynamicStyle
()
# Create adder
this
.
adder
=
$
(
this
.
html
.
adder
).
appendTo
(
@
wrapper
).
hide
()
Annotator
.
_instances
.
push
(
this
)
# Wraps the children of @element in a @wrapper div. NOTE: This method will also
# remove any script elements inside @element to prevent them re-executing.
#
# Returns itself to allow chaining.
_setupWrapper
:
->
@
wrapper
=
$
(
@
html
.
wrapper
)
# We need to remove all scripts within the element before wrapping the
# contents within a div. Otherwise when scripts are reappended to the DOM
# they will re-execute. This is an issue for scripts that call
# document.write() - such as ads - as they will clear the page.
@
element
.
find
(
'script'
).
remove
()
@
element
.
wrapInner
(
@
wrapper
)
@
wrapper
=
@
element
.
find
(
'.annotator-wrapper'
)
this
# Creates an instance of Annotator.Viewer and assigns it to the @viewer
# property, appends it to the @wrapper and sets up event listeners.
#
# Returns itself to allow chaining.
_setupViewer
:
->
@
viewer
=
new
Annotator
.
Viewer
(
readOnly
:
@
options
.
readOnly
)
@
viewer
.
hide
()
.
on
(
"edit"
,
this
.
onEditAnnotation
)
.
on
(
"delete"
,
this
.
onDeleteAnnotation
)
.
addField
({
load
:
(
field
,
annotation
)
=>
if
annotation
.
text
$
(
field
).
html
(
Util
.
escape
(
annotation
.
text
))
else
$
(
field
).
html
(
"<i>
#{
_t
'No Comment'
}
</i>"
)
this
.
publish
(
'annotationViewerTextField'
,
[
field
,
annotation
])
})
.
element
.
appendTo
(
@
wrapper
).
bind
({
"mouseover"
:
this
.
clearViewerHideTimer
"mouseout"
:
this
.
startViewerHideTimer
})
this
# Creates an instance of the Annotator.Editor and assigns it to @editor.
# Appends this to the @wrapper and sets up event listeners.
#
# Returns itself for chaining.
_setupEditor
:
->
@
editor
=
new
Annotator
.
Editor
()
@
editor
.
hide
()
.
on
(
'hide'
,
this
.
onEditorHide
)
.
on
(
'save'
,
this
.
onEditorSubmit
)
.
addField
({
type
:
'textarea'
,
label
:
_t
(
'Comments'
)
+
'
\u
2026'
load
:
(
field
,
annotation
)
->
$
(
field
).
find
(
'textarea'
).
val
(
annotation
.
text
||
''
)
submit
:
(
field
,
annotation
)
->
annotation
.
text
=
$
(
field
).
find
(
'textarea'
).
val
()
})
@
editor
.
element
.
appendTo
(
@
wrapper
)
this
# Sets up the selection event listeners to watch mouse actions on the document.
#
# Returns itself for chaining.
_setupDocumentEvents
:
->
$
(
document
).
bind
({
"mouseup"
:
this
.
checkForEndSelection
"mousedown"
:
this
.
checkForStartSelection
})
this
# Sets up any dynamically calculated CSS for the Annotator.
#
# Returns itself for chaining.
_setupDynamicStyle
:
->
style
=
$
(
'#annotator-dynamic-style'
)
if
(
!
style
.
length
)
style
=
$
(
'<style id="annotator-dynamic-style"></style>'
).
appendTo
(
document
.
head
)
sel
=
'*'
+
(
":not(.annotator-
#{
x
}
)"
for
x
in
[
'adder'
,
'outer'
,
'notice'
,
'filter'
]).
join
(
''
)
# use the maximum z-index in the page
max
=
Util
.
maxZIndex
(
$
(
document
.
body
).
find
(
sel
))
# but don't go smaller than 1010, because this isn't bulletproof --
# dynamic elements in the page (notifications, dialogs, etc.) may well
# have high z-indices that we can't catch using the above method.
max
=
Math
.
max
(
max
,
1000
)
style
.
text
[
".annotator-adder, .annotator-outer, .annotator-notice {"
" z-index:
#{
max
+
20
}
;"
"}"
".annotator-filter {"
" z-index:
#{
max
+
10
}
;"
"}"
].
join
(
"
\n
"
)
this
# Public: Destroy the current Annotator instance, unbinding all events and
# disposing of all relevant elements.
#
# Returns nothing.
destroy
:
->
super
$
(
document
).
unbind
({
"mouseup"
:
this
.
checkForEndSelection
"mousedown"
:
this
.
checkForStartSelection
})
$
(
'#annotator-dynamic-style'
).
remove
()
@
adder
.
remove
()
@
viewer
.
destroy
()
@
editor
.
destroy
()
@
wrapper
.
find
(
'.annotator-hl'
).
each
->
$
(
this
).
contents
().
insertBefore
(
this
)
$
(
this
).
remove
()
@
wrapper
.
contents
().
insertBefore
(
@
wrapper
)
@
wrapper
.
remove
()
@
element
.
data
(
'annotator'
,
null
)
for
name
,
plugin
of
@
plugins
@
plugins
[
name
].
destroy
?
()
idx
=
Annotator
.
_instances
.
indexOf
(
this
)
if
idx
!=
-
1
Annotator
.
_instances
.
splice
(
idx
,
1
)
# Public: Gets the current selection excluding any nodes that fall outside of
# the @wrapper. Then returns and Array of NormalizedRange instances.
#
# Examples
#
# # A selection inside @wrapper
# annotation.getSelectedRanges()
# # => Returns [NormalizedRange]
#
# # A selection outside of @wrapper
# annotation.getSelectedRanges()
# # => Returns []
#
# Returns Array of NormalizedRange instances.
getSelectedRanges
:
->
selection
=
Util
.
getGlobal
().
getSelection
()
ranges
=
[]
rangesToIgnore
=
[]
unless
selection
.
isCollapsed
ranges
=
for
i
in
[
0
...
selection
.
rangeCount
]
r
=
selection
.
getRangeAt
(
i
)
browserRange
=
new
Range
.
BrowserRange
(
r
)
normedRange
=
browserRange
.
normalize
().
limit
(
@
wrapper
[
0
])
# If the new range falls fully outside the wrapper, we
# should add it back to the document but not return it from
# this method
rangesToIgnore
.
push
(
r
)
if
normedRange
is
null
normedRange
# BrowserRange#normalize() modifies the DOM structure and deselects the
# underlying text as a result. So here we remove the selected ranges and
# reapply the new ones.
selection
.
removeAllRanges
()
for
r
in
rangesToIgnore
selection
.
addRange
(
r
)
# Remove any ranges that fell outside of @wrapper.
$
.
grep
ranges
,
(
range
)
->
# Add the normed range back to the selection if it exists.
selection
.
addRange
(
range
.
toRange
())
if
range
range
# Public: Creates and returns a new annotation object. Publishes the
# 'beforeAnnotationCreated' event to allow the new annotation to be modified.
#
# Examples
#
# annotator.createAnnotation() # Returns {}
#
# annotator.on 'beforeAnnotationCreated', (annotation) ->
# annotation.myProperty = 'This is a custom property'
# annotator.createAnnotation() # Returns {myProperty: "This is a…"}
#
# Returns a newly created annotation Object.
createAnnotation
:
()
->
annotation
=
{}
this
.
publish
(
'beforeAnnotationCreated'
,
[
annotation
])
annotation
# Public: Initialises an annotation either from an object representation or
# an annotation created with Annotator#createAnnotation(). It finds the
# selected range and higlights the selection in the DOM.
#
# annotation - An annotation Object to initialise.
#
# Examples
#
# # Create a brand new annotation from the currently selected text.
# annotation = annotator.createAnnotation()
# annotation = annotator.setupAnnotation(annotation)
# # annotation has now been assigned the currently selected range
# # and a highlight appended to the DOM.
#
# # Add an existing annotation that has been stored elsewere to the DOM.
# annotation = getStoredAnnotationWithSerializedRanges()
# annotation = annotator.setupAnnotation(annotation)
#
# Returns the initialised annotation.
setupAnnotation
:
(
annotation
)
->
root
=
@
wrapper
[
0
]
annotation
.
ranges
or=
@
selectedRanges
normedRanges
=
[]
for
r
in
annotation
.
ranges
try
normedRanges
.
push
(
Range
.
sniff
(
r
).
normalize
(
root
))
catch
e
if
e
instanceof
Range
.
RangeError
this
.
publish
(
'rangeNormalizeFail'
,
[
annotation
,
r
,
e
])
else
# Oh Javascript, why you so crap? This will lose the traceback.
throw
e
annotation
.
quote
=
[]
annotation
.
ranges
=
[]
annotation
.
highlights
=
[]
for
normed
in
normedRanges
annotation
.
quote
.
push
$
.
trim
(
normed
.
text
())
annotation
.
ranges
.
push
normed
.
serialize
(
@
wrapper
[
0
],
'.annotator-hl'
)
$
.
merge
annotation
.
highlights
,
this
.
highlightRange
(
normed
)
# Join all the quotes into one string.
annotation
.
quote
=
annotation
.
quote
.
join
(
' / '
)
# Save the annotation data on each highlighter element.
$
(
annotation
.
highlights
).
data
(
'annotation'
,
annotation
)
$
(
annotation
.
highlights
).
attr
(
'data-annotation-id'
,
annotation
.
id
)
annotation
# Public: Publishes the 'beforeAnnotationUpdated' and 'annotationUpdated'
# events. Listeners wishing to modify an updated annotation should subscribe
# to 'beforeAnnotationUpdated' while listeners storing annotations should
# subscribe to 'annotationUpdated'.
#
# annotation - An annotation Object to update.
#
# Examples
#
# annotation = {tags: 'apples oranges pears'}
# annotator.on 'beforeAnnotationUpdated', (annotation) ->
# # validate or modify a property.
# annotation.tags = annotation.tags.split(' ')
# annotator.updateAnnotation(annotation)
# # => Returns ["apples", "oranges", "pears"]
#
# Returns annotation Object.
updateAnnotation
:
(
annotation
)
->
this
.
publish
(
'beforeAnnotationUpdated'
,
[
annotation
])
$
(
annotation
.
highlights
).
attr
(
'data-annotation-id'
,
annotation
.
id
)
this
.
publish
(
'annotationUpdated'
,
[
annotation
])
annotation
# Public: Deletes the annotation by removing the highlight from the DOM.
# Publishes the 'annotationDeleted' event on completion.
#
# annotation - An annotation Object to delete.
#
# Returns deleted annotation.
deleteAnnotation
:
(
annotation
)
->
if
annotation
.
highlights
?
for
h
in
annotation
.
highlights
when
h
.
parentNode
?
child
=
h
.
childNodes
[
0
]
$
(
h
).
replaceWith
(
h
.
childNodes
)
this
.
publish
(
'annotationDeleted'
,
[
annotation
])
annotation
# Public: Loads an Array of annotations into the @element. Breaks the task
# into chunks of 10 annotations.
#
# annotations - An Array of annotation Objects.
#
# Examples
#
# loadAnnotationsFromStore (annotations) ->
# annotator.loadAnnotations(annotations)
#
# Returns itself for chaining.
loadAnnotations
:
(
annotations
=
[])
->
loader
=
(
annList
=
[])
=>
now
=
annList
.
splice
(
0
,
10
)
for
n
in
now
this
.
setupAnnotation
(
n
)
# If there are more to do, do them after a 10ms break (for browser
# responsiveness).
if
annList
.
length
>
0
setTimeout
((
->
loader
(
annList
)),
10
)
else
this
.
publish
'annotationsLoaded'
,
[
clone
]
clone
=
annotations
.
slice
()
loader
annotations
this
# Public: Calls the Store#dumpAnnotations() method.
#
# Returns dumped annotations Array or false if Store is not loaded.
dumpAnnotations
:
()
->
if
@
plugins
[
'Store'
]
@
plugins
[
'Store'
].
dumpAnnotations
()
else
console
.
warn
(
_t
(
"Can't dump annotations without Store plugin."
))
return
false
# Public: Wraps the DOM Nodes within the provided range with a highlight
# element of the specified class and returns the highlight Elements.
#
# normedRange - A NormalizedRange to be highlighted.
# cssClass - A CSS class to use for the highlight (default: 'annotator-hl')
#
# Returns an array of highlight Elements.
highlightRange
:
(
normedRange
,
cssClass
=
'annotator-hl'
)
->
white
=
/^\s*$/
hl
=
$
(
"<span class='
#{
cssClass
}
'></span>"
)
# Ignore text nodes that contain only whitespace characters. This prevents
# spans being injected between elements that can only contain a restricted
# subset of nodes such as table rows and lists. This does mean that there
# may be the odd abandoned whitespace node in a paragraph that is skipped
# but better than breaking table layouts.
for
node
in
normedRange
.
textNodes
()
when
not
white
.
test
(
node
.
nodeValue
)
$
(
node
).
wrapAll
(
hl
).
parent
().
show
()[
0
]
# Public: highlight a list of ranges
#
# normedRanges - An array of NormalizedRanges to be highlighted.
# cssClass - A CSS class to use for the highlight (default: 'annotator-hl')
#
# Returns an array of highlight Elements.
highlightRanges
:
(
normedRanges
,
cssClass
=
'annotator-hl'
)
->
highlights
=
[]
for
r
in
normedRanges
$
.
merge
highlights
,
this
.
highlightRange
(
r
,
cssClass
)
highlights
# Public: Registers a plugin with the Annotator. A plugin can only be
# registered once. The plugin will be instantiated in the following order.
#
# 1. A new instance of the plugin will be created (providing the @element and
# options as params) then assigned to the @plugins registry.
# 2. The current Annotator instance will be attached to the plugin.
# 3. The Plugin#pluginInit() method will be called if it exists.
#
# name - Plugin to instantiate. Must be in the Annotator.Plugins namespace.
# options - Any options to be provided to the plugin constructor.
#
# Examples
#
# annotator
# .addPlugin('Tags')
# .addPlugin('Store', {
# prefix: '/store'
# })
# .addPlugin('Permissions', {
# user: 'Bill'
# })
#
# Returns itself to allow chaining.
addPlugin
:
(
name
,
options
)
->
if
@
plugins
[
name
]
console
.
error
_t
(
"You cannot have more than one instance of any plugin."
)
else
klass
=
Annotator
.
Plugin
[
name
]
if
typeof
klass
is
'function'
@
plugins
[
name
]
=
new
klass
(
@
element
[
0
],
options
)
@
plugins
[
name
].
annotator
=
this
@
plugins
[
name
].
pluginInit
?
()
else
console
.
error
_t
(
"Could not load "
)
+
name
+
_t
(
" plugin. Have you included the appropriate <script> tag?"
)
this
# allow chaining
# Public: Loads the @editor with the provided annotation and updates its
# position in the window.
#
# annotation - An annotation to load into the editor.
# location - Position to set the Editor in the form {top: y, left: x}
#
# Examples
#
# annotator.showEditor({text: "my comment"}, {top: 34, left: 234})
#
# Returns itself to allow chaining.
showEditor
:
(
annotation
,
location
)
=>
@
editor
.
element
.
css
(
location
)
@
editor
.
load
(
annotation
)
this
.
publish
(
'annotationEditorShown'
,
[
@
editor
,
annotation
])
this
# Callback method called when the @editor fires the "hide" event. Itself
# publishes the 'annotationEditorHidden' event and resets the @ignoreMouseup
# property to allow listening to mouse events.
#
# Returns nothing.
onEditorHide
:
=>
this
.
publish
(
'annotationEditorHidden'
,
[
@
editor
])
@
ignoreMouseup
=
false
# Callback method called when the @editor fires the "save" event. Itself
# publishes the 'annotationEditorSubmit' event and creates/updates the
# edited annotation.
#
# Returns nothing.
onEditorSubmit
:
(
annotation
)
=>
this
.
publish
(
'annotationEditorSubmit'
,
[
@
editor
,
annotation
])
# Public: Loads the @viewer with an Array of annotations and positions it
# at the location provided. Calls the 'annotationViewerShown' event.
#
# annotation - An Array of annotations to load into the viewer.
# location - Position to set the Viewer in the form {top: y, left: x}
#
# Examples
#
# annotator.showViewer(
# [{text: "my comment"}, {text: "my other comment"}],
# {top: 34, left: 234})
# )
#
# Returns itself to allow chaining.
showViewer
:
(
annotations
,
location
)
=>
@
viewer
.
element
.
css
(
location
)
@
viewer
.
load
(
annotations
)
this
.
publish
(
'annotationViewerShown'
,
[
@
viewer
,
annotations
])
# Annotator#element event callback. Allows 250ms for mouse pointer to get from
# annotation highlight to @viewer to manipulate annotations. If timer expires
# the @viewer is hidden.
#
# Returns nothing.
startViewerHideTimer
:
=>
# Don't do this if timer has already been set by another annotation.
if
not
@
viewerHideTimer
@
viewerHideTimer
=
setTimeout
@
viewer
.
hide
,
250
# Viewer#element event callback. Clears the timer set by
# Annotator#startViewerHideTimer() when the @viewer is moused over.
#
# Returns nothing.
clearViewerHideTimer
:
()
=>
clearTimeout
(
@
viewerHideTimer
)
@
viewerHideTimer
=
false
# Annotator#element callback. Sets the @mouseIsDown property used to
# determine if a selection may have started to true. Also calls
# Annotator#startViewerHideTimer() to hide the Annotator#viewer.
#
# event - A mousedown Event object.
#
# Returns nothing.
checkForStartSelection
:
(
event
)
=>
unless
event
and
this
.
isAnnotator
(
event
.
target
)
this
.
startViewerHideTimer
()
@
mouseIsDown
=
true
# Annotator#element callback. Checks to see if a selection has been made
# on mouseup and if so displays the Annotator#adder. If @ignoreMouseup is
# set will do nothing. Also resets the @mouseIsDown property.
#
# event - A mouseup Event object.
#
# Returns nothing.
checkForEndSelection
:
(
event
)
=>
@
mouseIsDown
=
false
# This prevents the note image from jumping away on the mouseup
# of a click on icon.
if
@
ignoreMouseup
return
# Get the currently selected ranges.
@
selectedRanges
=
this
.
getSelectedRanges
()
for
range
in
@
selectedRanges
container
=
range
.
commonAncestor
if
$
(
container
).
hasClass
(
'annotator-hl'
)
container
=
$
(
container
).
parents
(
'[class!=annotator-hl]'
)[
0
]
return
if
this
.
isAnnotator
(
container
)
if
event
and
@
selectedRanges
.
length
@
adder
.
css
(
Util
.
mousePosition
(
event
,
@
wrapper
[
0
]))
.
show
()
else
@
adder
.
hide
()
# Public: Determines if the provided element is part of the annotator plugin.
# Useful for ignoring mouse actions on the annotator elements.
# NOTE: The @wrapper is not included in this check.
#
# element - An Element or TextNode to check.
#
# Examples
#
# span = document.createElement('span')
# annotator.isAnnotator(span) # => Returns false
#
# annotator.isAnnotator(annotator.viewer.element) # => Returns true
#
# Returns true if the element is a child of an annotator element.
isAnnotator
:
(
element
)
->
!!
$
(
element
).
parents
().
addBack
().
filter
(
'[class^=annotator-]'
).
not
(
@
wrapper
).
length
# Annotator#element callback. Displays viewer with all annotations
# associated with highlight Elements under the cursor.
#
# event - A mouseover Event object.
#
# Returns nothing.
onHighlightMouseover
:
(
event
)
=>
# Cancel any pending hiding of the viewer.
this
.
clearViewerHideTimer
()
# Don't do anything if we're making a selection
return
false
if
@
mouseIsDown
# If the viewer is already shown, hide it first
@
viewer
.
hide
()
if
@
viewer
.
isShown
()
annotations
=
$
(
event
.
target
)
.
parents
(
'.annotator-hl'
)
.
addBack
()
.
map
(
->
return
$
(
this
).
data
(
"annotation"
))
.
toArray
()
# Now show the viewer with the wanted annotations
this
.
showViewer
(
annotations
,
Util
.
mousePosition
(
event
,
@
wrapper
[
0
]))
# Annotator#element callback. Sets @ignoreMouseup to true to prevent
# the annotation selection events firing when the adder is clicked.
#
# event - A mousedown Event object
#
# Returns nothing.
onAdderMousedown
:
(
event
)
=>
event
?
.
preventDefault
()
@
ignoreMouseup
=
true
# Annotator#element callback. Displays the @editor in place of the @adder and
# loads in a newly created annotation Object. The click event is used as well
# as the mousedown so that we get the :active state on the @adder when clicked
#
# event - A mousedown Event object
#
# Returns nothing.
onAdderClick
:
(
event
)
=>
event
?
.
preventDefault
()
# Hide the adder
position
=
@
adder
.
position
()
@
adder
.
hide
()
# Show a temporary highlight so the user can see what they selected
# Also extract the quotation and serialize the ranges
annotation
=
this
.
setupAnnotation
(
this
.
createAnnotation
())
$
(
annotation
.
highlights
).
addClass
(
'annotator-hl-temporary'
)
# Subscribe to the editor events
# Make the highlights permanent if the annotation is saved
save
=
=>
do
cleanup
$
(
annotation
.
highlights
).
removeClass
(
'annotator-hl-temporary'
)
# Fire annotationCreated events so that plugins can react to them
this
.
publish
(
'annotationCreated'
,
[
annotation
])
# Remove the highlights if the edit is cancelled
cancel
=
=>
do
cleanup
this
.
deleteAnnotation
(
annotation
)
# Don't leak handlers at the end
cleanup
=
=>
this
.
unsubscribe
(
'annotationEditorHidden'
,
cancel
)
this
.
unsubscribe
(
'annotationEditorSubmit'
,
save
)
this
.
subscribe
(
'annotationEditorHidden'
,
cancel
)
this
.
subscribe
(
'annotationEditorSubmit'
,
save
)
# Display the editor.
this
.
showEditor
(
annotation
,
position
)
# Annotator#viewer callback function. Displays the Annotator#editor in the
# positions of the Annotator#viewer and loads the passed annotation for
# editing.
#
# annotation - An annotation Object for editing.
#
# Returns nothing.
onEditAnnotation
:
(
annotation
)
=>
offset
=
@
viewer
.
element
.
position
()
# Subscribe once to editor events
# Update the annotation when the editor is saved
update
=
=>
do
cleanup
this
.
updateAnnotation
(
annotation
)
# Remove handlers when the editor is hidden
cleanup
=
=>
this
.
unsubscribe
(
'annotationEditorHidden'
,
cleanup
)
this
.
unsubscribe
(
'annotationEditorSubmit'
,
update
)
this
.
subscribe
(
'annotationEditorHidden'
,
cleanup
)
this
.
subscribe
(
'annotationEditorSubmit'
,
update
)
# Replace the viewer with the editor
@
viewer
.
hide
()
this
.
showEditor
(
annotation
,
offset
)
# Annotator#viewer callback function. Deletes the annotation provided to the
# callback.
#
# annotation - An annotation Object for deletion.
#
# Returns nothing.
onDeleteAnnotation
:
(
annotation
)
=>
@
viewer
.
hide
()
# Delete highlight elements.
this
.
deleteAnnotation
annotation
# Create namespace for Annotator plugins
class
Annotator
.
Plugin
extends
Delegator
constructor
:
(
element
,
options
)
->
super
pluginInit
:
->
# Sniff the browser environment and attempt to add missing functionality.
g
=
Util
.
getGlobal
()
if
not
g
.
document
?
.
evaluate
?
$
.
getScript
(
'http://assets.annotateit.org/vendor/xpath.min.js'
)
if
not
g
.
getSelection
?
$
.
getScript
(
'http://assets.annotateit.org/vendor/ierange.min.js'
)
if
not
g
.
JSON
?
$
.
getScript
(
'http://assets.annotateit.org/vendor/json2.min.js'
)
# Ensure the Node constants are defined
if
not
g
.
Node
?
g
.
Node
=
ELEMENT_NODE
:
1
ATTRIBUTE_NODE
:
2
TEXT_NODE
:
3
CDATA_SECTION_NODE
:
4
ENTITY_REFERENCE_NODE
:
5
ENTITY_NODE
:
6
PROCESSING_INSTRUCTION_NODE
:
7
COMMENT_NODE
:
8
DOCUMENT_NODE
:
9
DOCUMENT_TYPE_NODE
:
10
DOCUMENT_FRAGMENT_NODE
:
11
NOTATION_NODE
:
12
# Bind our local copy of jQuery so plugins can use the extensions.
Annotator
.
$
=
$
# Export other modules for use in plugins.
Annotator
.
Delegator
=
Delegator
Annotator
.
Range
=
Range
Annotator
.
Util
=
Util
# Expose a global instance registry
Annotator
.
_instances
=
[]
# Bind gettext helper so plugins can use localisation.
Annotator
.
_t
=
_t
# Returns true if the Annotator can be used in the current browser.
Annotator
.
supported
=
->
(
->
!!
this
.
getSelection
)()
# Restores the Annotator property on the global object to it's
# previous value and returns the Annotator.
Annotator
.
noConflict
=
->
Util
.
getGlobal
().
Annotator
=
_Annotator
this
# Create global access for Annotator
$
.
fn
.
annotator
=
(
options
)
->
args
=
Array
::
slice
.
call
(
arguments
,
1
)
this
.
each
->
# check the data() cache, if it's there we'll call the method requested
instance
=
$
.
data
(
this
,
'annotator'
)
if
options
is
'destroy'
$
.
removeData
(
this
,
'annotator'
)
instance
?
.
destroy
(
args
)
else
if
instance
options
&&
instance
[
options
].
apply
(
instance
,
args
)
else
instance
=
new
Annotator
(
this
,
options
)
$
.
data
(
this
,
'annotator'
,
instance
)
# Export Annotator object.
this
.
Annotator
=
Annotator
;
h/static/scripts/annotator/class.coffee
deleted
100644 → 0
View file @
16f4d24a
# Public: Delegator is the base class that all of Annotators objects inherit
# from. It provides basic functionality such as instance options, event
# delegation and pub/sub methods.
class
Delegator
# Public: Events object. This contains a key/pair hash of events/methods that
# should be bound. See Delegator#addEvents() for usage.
events
:
{}
# Public: Options object. Extended on initialisation.
options
:
{}
# A jQuery object wrapping the DOM Element provided on initialisation.
element
:
null
# Public: Constructor function that sets up the instance. Binds the @events
# hash and extends the @options object.
#
# element - The DOM element that this intance represents.
# options - An Object literal of options.
#
# Examples
#
# element = document.getElementById('my-element')
# instance = new Delegator(element, {
# option: 'my-option'
# })
#
# Returns a new instance of Delegator.
constructor
:
(
element
,
options
)
->
@
options
=
$
.
extend
(
true
,
{},
@
options
,
options
)
@
element
=
$
(
element
)
# Delegator creates closures for each event it binds. This is a private
# registry of created closures, used to enable event unbinding.
@
_closures
=
{}
this
.
on
=
this
.
subscribe
this
.
addEvents
()
# Public: Destroy the instance, unbinding all events.
#
# Returns nothing.
destroy
:
->
this
.
removeEvents
()
# Public: binds the function names in the @events Object to their events.
#
# The @events Object should be a set of key/value pairs where the key is the
# event name with optional CSS selector. The value should be a String method
# name on the current class.
#
# This is called by the default Delegator constructor and so shouldn't usually
# need to be called by the user.
#
# Examples
#
# # This will bind the clickedElement() method to the click event on @element.
# @options = {"click": "clickedElement"}
#
# # This will delegate the submitForm() method to the submit event on the
# # form within the @element.
# @options = {"form submit": "submitForm"}
#
# # This will bind the updateAnnotationStore() method to the custom
# # annotation:save event. NOTE: Because this is a custom event the
# # Delegator#subscribe() method will be used and updateAnnotationStore()
# # will not recieve an event parameter like the previous two examples.
# @options = {"annotation:save": "updateAnnotationStore"}
#
# Returns nothing.
addEvents
:
->
for
event
in
Delegator
.
_parseEvents
(
@
events
)
this
.
_addEvent
event
.
selector
,
event
.
event
,
event
.
functionName
# Public: unbinds functions previously bound to events by addEvents().
#
# The @events Object should be a set of key/value pairs where the key is the
# event name with optional CSS selector. The value should be a String method
# name on the current class.
#
# Returns nothing.
removeEvents
:
->
for
event
in
Delegator
.
_parseEvents
(
@
events
)
this
.
_removeEvent
event
.
selector
,
event
.
event
,
event
.
functionName
# Binds an event to a callback function represented by a String. A selector
# can be provided in order to watch for events on a child element.
#
# The event can be any standard event supported by jQuery or a custom String.
# If a custom string is used the callback function will not recieve an
# event object as it's first parameter.
#
# selector - Selector String matching child elements. (default: '')
# event - The event to listen for.
# functionName - A String function name to bind to the event.
#
# Examples
#
# # Listens for all click events on instance.element.
# instance._addEvent('', 'click', 'onClick')
#
# # Delegates the instance.onInputFocus() method to focus events on all
# # form inputs within instance.element.
# instance._addEvent('form :input', 'focus', 'onInputFocus')
#
# Returns itself.
_addEvent
:
(
selector
,
event
,
functionName
)
->
closure
=
=>
this
[
functionName
].
apply
(
this
,
arguments
)
if
selector
==
''
and
Delegator
.
_isCustomEvent
(
event
)
this
.
subscribe
(
event
,
closure
)
else
@
element
.
delegate
(
selector
,
event
,
closure
)
@
_closures
[
"
#{
selector
}
/
#{
event
}
/
#{
functionName
}
"
]
=
closure
this
# Unbinds a function previously bound to an event by the _addEvent method.
#
# Takes the same arguments as _addEvent(), and an event will only be
# successfully unbound if the arguments to removeEvent() are exactly the same
# as the original arguments to _addEvent(). This would usually be called by
# _removeEvents().
#
# selector - Selector String matching child elements. (default: '')
# event - The event to listen for.
# functionName - A String function name to bind to the event.
#
# Returns itself.
_removeEvent
:
(
selector
,
event
,
functionName
)
->
closure
=
@
_closures
[
"
#{
selector
}
/
#{
event
}
/
#{
functionName
}
"
]
if
selector
==
''
and
Delegator
.
_isCustomEvent
(
event
)
this
.
unsubscribe
(
event
,
closure
)
else
@
element
.
undelegate
(
selector
,
event
,
closure
)
delete
@
_closures
[
"
#{
selector
}
/
#{
event
}
/
#{
functionName
}
"
]
this
# Public: Fires an event and calls all subscribed callbacks with any parameters
# provided. This is essentially an alias of @element.triggerHandler() but
# should be used to fire custom events.
#
# NOTE: Events fired using .publish() will not bubble up the DOM.
#
# event - A String event name.
# params - An Array of parameters to provide to callbacks.
#
# Examples
#
# instance.subscribe('annotation:save', (msg) -> console.log(msg))
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# Returns itself.
publish
:
()
->
@
element
.
triggerHandler
.
apply
@
element
,
arguments
this
# Public: Listens for custom event which when published will call the provided
# callback. This is essentially a wrapper around @element.bind() but removes
# the event parameter that jQuery event callbacks always recieve. These
# parameters are unnessecary for custom events.
#
# event - A String event name.
# callback - A callback function called when the event is published.
#
# Examples
#
# instance.subscribe('annotation:save', (msg) -> console.log(msg))
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# Returns itself.
subscribe
:
(
event
,
callback
)
->
closure
=
->
callback
.
apply
(
this
,
[].
slice
.
call
(
arguments
,
1
))
# Ensure both functions have the same unique id so that jQuery will accept
# callback when unbinding closure.
closure
.
guid
=
callback
.
guid
=
(
$
.
guid
+=
1
)
@
element
.
bind
event
,
closure
this
# Public: Unsubscribes a callback from an event. The callback will no longer
# be called when the event is published.
#
# event - A String event name.
# callback - A callback function to be removed.
#
# Examples
#
# callback = (msg) -> console.log(msg)
# instance.subscribe('annotation:save', callback)
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# instance.unsubscribe('annotation:save', callback)
# instance.publish('annotation:save', ['Hello Again'])
# # => No output.
#
# Returns itself.
unsubscribe
:
->
@
element
.
unbind
.
apply
@
element
,
arguments
this
# Parse the @events object of a Delegator into an array of objects containing
# string-valued "selector", "event", and "func" keys.
Delegator
.
_parseEvents
=
(
eventsObj
)
->
events
=
[]
for
sel
,
functionName
of
eventsObj
[
selector
...,
event
]
=
sel
.
split
' '
events
.
push
({
selector
:
selector
.
join
(
' '
),
event
:
event
,
functionName
:
functionName
})
return
events
# Native jQuery events that should recieve an event object. Plugins can
# add their own methods to this if required.
Delegator
.
natives
=
do
->
specials
=
(
key
for
own
key
,
val
of
jQuery
.
event
.
special
)
"""
blur focus focusin focusout load resize scroll unload click dblclick
mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave
change select submit keydown keypress keyup error
"""
.
split
(
/[^a-z]+/
).
concat
(
specials
)
# Checks to see if the provided event is a DOM event supported by jQuery or
# a custom user event.
#
# event - String event name.
#
# Examples
#
# Delegator._isCustomEvent('click') # => false
# Delegator._isCustomEvent('mousedown') # => false
# Delegator._isCustomEvent('annotation:created') # => true
#
# Returns true if event is a custom user event.
Delegator
.
_isCustomEvent
=
(
event
)
->
[
event
]
=
event
.
split
(
'.'
)
$
.
inArray
(
event
,
Delegator
.
natives
)
==
-
1
h/static/scripts/annotator/console.coffee
deleted
100644 → 0
View file @
16f4d24a
# Stub the console when not available so that everything still works.
functions
=
[
"log"
,
"debug"
,
"info"
,
"warn"
,
"exception"
,
"assert"
,
"dir"
,
"dirxml"
,
"trace"
,
"group"
,
"groupEnd"
,
"groupCollapsed"
,
"time"
,
"timeEnd"
,
"profile"
,
"profileEnd"
,
"count"
,
"clear"
,
"table"
,
"error"
,
"notifyFirebug"
,
"firebug"
,
"userObjects"
]
if
console
?
# Opera's console doesn't have a group function as of 2010-07-01
if
not
console
.
group
?
console
.
group
=
(
name
)
->
console
.
log
"GROUP: "
,
name
# Webkit's developer console has yet to implement groupCollapsed as of 2010-07-01
if
not
console
.
groupCollapsed
?
console
.
groupCollapsed
=
console
.
group
# Stub out any remaining functions
for
fn
in
functions
if
not
console
[
fn
]
?
console
[
fn
]
=
->
console
.
log
_t
(
"Not implemented:"
)
+
" console.
#{
name
}
"
else
this
.
console
=
{}
for
fn
in
functions
this
.
console
[
fn
]
=
->
this
.
console
[
'error'
]
=
(
args
...)
->
alert
(
"ERROR:
#{
args
.
join
(
', '
)
}
"
)
this
.
console
[
'warn'
]
=
(
args
...)
->
alert
(
"WARNING:
#{
args
.
join
(
', '
)
}
"
)
h/static/scripts/annotator/editor.coffee
deleted
100644 → 0
View file @
16f4d24a
# Public: Creates an element for editing annotations.
class
Annotator
.
Editor
extends
Annotator
.
Widget
# Events to be bound to @element.
events
:
"form submit"
:
"submit"
".annotator-save click"
:
"submit"
".annotator-cancel click"
:
"hide"
".annotator-cancel mouseover"
:
"onCancelButtonMouseover"
"textarea keydown"
:
"processKeypress"
# Classes to toggle state.
classes
:
hide
:
'annotator-hide'
focus
:
'annotator-focus'
# HTML template for @element.
html
:
"""
<div class="annotator-outer annotator-editor">
<form class="annotator-widget">
<ul class="annotator-listing"></ul>
<div class="annotator-controls">
<a href="#cancel" class="annotator-cancel">"""
+
_t
(
'Cancel'
)
+
"""</a>
<a href="#save" class="annotator-save annotator-focus">"""
+
_t
(
'Save'
)
+
"""</a>
</div>
</form>
</div>
"""
options
:
{}
# Configuration options
# Public: Creates an instance of the Editor object. This will create the
# @element from the @html string and set up all events.
#
# options - An Object literal containing options. There are currently no
# options implemented.
#
# Examples
#
# # Creates a new editor, adds a custom field and
# # loads an annotation for editing.
# editor = new Annotator.Editor
# editor.addField({
# label: 'My custom input field',
# type: 'textarea'
# load: someLoadCallback
# save: someSaveCallback
# })
# editor.load(annotation)
#
# Returns a new Editor instance.
constructor
:
(
options
)
->
super
$
(
@
html
)[
0
],
options
@
fields
=
[]
@
annotation
=
{}
# Public: Displays the Editor and fires a "show" event.
# Can be used as an event callback and will call Event#preventDefault()
# on the supplied event.
#
# event - Event object provided if method is called by event
# listener (default:undefined)
#
# Examples
#
# # Displays the editor.
# editor.show()
#
# # Displays the editor on click (prevents default action).
# $('a.show-editor').bind('click', editor.show)
#
# Returns itself.
show
:
(
event
)
=>
Annotator
.
Util
.
preventEventDefault
event
@
element
.
removeClass
(
@
classes
.
hide
)
@
element
.
find
(
'.annotator-save'
).
addClass
(
@
classes
.
focus
)
# invert if necessary
this
.
checkOrientation
()
# give main textarea focus
@
element
.
find
(
":input:first"
).
focus
()
this
.
setupDraggables
()
this
.
publish
(
'show'
)
# Public: Hides the Editor and fires a "hide" event. Can be used as an event
# callback and will call Event#preventDefault() on the supplied event.
#
# event - Event object provided if method is called by event
# listener (default:undefined)
#
# Examples
#
# # Hides the editor.
# editor.hide()
#
# # Hide the editor on click (prevents default action).
# $('a.hide-editor').bind('click', editor.hide)
#
# Returns itself.
hide
:
(
event
)
=>
Annotator
.
Util
.
preventEventDefault
event
@
element
.
addClass
(
@
classes
.
hide
)
this
.
publish
(
'hide'
)
# Public: Loads an annotation into the Editor and displays it setting
# Editor#annotation to the provided annotation. It fires the "load" event
# providing the current annotation subscribers can modify the annotation
# before it updates the editor fields.
#
# annotation - An annotation Object to display for editing.
#
# Examples
#
# # Diplays the editor with the annotation loaded.
# editor.load({text: 'My Annotation'})
#
# editor.on('load', (annotation) ->
# console.log annotation.text
# ).load({text: 'My Annotation'})
# # => Outputs "My Annotation"
#
# Returns itself.
load
:
(
annotation
)
=>
@
annotation
=
annotation
this
.
publish
(
'load'
,
[
@
annotation
])
for
field
in
@
fields
field
.
load
(
field
.
element
,
@
annotation
)
this
.
show
()
# Public: Hides the Editor and passes the annotation to all registered fields
# so they can update its state. It then fires the "save" event so that other
# parties can further modify the annotation.
# Can be used as an event callback and will call Event#preventDefault() on the
# supplied event.
#
# event - Event object provided if method is called by event
# listener (default:undefined)
#
# Examples
#
# # Submits the editor.
# editor.submit()
#
# # Submits the editor on click (prevents default action).
# $('button.submit-editor').bind('click', editor.submit)
#
# # Appends "Comment: " to the annotation comment text.
# editor.on('save', (annotation) ->
# annotation.text = "Comment: " + annotation.text
# ).submit()
#
# Returns itself.
submit
:
(
event
)
=>
Annotator
.
Util
.
preventEventDefault
event
for
field
in
@
fields
field
.
submit
(
field
.
element
,
@
annotation
)
this
.
publish
(
'save'
,
[
@
annotation
])
this
.
hide
()
# Public: Adds an addional form field to the editor. Callbacks can be provided
# to update the view and anotations on load and submission.
#
# options - An options Object. Options are as follows:
# id - A unique id for the form element will also be set as the
# "for" attrubute of a label if there is one. Defaults to
# a timestamp. (default: "annotator-field-{timestamp}")
# type - Input type String. One of "input", "textarea",
# "checkbox", "select" (default: "input")
# label - Label to display either in a label Element or as place-
# holder text depending on the type. (default: "")
# load - Callback Function called when the editor is loaded with a
# new annotation. Recieves the field <li> element and the
# annotation to be loaded.
# submit - Callback Function called when the editor is submitted.
# Recieves the field <li> element and the annotation to be
# updated.
#
# Examples
#
# # Add a new input element.
# editor.addField({
# label: "Tags",
#
# # This is called when the editor is loaded use it to update your input.
# load: (field, annotation) ->
# # Do something with the annotation.
# value = getTagString(annotation.tags)
# $(field).find('input').val(value)
#
# # This is called when the editor is submitted use it to retrieve data
# # from your input and save it to the annotation.
# submit: (field, annotation) ->
# value = $(field).find('input').val()
# annotation.tags = getTagsFromString(value)
# })
#
# # Add a new checkbox element.
# editor.addField({
# type: 'checkbox',
# id: 'annotator-field-my-checkbox',
# label: 'Allow anyone to see this annotation',
# load: (field, annotation) ->
# # Check what state of input should be.
# if checked
# $(field).find('input').attr('checked', 'checked')
# else
# $(field).find('input').removeAttr('checked')
# submit: (field, annotation) ->
# checked = $(field).find('input').is(':checked')
# # Do something.
# })
#
# Returns the created <li> Element.
addField
:
(
options
)
->
field
=
$
.
extend
({
id
:
'annotator-field-'
+
Annotator
.
Util
.
uuid
()
type
:
'input'
label
:
''
load
:
->
submit
:
->
},
options
)
input
=
null
element
=
$
(
'<li class="annotator-item" />'
)
field
.
element
=
element
[
0
]
switch
(
field
.
type
)
when
'textarea'
then
input
=
$
(
'<textarea />'
)
when
'input'
,
'checkbox'
then
input
=
$
(
'<input />'
)
when
'select'
then
input
=
$
(
'<select />'
)
element
.
append
(
input
)
input
.
attr
({
id
:
field
.
id
placeholder
:
field
.
label
})
if
field
.
type
==
'checkbox'
input
[
0
].
type
=
'checkbox'
element
.
addClass
(
'annotator-checkbox'
)
element
.
append
(
$
(
'<label />'
,
{
for
:
field
.
id
,
html
:
field
.
label
}))
@
element
.
find
(
'ul:first'
).
append
(
element
)
@
fields
.
push
field
field
.
element
checkOrientation
:
->
super
list
=
@
element
.
find
(
'ul'
)
controls
=
@
element
.
find
(
'.annotator-controls'
)
if
@
element
.
hasClass
(
@
classes
.
invert
.
y
)
controls
.
insertBefore
(
list
)
else
if
controls
.
is
(
':first-child'
)
controls
.
insertAfter
(
list
)
this
# Event callback. Listens for the following special keypresses.
# - escape: Hides the editor
# - enter: Submits the editor
#
# event - A keydown Event object.
#
# Returns nothing
processKeypress
:
(
event
)
=>
if
event
.
keyCode
is
27
# "Escape" key => abort.
this
.
hide
()
else
if
event
.
keyCode
is
13
and
!
event
.
shiftKey
# If "return" was pressed without the shift key, we're done.
this
.
submit
()
# Event callback. Removes the focus class from the submit button when the
# cancel button is hovered.
#
# Returns nothing
onCancelButtonMouseover
:
=>
@
element
.
find
(
'.'
+
@
classes
.
focus
).
removeClass
(
@
classes
.
focus
)
# Sets up mouse events for resizing and dragging the editor window.
# window events are bound only when needed and throttled to only update
# the positions at most 60 times a second.
#
# Returns nothing.
setupDraggables
:
()
->
@
element
.
find
(
'.annotator-resize'
).
remove
()
# Find the first/last item element depending on orientation
if
@
element
.
hasClass
(
@
classes
.
invert
.
y
)
cornerItem
=
@
element
.
find
(
'.annotator-item:last'
)
else
cornerItem
=
@
element
.
find
(
'.annotator-item:first'
)
if
cornerItem
$
(
'<span class="annotator-resize"></span>'
).
appendTo
(
cornerItem
)
mousedown
=
null
classes
=
@
classes
editor
=
@
element
textarea
=
null
resize
=
editor
.
find
(
'.annotator-resize'
)
controls
=
editor
.
find
(
'.annotator-controls'
)
throttle
=
false
onMousedown
=
(
event
)
->
if
event
.
target
==
this
mousedown
=
{
element
:
this
top
:
event
.
pageY
left
:
event
.
pageX
}
# Find the first text area if there is one.
textarea
=
editor
.
find
(
'textarea:first'
)
$
(
window
).
bind
({
'mouseup.annotator-editor-resize'
:
onMouseup
'mousemove.annotator-editor-resize'
:
onMousemove
})
event
.
preventDefault
()
onMouseup
=
->
mousedown
=
null
$
(
window
).
unbind
'.annotator-editor-resize'
onMousemove
=
(
event
)
=>
if
mousedown
and
throttle
==
false
diff
=
{
top
:
event
.
pageY
-
mousedown
.
top
left
:
event
.
pageX
-
mousedown
.
left
}
if
mousedown
.
element
==
resize
[
0
]
height
=
textarea
.
outerHeight
()
width
=
textarea
.
outerWidth
()
directionX
=
if
editor
.
hasClass
(
classes
.
invert
.
x
)
then
-
1
else
1
directionY
=
if
editor
.
hasClass
(
classes
.
invert
.
y
)
then
1
else
-
1
textarea
.
height
height
+
(
diff
.
top
*
directionY
)
textarea
.
width
width
+
(
diff
.
left
*
directionX
)
# Only update the mousedown object if the dimensions
# have changed, otherwise they have reached their minimum
# values.
mousedown
.
top
=
event
.
pageY
unless
textarea
.
outerHeight
()
==
height
mousedown
.
left
=
event
.
pageX
unless
textarea
.
outerWidth
()
==
width
else
if
mousedown
.
element
==
controls
[
0
]
editor
.
css
({
top
:
parseInt
(
editor
.
css
(
'top'
),
10
)
+
diff
.
top
left
:
parseInt
(
editor
.
css
(
'left'
),
10
)
+
diff
.
left
})
mousedown
.
top
=
event
.
pageY
mousedown
.
left
=
event
.
pageX
throttle
=
true
;
setTimeout
(
->
throttle
=
false
,
1000
/
60
)
resize
.
bind
'mousedown'
,
onMousedown
controls
.
bind
'mousedown'
,
onMousedown
h/static/scripts/annotator/
annotator_
monkey.coffee
→
h/static/scripts/annotator/monkey.coffee
View file @
a4ce9479
# Save references to Range and Util (because we call Annotator.noConflict() when
# bootstrapping)
Range
=
Annotator
.
Range
Util
=
Annotator
.
Util
# Disable Annotator's default highlight events
delete
Annotator
.
prototype
.
events
[
".annotator-hl mouseover"
]
delete
Annotator
.
prototype
.
events
[
".annotator-hl mouseout"
]
...
...
@@ -188,3 +194,104 @@ Annotator.prototype.onAnchorClick = ->
# It is always safe to install it, it'll not overwrite existing functions
g
=
Annotator
.
Util
.
getGlobal
()
if
g
.
wgxpath
?
then
g
.
wgxpath
.
install
()
Range
.
BrowserRange
.
prototype
.
normalize
=
(
root
)
->
if
@
tainted
console
.
error
(
_t
(
"You may only call normalize() once on a BrowserRange!"
))
return
false
else
@
tainted
=
true
r
=
{}
# Look at the start
if
@
startContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# We are dealing with element nodes
r
.
start
=
Util
.
getFirstTextNodeNotBefore
@
startContainer
.
childNodes
[
@
startOffset
]
r
.
startOffset
=
0
else
# We are dealing with simple text nodes
r
.
start
=
@
startContainer
r
.
startOffset
=
@
startOffset
# Look at the end
if
@
endContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# Get specified node.
node
=
@
endContainer
.
childNodes
[
@
endOffset
]
if
node
?
# Does that node exist?
# Look for a text node either at the immediate beginning of node
n
=
node
while
n
?
and
(
n
.
nodeType
isnt
Node
.
TEXT_NODE
)
n
=
n
.
firstChild
if
n
?
# Did we find a text node at the start of this element?
# Check the previous sibling
prev
=
n
.
previousSibling
if
prev
?
and
(
prev
.
nodeType
is
Node
.
TEXT_NODE
)
# We have another text righ before us. Use that instead.
r
.
end
=
prev
r
.
endOffset
=
prev
.
nodeValue
.
length
else
# No, we need to stick to this node.
r
.
end
=
n
r
.
endOffset
=
0
unless
r
.
end
?
# We need to find a text node in the previous sibling of the node at the
# given offset, if one exists, or in the previous sibling of its container.
if
@
endOffset
node
=
@
endContainer
.
childNodes
[
@
endOffset
-
1
]
else
node
=
@
endContainer
.
previousSibling
r
.
end
=
Util
.
getLastTextNodeUpTo
node
r
.
endOffset
=
r
.
end
.
nodeValue
.
length
else
# We are dealing with simple text nodes
r
.
end
=
@
endContainer
r
.
endOffset
=
@
endOffset
# We have collected the initial data.
# Now let's start to slice & dice the text elements!
nr
=
{}
changed
=
false
if
r
.
startOffset
>
0
# Do we really have to cut?
if
r
.
start
.
nodeValue
.
length
>
r
.
startOffset
# Yes. Cut.
nr
.
start
=
r
.
start
.
splitText
(
r
.
startOffset
)
changed
=
true
else
# Avoid splitting off zero-length pieces.
nr
.
start
=
r
.
start
.
nextSibling
else
nr
.
start
=
r
.
start
# is the whole selection inside one text element ?
if
r
.
start
is
r
.
end
if
nr
.
start
.
nodeValue
.
length
>
(
r
.
endOffset
-
r
.
startOffset
)
nr
.
start
.
splitText
(
r
.
endOffset
-
r
.
startOffset
)
changed
=
true
nr
.
end
=
nr
.
start
else
# no, the end of the selection is in a separate text element
# does the end need to be cut?
if
r
.
end
.
nodeValue
.
length
>
r
.
endOffset
r
.
end
.
splitText
(
r
.
endOffset
)
changed
=
true
nr
.
end
=
r
.
end
# Make sure the common ancestor is an element node.
nr
.
commonAncestor
=
@
commonAncestorContainer
while
nr
.
commonAncestor
.
nodeType
isnt
Node
.
ELEMENT_NODE
nr
.
commonAncestor
=
nr
.
commonAncestor
.
parentNode
if
changed
event
=
document
.
createEvent
"UIEvents"
event
.
initUIEvent
"domChange"
,
true
,
false
,
window
,
0
event
.
reason
=
"range normalization"
event
.
data
=
nr
nr
.
commonAncestor
.
dispatchEvent
event
new
Range
.
NormalizedRange
(
nr
)
h/static/scripts/annotator/plugin/auth.coffee
deleted
100644 → 0
View file @
16f4d24a
# Public: Creates a Date object from an ISO8601 formatted date String.
#
# string - ISO8601 formatted date String.
#
# Returns Date instance.
createDateFromISO8601
=
(
string
)
->
regexp
=
"([0-9]{4})(-([0-9]{2})(-([0-9]{2})"
+
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(
\.
([0-9]+))?)?"
+
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
d
=
string
.
match
(
new
RegExp
(
regexp
))
offset
=
0
date
=
new
Date
(
d
[
1
],
0
,
1
)
date
.
setMonth
(
d
[
3
]
-
1
)
if
d
[
3
]
date
.
setDate
(
d
[
5
])
if
d
[
5
]
date
.
setHours
(
d
[
7
])
if
d
[
7
]
date
.
setMinutes
(
d
[
8
])
if
d
[
8
]
date
.
setSeconds
(
d
[
10
])
if
d
[
10
]
date
.
setMilliseconds
(
Number
(
"0."
+
d
[
12
])
*
1000
)
if
d
[
12
]
if
d
[
14
]
offset
=
(
Number
(
d
[
16
])
*
60
)
+
Number
(
d
[
17
])
offset
*=
((
d
[
15
]
==
'-'
)
?
1
:
-
1
)
offset
-=
date
.
getTimezoneOffset
()
time
=
(
Number
(
date
)
+
(
offset
*
60
*
1000
))
date
.
setTime
(
Number
(
time
))
date
base64Decode
=
(
data
)
->
if
atob
?
# Gecko and Webkit provide native code for this
atob
(
data
)
else
# Adapted from MIT/BSD licensed code at http://phpjs.org/functions/base64_decode
# version 1109.2015
b64
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
i
=
0
ac
=
0
dec
=
""
tmp_arr
=
[]
if
not
data
return
data
data
+=
''
while
i
<
data
.
length
# unpack four hexets into three octets using index points in b64
h1
=
b64
.
indexOf
(
data
.
charAt
(
i
++
))
h2
=
b64
.
indexOf
(
data
.
charAt
(
i
++
))
h3
=
b64
.
indexOf
(
data
.
charAt
(
i
++
))
h4
=
b64
.
indexOf
(
data
.
charAt
(
i
++
))
bits
=
h1
<<
18
|
h2
<<
12
|
h3
<<
6
|
h4
o1
=
bits
>>
16
&
0xff
o2
=
bits
>>
8
&
0xff
o3
=
bits
&
0xff
if
h3
==
64
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
)
else
if
h4
==
64
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
,
o2
)
else
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
,
o2
,
o3
)
tmp_arr
.
join
(
''
)
base64UrlDecode
=
(
data
)
->
m
=
data
.
length
%
4
if
m
!=
0
for
i
in
[
0
...
4
-
m
]
data
+=
'='
data
=
data
.
replace
(
/-/g
,
'+'
)
data
=
data
.
replace
(
/_/g
,
'/'
)
base64Decode
(
data
)
parseToken
=
(
token
)
->
[
head
,
payload
,
sig
]
=
token
.
split
(
'.'
)
JSON
.
parse
(
base64UrlDecode
(
payload
))
# Public: Supports the Store plugin by providing Authentication headers.
class
Annotator
.
Plugin
.
Auth
extends
Annotator
.
Plugin
# User options that can be provided.
options
:
# An authentication token. Used to skip the request to the server for a
# a token.
token
:
null
# The URL on the local server to request an authentication token.
tokenUrl
:
'/auth/token'
# If true will try and fetch a token when the plugin is initialised.
autoFetch
:
true
# Public: Create a new instance of the Auth plugin.
#
# element - The element to bind all events to. Usually the Annotator#element.
# options - An Object literal containing user options.
#
# Examples
#
# plugin = new Annotator.Plugin.Auth(annotator.element, {
# tokenUrl: '/my/custom/path'
# })
#
# Returns instance of Auth.
constructor
:
(
element
,
options
)
->
super
# List of functions to be executed when we have a valid token.
@
waitingForToken
=
[]
if
@
options
.
token
this
.
setToken
(
@
options
.
token
)
else
this
.
requestToken
()
# Public: Makes a request to the local server for an authentication token.
#
# Examples
#
# auth.requestToken()
#
# Returns jqXHR object.
requestToken
:
->
@
requestInProgress
=
true
$
.
ajax
url
:
@
options
.
tokenUrl
dataType
:
'text'
xhrFields
:
withCredentials
:
true
# Send any auth cookies to the backend
# on success, set the auth token
.
done
(
data
,
status
,
xhr
)
=>
this
.
setToken
(
data
)
# on failure, relay any message given by the server to the user with a notification
.
fail
(
xhr
,
status
,
err
)
=>
msg
=
Annotator
.
_t
(
"Couldn't get auth token:"
)
console
.
error
"
#{
msg
}
#{
err
}
"
,
xhr
Annotator
.
showNotification
(
"
#{
msg
}
#{
xhr
.
responseText
}
"
,
Annotator
.
Notification
.
ERROR
)
# always reset the requestInProgress indicator
.
always
=>
@
requestInProgress
=
false
# Public: Sets the @token and checks it's validity. If the token is invalid
# requests a new one from the server.
#
# token - A token string.
#
# Examples
#
# auth.setToken('eyJh...9jQ3I')
#
# Returns nothing.
setToken
:
(
token
)
->
@
token
=
token
# Parse the token without verifying its authenticity:
@
_unsafeToken
=
parseToken
(
token
)
if
this
.
haveValidToken
()
if
@
options
.
autoFetch
# Set timeout to fetch new token 2 seconds before current token expiry
@
refreshTimeout
=
setTimeout
(()
=>
this
.
requestToken
()),
(
this
.
timeToExpiry
()
-
2
)
*
1000
# Set headers field on this.element
this
.
updateHeaders
()
# Run callbacks waiting for token
while
@
waitingForToken
.
length
>
0
@
waitingForToken
.
pop
()(
@
_unsafeToken
)
else
console
.
warn
Annotator
.
_t
(
"Didn't get a valid token."
)
if
@
options
.
autoFetch
console
.
warn
Annotator
.
_t
(
"Getting a new token in 10s."
)
setTimeout
(()
=>
this
.
requestToken
()),
10
*
1000
# Public: Checks the validity of the current token. Note that this *does
# not* check the authenticity of the token.
#
# Examples
#
# auth.haveValidToken() # => Returns true if valid.
#
# Returns true if the token is valid.
haveValidToken
:
()
->
allFields
=
@
_unsafeToken
&&
@
_unsafeToken
.
issuedAt
&&
@
_unsafeToken
.
ttl
&&
@
_unsafeToken
.
consumerKey
if
allFields
&&
this
.
timeToExpiry
()
>
0
return
true
else
return
false
# Public: Calculates the time in seconds until the current token expires.
#
# Returns Number of seconds until token expires.
timeToExpiry
:
->
now
=
new
Date
().
getTime
()
/
1000
issue
=
createDateFromISO8601
(
@
_unsafeToken
.
issuedAt
).
getTime
()
/
1000
expiry
=
issue
+
@
_unsafeToken
.
ttl
timeToExpiry
=
expiry
-
now
if
(
timeToExpiry
>
0
)
then
timeToExpiry
else
0
# Public: Updates the headers to be sent with the Store requests. This is
# achieved by updating the 'annotator:headers' key in the @element.data()
# store.
#
# Returns nothing.
updateHeaders
:
->
current
=
@
element
.
data
(
'annotator:headers'
)
@
element
.
data
(
'annotator:headers'
,
$
.
extend
(
current
,
{
'x-annotator-auth-token'
:
@
token
,
}))
# Runs the provided callback if a valid token is available. Otherwise requests
# a token until it recieves a valid one.
#
# callback - A callback function to call once a valid token is obtained.
#
# Examples
#
# auth.withToken ->
# store.loadAnnotations()
#
# Returns nothing.
withToken
:
(
callback
)
->
if
not
callback
?
return
if
this
.
haveValidToken
()
callback
(
@
_unsafeToken
)
else
this
.
waitingForToken
.
push
(
callback
)
if
not
@
requestInProgress
this
.
requestToken
()
h/static/scripts/annotator/plugin/document.coffee
deleted
100644 → 0
View file @
16f4d24a
class
Annotator
.
Plugin
.
Document
extends
Annotator
.
Plugin
$
=
Annotator
.
$
events
:
'beforeAnnotationCreated'
:
'beforeAnnotationCreated'
pluginInit
:
->
this
.
getDocumentMetadata
()
# returns the primary URI for the document being annotated
uri
:
=>
uri
=
decodeURIComponent
document
.
location
.
href
for
link
in
@
metadata
.
link
if
link
.
rel
==
"canonical"
uri
=
link
.
href
return
uri
# returns all uris for the document being annotated
uris
:
=>
uniqueUrls
=
{}
for
link
in
@
metadata
.
link
uniqueUrls
[
link
.
href
]
=
true
if
link
.
href
return
(
href
for
href
of
uniqueUrls
)
beforeAnnotationCreated
:
(
annotation
)
=>
annotation
.
document
=
@
metadata
getDocumentMetadata
:
=>
@
metadata
=
{}
# first look for some common metadata types
# TODO: look for microdata/rdfa?
this
.
_getHighwire
()
this
.
_getDublinCore
()
this
.
_getFacebook
()
this
.
_getEprints
()
this
.
_getPrism
()
this
.
_getTwitter
()
this
.
_getFavicon
()
this
.
_getDocOwners
()
# extract out/normalize some things
this
.
_getTitle
()
this
.
_getLinks
()
return
@
metadata
_getHighwire
:
=>
return
@
metadata
.
highwire
=
this
.
_getMetaTags
(
"citation"
,
"name"
,
"_"
)
_getFacebook
:
=>
return
@
metadata
.
facebook
=
this
.
_getMetaTags
(
"og"
,
"property"
,
":"
)
_getTwitter
:
=>
return
@
metadata
.
twitter
=
this
.
_getMetaTags
(
"twitter"
,
"name"
,
":"
)
_getDublinCore
:
=>
return
@
metadata
.
dc
=
this
.
_getMetaTags
(
"dc"
,
"name"
,
"."
)
_getPrism
:
=>
return
@
metadata
.
prism
=
this
.
_getMetaTags
(
"prism"
,
"name"
,
"."
)
_getEprints
:
=>
return
@
metadata
.
eprints
=
this
.
_getMetaTags
(
"eprints"
,
"name"
,
"."
)
_getMetaTags
:
(
prefix
,
attribute
,
delimiter
)
=>
tags
=
{}
for
meta
in
$
(
"meta"
)
name
=
$
(
meta
).
attr
(
attribute
)
content
=
$
(
meta
).
prop
(
"content"
)
if
name
match
=
name
.
match
(
RegExp
(
"^
#{
prefix
}#{
delimiter
}
(.+)$"
,
"i"
))
if
match
n
=
match
[
1
]
if
tags
[
n
]
tags
[
n
].
push
(
content
)
else
tags
[
n
]
=
[
content
]
return
tags
_getTitle
:
=>
if
@
metadata
.
highwire
.
title
@
metadata
.
title
=
@
metadata
.
highwire
.
title
[
0
]
else
if
@
metadata
.
eprints
.
title
@
metadata
.
title
=
@
metadata
.
eprints
.
title
else
if
@
metadata
.
prism
.
title
@
metadata
.
title
=
@
metadata
.
prism
.
title
else
if
@
metadata
.
facebook
.
title
@
metadata
.
title
=
@
metadata
.
facebook
.
title
else
if
@
metadata
.
twitter
.
title
@
metadata
.
title
=
@
metadata
.
twitter
.
title
else
if
@
metadata
.
dc
.
title
@
metadata
.
title
=
@
metadata
.
dc
.
title
else
@
metadata
.
title
=
$
(
"head title"
).
text
()
_getLinks
:
=>
# we know our current location is a link for the document
@
metadata
.
link
=
[
href
:
document
.
location
.
href
]
# look for some relevant link relations
for
link
in
$
(
"link"
)
l
=
$
(
link
)
href
=
this
.
_absoluteUrl
(
l
.
prop
(
'href'
))
# get absolute url
rel
=
l
.
prop
(
'rel'
)
type
=
l
.
prop
(
'type'
)
relTypes
=
[
"alternate"
,
"canonical"
,
"bookmark"
,
"shortlink"
]
dropTypes
=
[
"application/rss+xml"
,
"application/atom+xml"
]
if
rel
in
relTypes
and
type
not
in
dropTypes
@
metadata
.
link
.
push
(
href
:
href
,
rel
:
rel
,
type
:
type
)
# look for links in scholar metadata
for
name
,
values
of
@
metadata
.
highwire
if
name
==
"pdf_url"
for
url
in
values
@
metadata
.
link
.
push
href
:
this
.
_absoluteUrl
(
url
)
type
:
"application/pdf"
# kind of a hack to express DOI identifiers as links but it's a
# convenient place to look them up later, and somewhat sane since
# they don't have a type
if
name
==
"doi"
for
doi
in
values
if
doi
[
0
..
3
]
!=
"doi:"
doi
=
"doi:"
+
doi
@
metadata
.
link
.
push
(
href
:
doi
)
# look for links in dublincore data
for
name
,
values
of
@
metadata
.
dc
if
name
==
"identifier"
for
id
in
values
if
id
[
0
..
3
]
==
"doi:"
@
metadata
.
link
.
push
(
href
:
id
)
_getFavicon
:
=>
for
link
in
$
(
"link"
)
if
$
(
link
).
prop
(
"rel"
)
in
[
"shortcut icon"
,
"icon"
]
@
metadata
[
"favicon"
]
=
this
.
_absoluteUrl
(
link
.
href
)
_getDocOwners
:
=>
@
metadata
.
reply_to
=
[]
for
a
in
$
(
"a"
)
if
a
.
rel
is
'reply-to'
if
a
.
href
.
toLowerCase
().
slice
(
0
,
7
)
is
"mailto:"
@
metadata
.
reply_to
.
push
a
.
href
[
7
..]
else
@
metadata
.
reply_to
.
push
a
.
href
# hack to get a absolute url from a possibly relative one
_absoluteUrl
:
(
url
)
->
d
=
document
.
createElement
(
'a'
)
d
.
href
=
url
d
.
href
h/static/scripts/annotator/range.coffee
deleted
100644 → 0
View file @
16f4d24a
Range
=
{}
# Public: Determines the type of Range of the provided object and returns
# a suitable Range instance.
#
# r - A range Object.
#
# Examples
#
# selection = window.getSelection()
# Range.sniff(selection.getRangeAt(0))
# # => Returns a BrowserRange instance.
#
# Returns a Range object or false.
Range
.
sniff
=
(
r
)
->
if
r
.
commonAncestorContainer
?
new
Range
.
BrowserRange
(
r
)
else
if
typeof
r
.
start
is
"string"
new
Range
.
SerializedRange
(
r
)
else
if
r
.
start
and
typeof
r
.
start
is
"object"
new
Range
.
NormalizedRange
(
r
)
else
console
.
error
(
_t
(
"Could not sniff range type"
))
false
# Public: Finds an Element Node using an XPath relative to the document root.
#
# If the document is served as application/xhtml+xml it will try and resolve
# any namespaces within the XPath.
#
# xpath - An XPath String to query.
#
# Examples
#
# node = Range.nodeFromXPath('/html/body/div/p[2]')
# if node
# # Do something with the node.
#
# Returns the Node if found otherwise null.
Range
.
nodeFromXPath
=
(
xpath
,
root
=
document
)
->
evaluateXPath
=
(
xp
,
nsResolver
=
null
)
->
try
document
.
evaluate
(
'.'
+
xp
,
root
,
nsResolver
,
XPathResult
.
FIRST_ORDERED_NODE_TYPE
,
null
).
singleNodeValue
catch
exception
# There are cases when the evaluation fails, because the
# HTML documents contains nodes with invalid names,
# for example tags with equal signs in them, or something like that.
# In these cases, the XPath expressions will have these abominations,
# too, and then they can not be evaluated.
# In these cases, we get an XPathException, with error code 52.
# See http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathException
# This does not necessarily make any sense, but this what we see
# happening.
console
.
log
"XPath evaluation failed."
console
.
log
"Trying fallback..."
# We have a an 'evaluator' for the really simple expressions that
# should work for the simple expressions we generate.
Util
.
nodeFromXPath
(
xp
,
root
)
if
not
$
.
isXMLDoc
document
.
documentElement
evaluateXPath
xpath
else
# We're in an XML document, create a namespace resolver function to try
# and resolve any namespaces in the current document.
# https://developer.mozilla.org/en/DOM/document.createNSResolver
customResolver
=
document
.
createNSResolver
(
if
document
.
ownerDocument
==
null
document
.
documentElement
else
document
.
ownerDocument
.
documentElement
)
node
=
evaluateXPath
xpath
,
customResolver
unless
node
# If the previous search failed to find a node then we must try to
# provide a custom namespace resolver to take into account the default
# namespace. We also prefix all node names with a custom xhtml namespace
# eg. 'div' => 'xhtml:div'.
xpath
=
(
for
segment
in
xpath
.
split
'/'
if
segment
and
segment
.
indexOf
(
':'
)
==
-
1
segment
.
replace
(
/^([a-z]+)/
,
'xhtml:$1'
)
else
segment
).
join
(
'/'
)
# Find the default document namespace.
namespace
=
document
.
lookupNamespaceURI
null
# Try and resolve the namespace, first seeing if it is an xhtml node
# otherwise check the head attributes.
customResolver
=
(
ns
)
->
if
ns
==
'xhtml'
then
namespace
else
document
.
documentElement
.
getAttribute
(
'xmlns:'
+
ns
)
node
=
evaluateXPath
xpath
,
customResolver
node
class
Range
.
RangeError
extends
Error
constructor
:
(
@
type
,
@
message
,
@
parent
=
null
)
->
super
(
@
message
)
# Public: Creates a wrapper around a range object obtained from a DOMSelection.
class
Range
.
BrowserRange
# Public: Creates an instance of BrowserRange.
#
# object - A range object obtained via DOMSelection#getRangeAt().
#
# Examples
#
# selection = window.getSelection()
# range = new Range.BrowserRange(selection.getRangeAt(0))
#
# Returns an instance of BrowserRange.
constructor
:
(
obj
)
->
@
commonAncestorContainer
=
obj
.
commonAncestorContainer
@
startContainer
=
obj
.
startContainer
@
startOffset
=
obj
.
startOffset
@
endContainer
=
obj
.
endContainer
@
endOffset
=
obj
.
endOffset
# Public: normalize works around the fact that browsers don't generate
# ranges/selections in a consistent manner. Some (Safari) will create
# ranges that have (say) a textNode startContainer and elementNode
# endContainer. Others (Firefox) seem to only ever generate
# textNode/textNode or elementNode/elementNode pairs.
#
# Returns an instance of Range.NormalizedRange
normalize
:
(
root
)
->
if
@
tainted
console
.
error
(
_t
(
"You may only call normalize() once on a BrowserRange!"
))
return
false
else
@
tainted
=
true
r
=
{}
# Look at the start
if
@
startContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# We are dealing with element nodes
r
.
start
=
Util
.
getFirstTextNodeNotBefore
@
startContainer
.
childNodes
[
@
startOffset
]
r
.
startOffset
=
0
else
# We are dealing with simple text nodes
r
.
start
=
@
startContainer
r
.
startOffset
=
@
startOffset
# Look at the end
if
@
endContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# Get specified node.
node
=
@
endContainer
.
childNodes
[
@
endOffset
]
if
node
?
# Does that node exist?
# Look for a text node either at the immediate beginning of node
n
=
node
while
n
?
and
(
n
.
nodeType
isnt
Node
.
TEXT_NODE
)
n
=
n
.
firstChild
if
n
?
# Did we find a text node at the start of this element?
r
.
end
=
n
r
.
endOffset
=
0
unless
r
.
end
?
# We need to find a text node in the previous node.
node
=
@
endContainer
.
childNodes
[
@
endOffset
-
1
]
r
.
end
=
Util
.
getLastTextNodeUpTo
node
r
.
endOffset
=
r
.
end
.
nodeValue
.
length
else
# We are dealing with simple text nodes
r
.
end
=
@
endContainer
r
.
endOffset
=
@
endOffset
# We have collected the initial data.
# Now let's start to slice & dice the text elements!
nr
=
{}
if
r
.
startOffset
>
0
# Do we really have to cut?
if
r
.
start
.
nodeValue
.
length
>
r
.
startOffset
# Yes. Cut.
nr
.
start
=
r
.
start
.
splitText
(
r
.
startOffset
)
else
# Avoid splitting off zero-length pieces.
nr
.
start
=
r
.
start
.
nextSibling
else
nr
.
start
=
r
.
start
# is the whole selection inside one text element ?
if
r
.
start
is
r
.
end
if
nr
.
start
.
nodeValue
.
length
>
(
r
.
endOffset
-
r
.
startOffset
)
nr
.
start
.
splitText
(
r
.
endOffset
-
r
.
startOffset
)
nr
.
end
=
nr
.
start
else
# no, the end of the selection is in a separate text element
# does the end need to be cut?
if
r
.
end
.
nodeValue
.
length
>
r
.
endOffset
r
.
end
.
splitText
(
r
.
endOffset
)
nr
.
end
=
r
.
end
# Make sure the common ancestor is an element node.
nr
.
commonAncestor
=
@
commonAncestorContainer
while
nr
.
commonAncestor
.
nodeType
isnt
Node
.
ELEMENT_NODE
nr
.
commonAncestor
=
nr
.
commonAncestor
.
parentNode
new
Range
.
NormalizedRange
(
nr
)
# Public: Creates a range suitable for storage.
#
# root - A root Element from which to anchor the serialisation.
# ignoreSelector - A selector String of elements to ignore. For example
# elements injected by the annotator.
#
# Returns an instance of SerializedRange.
serialize
:
(
root
,
ignoreSelector
)
->
this
.
normalize
(
root
).
serialize
(
root
,
ignoreSelector
)
# Public: A normalised range is most commonly used throughout the annotator.
# its the result of a deserialised SerializedRange or a BrowserRange with
# out browser inconsistencies.
class
Range
.
NormalizedRange
# Public: Creates an instance of a NormalizedRange.
#
# This is usually created by calling the .normalize() method on one of the
# other Range classes rather than manually.
#
# obj - An Object literal. Should have the following properties.
# commonAncestor: A Element that encompasses both the start and end nodes
# start: The first TextNode in the range.
# end The last TextNode in the range.
#
# Returns an instance of NormalizedRange.
constructor
:
(
obj
)
->
@
commonAncestor
=
obj
.
commonAncestor
@
start
=
obj
.
start
@
end
=
obj
.
end
# Public: For API consistency.
#
# Returns itself.
normalize
:
(
root
)
->
this
# Public: Limits the nodes within the NormalizedRange to those contained
# withing the bounds parameter. It returns an updated range with all
# properties updated. NOTE: Method returns null if all nodes fall outside
# of the bounds.
#
# bounds - An Element to limit the range to.
#
# Returns updated self or null.
limit
:
(
bounds
)
->
nodes
=
$
.
grep
this
.
textNodes
(),
(
node
)
->
node
.
parentNode
==
bounds
or
$
.
contains
(
bounds
,
node
.
parentNode
)
return
null
unless
nodes
.
length
@
start
=
nodes
[
0
]
@
end
=
nodes
[
nodes
.
length
-
1
]
startParents
=
$
(
@
start
).
parents
()
for
parent
in
$
(
@
end
).
parents
()
if
startParents
.
index
(
parent
)
!=
-
1
@
commonAncestor
=
parent
break
this
# Convert this range into an object consisting of two pairs of (xpath,
# character offset), which can be easily stored in a database.
#
# root - The root Element relative to which XPaths should be calculated
# ignoreSelector - A selector String of elements to ignore. For example
# elements injected by the annotator.
#
# Returns an instance of SerializedRange.
serialize
:
(
root
,
ignoreSelector
)
->
serialization
=
(
node
,
isEnd
)
->
if
ignoreSelector
origParent
=
$
(
node
).
parents
(
":not(
#{
ignoreSelector
}
)"
).
eq
(
0
)
else
origParent
=
$
(
node
).
parent
()
xpath
=
Util
.
xpathFromNode
(
origParent
,
root
)[
0
]
textNodes
=
Util
.
getTextNodes
(
origParent
)
# Calculate real offset as the combined length of all the
# preceding textNode siblings. We include the length of the
# node if it's the end node.
nodes
=
textNodes
.
slice
(
0
,
textNodes
.
index
(
node
))
offset
=
0
for
n
in
nodes
offset
+=
n
.
nodeValue
.
length
if
isEnd
then
[
xpath
,
offset
+
node
.
nodeValue
.
length
]
else
[
xpath
,
offset
]
start
=
serialization
(
@
start
)
end
=
serialization
(
@
end
,
true
)
new
Range
.
SerializedRange
({
# XPath strings
start
:
start
[
0
]
end
:
end
[
0
]
# Character offsets (integer)
startOffset
:
start
[
1
]
endOffset
:
end
[
1
]
})
# Public: Creates a concatenated String of the contents of all the text nodes
# within the range.
#
# Returns a String.
text
:
->
(
for
node
in
this
.
textNodes
()
node
.
nodeValue
).
join
''
# Public: Fetches only the text nodes within th range.
#
# Returns an Array of TextNode instances.
textNodes
:
->
textNodes
=
Util
.
getTextNodes
(
$
(
this
.
commonAncestor
))
[
start
,
end
]
=
[
textNodes
.
index
(
this
.
start
),
textNodes
.
index
(
this
.
end
)]
# Return the textNodes that fall between the start and end indexes.
$
.
makeArray
textNodes
[
start
..
end
]
# Public: Converts the Normalized range to a native browser range.
#
# See: https://developer.mozilla.org/en/DOM/range
#
# Examples
#
# selection = window.getSelection()
# selection.removeAllRanges()
# selection.addRange(normedRange.toRange())
#
# Returns a Range object.
toRange
:
->
range
=
document
.
createRange
()
range
.
setStartBefore
(
@
start
)
range
.
setEndAfter
(
@
end
)
range
# Public: A range suitable for storing in local storage or serializing to JSON.
class
Range
.
SerializedRange
# Public: Creates a SerializedRange
#
# obj - The stored object. It should have the following properties.
# start: An xpath to the Element containing the first TextNode
# relative to the root Element.
# startOffset: The offset to the start of the selection from obj.start.
# end: An xpath to the Element containing the last TextNode
# relative to the root Element.
# startOffset: The offset to the end of the selection from obj.end.
#
# Returns an instance of SerializedRange
constructor
:
(
obj
)
->
@
start
=
obj
.
start
@
startOffset
=
obj
.
startOffset
@
end
=
obj
.
end
@
endOffset
=
obj
.
endOffset
# Public: Creates a NormalizedRange.
#
# root - The root Element from which the XPaths were generated.
#
# Returns a NormalizedRange instance.
normalize
:
(
root
)
->
range
=
{}
for
p
in
[
'start'
,
'end'
]
try
node
=
Range
.
nodeFromXPath
(
this
[
p
],
root
)
catch
e
throw
new
Range
.
RangeError
(
p
,
"Error while finding
#{
p
}
node:
#{
this
[
p
]
}
: "
+
e
,
e
)
if
not
node
throw
new
Range
.
RangeError
(
p
,
"Couldn't find
#{
p
}
node:
#{
this
[
p
]
}
"
)
# Unfortunately, we *can't* guarantee only one textNode per
# elementNode, so we have to walk along the element's textNodes until
# the combined length of the textNodes to that point exceeds or
# matches the value of the offset.
length
=
0
targetOffset
=
this
[
p
+
'Offset'
]
# Range excludes its endpoint because it describes the boundary position.
# Target the string index of the last character inside the range.
if
p
is
'end'
then
targetOffset
--
for
tn
in
Util
.
getTextNodes
(
$
(
node
))
if
(
length
+
tn
.
nodeValue
.
length
>
targetOffset
)
range
[
p
+
'Container'
]
=
tn
range
[
p
+
'Offset'
]
=
this
[
p
+
'Offset'
]
-
length
break
else
length
+=
tn
.
nodeValue
.
length
# If we fall off the end of the for loop without having set
# 'startOffset'/'endOffset', the element has shorter content than when
# we annotated, so throw an error:
if
not
range
[
p
+
'Offset'
]
?
throw
new
Range
.
RangeError
(
"
#{
p
}
offset"
,
"Couldn't find offset
#{
this
[
p
+
'Offset'
]
}
in element
#{
this
[
p
]
}
"
)
# Here's an elegant next step...
#
# range.commonAncestorContainer = $(range.startContainer).parents().has(range.endContainer)[0]
#
# ...but unfortunately Node.contains() is broken in Safari 5.1.5 (7534.55.3)
# and presumably other earlier versions of WebKit. In particular, in a
# document like
#
# <p>Hello</p>
#
# the code
#
# p = document.getElementsByTagName('p')[0]
# p.contains(p.firstChild)
#
# returns `false`. Yay.
#
# So instead, we step through the parents from the bottom up and use
# Node.compareDocumentPosition() to decide when to set the
# commonAncestorContainer and bail out.
contains
=
if
not
document
.
compareDocumentPosition
?
# IE
(
a
,
b
)
->
a
.
contains
(
b
)
else
# Everyone else
(
a
,
b
)
->
a
.
compareDocumentPosition
(
b
)
&
16
$
(
range
.
startContainer
).
parents
().
each
->
if
contains
(
this
,
range
.
endContainer
)
range
.
commonAncestorContainer
=
this
return
false
new
Range
.
BrowserRange
(
range
).
normalize
(
root
)
# Public: Creates a range suitable for storage.
#
# root - A root Element from which to anchor the serialisation.
# ignoreSelector - A selector String of elements to ignore. For example
# elements injected by the annotator.
#
# Returns an instance of SerializedRange.
serialize
:
(
root
,
ignoreSelector
)
->
this
.
normalize
(
root
).
serialize
(
root
,
ignoreSelector
)
# Public: Returns the range as an Object literal.
toObject
:
->
{
start
:
@
start
startOffset
:
@
startOffset
end
:
@
end
endOffset
:
@
endOffset
}
h/static/scripts/annotator/range_monkey.coffee
deleted
100644 → 0
View file @
16f4d24a
Range
.
BrowserRange
.
prototype
.
normalize
=
(
root
)
->
if
@
tainted
console
.
error
(
_t
(
"You may only call normalize() once on a BrowserRange!"
))
return
false
else
@
tainted
=
true
r
=
{}
# Look at the start
if
@
startContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# We are dealing with element nodes
r
.
start
=
Util
.
getFirstTextNodeNotBefore
@
startContainer
.
childNodes
[
@
startOffset
]
r
.
startOffset
=
0
else
# We are dealing with simple text nodes
r
.
start
=
@
startContainer
r
.
startOffset
=
@
startOffset
# Look at the end
if
@
endContainer
.
nodeType
is
Node
.
ELEMENT_NODE
# Get specified node.
node
=
@
endContainer
.
childNodes
[
@
endOffset
]
if
node
?
# Does that node exist?
# Look for a text node either at the immediate beginning of node
n
=
node
while
n
?
and
(
n
.
nodeType
isnt
Node
.
TEXT_NODE
)
n
=
n
.
firstChild
if
n
?
# Did we find a text node at the start of this element?
# Check the previous sibling
prev
=
n
.
previousSibling
if
prev
?
and
(
prev
.
nodeType
is
Node
.
TEXT_NODE
)
# We have another text righ before us. Use that instead.
r
.
end
=
prev
r
.
endOffset
=
prev
.
nodeValue
.
length
else
# No, we need to stick to this node.
r
.
end
=
n
r
.
endOffset
=
0
unless
r
.
end
?
# We need to find a text node in the previous sibling of the node at the
# given offset, if one exists, or in the previous sibling of its container.
if
@
endOffset
node
=
@
endContainer
.
childNodes
[
@
endOffset
-
1
]
else
node
=
@
endContainer
.
previousSibling
r
.
end
=
Util
.
getLastTextNodeUpTo
node
r
.
endOffset
=
r
.
end
.
nodeValue
.
length
else
# We are dealing with simple text nodes
r
.
end
=
@
endContainer
r
.
endOffset
=
@
endOffset
# We have collected the initial data.
# Now let's start to slice & dice the text elements!
nr
=
{}
changed
=
false
if
r
.
startOffset
>
0
# Do we really have to cut?
if
r
.
start
.
nodeValue
.
length
>
r
.
startOffset
# Yes. Cut.
nr
.
start
=
r
.
start
.
splitText
(
r
.
startOffset
)
changed
=
true
else
# Avoid splitting off zero-length pieces.
nr
.
start
=
r
.
start
.
nextSibling
else
nr
.
start
=
r
.
start
# is the whole selection inside one text element ?
if
r
.
start
is
r
.
end
if
nr
.
start
.
nodeValue
.
length
>
(
r
.
endOffset
-
r
.
startOffset
)
nr
.
start
.
splitText
(
r
.
endOffset
-
r
.
startOffset
)
changed
=
true
nr
.
end
=
nr
.
start
else
# no, the end of the selection is in a separate text element
# does the end need to be cut?
if
r
.
end
.
nodeValue
.
length
>
r
.
endOffset
r
.
end
.
splitText
(
r
.
endOffset
)
changed
=
true
nr
.
end
=
r
.
end
# Make sure the common ancestor is an element node.
nr
.
commonAncestor
=
@
commonAncestorContainer
while
nr
.
commonAncestor
.
nodeType
isnt
Node
.
ELEMENT_NODE
nr
.
commonAncestor
=
nr
.
commonAncestor
.
parentNode
if
changed
event
=
document
.
createEvent
"UIEvents"
event
.
initUIEvent
"domChange"
,
true
,
false
,
window
,
0
event
.
reason
=
"range normalization"
event
.
data
=
nr
nr
.
commonAncestor
.
dispatchEvent
event
new
Range
.
NormalizedRange
(
nr
)
h/static/scripts/annotator/util.coffee
deleted
100644 → 0
View file @
16f4d24a
# I18N
gettext
=
null
if
Gettext
?
_gettext
=
new
Gettext
(
domain
:
"annotator"
)
gettext
=
(
msgid
)
->
_gettext
.
gettext
(
msgid
)
else
gettext
=
(
msgid
)
->
msgid
_t
=
(
msgid
)
->
gettext
(
msgid
)
unless
jQuery
?
.
fn
?
.
jquery
console
.
error
(
_t
(
"Annotator requires jQuery: have you included lib/vendor/jquery.js?"
))
unless
JSON
and
JSON
.
parse
and
JSON
.
stringify
console
.
error
(
_t
(
"Annotator requires a JSON implementation: have you included lib/vendor/json2.js?"
))
$
=
jQuery
Util
=
{}
# Public: Flatten a nested array structure
#
# Returns an array
Util
.
flatten
=
(
array
)
->
flatten
=
(
ary
)
->
flat
=
[]
for
el
in
ary
flat
=
flat
.
concat
(
if
el
and
$
.
isArray
(
el
)
then
flatten
(
el
)
else
el
)
return
flat
flatten
(
array
)
# Public: decides whether node A is an ancestor of node B.
#
# This function purposefully ignores the native browser function for this,
# because it acts weird in PhantomJS.
# Issue: https://github.com/ariya/phantomjs/issues/11479
Util
.
contains
=
(
parent
,
child
)
->
node
=
child
while
node
?
if
node
is
parent
then
return
true
node
=
node
.
parentNode
return
false
# Public: Finds all text nodes within the elements in the current collection.
#
# Returns a new jQuery collection of text nodes.
Util
.
getTextNodes
=
(
jq
)
->
getTextNodes
=
(
node
)
->
if
node
and
node
.
nodeType
!=
Node
.
TEXT_NODE
nodes
=
[]
# If not a comment then traverse children collecting text nodes.
# We traverse the child nodes manually rather than using the .childNodes
# property because IE9 does not update the .childNodes property after
# .splitText() is called on a child text node.
if
node
.
nodeType
!=
Node
.
COMMENT_NODE
# Start at the last child and walk backwards through siblings.
node
=
node
.
lastChild
while
node
nodes
.
push
getTextNodes
(
node
)
node
=
node
.
previousSibling
# Finally reverse the array so that nodes are in the correct order.
return
nodes
.
reverse
()
else
return
node
jq
.
map
->
Util
.
flatten
(
getTextNodes
(
this
))
# Public: determine the last text node inside or before the given node
Util
.
getLastTextNodeUpTo
=
(
n
)
->
switch
n
.
nodeType
when
Node
.
TEXT_NODE
return
n
# We have found our text node.
when
Node
.
ELEMENT_NODE
# This is an element, we need to dig in
if
n
.
lastChild
?
# Does it have children at all?
result
=
Util
.
getLastTextNodeUpTo
n
.
lastChild
if
result
?
then
return
result
else
# Not a text node, and not an element node.
# Could not find a text node in current node, go backwards
n
=
n
.
previousSibling
if
n
?
Util
.
getLastTextNodeUpTo
n
else
null
# Public: determine the first text node in or after the given jQuery node.
Util
.
getFirstTextNodeNotBefore
=
(
n
)
->
switch
n
.
nodeType
when
Node
.
TEXT_NODE
return
n
# We have found our text node.
when
Node
.
ELEMENT_NODE
# This is an element, we need to dig in
if
n
.
firstChild
?
# Does it have children at all?
result
=
Util
.
getFirstTextNodeNotBefore
n
.
firstChild
if
result
?
then
return
result
else
# Not a text or an element node.
# Could not find a text node in current node, go forward
n
=
n
.
nextSibling
if
n
?
Util
.
getFirstTextNodeNotBefore
n
else
null
# Public: read out the text value of a range using the selection API
#
# This method selects the specified range, and asks for the string
# value of the selection. What this returns is very close to what the user
# actually sees.
Util
.
readRangeViaSelection
=
(
range
)
->
sel
=
Util
.
getGlobal
().
getSelection
()
# Get the browser selection object
sel
.
removeAllRanges
()
# clear the selection
sel
.
addRange
range
.
toRange
()
# Select the range
sel
.
toString
()
# Read out the selection
Util
.
xpathFromNode
=
(
el
,
relativeRoot
)
->
try
result
=
simpleXPathJQuery
.
call
el
,
relativeRoot
catch
exception
console
.
log
"jQuery-based XPath construction failed! Falling back to manual."
result
=
simpleXPathPure
.
call
el
,
relativeRoot
result
Util
.
nodeFromXPath
=
(
xp
,
root
)
->
steps
=
xp
.
substring
(
1
).
split
(
"/"
)
node
=
root
for
step
in
steps
[
name
,
idx
]
=
step
.
split
"["
idx
=
if
idx
?
then
parseInt
(
idx
?
.
split
"]"
)[
0
]
else
1
node
=
findChild
node
,
name
.
toLowerCase
(),
idx
node
Util
.
escape
=
(
html
)
->
html
.
replace
(
/&(?!\w+;)/g
,
'&'
)
.
replace
(
/</g
,
'<'
)
.
replace
(
/>/g
,
'>'
)
.
replace
(
/"/g
,
'"'
)
Util
.
uuid
=
(
->
counter
=
0
;
->
counter
++
)()
Util
.
getGlobal
=
->
(
->
this
)()
# Return the maximum z-index of any element in $elements (a jQuery collection).
Util
.
maxZIndex
=
(
$elements
)
->
all
=
for
el
in
$elements
if
$
(
el
).
css
(
'position'
)
==
'static'
-
1
else
# Use parseFloat since we may get scientific notation for large
# values.
parseFloat
(
$
(
el
).
css
(
'z-index'
))
or
-
1
Math
.
max
.
apply
(
Math
,
all
)
Util
.
mousePosition
=
(
e
,
offsetEl
)
->
# If the offset element is not a positioning root use its offset parent
unless
$
(
offsetEl
).
css
(
'position'
)
in
[
'absolute'
,
'fixed'
,
'relative'
]
offsetEl
=
$
(
offsetEl
).
offsetParent
()[
0
]
offset
=
$
(
offsetEl
).
offset
()
{
top
:
e
.
pageY
-
offset
.
top
,
left
:
e
.
pageX
-
offset
.
left
}
# Checks to see if an event parameter is provided and contains the prevent
# default method. If it does it calls it.
#
# This is useful for methods that can be optionally used as callbacks
# where the existance of the parameter must be checked before calling.
Util
.
preventEventDefault
=
(
event
)
->
event
?
.
preventDefault
?
()
h/static/scripts/annotator/viewer.coffee
deleted
100644 → 0
View file @
16f4d24a
# Public: Creates an element for viewing annotations.
class
Annotator
.
Viewer
extends
Annotator
.
Widget
# Events to be bound to the @element.
events
:
".annotator-edit click"
:
"onEditClick"
".annotator-delete click"
:
"onDeleteClick"
# Classes for toggling annotator state.
classes
:
hide
:
'annotator-hide'
showControls
:
'annotator-visible'
# HTML templates for @element and @item properties.
html
:
element
:
"""
<div class="annotator-outer annotator-viewer">
<ul class="annotator-widget annotator-listing"></ul>
</div>
"""
item
:
"""
<li class="annotator-annotation annotator-item">
<span class="annotator-controls">
<a href="#" title="View as webpage" class="annotator-link">View as webpage</a>
<button title="Edit" class="annotator-edit">Edit</button>
<button title="Delete" class="annotator-delete">Delete</button>
</span>
</li>
"""
# Configuration options
options
:
readOnly
:
false
# Start the viewer in read-only mode. No controls will be shown.
# Public: Creates an instance of the Viewer object. This will create the
# @element from the @html.element string and set up all events.
#
# options - An Object literal containing options.
#
# Examples
#
# # Creates a new viewer, adds a custom field and displays an annotation.
# viewer = new Annotator.Viewer()
# viewer.addField({
# load: someLoadCallback
# })
# viewer.load(annotation)
#
# Returns a new Viewer instance.
constructor
:
(
options
)
->
super
$
(
@
html
.
element
)[
0
],
options
@
item
=
$
(
@
html
.
item
)[
0
]
@
fields
=
[]
@
annotations
=
[]
# Public: Displays the Viewer and first the "show" event. Can be used as an
# event callback and will call Event#preventDefault() on the supplied event.
#
# event - Event object provided if method is called by event
# listener (default:undefined)
#
# Examples
#
# # Displays the editor.
# viewer.show()
#
# # Displays the viewer on click (prevents default action).
# $('a.show-viewer').bind('click', viewer.show)
#
# Returns itself.
show
:
(
event
)
=>
Annotator
.
Util
.
preventEventDefault
event
controls
=
@
element
.
find
(
'.annotator-controls'
)
.
addClass
(
@
classes
.
showControls
)
setTimeout
((
=>
controls
.
removeClass
(
@
classes
.
showControls
)),
500
)
@
element
.
removeClass
(
@
classes
.
hide
)
this
.
checkOrientation
().
publish
(
'show'
)
# Public: Checks to see if the Viewer is currently displayed.
#
# Examples
#
# viewer.show()
# viewer.isShown() # => Returns true
#
# viewer.hide()
# viewer.isShown() # => Returns false
#
# Returns true if the Viewer is visible.
isShown
:
->
not
@
element
.
hasClass
(
@
classes
.
hide
)
# Public: Hides the Editor and fires the "hide" event. Can be used as an event
# callback and will call Event#preventDefault() on the supplied event.
#
# event - Event object provided if method is called by event
# listener (default:undefined)
#
# Examples
#
# # Hides the editor.
# viewer.hide()
#
# # Hide the viewer on click (prevents default action).
# $('a.hide-viewer').bind('click', viewer.hide)
#
# Returns itself.
hide
:
(
event
)
=>
Annotator
.
Util
.
preventEventDefault
event
@
element
.
addClass
(
@
classes
.
hide
)
this
.
publish
(
'hide'
)
# Public: Loads annotations into the viewer and shows it. Fires the "load"
# event once the viewer is loaded passing the annotations into the callback.
#
# annotation - An Array of annotation elements.
#
# Examples
#
# viewer.load([annotation1, annotation2, annotation3])
#
# Returns itslef.
load
:
(
annotations
)
=>
@
annotations
=
annotations
||
[]
list
=
@
element
.
find
(
'ul:first'
).
empty
()
for
annotation
in
@
annotations
item
=
$
(
@
item
).
clone
().
appendTo
(
list
).
data
(
'annotation'
,
annotation
)
controls
=
item
.
find
(
'.annotator-controls'
)
link
=
controls
.
find
(
'.annotator-link'
)
edit
=
controls
.
find
(
'.annotator-edit'
)
del
=
controls
.
find
(
'.annotator-delete'
)
links
=
new
LinkParser
(
annotation
.
links
or
[]).
get
(
'alternate'
,
{
'type'
:
'text/html'
})
if
links
.
length
is
0
or
not
links
[
0
].
href
?
link
.
remove
()
else
link
.
attr
(
'href'
,
links
[
0
].
href
)
if
@
options
.
readOnly
edit
.
remove
()
del
.
remove
()
else
controller
=
{
showEdit
:
->
edit
.
removeAttr
(
'disabled'
)
hideEdit
:
->
edit
.
attr
(
'disabled'
,
'disabled'
)
showDelete
:
->
del
.
removeAttr
(
'disabled'
)
hideDelete
:
->
del
.
attr
(
'disabled'
,
'disabled'
)
}
for
field
in
@
fields
element
=
$
(
field
.
element
).
clone
().
appendTo
(
item
)[
0
]
field
.
load
(
element
,
annotation
,
controller
)
this
.
publish
(
'load'
,
[
@
annotations
])
this
.
show
()
# Public: Adds an addional field to an annotation view. A callback can be
# provided to update the view on load.
#
# options - An options Object. Options are as follows:
# load - Callback Function called when the view is loaded with an
# annotation. Recieves a newly created clone of @item and
# the annotation to be displayed (it will be called once
# for each annotation being loaded).
#
# Examples
#
# # Display a user name.
# viewer.addField({
# # This is called when the viewer is loaded.
# load: (field, annotation) ->
# field = $(field)
#
# if annotation.user
# field.text(annotation.user) # Display the user
# else
# field.remove() # Do not display the field.
# })
#
# Returns itself.
addField
:
(
options
)
->
field
=
$
.
extend
({
load
:
->
},
options
)
field
.
element
=
$
(
'<div />'
)[
0
]
@
fields
.
push
field
field
.
element
this
# Callback function: called when the edit button is clicked.
#
# event - An Event object.
#
# Returns nothing.
onEditClick
:
(
event
)
=>
this
.
onButtonClick
(
event
,
'edit'
)
# Callback function: called when the delete button is clicked.
#
# event - An Event object.
#
# Returns nothing.
onDeleteClick
:
(
event
)
=>
this
.
onButtonClick
(
event
,
'delete'
)
# Fires an event of type and passes in the associated annotation.
#
# event - An Event object.
# type - The type of event to fire. Either "edit" or "delete".
#
# Returns nothing.
onButtonClick
:
(
event
,
type
)
->
item
=
$
(
event
.
target
).
parents
(
'.annotator-annotation'
)
this
.
publish
(
type
,
[
item
.
data
(
'annotation'
)])
# Private: simple parser for hypermedia link structure
#
# Examples:
#
# links = [
# { rel: 'alternate', href: 'http://example.com/pages/14.json', type: 'application/json' },
# { rel: 'prev': href: 'http://example.com/pages/13' }
# ]
#
# lp = LinkParser(links)
# lp.get('alternate') # => [ { rel: 'alternate', href: 'http://...', ... } ]
# lp.get('alternate', {type: 'text/html'}) # => []
#
class
LinkParser
constructor
:
(
@
data
)
->
get
:
(
rel
,
cond
=
{})
->
cond
=
$
.
extend
({},
cond
,
{
rel
:
rel
})
keys
=
(
k
for
own
k
,
v
of
cond
)
for
d
in
@
data
match
=
keys
.
reduce
((
m
,
k
)
->
m
and
(
d
[
k
]
is
cond
[
k
])),
true
if
match
d
else
continue
h/static/scripts/annotator/widget.coffee
deleted
100644 → 0
View file @
16f4d24a
# Public: Base class for the Editor and Viewer elements. Contains methods that
# are shared between the two.
class
Annotator
.
Widget
extends
Delegator
# Classes used to alter the widgets state.
classes
:
hide
:
'annotator-hide'
invert
:
x
:
'annotator-invert-x'
y
:
'annotator-invert-y'
# Public: Creates a new Widget instance.
#
# element - The Element that represents the widget in the DOM.
# options - An Object literal of options.
#
# Examples
#
# element = document.createElement('div')
# widget = new Annotator.Widget(element)
#
# Returns a new Widget instance.
constructor
:
(
element
,
options
)
->
super
@
classes
=
$
.
extend
{},
Annotator
.
Widget
.
prototype
.
classes
,
@
classes
# Public: Unbind the widget's events and remove its element from the DOM.
#
# Returns nothing.
destroy
:
->
this
.
removeEvents
()
@
element
.
remove
()
checkOrientation
:
->
this
.
resetOrientation
()
window
=
$
(
Annotator
.
Util
.
getGlobal
())
widget
=
@
element
.
children
(
":first"
)
offset
=
widget
.
offset
()
viewport
=
{
top
:
window
.
scrollTop
(),
right
:
window
.
width
()
+
window
.
scrollLeft
()
}
current
=
{
top
:
offset
.
top
right
:
offset
.
left
+
widget
.
width
()
}
if
(
current
.
top
-
viewport
.
top
)
<
0
this
.
invertY
()
if
(
current
.
right
-
viewport
.
right
)
>
0
this
.
invertX
()
this
# Public: Resets orientation of widget on the X & Y axis.
#
# Examples
#
# widget.resetOrientation() # Widget is original way up.
#
# Returns itself for chaining.
resetOrientation
:
->
@
element
.
removeClass
(
@
classes
.
invert
.
x
).
removeClass
(
@
classes
.
invert
.
y
)
this
# Public: Inverts the widget on the X axis.
#
# Examples
#
# widget.invertX() # Widget is now right aligned.
#
# Returns itself for chaining.
invertX
:
->
@
element
.
addClass
@
classes
.
invert
.
x
this
# Public: Inverts the widget on the Y axis.
#
# Examples
#
# widget.invertY() # Widget is now upside down.
#
# Returns itself for chaining.
invertY
:
->
@
element
.
addClass
@
classes
.
invert
.
y
this
# Public: Find out whether or not the widget is currently upside down
#
# Returns a boolean: true if the widget is upside down
isInvertedY
:
->
@
element
.
hasClass
@
classes
.
invert
.
y
# Public: Find out whether or not the widget is currently right aligned
#
# Returns a boolean: true if the widget is right aligned
isInvertedX
:
->
@
element
.
hasClass
@
classes
.
invert
.
x
h/static/scripts/annotator/xpath.coffee
deleted
100644 → 0
View file @
16f4d24a
# A simple XPath evaluator using jQuery which can evaluate queries of
simpleXPathJQuery
=
(
relativeRoot
)
->
jq
=
this
.
map
->
path
=
''
elem
=
this
while
elem
?
.
nodeType
==
Node
.
ELEMENT_NODE
and
elem
isnt
relativeRoot
tagName
=
elem
.
tagName
.
replace
(
":"
,
"
\\
:"
)
idx
=
$
(
elem
.
parentNode
).
children
(
tagName
).
index
(
elem
)
+
1
idx
=
"[
#{
idx
}
]"
path
=
"/"
+
elem
.
tagName
.
toLowerCase
()
+
idx
+
path
elem
=
elem
.
parentNode
path
jq
.
get
()
# A simple XPath evaluator using only standard DOM methods which can
# evaluate queries of the form /tag[index]/tag[index].
simpleXPathPure
=
(
relativeRoot
)
->
getPathSegment
=
(
node
)
->
name
=
getNodeName
node
pos
=
getNodePosition
node
"
#{
name
}
[
#{
pos
}
]"
rootNode
=
relativeRoot
getPathTo
=
(
node
)
->
xpath
=
''
;
while
node
!=
rootNode
unless
node
?
throw
new
Error
"Called getPathTo on a node which was not a descendant of @rootNode. "
+
rootNode
xpath
=
(
getPathSegment
node
)
+
'/'
+
xpath
node
=
node
.
parentNode
xpath
=
'/'
+
xpath
xpath
=
xpath
.
replace
/\/$/
,
''
xpath
jq
=
this
.
map
->
path
=
getPathTo
this
path
jq
.
get
()
findChild
=
(
node
,
type
,
index
)
->
unless
node
.
hasChildNodes
()
throw
new
Error
"XPath error: node has no children!"
children
=
node
.
childNodes
found
=
0
for
child
in
children
name
=
getNodeName
child
if
name
is
type
found
+=
1
if
found
is
index
return
child
throw
new
Error
"XPath error: wanted child not found."
# Get the node name for use in generating an xpath expression.
getNodeName
=
(
node
)
->
nodeName
=
node
.
nodeName
.
toLowerCase
()
switch
nodeName
when
"#text"
then
return
"text()"
when
"#comment"
then
return
"comment()"
when
"#cdata-section"
then
return
"cdata-section()"
else
return
nodeName
# Get the index of the node as it appears in its parent's child list
getNodePosition
=
(
node
)
->
pos
=
0
tmp
=
node
while
tmp
if
tmp
.
nodeName
is
node
.
nodeName
pos
++
tmp
=
tmp
.
previousSibling
pos
\ No newline at end of file
h/static/scripts/vendor/annotator.auth.js
0 → 100644
View file @
a4ce9479
/*
** Annotator v1.2.9-dev-b091a74
** https://github.com/okfn/annotator/
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2015-01-19 11:35:26Z
*/
//
// Generated by CoffeeScript 1.6.3
(
function
()
{
var
base64Decode
,
base64UrlDecode
,
createDateFromISO8601
,
parseToken
,
__hasProp
=
{}.
hasOwnProperty
,
__extends
=
function
(
child
,
parent
)
{
for
(
var
key
in
parent
)
{
if
(
__hasProp
.
call
(
parent
,
key
))
child
[
key
]
=
parent
[
key
];
}
function
ctor
()
{
this
.
constructor
=
child
;
}
ctor
.
prototype
=
parent
.
prototype
;
child
.
prototype
=
new
ctor
();
child
.
__super__
=
parent
.
prototype
;
return
child
;
};
createDateFromISO8601
=
function
(
string
)
{
var
d
,
date
,
offset
,
regexp
,
time
,
_ref
;
regexp
=
"([0-9]{4})(-([0-9]{2})(-([0-9]{2})"
+
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(
\\
.([0-9]+))?)?"
+
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
;
d
=
string
.
match
(
new
RegExp
(
regexp
));
offset
=
0
;
date
=
new
Date
(
d
[
1
],
0
,
1
);
if
(
d
[
3
])
{
date
.
setMonth
(
d
[
3
]
-
1
);
}
if
(
d
[
5
])
{
date
.
setDate
(
d
[
5
]);
}
if
(
d
[
7
])
{
date
.
setHours
(
d
[
7
]);
}
if
(
d
[
8
])
{
date
.
setMinutes
(
d
[
8
]);
}
if
(
d
[
10
])
{
date
.
setSeconds
(
d
[
10
]);
}
if
(
d
[
12
])
{
date
.
setMilliseconds
(
Number
(
"0."
+
d
[
12
])
*
1000
);
}
if
(
d
[
14
])
{
offset
=
(
Number
(
d
[
16
])
*
60
)
+
Number
(
d
[
17
]);
offset
*=
(
_ref
=
d
[
15
]
===
'-'
)
!=
null
?
_ref
:
{
1
:
-
1
};
}
offset
-=
date
.
getTimezoneOffset
();
time
=
Number
(
date
)
+
(
offset
*
60
*
1000
);
date
.
setTime
(
Number
(
time
));
return
date
;
};
base64Decode
=
function
(
data
)
{
var
ac
,
b64
,
bits
,
dec
,
h1
,
h2
,
h3
,
h4
,
i
,
o1
,
o2
,
o3
,
tmp_arr
;
if
(
typeof
atob
!==
"undefined"
&&
atob
!==
null
)
{
return
atob
(
data
);
}
else
{
b64
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
;
i
=
0
;
ac
=
0
;
dec
=
""
;
tmp_arr
=
[];
if
(
!
data
)
{
return
data
;
}
data
+=
''
;
while
(
i
<
data
.
length
)
{
h1
=
b64
.
indexOf
(
data
.
charAt
(
i
++
));
h2
=
b64
.
indexOf
(
data
.
charAt
(
i
++
));
h3
=
b64
.
indexOf
(
data
.
charAt
(
i
++
));
h4
=
b64
.
indexOf
(
data
.
charAt
(
i
++
));
bits
=
h1
<<
18
|
h2
<<
12
|
h3
<<
6
|
h4
;
o1
=
bits
>>
16
&
0xff
;
o2
=
bits
>>
8
&
0xff
;
o3
=
bits
&
0xff
;
if
(
h3
===
64
)
{
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
);
}
else
if
(
h4
===
64
)
{
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
,
o2
);
}
else
{
tmp_arr
[
ac
++
]
=
String
.
fromCharCode
(
o1
,
o2
,
o3
);
}
}
return
tmp_arr
.
join
(
''
);
}
};
base64UrlDecode
=
function
(
data
)
{
var
i
,
m
,
_i
,
_ref
;
m
=
data
.
length
%
4
;
if
(
m
!==
0
)
{
for
(
i
=
_i
=
0
,
_ref
=
4
-
m
;
0
<=
_ref
?
_i
<
_ref
:
_i
>
_ref
;
i
=
0
<=
_ref
?
++
_i
:
--
_i
)
{
data
+=
'='
;
}
}
data
=
data
.
replace
(
/-/g
,
'+'
);
data
=
data
.
replace
(
/_/g
,
'/'
);
return
base64Decode
(
data
);
};
parseToken
=
function
(
token
)
{
var
head
,
payload
,
sig
,
_ref
;
_ref
=
token
.
split
(
'.'
),
head
=
_ref
[
0
],
payload
=
_ref
[
1
],
sig
=
_ref
[
2
];
return
JSON
.
parse
(
base64UrlDecode
(
payload
));
};
Annotator
.
Plugin
.
Auth
=
(
function
(
_super
)
{
__extends
(
Auth
,
_super
);
Auth
.
prototype
.
options
=
{
token
:
null
,
tokenUrl
:
'/auth/token'
,
autoFetch
:
true
};
function
Auth
(
element
,
options
)
{
Auth
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
this
.
waitingForToken
=
[];
if
(
this
.
options
.
token
)
{
this
.
setToken
(
this
.
options
.
token
);
}
else
{
this
.
requestToken
();
}
}
Auth
.
prototype
.
requestToken
=
function
()
{
var
_this
=
this
;
this
.
requestInProgress
=
true
;
return
$
.
ajax
({
url
:
this
.
options
.
tokenUrl
,
dataType
:
'text'
,
xhrFields
:
{
withCredentials
:
true
}
}).
done
(
function
(
data
,
status
,
xhr
)
{
return
_this
.
setToken
(
data
);
}).
fail
(
function
(
xhr
,
status
,
err
)
{
var
msg
;
msg
=
Annotator
.
_t
(
"Couldn't get auth token:"
);
console
.
error
(
""
+
msg
+
" "
+
err
,
xhr
);
return
Annotator
.
showNotification
(
""
+
msg
+
" "
+
xhr
.
responseText
,
Annotator
.
Notification
.
ERROR
);
}).
always
(
function
()
{
return
_this
.
requestInProgress
=
false
;
});
};
Auth
.
prototype
.
setToken
=
function
(
token
)
{
var
_results
,
_this
=
this
;
this
.
token
=
token
;
this
.
_unsafeToken
=
parseToken
(
token
);
if
(
this
.
haveValidToken
())
{
if
(
this
.
options
.
autoFetch
)
{
this
.
refreshTimeout
=
setTimeout
((
function
()
{
return
_this
.
requestToken
();
}),
(
this
.
timeToExpiry
()
-
2
)
*
1000
);
}
this
.
updateHeaders
();
_results
=
[];
while
(
this
.
waitingForToken
.
length
>
0
)
{
_results
.
push
(
this
.
waitingForToken
.
pop
()(
this
.
_unsafeToken
));
}
return
_results
;
}
else
{
console
.
warn
(
Annotator
.
_t
(
"Didn't get a valid token."
));
if
(
this
.
options
.
autoFetch
)
{
console
.
warn
(
Annotator
.
_t
(
"Getting a new token in 10s."
));
return
setTimeout
((
function
()
{
return
_this
.
requestToken
();
}),
10
*
1000
);
}
}
};
Auth
.
prototype
.
haveValidToken
=
function
()
{
var
allFields
;
allFields
=
this
.
_unsafeToken
&&
this
.
_unsafeToken
.
issuedAt
&&
this
.
_unsafeToken
.
ttl
&&
this
.
_unsafeToken
.
consumerKey
;
if
(
allFields
&&
this
.
timeToExpiry
()
>
0
)
{
return
true
;
}
else
{
return
false
;
}
};
Auth
.
prototype
.
timeToExpiry
=
function
()
{
var
expiry
,
issue
,
now
,
timeToExpiry
;
now
=
new
Date
().
getTime
()
/
1000
;
issue
=
createDateFromISO8601
(
this
.
_unsafeToken
.
issuedAt
).
getTime
()
/
1000
;
expiry
=
issue
+
this
.
_unsafeToken
.
ttl
;
timeToExpiry
=
expiry
-
now
;
if
(
timeToExpiry
>
0
)
{
return
timeToExpiry
;
}
else
{
return
0
;
}
};
Auth
.
prototype
.
updateHeaders
=
function
()
{
var
current
;
current
=
this
.
element
.
data
(
'annotator:headers'
);
return
this
.
element
.
data
(
'annotator:headers'
,
$
.
extend
(
current
,
{
'x-annotator-auth-token'
:
this
.
token
}));
};
Auth
.
prototype
.
withToken
=
function
(
callback
)
{
if
(
callback
==
null
)
{
return
;
}
if
(
this
.
haveValidToken
())
{
return
callback
(
this
.
_unsafeToken
);
}
else
{
this
.
waitingForToken
.
push
(
callback
);
if
(
!
this
.
requestInProgress
)
{
return
this
.
requestToken
();
}
}
};
return
Auth
;
})(
Annotator
.
Plugin
);
}).
call
(
this
);
//
//# sourceMappingURL=annotator.auth.map
\ No newline at end of file
h/static/scripts/vendor/annotator.document.js
0 → 100644
View file @
a4ce9479
/*
** Annotator v1.2.9-dev-b091a74
** https://github.com/okfn/annotator/
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2015-01-19 11:35:26Z
*/
//
// Generated by CoffeeScript 1.6.3
(
function
()
{
var
_ref
,
__bind
=
function
(
fn
,
me
){
return
function
(){
return
fn
.
apply
(
me
,
arguments
);
};
},
__hasProp
=
{}.
hasOwnProperty
,
__extends
=
function
(
child
,
parent
)
{
for
(
var
key
in
parent
)
{
if
(
__hasProp
.
call
(
parent
,
key
))
child
[
key
]
=
parent
[
key
];
}
function
ctor
()
{
this
.
constructor
=
child
;
}
ctor
.
prototype
=
parent
.
prototype
;
child
.
prototype
=
new
ctor
();
child
.
__super__
=
parent
.
prototype
;
return
child
;
};
Annotator
.
Plugin
.
Document
=
(
function
(
_super
)
{
var
$
;
__extends
(
Document
,
_super
);
function
Document
()
{
this
.
_getFavicon
=
__bind
(
this
.
_getFavicon
,
this
);
this
.
_getLinks
=
__bind
(
this
.
_getLinks
,
this
);
this
.
_getTitle
=
__bind
(
this
.
_getTitle
,
this
);
this
.
_getMetaTags
=
__bind
(
this
.
_getMetaTags
,
this
);
this
.
_getEprints
=
__bind
(
this
.
_getEprints
,
this
);
this
.
_getPrism
=
__bind
(
this
.
_getPrism
,
this
);
this
.
_getDublinCore
=
__bind
(
this
.
_getDublinCore
,
this
);
this
.
_getTwitter
=
__bind
(
this
.
_getTwitter
,
this
);
this
.
_getFacebook
=
__bind
(
this
.
_getFacebook
,
this
);
this
.
_getHighwire
=
__bind
(
this
.
_getHighwire
,
this
);
this
.
getDocumentMetadata
=
__bind
(
this
.
getDocumentMetadata
,
this
);
this
.
beforeAnnotationCreated
=
__bind
(
this
.
beforeAnnotationCreated
,
this
);
this
.
uris
=
__bind
(
this
.
uris
,
this
);
this
.
uri
=
__bind
(
this
.
uri
,
this
);
_ref
=
Document
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
return
_ref
;
}
$
=
Annotator
.
$
;
Document
.
prototype
.
events
=
{
'beforeAnnotationCreated'
:
'beforeAnnotationCreated'
};
Document
.
prototype
.
pluginInit
=
function
()
{
return
this
.
getDocumentMetadata
();
};
Document
.
prototype
.
uri
=
function
()
{
var
link
,
uri
,
_i
,
_len
,
_ref1
;
uri
=
decodeURIComponent
(
document
.
location
.
href
);
_ref1
=
this
.
metadata
;
for
(
_i
=
0
,
_len
=
_ref1
.
length
;
_i
<
_len
;
_i
++
)
{
link
=
_ref1
[
_i
];
if
(
link
.
rel
===
"canonical"
)
{
uri
=
link
.
href
;
}
}
return
uri
;
};
Document
.
prototype
.
uris
=
function
()
{
var
href
,
link
,
uniqueUrls
,
_i
,
_len
,
_ref1
;
uniqueUrls
=
{};
_ref1
=
this
.
metadata
.
link
;
for
(
_i
=
0
,
_len
=
_ref1
.
length
;
_i
<
_len
;
_i
++
)
{
link
=
_ref1
[
_i
];
if
(
link
.
href
)
{
uniqueUrls
[
link
.
href
]
=
true
;
}
}
return
(
function
()
{
var
_results
;
_results
=
[];
for
(
href
in
uniqueUrls
)
{
_results
.
push
(
href
);
}
return
_results
;
})();
};
Document
.
prototype
.
beforeAnnotationCreated
=
function
(
annotation
)
{
return
annotation
.
document
=
this
.
metadata
;
};
Document
.
prototype
.
getDocumentMetadata
=
function
()
{
this
.
metadata
=
{};
this
.
_getHighwire
();
this
.
_getDublinCore
();
this
.
_getFacebook
();
this
.
_getEprints
();
this
.
_getPrism
();
this
.
_getTwitter
();
this
.
_getFavicon
();
this
.
_getTitle
();
this
.
_getLinks
();
return
this
.
metadata
;
};
Document
.
prototype
.
_getHighwire
=
function
()
{
return
this
.
metadata
.
highwire
=
this
.
_getMetaTags
(
"citation"
,
"name"
,
"_"
);
};
Document
.
prototype
.
_getFacebook
=
function
()
{
return
this
.
metadata
.
facebook
=
this
.
_getMetaTags
(
"og"
,
"property"
,
":"
);
};
Document
.
prototype
.
_getTwitter
=
function
()
{
return
this
.
metadata
.
twitter
=
this
.
_getMetaTags
(
"twitter"
,
"name"
,
":"
);
};
Document
.
prototype
.
_getDublinCore
=
function
()
{
return
this
.
metadata
.
dc
=
this
.
_getMetaTags
(
"dc"
,
"name"
,
"."
);
};
Document
.
prototype
.
_getPrism
=
function
()
{
return
this
.
metadata
.
prism
=
this
.
_getMetaTags
(
"prism"
,
"name"
,
"."
);
};
Document
.
prototype
.
_getEprints
=
function
()
{
return
this
.
metadata
.
eprints
=
this
.
_getMetaTags
(
"eprints"
,
"name"
,
"."
);
};
Document
.
prototype
.
_getMetaTags
=
function
(
prefix
,
attribute
,
delimiter
)
{
var
content
,
match
,
meta
,
n
,
name
,
tags
,
_i
,
_len
,
_ref1
;
tags
=
{};
_ref1
=
$
(
"meta"
);
for
(
_i
=
0
,
_len
=
_ref1
.
length
;
_i
<
_len
;
_i
++
)
{
meta
=
_ref1
[
_i
];
name
=
$
(
meta
).
attr
(
attribute
);
content
=
$
(
meta
).
prop
(
"content"
);
if
(
name
)
{
match
=
name
.
match
(
RegExp
(
"^"
+
prefix
+
delimiter
+
"(.+)$"
,
"i"
));
if
(
match
)
{
n
=
match
[
1
];
if
(
tags
[
n
])
{
tags
[
n
].
push
(
content
);
}
else
{
tags
[
n
]
=
[
content
];
}
}
}
}
return
tags
;
};
Document
.
prototype
.
_getTitle
=
function
()
{
if
(
this
.
metadata
.
highwire
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
highwire
.
title
[
0
];
}
else
if
(
this
.
metadata
.
eprints
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
eprints
.
title
;
}
else
if
(
this
.
metadata
.
prism
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
prism
.
title
;
}
else
if
(
this
.
metadata
.
facebook
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
facebook
.
title
;
}
else
if
(
this
.
metadata
.
twitter
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
twitter
.
title
;
}
else
if
(
this
.
metadata
.
dc
.
title
)
{
return
this
.
metadata
.
title
=
this
.
metadata
.
dc
.
title
;
}
else
{
return
this
.
metadata
.
title
=
$
(
"head title"
).
text
();
}
};
Document
.
prototype
.
_getLinks
=
function
()
{
var
doi
,
href
,
id
,
l
,
lang
,
link
,
name
,
rel
,
type
,
url
,
values
,
_i
,
_j
,
_k
,
_len
,
_len1
,
_len2
,
_ref1
,
_ref2
,
_ref3
,
_results
;
this
.
metadata
.
link
=
[
{
href
:
document
.
location
.
href
}
];
_ref1
=
$
(
"link"
);
for
(
_i
=
0
,
_len
=
_ref1
.
length
;
_i
<
_len
;
_i
++
)
{
link
=
_ref1
[
_i
];
l
=
$
(
link
);
href
=
this
.
_absoluteUrl
(
l
.
prop
(
'href'
));
rel
=
l
.
prop
(
'rel'
);
type
=
l
.
prop
(
'type'
);
lang
=
l
.
prop
(
'hreflang'
);
if
(
rel
!==
"alternate"
&&
rel
!==
"canonical"
&&
rel
!==
"bookmark"
)
{
continue
;
}
if
(
rel
===
'alternate'
)
{
if
(
type
&&
type
.
match
(
/^application
\/(
rss|atom
)\+
xml/
))
{
continue
;
}
if
(
lang
)
{
continue
;
}
}
this
.
metadata
.
link
.
push
({
href
:
href
,
rel
:
rel
,
type
:
type
});
}
_ref2
=
this
.
metadata
.
highwire
;
for
(
name
in
_ref2
)
{
values
=
_ref2
[
name
];
if
(
name
===
"pdf_url"
)
{
for
(
_j
=
0
,
_len1
=
values
.
length
;
_j
<
_len1
;
_j
++
)
{
url
=
values
[
_j
];
this
.
metadata
.
link
.
push
({
href
:
this
.
_absoluteUrl
(
url
),
type
:
"application/pdf"
});
}
}
if
(
name
===
"doi"
)
{
for
(
_k
=
0
,
_len2
=
values
.
length
;
_k
<
_len2
;
_k
++
)
{
doi
=
values
[
_k
];
if
(
doi
.
slice
(
0
,
4
)
!==
"doi:"
)
{
doi
=
"doi:"
+
doi
;
}
this
.
metadata
.
link
.
push
({
href
:
doi
});
}
}
}
_ref3
=
this
.
metadata
.
dc
;
_results
=
[];
for
(
name
in
_ref3
)
{
values
=
_ref3
[
name
];
if
(
name
===
"identifier"
)
{
_results
.
push
((
function
()
{
var
_l
,
_len3
,
_results1
;
_results1
=
[];
for
(
_l
=
0
,
_len3
=
values
.
length
;
_l
<
_len3
;
_l
++
)
{
id
=
values
[
_l
];
if
(
id
.
slice
(
0
,
4
)
===
"doi:"
)
{
_results1
.
push
(
this
.
metadata
.
link
.
push
({
href
:
id
}));
}
else
{
_results1
.
push
(
void
0
);
}
}
return
_results1
;
}).
call
(
this
));
}
else
{
_results
.
push
(
void
0
);
}
}
return
_results
;
};
Document
.
prototype
.
_getFavicon
=
function
()
{
var
link
,
_i
,
_len
,
_ref1
,
_ref2
,
_results
;
_ref1
=
$
(
"link"
);
_results
=
[];
for
(
_i
=
0
,
_len
=
_ref1
.
length
;
_i
<
_len
;
_i
++
)
{
link
=
_ref1
[
_i
];
if
((
_ref2
=
$
(
link
).
prop
(
"rel"
))
===
"shortcut icon"
||
_ref2
===
"icon"
)
{
_results
.
push
(
this
.
metadata
[
"favicon"
]
=
this
.
_absoluteUrl
(
link
.
href
));
}
else
{
_results
.
push
(
void
0
);
}
}
return
_results
;
};
Document
.
prototype
.
_absoluteUrl
=
function
(
url
)
{
var
d
;
d
=
document
.
createElement
(
'a'
);
d
.
href
=
url
;
return
d
.
href
;
};
return
Document
;
})(
Annotator
.
Plugin
);
}).
call
(
this
);
//
//# sourceMappingURL=annotator.document.map
\ No newline at end of file
h/static/scripts/vendor/annotator.js
0 → 100644
View file @
a4ce9479
/*
** Annotator v1.2.9-dev-b091a74
** https://github.com/okfn/annotator/
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2015-01-19 11:35:26Z
*/
//
// Generated by CoffeeScript 1.6.3
(
function
()
{
var
$
,
Annotator
,
Delegator
,
LinkParser
,
Range
,
Util
,
findChild
,
fn
,
functions
,
g
,
getNodeName
,
getNodePosition
,
gettext
,
simpleXPathJQuery
,
simpleXPathPure
,
_Annotator
,
_gettext
,
_i
,
_j
,
_len
,
_len1
,
_ref
,
_ref1
,
_t
,
__slice
=
[].
slice
,
__hasProp
=
{}.
hasOwnProperty
,
__extends
=
function
(
child
,
parent
)
{
for
(
var
key
in
parent
)
{
if
(
__hasProp
.
call
(
parent
,
key
))
child
[
key
]
=
parent
[
key
];
}
function
ctor
()
{
this
.
constructor
=
child
;
}
ctor
.
prototype
=
parent
.
prototype
;
child
.
prototype
=
new
ctor
();
child
.
__super__
=
parent
.
prototype
;
return
child
;
},
__bind
=
function
(
fn
,
me
){
return
function
(){
return
fn
.
apply
(
me
,
arguments
);
};
};
simpleXPathJQuery
=
function
(
relativeRoot
)
{
var
jq
;
jq
=
this
.
map
(
function
()
{
var
elem
,
idx
,
path
,
tagName
;
path
=
''
;
elem
=
this
;
while
((
elem
!=
null
?
elem
.
nodeType
:
void
0
)
===
Node
.
ELEMENT_NODE
&&
elem
!==
relativeRoot
)
{
tagName
=
elem
.
tagName
.
replace
(
":"
,
"
\\
:"
);
idx
=
$
(
elem
.
parentNode
).
children
(
tagName
).
index
(
elem
)
+
1
;
idx
=
"["
+
idx
+
"]"
;
path
=
"/"
+
elem
.
tagName
.
toLowerCase
()
+
idx
+
path
;
elem
=
elem
.
parentNode
;
}
return
path
;
});
return
jq
.
get
();
};
simpleXPathPure
=
function
(
relativeRoot
)
{
var
getPathSegment
,
getPathTo
,
jq
,
rootNode
;
getPathSegment
=
function
(
node
)
{
var
name
,
pos
;
name
=
getNodeName
(
node
);
pos
=
getNodePosition
(
node
);
return
""
+
name
+
"["
+
pos
+
"]"
;
};
rootNode
=
relativeRoot
;
getPathTo
=
function
(
node
)
{
var
xpath
;
xpath
=
''
;
while
(
node
!==
rootNode
)
{
if
(
node
==
null
)
{
throw
new
Error
(
"Called getPathTo on a node which was not a descendant of @rootNode. "
+
rootNode
);
}
xpath
=
(
getPathSegment
(
node
))
+
'/'
+
xpath
;
node
=
node
.
parentNode
;
}
xpath
=
'/'
+
xpath
;
xpath
=
xpath
.
replace
(
/
\/
$/
,
''
);
return
xpath
;
};
jq
=
this
.
map
(
function
()
{
var
path
;
path
=
getPathTo
(
this
);
return
path
;
});
return
jq
.
get
();
};
findChild
=
function
(
node
,
type
,
index
)
{
var
child
,
children
,
found
,
name
,
_i
,
_len
;
if
(
!
node
.
hasChildNodes
())
{
throw
new
Error
(
"XPath error: node has no children!"
);
}
children
=
node
.
childNodes
;
found
=
0
;
for
(
_i
=
0
,
_len
=
children
.
length
;
_i
<
_len
;
_i
++
)
{
child
=
children
[
_i
];
name
=
getNodeName
(
child
);
if
(
name
===
type
)
{
found
+=
1
;
if
(
found
===
index
)
{
return
child
;
}
}
}
throw
new
Error
(
"XPath error: wanted child not found."
);
};
getNodeName
=
function
(
node
)
{
var
nodeName
;
nodeName
=
node
.
nodeName
.
toLowerCase
();
switch
(
nodeName
)
{
case
"#text"
:
return
"text()"
;
case
"#comment"
:
return
"comment()"
;
case
"#cdata-section"
:
return
"cdata-section()"
;
default
:
return
nodeName
;
}
};
getNodePosition
=
function
(
node
)
{
var
pos
,
tmp
;
pos
=
0
;
tmp
=
node
;
while
(
tmp
)
{
if
(
tmp
.
nodeName
===
node
.
nodeName
)
{
pos
++
;
}
tmp
=
tmp
.
previousSibling
;
}
return
pos
;
};
gettext
=
null
;
if
(
typeof
Gettext
!==
"undefined"
&&
Gettext
!==
null
)
{
_gettext
=
new
Gettext
({
domain
:
"annotator"
});
gettext
=
function
(
msgid
)
{
return
_gettext
.
gettext
(
msgid
);
};
}
else
{
gettext
=
function
(
msgid
)
{
return
msgid
;
};
}
_t
=
function
(
msgid
)
{
return
gettext
(
msgid
);
};
if
(
!
(
typeof
jQuery
!==
"undefined"
&&
jQuery
!==
null
?
(
_ref
=
jQuery
.
fn
)
!=
null
?
_ref
.
jquery
:
void
0
:
void
0
))
{
console
.
error
(
_t
(
"Annotator requires jQuery: have you included lib/vendor/jquery.js?"
));
}
if
(
!
(
JSON
&&
JSON
.
parse
&&
JSON
.
stringify
))
{
console
.
error
(
_t
(
"Annotator requires a JSON implementation: have you included lib/vendor/json2.js?"
));
}
$
=
jQuery
;
Util
=
{};
Util
.
flatten
=
function
(
array
)
{
var
flatten
;
flatten
=
function
(
ary
)
{
var
el
,
flat
,
_i
,
_len
;
flat
=
[];
for
(
_i
=
0
,
_len
=
ary
.
length
;
_i
<
_len
;
_i
++
)
{
el
=
ary
[
_i
];
flat
=
flat
.
concat
(
el
&&
$
.
isArray
(
el
)
?
flatten
(
el
)
:
el
);
}
return
flat
;
};
return
flatten
(
array
);
};
Util
.
contains
=
function
(
parent
,
child
)
{
var
node
;
node
=
child
;
while
(
node
!=
null
)
{
if
(
node
===
parent
)
{
return
true
;
}
node
=
node
.
parentNode
;
}
return
false
;
};
Util
.
getTextNodes
=
function
(
jq
)
{
var
getTextNodes
;
getTextNodes
=
function
(
node
)
{
var
nodes
;
if
(
node
&&
node
.
nodeType
!==
Node
.
TEXT_NODE
)
{
nodes
=
[];
if
(
node
.
nodeType
!==
Node
.
COMMENT_NODE
)
{
node
=
node
.
lastChild
;
while
(
node
)
{
nodes
.
push
(
getTextNodes
(
node
));
node
=
node
.
previousSibling
;
}
}
return
nodes
.
reverse
();
}
else
{
return
node
;
}
};
return
jq
.
map
(
function
()
{
return
Util
.
flatten
(
getTextNodes
(
this
));
});
};
Util
.
getLastTextNodeUpTo
=
function
(
n
)
{
var
result
;
switch
(
n
.
nodeType
)
{
case
Node
.
TEXT_NODE
:
return
n
;
case
Node
.
ELEMENT_NODE
:
if
(
n
.
lastChild
!=
null
)
{
result
=
Util
.
getLastTextNodeUpTo
(
n
.
lastChild
);
if
(
result
!=
null
)
{
return
result
;
}
}
break
;
}
n
=
n
.
previousSibling
;
if
(
n
!=
null
)
{
return
Util
.
getLastTextNodeUpTo
(
n
);
}
else
{
return
null
;
}
};
Util
.
getFirstTextNodeNotBefore
=
function
(
n
)
{
var
result
;
switch
(
n
.
nodeType
)
{
case
Node
.
TEXT_NODE
:
return
n
;
case
Node
.
ELEMENT_NODE
:
if
(
n
.
firstChild
!=
null
)
{
result
=
Util
.
getFirstTextNodeNotBefore
(
n
.
firstChild
);
if
(
result
!=
null
)
{
return
result
;
}
}
break
;
}
n
=
n
.
nextSibling
;
if
(
n
!=
null
)
{
return
Util
.
getFirstTextNodeNotBefore
(
n
);
}
else
{
return
null
;
}
};
Util
.
readRangeViaSelection
=
function
(
range
)
{
var
sel
;
sel
=
Util
.
getGlobal
().
getSelection
();
sel
.
removeAllRanges
();
sel
.
addRange
(
range
.
toRange
());
return
sel
.
toString
();
};
Util
.
xpathFromNode
=
function
(
el
,
relativeRoot
)
{
var
exception
,
result
;
try
{
result
=
simpleXPathJQuery
.
call
(
el
,
relativeRoot
);
}
catch
(
_error
)
{
exception
=
_error
;
console
.
log
(
"jQuery-based XPath construction failed! Falling back to manual."
);
result
=
simpleXPathPure
.
call
(
el
,
relativeRoot
);
}
return
result
;
};
Util
.
nodeFromXPath
=
function
(
xp
,
root
)
{
var
idx
,
name
,
node
,
step
,
steps
,
_i
,
_len
,
_ref1
;
steps
=
xp
.
substring
(
1
).
split
(
"/"
);
node
=
root
;
for
(
_i
=
0
,
_len
=
steps
.
length
;
_i
<
_len
;
_i
++
)
{
step
=
steps
[
_i
];
_ref1
=
step
.
split
(
"["
),
name
=
_ref1
[
0
],
idx
=
_ref1
[
1
];
idx
=
idx
!=
null
?
parseInt
((
idx
!=
null
?
idx
.
split
(
"]"
)
:
void
0
)[
0
])
:
1
;
node
=
findChild
(
node
,
name
.
toLowerCase
(),
idx
);
}
return
node
;
};
Util
.
escape
=
function
(
html
)
{
return
html
.
replace
(
/&
(?!\w
+;
)
/g
,
'&'
).
replace
(
/</g
,
'<'
).
replace
(
/>/g
,
'>'
).
replace
(
/"/g
,
'"'
);
};
Util
.
uuid
=
(
function
()
{
var
counter
;
counter
=
0
;
return
function
()
{
return
counter
++
;
};
})();
Util
.
getGlobal
=
function
()
{
return
(
function
()
{
return
this
;
})();
};
Util
.
maxZIndex
=
function
(
$elements
)
{
var
all
,
el
;
all
=
(
function
()
{
var
_i
,
_len
,
_results
;
_results
=
[];
for
(
_i
=
0
,
_len
=
$elements
.
length
;
_i
<
_len
;
_i
++
)
{
el
=
$elements
[
_i
];
if
(
$
(
el
).
css
(
'position'
)
===
'static'
)
{
_results
.
push
(
-
1
);
}
else
{
_results
.
push
(
parseFloat
(
$
(
el
).
css
(
'z-index'
))
||
-
1
);
}
}
return
_results
;
})();
return
Math
.
max
.
apply
(
Math
,
all
);
};
Util
.
mousePosition
=
function
(
e
,
offsetEl
)
{
var
offset
,
_ref1
;
if
((
_ref1
=
$
(
offsetEl
).
css
(
'position'
))
!==
'absolute'
&&
_ref1
!==
'fixed'
&&
_ref1
!==
'relative'
)
{
offsetEl
=
$
(
offsetEl
).
offsetParent
()[
0
];
}
offset
=
$
(
offsetEl
).
offset
();
return
{
top
:
e
.
pageY
-
offset
.
top
,
left
:
e
.
pageX
-
offset
.
left
};
};
Util
.
preventEventDefault
=
function
(
event
)
{
return
event
!=
null
?
typeof
event
.
preventDefault
===
"function"
?
event
.
preventDefault
()
:
void
0
:
void
0
;
};
functions
=
[
"log"
,
"debug"
,
"info"
,
"warn"
,
"exception"
,
"assert"
,
"dir"
,
"dirxml"
,
"trace"
,
"group"
,
"groupEnd"
,
"groupCollapsed"
,
"time"
,
"timeEnd"
,
"profile"
,
"profileEnd"
,
"count"
,
"clear"
,
"table"
,
"error"
,
"notifyFirebug"
,
"firebug"
,
"userObjects"
];
if
(
typeof
console
!==
"undefined"
&&
console
!==
null
)
{
if
(
console
.
group
==
null
)
{
console
.
group
=
function
(
name
)
{
return
console
.
log
(
"GROUP: "
,
name
);
};
}
if
(
console
.
groupCollapsed
==
null
)
{
console
.
groupCollapsed
=
console
.
group
;
}
for
(
_i
=
0
,
_len
=
functions
.
length
;
_i
<
_len
;
_i
++
)
{
fn
=
functions
[
_i
];
if
(
console
[
fn
]
==
null
)
{
console
[
fn
]
=
function
()
{
return
console
.
log
(
_t
(
"Not implemented:"
)
+
(
" console."
+
name
));
};
}
}
}
else
{
this
.
console
=
{};
for
(
_j
=
0
,
_len1
=
functions
.
length
;
_j
<
_len1
;
_j
++
)
{
fn
=
functions
[
_j
];
this
.
console
[
fn
]
=
function
()
{};
}
this
.
console
[
'error'
]
=
function
()
{
var
args
;
args
=
1
<=
arguments
.
length
?
__slice
.
call
(
arguments
,
0
)
:
[];
return
alert
(
"ERROR: "
+
(
args
.
join
(
', '
)));
};
this
.
console
[
'warn'
]
=
function
()
{
var
args
;
args
=
1
<=
arguments
.
length
?
__slice
.
call
(
arguments
,
0
)
:
[];
return
alert
(
"WARNING: "
+
(
args
.
join
(
', '
)));
};
}
Delegator
=
(
function
()
{
Delegator
.
prototype
.
events
=
{};
Delegator
.
prototype
.
options
=
{};
Delegator
.
prototype
.
element
=
null
;
function
Delegator
(
element
,
options
)
{
this
.
options
=
$
.
extend
(
true
,
{},
this
.
options
,
options
);
this
.
element
=
$
(
element
);
this
.
_closures
=
{};
this
.
on
=
this
.
subscribe
;
this
.
addEvents
();
}
Delegator
.
prototype
.
destroy
=
function
()
{
return
this
.
removeEvents
();
};
Delegator
.
prototype
.
addEvents
=
function
()
{
var
event
,
_k
,
_len2
,
_ref1
,
_results
;
_ref1
=
Delegator
.
_parseEvents
(
this
.
events
);
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
event
=
_ref1
[
_k
];
_results
.
push
(
this
.
_addEvent
(
event
.
selector
,
event
.
event
,
event
.
functionName
));
}
return
_results
;
};
Delegator
.
prototype
.
removeEvents
=
function
()
{
var
event
,
_k
,
_len2
,
_ref1
,
_results
;
_ref1
=
Delegator
.
_parseEvents
(
this
.
events
);
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
event
=
_ref1
[
_k
];
_results
.
push
(
this
.
_removeEvent
(
event
.
selector
,
event
.
event
,
event
.
functionName
));
}
return
_results
;
};
Delegator
.
prototype
.
_addEvent
=
function
(
selector
,
event
,
functionName
)
{
var
closure
,
_this
=
this
;
closure
=
function
()
{
return
_this
[
functionName
].
apply
(
_this
,
arguments
);
};
if
(
selector
===
''
&&
Delegator
.
_isCustomEvent
(
event
))
{
this
.
subscribe
(
event
,
closure
);
}
else
{
this
.
element
.
delegate
(
selector
,
event
,
closure
);
}
this
.
_closures
[
""
+
selector
+
"/"
+
event
+
"/"
+
functionName
]
=
closure
;
return
this
;
};
Delegator
.
prototype
.
_removeEvent
=
function
(
selector
,
event
,
functionName
)
{
var
closure
;
closure
=
this
.
_closures
[
""
+
selector
+
"/"
+
event
+
"/"
+
functionName
];
if
(
selector
===
''
&&
Delegator
.
_isCustomEvent
(
event
))
{
this
.
unsubscribe
(
event
,
closure
);
}
else
{
this
.
element
.
undelegate
(
selector
,
event
,
closure
);
}
delete
this
.
_closures
[
""
+
selector
+
"/"
+
event
+
"/"
+
functionName
];
return
this
;
};
Delegator
.
prototype
.
publish
=
function
()
{
this
.
element
.
triggerHandler
.
apply
(
this
.
element
,
arguments
);
return
this
;
};
Delegator
.
prototype
.
subscribe
=
function
(
event
,
callback
)
{
var
closure
;
closure
=
function
()
{
return
callback
.
apply
(
this
,
[].
slice
.
call
(
arguments
,
1
));
};
closure
.
guid
=
callback
.
guid
=
(
$
.
guid
+=
1
);
this
.
element
.
bind
(
event
,
closure
);
return
this
;
};
Delegator
.
prototype
.
unsubscribe
=
function
()
{
this
.
element
.
unbind
.
apply
(
this
.
element
,
arguments
);
return
this
;
};
return
Delegator
;
})();
Delegator
.
_parseEvents
=
function
(
eventsObj
)
{
var
event
,
events
,
functionName
,
sel
,
selector
,
_k
,
_ref1
;
events
=
[];
for
(
sel
in
eventsObj
)
{
functionName
=
eventsObj
[
sel
];
_ref1
=
sel
.
split
(
' '
),
selector
=
2
<=
_ref1
.
length
?
__slice
.
call
(
_ref1
,
0
,
_k
=
_ref1
.
length
-
1
)
:
(
_k
=
0
,
[]),
event
=
_ref1
[
_k
++
];
events
.
push
({
selector
:
selector
.
join
(
' '
),
event
:
event
,
functionName
:
functionName
});
}
return
events
;
};
Delegator
.
natives
=
(
function
()
{
var
key
,
specials
,
val
;
specials
=
(
function
()
{
var
_ref1
,
_results
;
_ref1
=
jQuery
.
event
.
special
;
_results
=
[];
for
(
key
in
_ref1
)
{
if
(
!
__hasProp
.
call
(
_ref1
,
key
))
continue
;
val
=
_ref1
[
key
];
_results
.
push
(
key
);
}
return
_results
;
})();
return
"blur focus focusin focusout load resize scroll unload click dblclick
\n
mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave
\n
change select submit keydown keypress keyup error"
.
split
(
/
[^
a-z
]
+/
).
concat
(
specials
);
})();
Delegator
.
_isCustomEvent
=
function
(
event
)
{
event
=
event
.
split
(
'.'
)[
0
];
return
$
.
inArray
(
event
,
Delegator
.
natives
)
===
-
1
;
};
Range
=
{};
Range
.
sniff
=
function
(
r
)
{
if
(
r
.
commonAncestorContainer
!=
null
)
{
return
new
Range
.
BrowserRange
(
r
);
}
else
if
(
typeof
r
.
start
===
"string"
)
{
return
new
Range
.
SerializedRange
(
r
);
}
else
if
(
r
.
start
&&
typeof
r
.
start
===
"object"
)
{
return
new
Range
.
NormalizedRange
(
r
);
}
else
{
console
.
error
(
_t
(
"Could not sniff range type"
));
return
false
;
}
};
Range
.
nodeFromXPath
=
function
(
xpath
,
root
)
{
var
customResolver
,
evaluateXPath
,
namespace
,
node
,
segment
;
if
(
root
==
null
)
{
root
=
document
;
}
evaluateXPath
=
function
(
xp
,
nsResolver
)
{
var
exception
;
if
(
nsResolver
==
null
)
{
nsResolver
=
null
;
}
try
{
return
document
.
evaluate
(
'.'
+
xp
,
root
,
nsResolver
,
XPathResult
.
FIRST_ORDERED_NODE_TYPE
,
null
).
singleNodeValue
;
}
catch
(
_error
)
{
exception
=
_error
;
console
.
log
(
"XPath evaluation failed."
);
console
.
log
(
"Trying fallback..."
);
return
Util
.
nodeFromXPath
(
xp
,
root
);
}
};
if
(
!
$
.
isXMLDoc
(
document
.
documentElement
))
{
return
evaluateXPath
(
xpath
);
}
else
{
customResolver
=
document
.
createNSResolver
(
document
.
ownerDocument
===
null
?
document
.
documentElement
:
document
.
ownerDocument
.
documentElement
);
node
=
evaluateXPath
(
xpath
,
customResolver
);
if
(
!
node
)
{
xpath
=
((
function
()
{
var
_k
,
_len2
,
_ref1
,
_results
;
_ref1
=
xpath
.
split
(
'/'
);
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
segment
=
_ref1
[
_k
];
if
(
segment
&&
segment
.
indexOf
(
':'
)
===
-
1
)
{
_results
.
push
(
segment
.
replace
(
/^
([
a-z
]
+
)
/
,
'xhtml:$1'
));
}
else
{
_results
.
push
(
segment
);
}
}
return
_results
;
})()).
join
(
'/'
);
namespace
=
document
.
lookupNamespaceURI
(
null
);
customResolver
=
function
(
ns
)
{
if
(
ns
===
'xhtml'
)
{
return
namespace
;
}
else
{
return
document
.
documentElement
.
getAttribute
(
'xmlns:'
+
ns
);
}
};
node
=
evaluateXPath
(
xpath
,
customResolver
);
}
return
node
;
}
};
Range
.
RangeError
=
(
function
(
_super
)
{
__extends
(
RangeError
,
_super
);
function
RangeError
(
type
,
message
,
parent
)
{
this
.
type
=
type
;
this
.
message
=
message
;
this
.
parent
=
parent
!=
null
?
parent
:
null
;
RangeError
.
__super__
.
constructor
.
call
(
this
,
this
.
message
);
}
return
RangeError
;
})(
Error
);
Range
.
BrowserRange
=
(
function
()
{
function
BrowserRange
(
obj
)
{
this
.
commonAncestorContainer
=
obj
.
commonAncestorContainer
;
this
.
startContainer
=
obj
.
startContainer
;
this
.
startOffset
=
obj
.
startOffset
;
this
.
endContainer
=
obj
.
endContainer
;
this
.
endOffset
=
obj
.
endOffset
;
}
BrowserRange
.
prototype
.
normalize
=
function
(
root
)
{
var
n
,
node
,
nr
,
r
;
if
(
this
.
tainted
)
{
console
.
error
(
_t
(
"You may only call normalize() once on a BrowserRange!"
));
return
false
;
}
else
{
this
.
tainted
=
true
;
}
r
=
{};
if
(
this
.
startContainer
.
nodeType
===
Node
.
ELEMENT_NODE
)
{
r
.
start
=
Util
.
getFirstTextNodeNotBefore
(
this
.
startContainer
.
childNodes
[
this
.
startOffset
]);
r
.
startOffset
=
0
;
}
else
{
r
.
start
=
this
.
startContainer
;
r
.
startOffset
=
this
.
startOffset
;
}
if
(
this
.
endContainer
.
nodeType
===
Node
.
ELEMENT_NODE
)
{
node
=
this
.
endContainer
.
childNodes
[
this
.
endOffset
];
if
(
node
!=
null
)
{
n
=
node
;
while
((
n
!=
null
)
&&
(
n
.
nodeType
!==
Node
.
TEXT_NODE
))
{
n
=
n
.
firstChild
;
}
if
(
n
!=
null
)
{
r
.
end
=
n
;
r
.
endOffset
=
0
;
}
}
if
(
r
.
end
==
null
)
{
node
=
this
.
endContainer
.
childNodes
[
this
.
endOffset
-
1
];
r
.
end
=
Util
.
getLastTextNodeUpTo
(
node
);
r
.
endOffset
=
r
.
end
.
nodeValue
.
length
;
}
}
else
{
r
.
end
=
this
.
endContainer
;
r
.
endOffset
=
this
.
endOffset
;
}
nr
=
{};
if
(
r
.
startOffset
>
0
)
{
if
(
r
.
start
.
nodeValue
.
length
>
r
.
startOffset
)
{
nr
.
start
=
r
.
start
.
splitText
(
r
.
startOffset
);
}
else
{
nr
.
start
=
r
.
start
.
nextSibling
;
}
}
else
{
nr
.
start
=
r
.
start
;
}
if
(
r
.
start
===
r
.
end
)
{
if
(
nr
.
start
.
nodeValue
.
length
>
(
r
.
endOffset
-
r
.
startOffset
))
{
nr
.
start
.
splitText
(
r
.
endOffset
-
r
.
startOffset
);
}
nr
.
end
=
nr
.
start
;
}
else
{
if
(
r
.
end
.
nodeValue
.
length
>
r
.
endOffset
)
{
r
.
end
.
splitText
(
r
.
endOffset
);
}
nr
.
end
=
r
.
end
;
}
nr
.
commonAncestor
=
this
.
commonAncestorContainer
;
while
(
nr
.
commonAncestor
.
nodeType
!==
Node
.
ELEMENT_NODE
)
{
nr
.
commonAncestor
=
nr
.
commonAncestor
.
parentNode
;
}
return
new
Range
.
NormalizedRange
(
nr
);
};
BrowserRange
.
prototype
.
serialize
=
function
(
root
,
ignoreSelector
)
{
return
this
.
normalize
(
root
).
serialize
(
root
,
ignoreSelector
);
};
return
BrowserRange
;
})();
Range
.
NormalizedRange
=
(
function
()
{
function
NormalizedRange
(
obj
)
{
this
.
commonAncestor
=
obj
.
commonAncestor
;
this
.
start
=
obj
.
start
;
this
.
end
=
obj
.
end
;
}
NormalizedRange
.
prototype
.
normalize
=
function
(
root
)
{
return
this
;
};
NormalizedRange
.
prototype
.
limit
=
function
(
bounds
)
{
var
nodes
,
parent
,
startParents
,
_k
,
_len2
,
_ref1
;
nodes
=
$
.
grep
(
this
.
textNodes
(),
function
(
node
)
{
return
node
.
parentNode
===
bounds
||
$
.
contains
(
bounds
,
node
.
parentNode
);
});
if
(
!
nodes
.
length
)
{
return
null
;
}
this
.
start
=
nodes
[
0
];
this
.
end
=
nodes
[
nodes
.
length
-
1
];
startParents
=
$
(
this
.
start
).
parents
();
_ref1
=
$
(
this
.
end
).
parents
();
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
parent
=
_ref1
[
_k
];
if
(
startParents
.
index
(
parent
)
!==
-
1
)
{
this
.
commonAncestor
=
parent
;
break
;
}
}
return
this
;
};
NormalizedRange
.
prototype
.
serialize
=
function
(
root
,
ignoreSelector
)
{
var
end
,
serialization
,
start
;
serialization
=
function
(
node
,
isEnd
)
{
var
n
,
nodes
,
offset
,
origParent
,
textNodes
,
xpath
,
_k
,
_len2
;
if
(
ignoreSelector
)
{
origParent
=
$
(
node
).
parents
(
":not("
+
ignoreSelector
+
")"
).
eq
(
0
);
}
else
{
origParent
=
$
(
node
).
parent
();
}
xpath
=
Util
.
xpathFromNode
(
origParent
,
root
)[
0
];
textNodes
=
Util
.
getTextNodes
(
origParent
);
nodes
=
textNodes
.
slice
(
0
,
textNodes
.
index
(
node
));
offset
=
0
;
for
(
_k
=
0
,
_len2
=
nodes
.
length
;
_k
<
_len2
;
_k
++
)
{
n
=
nodes
[
_k
];
offset
+=
n
.
nodeValue
.
length
;
}
if
(
isEnd
)
{
return
[
xpath
,
offset
+
node
.
nodeValue
.
length
];
}
else
{
return
[
xpath
,
offset
];
}
};
start
=
serialization
(
this
.
start
);
end
=
serialization
(
this
.
end
,
true
);
return
new
Range
.
SerializedRange
({
start
:
start
[
0
],
end
:
end
[
0
],
startOffset
:
start
[
1
],
endOffset
:
end
[
1
]
});
};
NormalizedRange
.
prototype
.
text
=
function
()
{
var
node
;
return
((
function
()
{
var
_k
,
_len2
,
_ref1
,
_results
;
_ref1
=
this
.
textNodes
();
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
node
=
_ref1
[
_k
];
_results
.
push
(
node
.
nodeValue
);
}
return
_results
;
}).
call
(
this
)).
join
(
''
);
};
NormalizedRange
.
prototype
.
textNodes
=
function
()
{
var
end
,
start
,
textNodes
,
_ref1
;
textNodes
=
Util
.
getTextNodes
(
$
(
this
.
commonAncestor
));
_ref1
=
[
textNodes
.
index
(
this
.
start
),
textNodes
.
index
(
this
.
end
)],
start
=
_ref1
[
0
],
end
=
_ref1
[
1
];
return
$
.
makeArray
(
textNodes
.
slice
(
start
,
+
end
+
1
||
9
e9
));
};
NormalizedRange
.
prototype
.
toRange
=
function
()
{
var
range
;
range
=
document
.
createRange
();
range
.
setStartBefore
(
this
.
start
);
range
.
setEndAfter
(
this
.
end
);
return
range
;
};
return
NormalizedRange
;
})();
Range
.
SerializedRange
=
(
function
()
{
function
SerializedRange
(
obj
)
{
this
.
start
=
obj
.
start
;
this
.
startOffset
=
obj
.
startOffset
;
this
.
end
=
obj
.
end
;
this
.
endOffset
=
obj
.
endOffset
;
}
SerializedRange
.
prototype
.
normalize
=
function
(
root
)
{
var
contains
,
e
,
length
,
node
,
p
,
range
,
targetOffset
,
tn
,
_k
,
_l
,
_len2
,
_len3
,
_ref1
,
_ref2
;
range
=
{};
_ref1
=
[
'start'
,
'end'
];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
p
=
_ref1
[
_k
];
try
{
node
=
Range
.
nodeFromXPath
(
this
[
p
],
root
);
}
catch
(
_error
)
{
e
=
_error
;
throw
new
Range
.
RangeError
(
p
,
(
"Error while finding "
+
p
+
" node: "
+
this
[
p
]
+
": "
)
+
e
,
e
);
}
if
(
!
node
)
{
throw
new
Range
.
RangeError
(
p
,
"Couldn't find "
+
p
+
" node: "
+
this
[
p
]);
}
length
=
0
;
targetOffset
=
this
[
p
+
'Offset'
];
if
(
p
===
'end'
)
{
targetOffset
--
;
}
_ref2
=
Util
.
getTextNodes
(
$
(
node
));
for
(
_l
=
0
,
_len3
=
_ref2
.
length
;
_l
<
_len3
;
_l
++
)
{
tn
=
_ref2
[
_l
];
if
(
length
+
tn
.
nodeValue
.
length
>
targetOffset
)
{
range
[
p
+
'Container'
]
=
tn
;
range
[
p
+
'Offset'
]
=
this
[
p
+
'Offset'
]
-
length
;
break
;
}
else
{
length
+=
tn
.
nodeValue
.
length
;
}
}
if
(
range
[
p
+
'Offset'
]
==
null
)
{
throw
new
Range
.
RangeError
(
""
+
p
+
"offset"
,
"Couldn't find offset "
+
this
[
p
+
'Offset'
]
+
" in element "
+
this
[
p
]);
}
}
contains
=
document
.
compareDocumentPosition
==
null
?
function
(
a
,
b
)
{
return
a
.
contains
(
b
);
}
:
function
(
a
,
b
)
{
return
a
.
compareDocumentPosition
(
b
)
&
16
;
};
$
(
range
.
startContainer
).
parents
().
each
(
function
()
{
if
(
contains
(
this
,
range
.
endContainer
))
{
range
.
commonAncestorContainer
=
this
;
return
false
;
}
});
return
new
Range
.
BrowserRange
(
range
).
normalize
(
root
);
};
SerializedRange
.
prototype
.
serialize
=
function
(
root
,
ignoreSelector
)
{
return
this
.
normalize
(
root
).
serialize
(
root
,
ignoreSelector
);
};
SerializedRange
.
prototype
.
toObject
=
function
()
{
return
{
start
:
this
.
start
,
startOffset
:
this
.
startOffset
,
end
:
this
.
end
,
endOffset
:
this
.
endOffset
};
};
return
SerializedRange
;
})();
_Annotator
=
this
.
Annotator
;
Annotator
=
(
function
(
_super
)
{
__extends
(
Annotator
,
_super
);
Annotator
.
prototype
.
events
=
{
".annotator-adder button click"
:
"onAdderClick"
,
".annotator-adder button mousedown"
:
"onAdderMousedown"
,
".annotator-hl mouseover"
:
"onHighlightMouseover"
,
".annotator-hl mouseout"
:
"startViewerHideTimer"
};
Annotator
.
prototype
.
html
=
{
adder
:
'<div class="annotator-adder"><button>'
+
_t
(
'Annotate'
)
+
'</button></div>'
,
wrapper
:
'<div class="annotator-wrapper"></div>'
};
Annotator
.
prototype
.
options
=
{
readOnly
:
false
};
Annotator
.
prototype
.
plugins
=
{};
Annotator
.
prototype
.
editor
=
null
;
Annotator
.
prototype
.
viewer
=
null
;
Annotator
.
prototype
.
selectedRanges
=
null
;
Annotator
.
prototype
.
mouseIsDown
=
false
;
Annotator
.
prototype
.
ignoreMouseup
=
false
;
Annotator
.
prototype
.
viewerHideTimer
=
null
;
function
Annotator
(
element
,
options
)
{
this
.
onDeleteAnnotation
=
__bind
(
this
.
onDeleteAnnotation
,
this
);
this
.
onEditAnnotation
=
__bind
(
this
.
onEditAnnotation
,
this
);
this
.
onAdderClick
=
__bind
(
this
.
onAdderClick
,
this
);
this
.
onAdderMousedown
=
__bind
(
this
.
onAdderMousedown
,
this
);
this
.
onHighlightMouseover
=
__bind
(
this
.
onHighlightMouseover
,
this
);
this
.
checkForEndSelection
=
__bind
(
this
.
checkForEndSelection
,
this
);
this
.
checkForStartSelection
=
__bind
(
this
.
checkForStartSelection
,
this
);
this
.
clearViewerHideTimer
=
__bind
(
this
.
clearViewerHideTimer
,
this
);
this
.
startViewerHideTimer
=
__bind
(
this
.
startViewerHideTimer
,
this
);
this
.
showViewer
=
__bind
(
this
.
showViewer
,
this
);
this
.
onEditorSubmit
=
__bind
(
this
.
onEditorSubmit
,
this
);
this
.
onEditorHide
=
__bind
(
this
.
onEditorHide
,
this
);
this
.
showEditor
=
__bind
(
this
.
showEditor
,
this
);
Annotator
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
this
.
plugins
=
{};
if
(
!
Annotator
.
supported
())
{
return
this
;
}
if
(
!
this
.
options
.
readOnly
)
{
this
.
_setupDocumentEvents
();
}
this
.
_setupWrapper
().
_setupViewer
().
_setupEditor
();
this
.
_setupDynamicStyle
();
this
.
adder
=
$
(
this
.
html
.
adder
).
appendTo
(
this
.
wrapper
).
hide
();
Annotator
.
_instances
.
push
(
this
);
}
Annotator
.
prototype
.
_setupWrapper
=
function
()
{
this
.
wrapper
=
$
(
this
.
html
.
wrapper
);
this
.
element
.
find
(
'script'
).
remove
();
this
.
element
.
wrapInner
(
this
.
wrapper
);
this
.
wrapper
=
this
.
element
.
find
(
'.annotator-wrapper'
);
return
this
;
};
Annotator
.
prototype
.
_setupViewer
=
function
()
{
var
_this
=
this
;
this
.
viewer
=
new
Annotator
.
Viewer
({
readOnly
:
this
.
options
.
readOnly
});
this
.
viewer
.
hide
().
on
(
"edit"
,
this
.
onEditAnnotation
).
on
(
"delete"
,
this
.
onDeleteAnnotation
).
addField
({
load
:
function
(
field
,
annotation
)
{
if
(
annotation
.
text
)
{
$
(
field
).
html
(
Util
.
escape
(
annotation
.
text
));
}
else
{
$
(
field
).
html
(
"<i>"
+
(
_t
(
'No Comment'
))
+
"</i>"
);
}
return
_this
.
publish
(
'annotationViewerTextField'
,
[
field
,
annotation
]);
}
}).
element
.
appendTo
(
this
.
wrapper
).
bind
({
"mouseover"
:
this
.
clearViewerHideTimer
,
"mouseout"
:
this
.
startViewerHideTimer
});
return
this
;
};
Annotator
.
prototype
.
_setupEditor
=
function
()
{
this
.
editor
=
new
Annotator
.
Editor
();
this
.
editor
.
hide
().
on
(
'hide'
,
this
.
onEditorHide
).
on
(
'save'
,
this
.
onEditorSubmit
).
addField
({
type
:
'textarea'
,
label
:
_t
(
'Comments'
)
+
'
\
u2026'
,
load
:
function
(
field
,
annotation
)
{
return
$
(
field
).
find
(
'textarea'
).
val
(
annotation
.
text
||
''
);
},
submit
:
function
(
field
,
annotation
)
{
return
annotation
.
text
=
$
(
field
).
find
(
'textarea'
).
val
();
}
});
this
.
editor
.
element
.
appendTo
(
this
.
wrapper
);
return
this
;
};
Annotator
.
prototype
.
_setupDocumentEvents
=
function
()
{
$
(
document
).
bind
({
"mouseup"
:
this
.
checkForEndSelection
,
"mousedown"
:
this
.
checkForStartSelection
});
return
this
;
};
Annotator
.
prototype
.
_setupDynamicStyle
=
function
()
{
var
max
,
sel
,
style
,
x
;
style
=
$
(
'#annotator-dynamic-style'
);
if
(
!
style
.
length
)
{
style
=
$
(
'<style id="annotator-dynamic-style"></style>'
).
appendTo
(
document
.
head
);
}
sel
=
'*'
+
((
function
()
{
var
_k
,
_len2
,
_ref1
,
_results
;
_ref1
=
[
'adder'
,
'outer'
,
'notice'
,
'filter'
];
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
x
=
_ref1
[
_k
];
_results
.
push
(
":not(.annotator-"
+
x
+
")"
);
}
return
_results
;
})()).
join
(
''
);
max
=
Util
.
maxZIndex
(
$
(
document
.
body
).
find
(
sel
));
max
=
Math
.
max
(
max
,
1000
);
style
.
text
([
".annotator-adder, .annotator-outer, .annotator-notice {"
,
" z-index: "
+
(
max
+
20
)
+
";"
,
"}"
,
".annotator-filter {"
,
" z-index: "
+
(
max
+
10
)
+
";"
,
"}"
].
join
(
"
\n
"
));
return
this
;
};
Annotator
.
prototype
.
destroy
=
function
()
{
var
idx
,
name
,
plugin
,
_base
,
_ref1
;
Annotator
.
__super__
.
destroy
.
apply
(
this
,
arguments
);
$
(
document
).
unbind
({
"mouseup"
:
this
.
checkForEndSelection
,
"mousedown"
:
this
.
checkForStartSelection
});
$
(
'#annotator-dynamic-style'
).
remove
();
this
.
adder
.
remove
();
this
.
viewer
.
destroy
();
this
.
editor
.
destroy
();
this
.
wrapper
.
find
(
'.annotator-hl'
).
each
(
function
()
{
$
(
this
).
contents
().
insertBefore
(
this
);
return
$
(
this
).
remove
();
});
this
.
wrapper
.
contents
().
insertBefore
(
this
.
wrapper
);
this
.
wrapper
.
remove
();
this
.
element
.
data
(
'annotator'
,
null
);
_ref1
=
this
.
plugins
;
for
(
name
in
_ref1
)
{
plugin
=
_ref1
[
name
];
if
(
typeof
(
_base
=
this
.
plugins
[
name
]).
destroy
===
"function"
)
{
_base
.
destroy
();
}
}
idx
=
Annotator
.
_instances
.
indexOf
(
this
);
if
(
idx
!==
-
1
)
{
return
Annotator
.
_instances
.
splice
(
idx
,
1
);
}
};
Annotator
.
prototype
.
getSelectedRanges
=
function
()
{
var
browserRange
,
i
,
normedRange
,
r
,
ranges
,
rangesToIgnore
,
selection
,
_k
,
_len2
;
selection
=
Util
.
getGlobal
().
getSelection
();
ranges
=
[];
rangesToIgnore
=
[];
if
(
!
selection
.
isCollapsed
)
{
ranges
=
(
function
()
{
var
_k
,
_ref1
,
_results
;
_results
=
[];
for
(
i
=
_k
=
0
,
_ref1
=
selection
.
rangeCount
;
0
<=
_ref1
?
_k
<
_ref1
:
_k
>
_ref1
;
i
=
0
<=
_ref1
?
++
_k
:
--
_k
)
{
r
=
selection
.
getRangeAt
(
i
);
browserRange
=
new
Range
.
BrowserRange
(
r
);
normedRange
=
browserRange
.
normalize
().
limit
(
this
.
wrapper
[
0
]);
if
(
normedRange
===
null
)
{
rangesToIgnore
.
push
(
r
);
}
_results
.
push
(
normedRange
);
}
return
_results
;
}).
call
(
this
);
selection
.
removeAllRanges
();
}
for
(
_k
=
0
,
_len2
=
rangesToIgnore
.
length
;
_k
<
_len2
;
_k
++
)
{
r
=
rangesToIgnore
[
_k
];
selection
.
addRange
(
r
);
}
return
$
.
grep
(
ranges
,
function
(
range
)
{
if
(
range
)
{
selection
.
addRange
(
range
.
toRange
());
}
return
range
;
});
};
Annotator
.
prototype
.
createAnnotation
=
function
()
{
var
annotation
;
annotation
=
{};
this
.
publish
(
'beforeAnnotationCreated'
,
[
annotation
]);
return
annotation
;
};
Annotator
.
prototype
.
setupAnnotation
=
function
(
annotation
)
{
var
e
,
normed
,
normedRanges
,
r
,
root
,
_k
,
_l
,
_len2
,
_len3
,
_ref1
;
root
=
this
.
wrapper
[
0
];
annotation
.
ranges
||
(
annotation
.
ranges
=
this
.
selectedRanges
);
normedRanges
=
[];
_ref1
=
annotation
.
ranges
;
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
r
=
_ref1
[
_k
];
try
{
normedRanges
.
push
(
Range
.
sniff
(
r
).
normalize
(
root
));
}
catch
(
_error
)
{
e
=
_error
;
if
(
e
instanceof
Range
.
RangeError
)
{
this
.
publish
(
'rangeNormalizeFail'
,
[
annotation
,
r
,
e
]);
}
else
{
throw
e
;
}
}
}
annotation
.
quote
=
[];
annotation
.
ranges
=
[];
annotation
.
highlights
=
[];
for
(
_l
=
0
,
_len3
=
normedRanges
.
length
;
_l
<
_len3
;
_l
++
)
{
normed
=
normedRanges
[
_l
];
annotation
.
quote
.
push
(
$
.
trim
(
normed
.
text
()));
annotation
.
ranges
.
push
(
normed
.
serialize
(
this
.
wrapper
[
0
],
'.annotator-hl'
));
$
.
merge
(
annotation
.
highlights
,
this
.
highlightRange
(
normed
));
}
annotation
.
quote
=
annotation
.
quote
.
join
(
' / '
);
$
(
annotation
.
highlights
).
data
(
'annotation'
,
annotation
);
$
(
annotation
.
highlights
).
attr
(
'data-annotation-id'
,
annotation
.
id
);
return
annotation
;
};
Annotator
.
prototype
.
updateAnnotation
=
function
(
annotation
)
{
this
.
publish
(
'beforeAnnotationUpdated'
,
[
annotation
]);
$
(
annotation
.
highlights
).
attr
(
'data-annotation-id'
,
annotation
.
id
);
this
.
publish
(
'annotationUpdated'
,
[
annotation
]);
return
annotation
;
};
Annotator
.
prototype
.
deleteAnnotation
=
function
(
annotation
)
{
var
child
,
h
,
_k
,
_len2
,
_ref1
;
if
(
annotation
.
highlights
!=
null
)
{
_ref1
=
annotation
.
highlights
;
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
h
=
_ref1
[
_k
];
if
(
!
(
h
.
parentNode
!=
null
))
{
continue
;
}
child
=
h
.
childNodes
[
0
];
$
(
h
).
replaceWith
(
h
.
childNodes
);
}
}
this
.
publish
(
'annotationDeleted'
,
[
annotation
]);
return
annotation
;
};
Annotator
.
prototype
.
loadAnnotations
=
function
(
annotations
)
{
var
clone
,
loader
,
_this
=
this
;
if
(
annotations
==
null
)
{
annotations
=
[];
}
loader
=
function
(
annList
)
{
var
n
,
now
,
_k
,
_len2
;
if
(
annList
==
null
)
{
annList
=
[];
}
now
=
annList
.
splice
(
0
,
10
);
for
(
_k
=
0
,
_len2
=
now
.
length
;
_k
<
_len2
;
_k
++
)
{
n
=
now
[
_k
];
_this
.
setupAnnotation
(
n
);
}
if
(
annList
.
length
>
0
)
{
return
setTimeout
((
function
()
{
return
loader
(
annList
);
}),
10
);
}
else
{
return
_this
.
publish
(
'annotationsLoaded'
,
[
clone
]);
}
};
clone
=
annotations
.
slice
();
loader
(
annotations
);
return
this
;
};
Annotator
.
prototype
.
dumpAnnotations
=
function
()
{
if
(
this
.
plugins
[
'Store'
])
{
return
this
.
plugins
[
'Store'
].
dumpAnnotations
();
}
else
{
console
.
warn
(
_t
(
"Can't dump annotations without Store plugin."
));
return
false
;
}
};
Annotator
.
prototype
.
highlightRange
=
function
(
normedRange
,
cssClass
)
{
var
hl
,
node
,
white
,
_k
,
_len2
,
_ref1
,
_results
;
if
(
cssClass
==
null
)
{
cssClass
=
'annotator-hl'
;
}
white
=
/^
\s
*$/
;
hl
=
$
(
"<span class='"
+
cssClass
+
"'></span>"
);
_ref1
=
normedRange
.
textNodes
();
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
node
=
_ref1
[
_k
];
if
(
!
white
.
test
(
node
.
nodeValue
))
{
_results
.
push
(
$
(
node
).
wrapAll
(
hl
).
parent
().
show
()[
0
]);
}
}
return
_results
;
};
Annotator
.
prototype
.
highlightRanges
=
function
(
normedRanges
,
cssClass
)
{
var
highlights
,
r
,
_k
,
_len2
;
if
(
cssClass
==
null
)
{
cssClass
=
'annotator-hl'
;
}
highlights
=
[];
for
(
_k
=
0
,
_len2
=
normedRanges
.
length
;
_k
<
_len2
;
_k
++
)
{
r
=
normedRanges
[
_k
];
$
.
merge
(
highlights
,
this
.
highlightRange
(
r
,
cssClass
));
}
return
highlights
;
};
Annotator
.
prototype
.
addPlugin
=
function
(
name
,
options
)
{
var
klass
,
_base
;
if
(
this
.
plugins
[
name
])
{
console
.
error
(
_t
(
"You cannot have more than one instance of any plugin."
));
}
else
{
klass
=
Annotator
.
Plugin
[
name
];
if
(
typeof
klass
===
'function'
)
{
this
.
plugins
[
name
]
=
new
klass
(
this
.
element
[
0
],
options
);
this
.
plugins
[
name
].
annotator
=
this
;
if
(
typeof
(
_base
=
this
.
plugins
[
name
]).
pluginInit
===
"function"
)
{
_base
.
pluginInit
();
}
}
else
{
console
.
error
(
_t
(
"Could not load "
)
+
name
+
_t
(
" plugin. Have you included the appropriate <script> tag?"
));
}
}
return
this
;
};
Annotator
.
prototype
.
showEditor
=
function
(
annotation
,
location
)
{
this
.
editor
.
element
.
css
(
location
);
this
.
editor
.
load
(
annotation
);
this
.
publish
(
'annotationEditorShown'
,
[
this
.
editor
,
annotation
]);
return
this
;
};
Annotator
.
prototype
.
onEditorHide
=
function
()
{
this
.
publish
(
'annotationEditorHidden'
,
[
this
.
editor
]);
return
this
.
ignoreMouseup
=
false
;
};
Annotator
.
prototype
.
onEditorSubmit
=
function
(
annotation
)
{
return
this
.
publish
(
'annotationEditorSubmit'
,
[
this
.
editor
,
annotation
]);
};
Annotator
.
prototype
.
showViewer
=
function
(
annotations
,
location
)
{
this
.
viewer
.
element
.
css
(
location
);
this
.
viewer
.
load
(
annotations
);
return
this
.
publish
(
'annotationViewerShown'
,
[
this
.
viewer
,
annotations
]);
};
Annotator
.
prototype
.
startViewerHideTimer
=
function
()
{
if
(
!
this
.
viewerHideTimer
)
{
return
this
.
viewerHideTimer
=
setTimeout
(
this
.
viewer
.
hide
,
250
);
}
};
Annotator
.
prototype
.
clearViewerHideTimer
=
function
()
{
clearTimeout
(
this
.
viewerHideTimer
);
return
this
.
viewerHideTimer
=
false
;
};
Annotator
.
prototype
.
checkForStartSelection
=
function
(
event
)
{
if
(
!
(
event
&&
this
.
isAnnotator
(
event
.
target
)))
{
this
.
startViewerHideTimer
();
}
return
this
.
mouseIsDown
=
true
;
};
Annotator
.
prototype
.
checkForEndSelection
=
function
(
event
)
{
var
container
,
range
,
_k
,
_len2
,
_ref1
;
this
.
mouseIsDown
=
false
;
if
(
this
.
ignoreMouseup
)
{
return
;
}
this
.
selectedRanges
=
this
.
getSelectedRanges
();
_ref1
=
this
.
selectedRanges
;
for
(
_k
=
0
,
_len2
=
_ref1
.
length
;
_k
<
_len2
;
_k
++
)
{
range
=
_ref1
[
_k
];
container
=
range
.
commonAncestor
;
if
(
$
(
container
).
hasClass
(
'annotator-hl'
))
{
container
=
$
(
container
).
parents
(
'[class!=annotator-hl]'
)[
0
];
}
if
(
this
.
isAnnotator
(
container
))
{
return
;
}
}
if
(
event
&&
this
.
selectedRanges
.
length
)
{
return
this
.
adder
.
css
(
Util
.
mousePosition
(
event
,
this
.
wrapper
[
0
])).
show
();
}
else
{
return
this
.
adder
.
hide
();
}
};
Annotator
.
prototype
.
isAnnotator
=
function
(
element
)
{
return
!!
$
(
element
).
parents
().
addBack
().
filter
(
'[class^=annotator-]'
).
not
(
this
.
wrapper
).
length
;
};
Annotator
.
prototype
.
onHighlightMouseover
=
function
(
event
)
{
var
annotations
;
this
.
clearViewerHideTimer
();
if
(
this
.
mouseIsDown
)
{
return
false
;
}
if
(
this
.
viewer
.
isShown
())
{
this
.
viewer
.
hide
();
}
annotations
=
$
(
event
.
target
).
parents
(
'.annotator-hl'
).
addBack
().
map
(
function
()
{
return
$
(
this
).
data
(
"annotation"
);
}).
toArray
();
return
this
.
showViewer
(
annotations
,
Util
.
mousePosition
(
event
,
this
.
wrapper
[
0
]));
};
Annotator
.
prototype
.
onAdderMousedown
=
function
(
event
)
{
if
(
event
!=
null
)
{
event
.
preventDefault
();
}
return
this
.
ignoreMouseup
=
true
;
};
Annotator
.
prototype
.
onAdderClick
=
function
(
event
)
{
var
annotation
,
cancel
,
cleanup
,
position
,
save
,
_this
=
this
;
if
(
event
!=
null
)
{
event
.
preventDefault
();
}
position
=
this
.
adder
.
position
();
this
.
adder
.
hide
();
annotation
=
this
.
setupAnnotation
(
this
.
createAnnotation
());
$
(
annotation
.
highlights
).
addClass
(
'annotator-hl-temporary'
);
save
=
function
()
{
cleanup
();
$
(
annotation
.
highlights
).
removeClass
(
'annotator-hl-temporary'
);
return
_this
.
publish
(
'annotationCreated'
,
[
annotation
]);
};
cancel
=
function
()
{
cleanup
();
return
_this
.
deleteAnnotation
(
annotation
);
};
cleanup
=
function
()
{
_this
.
unsubscribe
(
'annotationEditorHidden'
,
cancel
);
return
_this
.
unsubscribe
(
'annotationEditorSubmit'
,
save
);
};
this
.
subscribe
(
'annotationEditorHidden'
,
cancel
);
this
.
subscribe
(
'annotationEditorSubmit'
,
save
);
return
this
.
showEditor
(
annotation
,
position
);
};
Annotator
.
prototype
.
onEditAnnotation
=
function
(
annotation
)
{
var
cleanup
,
offset
,
update
,
_this
=
this
;
offset
=
this
.
viewer
.
element
.
position
();
update
=
function
()
{
cleanup
();
return
_this
.
updateAnnotation
(
annotation
);
};
cleanup
=
function
()
{
_this
.
unsubscribe
(
'annotationEditorHidden'
,
cleanup
);
return
_this
.
unsubscribe
(
'annotationEditorSubmit'
,
update
);
};
this
.
subscribe
(
'annotationEditorHidden'
,
cleanup
);
this
.
subscribe
(
'annotationEditorSubmit'
,
update
);
this
.
viewer
.
hide
();
return
this
.
showEditor
(
annotation
,
offset
);
};
Annotator
.
prototype
.
onDeleteAnnotation
=
function
(
annotation
)
{
this
.
viewer
.
hide
();
return
this
.
deleteAnnotation
(
annotation
);
};
return
Annotator
;
})(
Delegator
);
Annotator
.
Plugin
=
(
function
(
_super
)
{
__extends
(
Plugin
,
_super
);
function
Plugin
(
element
,
options
)
{
Plugin
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
}
Plugin
.
prototype
.
pluginInit
=
function
()
{};
return
Plugin
;
})(
Delegator
);
g
=
Util
.
getGlobal
();
if
(((
_ref1
=
g
.
document
)
!=
null
?
_ref1
.
evaluate
:
void
0
)
==
null
)
{
$
.
getScript
(
'http://assets.annotateit.org/vendor/xpath.min.js'
);
}
if
(
g
.
getSelection
==
null
)
{
$
.
getScript
(
'http://assets.annotateit.org/vendor/ierange.min.js'
);
}
if
(
g
.
JSON
==
null
)
{
$
.
getScript
(
'http://assets.annotateit.org/vendor/json2.min.js'
);
}
if
(
g
.
Node
==
null
)
{
g
.
Node
=
{
ELEMENT_NODE
:
1
,
ATTRIBUTE_NODE
:
2
,
TEXT_NODE
:
3
,
CDATA_SECTION_NODE
:
4
,
ENTITY_REFERENCE_NODE
:
5
,
ENTITY_NODE
:
6
,
PROCESSING_INSTRUCTION_NODE
:
7
,
COMMENT_NODE
:
8
,
DOCUMENT_NODE
:
9
,
DOCUMENT_TYPE_NODE
:
10
,
DOCUMENT_FRAGMENT_NODE
:
11
,
NOTATION_NODE
:
12
};
}
Annotator
.
$
=
$
;
Annotator
.
Delegator
=
Delegator
;
Annotator
.
Range
=
Range
;
Annotator
.
Util
=
Util
;
Annotator
.
_instances
=
[];
Annotator
.
_t
=
_t
;
Annotator
.
supported
=
function
()
{
return
(
function
()
{
return
!!
this
.
getSelection
;
})();
};
Annotator
.
noConflict
=
function
()
{
Util
.
getGlobal
().
Annotator
=
_Annotator
;
return
this
;
};
$
.
fn
.
annotator
=
function
(
options
)
{
var
args
;
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
);
return
this
.
each
(
function
()
{
var
instance
;
instance
=
$
.
data
(
this
,
'annotator'
);
if
(
options
===
'destroy'
)
{
$
.
removeData
(
this
,
'annotator'
);
return
instance
!=
null
?
instance
.
destroy
(
args
)
:
void
0
;
}
else
if
(
instance
)
{
return
options
&&
instance
[
options
].
apply
(
instance
,
args
);
}
else
{
instance
=
new
Annotator
(
this
,
options
);
return
$
.
data
(
this
,
'annotator'
,
instance
);
}
});
};
this
.
Annotator
=
Annotator
;
Annotator
.
Widget
=
(
function
(
_super
)
{
__extends
(
Widget
,
_super
);
Widget
.
prototype
.
classes
=
{
hide
:
'annotator-hide'
,
invert
:
{
x
:
'annotator-invert-x'
,
y
:
'annotator-invert-y'
}
};
function
Widget
(
element
,
options
)
{
Widget
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
this
.
classes
=
$
.
extend
({},
Annotator
.
Widget
.
prototype
.
classes
,
this
.
classes
);
}
Widget
.
prototype
.
destroy
=
function
()
{
this
.
removeEvents
();
return
this
.
element
.
remove
();
};
Widget
.
prototype
.
checkOrientation
=
function
()
{
var
current
,
offset
,
viewport
,
widget
,
window
;
this
.
resetOrientation
();
window
=
$
(
Annotator
.
Util
.
getGlobal
());
widget
=
this
.
element
.
children
(
":first"
);
offset
=
widget
.
offset
();
viewport
=
{
top
:
window
.
scrollTop
(),
right
:
window
.
width
()
+
window
.
scrollLeft
()
};
current
=
{
top
:
offset
.
top
,
right
:
offset
.
left
+
widget
.
width
()
};
if
((
current
.
top
-
viewport
.
top
)
<
0
)
{
this
.
invertY
();
}
if
((
current
.
right
-
viewport
.
right
)
>
0
)
{
this
.
invertX
();
}
return
this
;
};
Widget
.
prototype
.
resetOrientation
=
function
()
{
this
.
element
.
removeClass
(
this
.
classes
.
invert
.
x
).
removeClass
(
this
.
classes
.
invert
.
y
);
return
this
;
};
Widget
.
prototype
.
invertX
=
function
()
{
this
.
element
.
addClass
(
this
.
classes
.
invert
.
x
);
return
this
;
};
Widget
.
prototype
.
invertY
=
function
()
{
this
.
element
.
addClass
(
this
.
classes
.
invert
.
y
);
return
this
;
};
Widget
.
prototype
.
isInvertedY
=
function
()
{
return
this
.
element
.
hasClass
(
this
.
classes
.
invert
.
y
);
};
Widget
.
prototype
.
isInvertedX
=
function
()
{
return
this
.
element
.
hasClass
(
this
.
classes
.
invert
.
x
);
};
return
Widget
;
})(
Delegator
);
Annotator
.
Editor
=
(
function
(
_super
)
{
__extends
(
Editor
,
_super
);
Editor
.
prototype
.
events
=
{
"form submit"
:
"submit"
,
".annotator-save click"
:
"submit"
,
".annotator-cancel click"
:
"hide"
,
".annotator-cancel mouseover"
:
"onCancelButtonMouseover"
,
"textarea keydown"
:
"processKeypress"
};
Editor
.
prototype
.
classes
=
{
hide
:
'annotator-hide'
,
focus
:
'annotator-focus'
};
Editor
.
prototype
.
html
=
"<div class=
\"
annotator-outer annotator-editor
\"
>
\n
<form class=
\"
annotator-widget
\"
>
\n
<ul class=
\"
annotator-listing
\"
></ul>
\n
<div class=
\"
annotator-controls
\"
>
\n
<a href=
\"
#cancel
\"
class=
\"
annotator-cancel
\"
>"
+
_t
(
'Cancel'
)
+
"</a>
\n
<a href=
\"
#save
\"
class=
\"
annotator-save annotator-focus
\"
>"
+
_t
(
'Save'
)
+
"</a>
\n
</div>
\n
</form>
\n
</div>"
;
Editor
.
prototype
.
options
=
{};
function
Editor
(
options
)
{
this
.
onCancelButtonMouseover
=
__bind
(
this
.
onCancelButtonMouseover
,
this
);
this
.
processKeypress
=
__bind
(
this
.
processKeypress
,
this
);
this
.
submit
=
__bind
(
this
.
submit
,
this
);
this
.
load
=
__bind
(
this
.
load
,
this
);
this
.
hide
=
__bind
(
this
.
hide
,
this
);
this
.
show
=
__bind
(
this
.
show
,
this
);
Editor
.
__super__
.
constructor
.
call
(
this
,
$
(
this
.
html
)[
0
],
options
);
this
.
fields
=
[];
this
.
annotation
=
{};
}
Editor
.
prototype
.
show
=
function
(
event
)
{
Annotator
.
Util
.
preventEventDefault
(
event
);
this
.
element
.
removeClass
(
this
.
classes
.
hide
);
this
.
element
.
find
(
'.annotator-save'
).
addClass
(
this
.
classes
.
focus
);
this
.
checkOrientation
();
this
.
element
.
find
(
":input:first"
).
focus
();
this
.
setupDraggables
();
return
this
.
publish
(
'show'
);
};
Editor
.
prototype
.
hide
=
function
(
event
)
{
Annotator
.
Util
.
preventEventDefault
(
event
);
this
.
element
.
addClass
(
this
.
classes
.
hide
);
return
this
.
publish
(
'hide'
);
};
Editor
.
prototype
.
load
=
function
(
annotation
)
{
var
field
,
_k
,
_len2
,
_ref2
;
this
.
annotation
=
annotation
;
this
.
publish
(
'load'
,
[
this
.
annotation
]);
_ref2
=
this
.
fields
;
for
(
_k
=
0
,
_len2
=
_ref2
.
length
;
_k
<
_len2
;
_k
++
)
{
field
=
_ref2
[
_k
];
field
.
load
(
field
.
element
,
this
.
annotation
);
}
return
this
.
show
();
};
Editor
.
prototype
.
submit
=
function
(
event
)
{
var
field
,
_k
,
_len2
,
_ref2
;
Annotator
.
Util
.
preventEventDefault
(
event
);
_ref2
=
this
.
fields
;
for
(
_k
=
0
,
_len2
=
_ref2
.
length
;
_k
<
_len2
;
_k
++
)
{
field
=
_ref2
[
_k
];
field
.
submit
(
field
.
element
,
this
.
annotation
);
}
this
.
publish
(
'save'
,
[
this
.
annotation
]);
return
this
.
hide
();
};
Editor
.
prototype
.
addField
=
function
(
options
)
{
var
element
,
field
,
input
;
field
=
$
.
extend
({
id
:
'annotator-field-'
+
Annotator
.
Util
.
uuid
(),
type
:
'input'
,
label
:
''
,
load
:
function
()
{},
submit
:
function
()
{}
},
options
);
input
=
null
;
element
=
$
(
'<li class="annotator-item" />'
);
field
.
element
=
element
[
0
];
switch
(
field
.
type
)
{
case
'textarea'
:
input
=
$
(
'<textarea />'
);
break
;
case
'input'
:
case
'checkbox'
:
input
=
$
(
'<input />'
);
break
;
case
'select'
:
input
=
$
(
'<select />'
);
}
element
.
append
(
input
);
input
.
attr
({
id
:
field
.
id
,
placeholder
:
field
.
label
});
if
(
field
.
type
===
'checkbox'
)
{
input
[
0
].
type
=
'checkbox'
;
element
.
addClass
(
'annotator-checkbox'
);
element
.
append
(
$
(
'<label />'
,
{
"for"
:
field
.
id
,
html
:
field
.
label
}));
}
this
.
element
.
find
(
'ul:first'
).
append
(
element
);
this
.
fields
.
push
(
field
);
return
field
.
element
;
};
Editor
.
prototype
.
checkOrientation
=
function
()
{
var
controls
,
list
;
Editor
.
__super__
.
checkOrientation
.
apply
(
this
,
arguments
);
list
=
this
.
element
.
find
(
'ul'
);
controls
=
this
.
element
.
find
(
'.annotator-controls'
);
if
(
this
.
element
.
hasClass
(
this
.
classes
.
invert
.
y
))
{
controls
.
insertBefore
(
list
);
}
else
if
(
controls
.
is
(
':first-child'
))
{
controls
.
insertAfter
(
list
);
}
return
this
;
};
Editor
.
prototype
.
processKeypress
=
function
(
event
)
{
if
(
event
.
keyCode
===
27
)
{
return
this
.
hide
();
}
else
if
(
event
.
keyCode
===
13
&&
!
event
.
shiftKey
)
{
return
this
.
submit
();
}
};
Editor
.
prototype
.
onCancelButtonMouseover
=
function
()
{
return
this
.
element
.
find
(
'.'
+
this
.
classes
.
focus
).
removeClass
(
this
.
classes
.
focus
);
};
Editor
.
prototype
.
setupDraggables
=
function
()
{
var
classes
,
controls
,
cornerItem
,
editor
,
mousedown
,
onMousedown
,
onMousemove
,
onMouseup
,
resize
,
textarea
,
throttle
,
_this
=
this
;
this
.
element
.
find
(
'.annotator-resize'
).
remove
();
if
(
this
.
element
.
hasClass
(
this
.
classes
.
invert
.
y
))
{
cornerItem
=
this
.
element
.
find
(
'.annotator-item:last'
);
}
else
{
cornerItem
=
this
.
element
.
find
(
'.annotator-item:first'
);
}
if
(
cornerItem
)
{
$
(
'<span class="annotator-resize"></span>'
).
appendTo
(
cornerItem
);
}
mousedown
=
null
;
classes
=
this
.
classes
;
editor
=
this
.
element
;
textarea
=
null
;
resize
=
editor
.
find
(
'.annotator-resize'
);
controls
=
editor
.
find
(
'.annotator-controls'
);
throttle
=
false
;
onMousedown
=
function
(
event
)
{
if
(
event
.
target
===
this
)
{
mousedown
=
{
element
:
this
,
top
:
event
.
pageY
,
left
:
event
.
pageX
};
textarea
=
editor
.
find
(
'textarea:first'
);
$
(
window
).
bind
({
'mouseup.annotator-editor-resize'
:
onMouseup
,
'mousemove.annotator-editor-resize'
:
onMousemove
});
return
event
.
preventDefault
();
}
};
onMouseup
=
function
()
{
mousedown
=
null
;
return
$
(
window
).
unbind
(
'.annotator-editor-resize'
);
};
onMousemove
=
function
(
event
)
{
var
diff
,
directionX
,
directionY
,
height
,
width
;
if
(
mousedown
&&
throttle
===
false
)
{
diff
=
{
top
:
event
.
pageY
-
mousedown
.
top
,
left
:
event
.
pageX
-
mousedown
.
left
};
if
(
mousedown
.
element
===
resize
[
0
])
{
height
=
textarea
.
outerHeight
();
width
=
textarea
.
outerWidth
();
directionX
=
editor
.
hasClass
(
classes
.
invert
.
x
)
?
-
1
:
1
;
directionY
=
editor
.
hasClass
(
classes
.
invert
.
y
)
?
1
:
-
1
;
textarea
.
height
(
height
+
(
diff
.
top
*
directionY
));
textarea
.
width
(
width
+
(
diff
.
left
*
directionX
));
if
(
textarea
.
outerHeight
()
!==
height
)
{
mousedown
.
top
=
event
.
pageY
;
}
if
(
textarea
.
outerWidth
()
!==
width
)
{
mousedown
.
left
=
event
.
pageX
;
}
}
else
if
(
mousedown
.
element
===
controls
[
0
])
{
editor
.
css
({
top
:
parseInt
(
editor
.
css
(
'top'
),
10
)
+
diff
.
top
,
left
:
parseInt
(
editor
.
css
(
'left'
),
10
)
+
diff
.
left
});
mousedown
.
top
=
event
.
pageY
;
mousedown
.
left
=
event
.
pageX
;
}
throttle
=
true
;
return
setTimeout
(
function
()
{
return
throttle
=
false
;
},
1000
/
60
);
}
};
resize
.
bind
(
'mousedown'
,
onMousedown
);
return
controls
.
bind
(
'mousedown'
,
onMousedown
);
};
return
Editor
;
})(
Annotator
.
Widget
);
Annotator
.
Viewer
=
(
function
(
_super
)
{
__extends
(
Viewer
,
_super
);
Viewer
.
prototype
.
events
=
{
".annotator-edit click"
:
"onEditClick"
,
".annotator-delete click"
:
"onDeleteClick"
};
Viewer
.
prototype
.
classes
=
{
hide
:
'annotator-hide'
,
showControls
:
'annotator-visible'
};
Viewer
.
prototype
.
html
=
{
element
:
"<div class=
\"
annotator-outer annotator-viewer
\"
>
\n
<ul class=
\"
annotator-widget annotator-listing
\"
></ul>
\n
</div>"
,
item
:
"<li class=
\"
annotator-annotation annotator-item
\"
>
\n
<span class=
\"
annotator-controls
\"
>
\n
<a href=
\"
#
\"
title=
\"
View as webpage
\"
class=
\"
annotator-link
\"
>View as webpage</a>
\n
<button title=
\"
Edit
\"
class=
\"
annotator-edit
\"
>Edit</button>
\n
<button title=
\"
Delete
\"
class=
\"
annotator-delete
\"
>Delete</button>
\n
</span>
\n
</li>"
};
Viewer
.
prototype
.
options
=
{
readOnly
:
false
};
function
Viewer
(
options
)
{
this
.
onDeleteClick
=
__bind
(
this
.
onDeleteClick
,
this
);
this
.
onEditClick
=
__bind
(
this
.
onEditClick
,
this
);
this
.
load
=
__bind
(
this
.
load
,
this
);
this
.
hide
=
__bind
(
this
.
hide
,
this
);
this
.
show
=
__bind
(
this
.
show
,
this
);
Viewer
.
__super__
.
constructor
.
call
(
this
,
$
(
this
.
html
.
element
)[
0
],
options
);
this
.
item
=
$
(
this
.
html
.
item
)[
0
];
this
.
fields
=
[];
this
.
annotations
=
[];
}
Viewer
.
prototype
.
show
=
function
(
event
)
{
var
controls
,
_this
=
this
;
Annotator
.
Util
.
preventEventDefault
(
event
);
controls
=
this
.
element
.
find
(
'.annotator-controls'
).
addClass
(
this
.
classes
.
showControls
);
setTimeout
((
function
()
{
return
controls
.
removeClass
(
_this
.
classes
.
showControls
);
}),
500
);
this
.
element
.
removeClass
(
this
.
classes
.
hide
);
return
this
.
checkOrientation
().
publish
(
'show'
);
};
Viewer
.
prototype
.
isShown
=
function
()
{
return
!
this
.
element
.
hasClass
(
this
.
classes
.
hide
);
};
Viewer
.
prototype
.
hide
=
function
(
event
)
{
Annotator
.
Util
.
preventEventDefault
(
event
);
this
.
element
.
addClass
(
this
.
classes
.
hide
);
return
this
.
publish
(
'hide'
);
};
Viewer
.
prototype
.
load
=
function
(
annotations
)
{
var
annotation
,
controller
,
controls
,
del
,
edit
,
element
,
field
,
item
,
link
,
links
,
list
,
_k
,
_l
,
_len2
,
_len3
,
_ref2
,
_ref3
;
this
.
annotations
=
annotations
||
[];
list
=
this
.
element
.
find
(
'ul:first'
).
empty
();
_ref2
=
this
.
annotations
;
for
(
_k
=
0
,
_len2
=
_ref2
.
length
;
_k
<
_len2
;
_k
++
)
{
annotation
=
_ref2
[
_k
];
item
=
$
(
this
.
item
).
clone
().
appendTo
(
list
).
data
(
'annotation'
,
annotation
);
controls
=
item
.
find
(
'.annotator-controls'
);
link
=
controls
.
find
(
'.annotator-link'
);
edit
=
controls
.
find
(
'.annotator-edit'
);
del
=
controls
.
find
(
'.annotator-delete'
);
links
=
new
LinkParser
(
annotation
.
links
||
[]).
get
(
'alternate'
,
{
'type'
:
'text/html'
});
if
(
links
.
length
===
0
||
(
links
[
0
].
href
==
null
))
{
link
.
remove
();
}
else
{
link
.
attr
(
'href'
,
links
[
0
].
href
);
}
if
(
this
.
options
.
readOnly
)
{
edit
.
remove
();
del
.
remove
();
}
else
{
controller
=
{
showEdit
:
function
()
{
return
edit
.
removeAttr
(
'disabled'
);
},
hideEdit
:
function
()
{
return
edit
.
attr
(
'disabled'
,
'disabled'
);
},
showDelete
:
function
()
{
return
del
.
removeAttr
(
'disabled'
);
},
hideDelete
:
function
()
{
return
del
.
attr
(
'disabled'
,
'disabled'
);
}
};
}
_ref3
=
this
.
fields
;
for
(
_l
=
0
,
_len3
=
_ref3
.
length
;
_l
<
_len3
;
_l
++
)
{
field
=
_ref3
[
_l
];
element
=
$
(
field
.
element
).
clone
().
appendTo
(
item
)[
0
];
field
.
load
(
element
,
annotation
,
controller
);
}
}
this
.
publish
(
'load'
,
[
this
.
annotations
]);
return
this
.
show
();
};
Viewer
.
prototype
.
addField
=
function
(
options
)
{
var
field
;
field
=
$
.
extend
({
load
:
function
()
{}
},
options
);
field
.
element
=
$
(
'<div />'
)[
0
];
this
.
fields
.
push
(
field
);
field
.
element
;
return
this
;
};
Viewer
.
prototype
.
onEditClick
=
function
(
event
)
{
return
this
.
onButtonClick
(
event
,
'edit'
);
};
Viewer
.
prototype
.
onDeleteClick
=
function
(
event
)
{
return
this
.
onButtonClick
(
event
,
'delete'
);
};
Viewer
.
prototype
.
onButtonClick
=
function
(
event
,
type
)
{
var
item
;
item
=
$
(
event
.
target
).
parents
(
'.annotator-annotation'
);
return
this
.
publish
(
type
,
[
item
.
data
(
'annotation'
)]);
};
return
Viewer
;
})(
Annotator
.
Widget
);
LinkParser
=
(
function
()
{
function
LinkParser
(
data
)
{
this
.
data
=
data
;
}
LinkParser
.
prototype
.
get
=
function
(
rel
,
cond
)
{
var
d
,
k
,
keys
,
match
,
v
,
_k
,
_len2
,
_ref2
,
_results
;
if
(
cond
==
null
)
{
cond
=
{};
}
cond
=
$
.
extend
({},
cond
,
{
rel
:
rel
});
keys
=
(
function
()
{
var
_results
;
_results
=
[];
for
(
k
in
cond
)
{
if
(
!
__hasProp
.
call
(
cond
,
k
))
continue
;
v
=
cond
[
k
];
_results
.
push
(
k
);
}
return
_results
;
})();
_ref2
=
this
.
data
;
_results
=
[];
for
(
_k
=
0
,
_len2
=
_ref2
.
length
;
_k
<
_len2
;
_k
++
)
{
d
=
_ref2
[
_k
];
match
=
keys
.
reduce
((
function
(
m
,
k
)
{
return
m
&&
(
d
[
k
]
===
cond
[
k
]);
}),
true
);
if
(
match
)
{
_results
.
push
(
d
);
}
else
{
continue
;
}
}
return
_results
;
};
return
LinkParser
;
})();
Annotator
=
Annotator
||
{};
Annotator
.
Notification
=
(
function
(
_super
)
{
__extends
(
Notification
,
_super
);
Notification
.
prototype
.
events
=
{
"click"
:
"hide"
};
Notification
.
prototype
.
options
=
{
html
:
"<div class='annotator-notice'></div>"
,
classes
:
{
show
:
"annotator-notice-show"
,
info
:
"annotator-notice-info"
,
success
:
"annotator-notice-success"
,
error
:
"annotator-notice-error"
}
};
function
Notification
(
options
)
{
this
.
hide
=
__bind
(
this
.
hide
,
this
);
this
.
show
=
__bind
(
this
.
show
,
this
);
Notification
.
__super__
.
constructor
.
call
(
this
,
$
(
this
.
options
.
html
).
appendTo
(
document
.
body
)[
0
],
options
);
}
Notification
.
prototype
.
show
=
function
(
message
,
status
)
{
if
(
status
==
null
)
{
status
=
Annotator
.
Notification
.
INFO
;
}
this
.
currentStatus
=
status
;
$
(
this
.
element
).
addClass
(
this
.
options
.
classes
.
show
).
addClass
(
this
.
options
.
classes
[
this
.
currentStatus
]).
html
(
Util
.
escape
(
message
||
""
));
setTimeout
(
this
.
hide
,
5000
);
return
this
;
};
Notification
.
prototype
.
hide
=
function
()
{
if
(
this
.
currentStatus
==
null
)
{
this
.
currentStatus
=
Annotator
.
Notification
.
INFO
;
}
$
(
this
.
element
).
removeClass
(
this
.
options
.
classes
.
show
).
removeClass
(
this
.
options
.
classes
[
this
.
currentStatus
]);
return
this
;
};
return
Notification
;
})(
Delegator
);
Annotator
.
Notification
.
INFO
=
'info'
;
Annotator
.
Notification
.
SUCCESS
=
'success'
;
Annotator
.
Notification
.
ERROR
=
'error'
;
$
(
function
()
{
var
notification
;
notification
=
new
Annotator
.
Notification
;
Annotator
.
showNotification
=
notification
.
show
;
return
Annotator
.
hideNotification
=
notification
.
hide
;
});
}).
call
(
this
);
//
//# sourceMappingURL=annotator.map
\ No newline at end of file
karma.config.js
View file @
a4ce9479
...
...
@@ -33,8 +33,9 @@ module.exports = function(config) {
'h/static/scripts/vendor/Markdown.Converter.js'
,
'h/static/scripts/vendor/unorm.js'
,
'h/static/scripts/vendor/uuid.js'
,
'h/static/scripts/annotator/annotator.min.js'
,
'h/static/scripts/annotator/plugin/auth.js'
,
'h/static/scripts/vendor/annotator.js'
,
'h/static/scripts/annotator/monkey.js'
,
'h/static/scripts/vendor/annotator.auth.js'
,
'h/static/scripts/annotator/plugin/bridge.js'
,
'h/static/scripts/annotator/plugin/bucket-bar.js'
,
'h/static/scripts/annotator/plugin/threading.js'
,
...
...
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