Commit 15f866ab authored by csillag's avatar csillag

Update to Annotator f58a5f3, with source map support

parent 2fd7f1d3
# 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.
util =
uuid: (-> counter = 0; -> counter++)()
getGlobal: -> (-> this)()
# Return the maximum z-index of any element in $elements (a jQuery collection).
maxZIndex: ($elements) ->
all = for el in $elements
if $(el).css('position') == 'static'
-1
else
parseInt($(el).css('z-index'), 10) or -1
Math.max.apply(Math, all)
mousePosition: (e, offsetEl) ->
offset = $(offsetEl).position()
{
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.
preventEventDefault: (event) ->
event?.preventDefault?()
# 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()
this._setupMatching() unless @options.noMatching
this._setupViewer()._setupEditor()
this._setupDynamicStyle()
# Perform initial DOM scan, unless told not to.
this._scan() unless (@options.noScan or @options.noMatching)
# Create adder
this.adder = $(this.html.adder).appendTo(@wrapper).hide()
# Initializes the components used for analyzing the DOM
_setupMatching: ->
@domMapper = new DomTextMapper()
@domMatcher = new DomTextMatcher @domMapper
@domMapper.setRootNode @wrapper[0]
this
# Perform a scan of the DOM. Required for finding anchors.
_scan: ->
@domMatcher.scan()
# 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') + '\u2026'
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
getHref: =>
uri = decodeURIComponent document.location.href
if document.location.hash then uri = uri.slice 0, (-1 * location.hash.length)
$('meta[property^="og:url"]').each -> uri = decodeURIComponent this.content
$('link[rel^="canonical"]').each -> uri = decodeURIComponent this.href
return uri
getRangeSelector: (range) ->
sr = range.serialize @wrapper[0]
selector =
type: "RangeSelector"
startContainer: sr.startContainer
startOffset: sr.startOffset
endContainer: sr.endContainer
endOffset: sr.endOffset
getTextQuoteSelector: (range) ->
unless range?
throw new Error "Called getTextQuoteSelector(range) with null range!"
rangeStart = range.start
unless rangeStart?
throw new Error "Called getTextQuoteSelector(range) on a range with no valid start."
startOffset = (@domMapper.getInfoForNode rangeStart).start
rangeEnd = range.end
unless rangeEnd?
throw new Error "Called getTextQuoteSelector(range) on a range with no valid end."
endOffset = (@domMapper.getInfoForNode rangeEnd).end
quote = @domMapper.getContentForCharRange startOffset, endOffset
[prefix, suffix] = @domMapper.getContextForCharRange startOffset, endOffset
selector =
type: "TextQuoteSelector"
exact: quote
prefix: prefix
suffix: suffix
getTextPositionSelector: (range) ->
startOffset = (@domMapper.getInfoForNode range.start).start
endOffset = (@domMapper.getInfoForNode range.end).end
selector =
type: "TextPositionSelector"
start: startOffset
end: endOffset
getQuoteForTarget: (target) ->
selector = this.findSelector target.selector, "TextQuoteSelector"
if selector?
this.normalizeString selector.exact
else
null
# 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: Gets the target identified by the given NormalizedRange.
#
#
# Returns an Object containing a `source` property and a `selector` Array.
getTargetFromRange: (range) ->
source: this.getHref()
selector: [
this.getRangeSelector range
this.getTextQuoteSelector range
this.getTextPositionSelector 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
# Do some normalization to get a "canonical" form of a string.
# Used to even out some browser differences.
normalizeString: (string) -> string.replace /\s{2,}/g, " "
# Find the given type of selector from an array of selectors, if it exists.
# If it does not exist, null is returned.
findSelector: (selectors, type) ->
for selector in selectors
if selector.type is type then return selector
null
# Try to determine the anchor position for a target
# using the saved Range selector. The quote is verified.
findAnchorFromRangeSelector: (target) ->
selector = this.findSelector target.selector, "RangeSelector"
unless selector? then return null
# Try to apply the saved XPath
normalizedRange = Range.sniff(selector).normalize @wrapper[0]
# Look up the saved quote
savedQuote = this.getQuoteForTarget target
if savedQuote?
# We have a saved quote, let's compare it to current content
startInfo = @domMapper.getInfoForNode normalizedRange.start
startOffset = startInfo.start
endInfo = @domMapper.getInfoForNode normalizedRange.end
endOffset = endInfo.end
content = @domMapper.getContentForCharRange startOffset, endOffset
currentQuote = this.normalizeString content
if currentQuote isnt savedQuote
console.log "Could not apply XPath selector to current document \
because the quote has changed. (Saved quote is '#{savedQuote}'. \
Current quote is '#{currentQuote}'.)"
return null
else
# console.log "Saved quote matches."
else
console.log "No saved quote, nothing to compare. Assume that it's OK."
range: normalizedRange
quote: savedQuote
# Try to determine the anchor position for a target
# using the saved position selector. The quote is verified.
findAnchorFromPositionSelector: (target) ->
selector = this.findSelector target.selector, "TextPositionSelector"
unless selector? then return null
savedQuote = this.getQuoteForTarget target
if savedQuote?
# We have a saved quote, let's compare it to current content
content = @domMapper.getContentForCharRange selector.start, selector.end
currentQuote = this.normalizeString content
if currentQuote isnt savedQuote
console.log "Could not apply position selector to current document \
because the quote has changed. (Saved quote is '#{savedQuote}'. \
Current quote is '#{currentQuote}'.)"
return null
else
# console.log "Saved quote matches."
else
console.log "No saved quote, nothing to compare. Assume that it's okay."
# OK, we have everything. Create a range from this.
mappings = this.domMapper.getMappingsForCharRange selector.start,
selector.end
browserRange = new Range.BrowserRange mappings.realRange
normalizedRange = browserRange.normalize @wrapper[0]
range: normalizedRange
quote: savedQuote
findAnchorWithTwoPhaseFuzzyMatching: (target) ->
# Fetch the quote and the context
quoteSelector = this.findSelector target.selector, "TextQuoteSelector"
prefix = quoteSelector?.prefix
suffix = quoteSelector?.suffix
quote = quoteSelector?.exact
# No context, to joy
unless (prefix? and suffix?) then return null
# Fetch the expected start and end positions
posSelector = this.findSelector target.selector, "TextPositionSelector"
expectedStart = posSelector?.start
expectedEnd = posSelector?.end
options =
contextMatchDistance: @domMapper.getDocLength() * 2
contextMatchThreshold: 0.5
patternMatchThreshold: 0.5
flexContext: true
result = @domMatcher.searchFuzzyWithContext prefix, suffix, quote,
expectedStart, expectedEnd, false, null, options
# If we did not got a result, give up
unless result.matches.length
console.log "Fuzzy matching did not return any results. Giving up on two-phase strategy."
return null
# here is our result
match = result.matches[0]
console.log "2-phase fuzzy found match:"
console.log match
# convert it to a Range
browserRange = new Range.BrowserRange match.realRange
normalizedRange = browserRange.normalize @wrapper[0]
# return the anchor
anchor =
range: normalizedRange
quote: unless match.exact then match.found
diffHTML: unless match.exact then match.comparison.diffHTML
diffCaseOnly: unless match.exact then match.exactExceptCase
anchor
findAnchorWithFuzzyMatching: (target) ->
# Fetch the quote
quoteSelector = this.findSelector target.selector, "TextQuoteSelector"
quote = quoteSelector?.exact
# No quote, no joy
unless quote? then return null
# Get a starting position for the search
posSelector = this.findSelector target.selector, "TextPositionSelector"
expectedStart = posSelector?.start
# Get full document length
len = this.domMapper.getDocLength()
# If we don't have the position saved, start at the middle of the doc
expectedStart ?= len / 2
# Do the fuzzy search
options =
matchDistance: len * 2
withFuzzyComparison: true
result = @domMatcher.searchFuzzy quote, expectedStart, false, null, options
# If we did not got a result, give up
unless result.matches.length
console.log "Fuzzy matching did not return any results. Giving up on one-phase strategy."
return null
# here is our result
match = result.matches[0]
console.log "1-phase fuzzy found match:"
console.log match
# convert it to a Range
browserRange = new Range.BrowserRange match.realRange
normalizedRange = browserRange.normalize @wrapper[0]
# return the anchor
anchor =
range: normalizedRange
quote: unless match.exact then match.found
diffHTML: unless match.exact then match.comparison.diffHTML
diffCaseOnly: unless match.exact then match.exactExceptCase
anchor
# Try to find the right anchoring point for a given target
#
# Returns a normalized range if succeeded, null otherwise
findAnchor: (target) ->
unless target?
throw new Error "Trying to find anchor for null target!"
# console.log "Trying to find anchor for target: "
# console.log target
strategies = [
# Simple strategy based on DOM Range
this.findAnchorFromRangeSelector
# Position-based strategy. (The quote is verified.)
# This can handle document structure changes,
# but not the content changes.
this.findAnchorFromPositionSelector
# Two-phased fuzzy text matching strategy. (Using context and quote.)
# This can handle document structure changes,
# and also content changes.
this.findAnchorWithTwoPhaseFuzzyMatching
# Naive fuzzy text matching strategy. (Using only the quote.)
# This can handle document structure changes,
# and also content changes.
this.findAnchorWithFuzzyMatching
]
error = null
anchor = null
for fn in strategies
try
anchor ?= fn.call this, target
catch error
unless error instanceof Range.RangeError
throw error
{error, anchor}
# 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, extracts the
# quoted text and serializes the range.
#
# 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]
ranges = annotation.ranges or @selectedRanges or []
# Upgrade format from v1.2.6 and earlier
if annotation.ranges? then delete annotation.ranges
annotation.target or= (this.getTargetFromRange(r) for r in ranges)
unless annotation.target?
throw new Error "Can not run setupAnnotation(). No target or selection available."
normedRanges = []
annotation.quote = []
for t in annotation.target
try
{anchor, error} = this.findAnchor t
if error instanceof Range.RangeError
this.publish('rangeNormalizeFail', [annotation, error.range, error])
if anchor?
t.quote = anchor.quote or $.trim(anchor.range.text())
t.diffHTML = anchor.diffHTML
t.diffCaseOnly = anchor.diffCaseOnly
normedRanges.push anchor.range
annotation.quote.push t.quote
else
console.log "Could not find anchor target for annotation '" +
annotation.id + "'."
catch exception
if exception.stack? then console.log exception.stack
console.log exception.message
console.log exception
annotation.ranges = []
annotation.highlights = []
for normed in normedRanges
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
# 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])
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)
window.DomTextMapper.changed child.parentNode,
"removed hilite (annotation deleted)"
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) if annotations.length
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
r = $(node).wrapAll(hl).parent().show()[0]
window.DomTextMapper.changed node, "created hilite"
r
# 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(':not([class^=annotator-hl])')[0]
return if this.isAnnotator(container)
if event and @selectedRanges.length
this.onSuccessfulSelection event
else
this.onFailedSelection event
onSuccessfulSelection: (event) ->
@adder
.css(util.mousePosition(event, @wrapper[0]))
.show()
onFailedSelection: (event) ->
@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().andSelf().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 or
# already displaying the viewer
return false if @mouseIsDown or @viewer.isShown()
annotations = $(event.target)
.parents('.annotator-hl')
.andSelf()
.map -> return $(this).data("annotation")
this.showViewer($.makeArray(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()
# Create a new annotation.
annotation = this.createAnnotation()
# Extract the quotation and serialize the ranges
annotation = this.setupAnnotation(annotation)
# Show a temporary highlight so the user can see what they selected
$(annotation.highlights).addClass('annotator-hl-temporary')
# 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)
# Subscribe to the editor events
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()
# Update the annotation when the editor is saved
update = =>
do cleanup
this.updateAnnotation(annotation)
# Remove handlers when finished
cleanup = =>
this.unsubscribe('annotationEditorHidden', cleanup)
this.unsubscribe('annotationEditorSubmit', update)
# Subscribe to the editor events
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
# 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 instance
options && instance[options].apply(instance, args)
else
instance = new Annotator(this, options)
$.data(this, 'annotator', instance)
# Export Annotator object.
this.Annotator = Annotator;
# 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)
this.on = this.subscribe
this.addEvents()
# Binds the function names in the @events Object to thier 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.
#
# 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 sel, functionName of @events
[selector..., event] = sel.split ' '
this.addEvent selector.join(' '), event, functionName
# Binds an event to a callback function represented by a String. An optional
# bindTo 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.
#
# bindTo - Selector String matching child elements. (default: @element)
# 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: (bindTo, event, functionName) ->
closure = => this[functionName].apply(this, arguments)
isBlankSelector = typeof bindTo is 'string' and bindTo.replace(/\s+/g, '') is ''
bindTo = @element if isBlankSelector
if typeof bindTo is 'string'
@element.delegate bindTo, event, closure
else
if this.isCustomEvent(event)
this.subscribe event, closure
else
$(bindTo).bind event, closure
this
# Checks to see if the provided event is a DOM event supported by jQuery or
# a custom user event.
#
# event - String event name.
#
# Examples
#
# this.isCustomEvent('click') # => false
# this.isCustomEvent('mousedown') # => false
# this.isCustomEvent('annotation:created') # => true
#
# Returns true if event is a custom user event.
isCustomEvent: (event) ->
[event] = event.split('.')
$.inArray(event, Delegator.natives) == -1
# 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
# Native jQuery events that should recieve an event object. Plugins can
# add thier 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)
# 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(', ')}")
# 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) =>
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) =>
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) =>
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-' + 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
# 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: 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))
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
Annotator = Annotator || {}
# Public: A simple notification system that can be used to display information,
# warnings and errors to the user. Display of notifications are controlled
# cmpletely by CSS by adding/removing the @options.classes.show class. This
# allows styling/animation using CSS rather than hardcoding styles.
class Annotator.Notification extends Delegator
# Sets events to be bound to the @element.
events:
"click": "hide"
# Default options.
options:
html: "<div class='annotator-notice'></div>"
classes:
show: "annotator-notice-show"
info: "annotator-notice-info"
success: "annotator-notice-success"
error: "annotator-notice-error"
# Public: Creates an instance of Notification and appends it to the
# document body.
#
# options - The following options can be provided.
# classes - A Object literal of classes used to determine state.
# html - An HTML string used to create the notification.
#
# Examples
#
# # Displays a notification with the text "Hello World"
# notification = new Annotator.Notification
# notification.show("Hello World")
#
# Returns
constructor: (options) ->
super $(@options.html).appendTo(document.body)[0], options
# Public: Displays the annotation with message and optional status. The
# message will hide itself after 5 seconds or if the user clicks on it.
#
# message - A message String to display (HTML will be escaped).
# status - A status constant. This will apply a class to the element for
# styling. (default: Annotator.Notification.INFO)
#
# Examples
#
# # Displays a notification with the text "Hello World"
# notification.show("Hello World")
#
# # Displays a notification with the text "An error has occurred"
# notification.show("An error has occurred", Annotator.Notification.ERROR)
#
# Returns itself.
show: (message, status=Annotator.Notification.INFO) =>
$(@element)
.addClass(@options.classes.show)
.addClass(@options.classes[status])
.html(Util.escape(message || ""))
setTimeout this.hide, 5000
this
# Public: Hides the notification.
#
# Examples
#
# # Hides the notification.
# notification.hide()
#
# Returns itself.
hide: =>
$(@element).removeClass(@options.classes.show)
this
# Constants for controlling the display of the notification. Each constant
# adds a different class to the Notification#element.
Annotator.Notification.INFO = 'show'
Annotator.Notification.SUCCESS = 'success'
Annotator.Notification.ERROR = 'error'
# Attach notification methods to the Annotation object on document ready.
$(->
notification = new Annotator.Notification
Annotator.showNotification = notification.show
Annotator.hideNotification = notification.hide
)
# Public: Plugin for managing user permissions under the rather more specialised
# permissions model used by [AnnotateIt](http://annotateit.org).
#
# element - A DOM Element upon which events are bound. When initialised by
# the Annotator it is the Annotator element.
# options - An Object literal containing custom options.
#
# Examples
#
# new Annotator.plugin.AnnotateItPermissions(annotator.element)
#
# Returns a new instance of the AnnotateItPermissions Object.
class Annotator.Plugin.AnnotateItPermissions extends Annotator.Plugin.Permissions
# A Object literal of default options for the class.
options:
# Displays an "Anyone can view this annotation" checkbox in the Editor.
showViewPermissionsCheckbox: true
# Displays an "Anyone can edit this annotation" checkbox in the Editor.
showEditPermissionsCheckbox: true
# Abstract user groups used by userAuthorize function
groups:
world: 'group:__world__'
authenticated: 'group:__authenticated__'
consumer: 'group:__consumer__'
userId: (user) -> user.userId
userString: (user) -> user.userId
# Public: Used by AnnotateItPermissions#authorize to determine whether a user can
# perform an action on an annotation.
#
# This should do more-or-less the same thing as the server-side authorization
# code, which is to be found at
# https://github.com/okfn/annotator-store/blob/master/annotator/authz.py
#
# Returns a Boolean, true if the user is authorised for the action provided.
userAuthorize: (action, annotation, user) ->
permissions = annotation.permissions or {}
action_field = permissions[action] or []
if @groups.world in action_field
return true
else if user? and user.userId? and user.consumerKey?
if user.userId == annotation.user and user.consumerKey == annotation.consumer
return true
else if @groups.authenticated in action_field
return true
else if user.consumerKey == annotation.consumer and @groups.consumer in action_field
return true
else if user.consumerKey == annotation.consumer and user.userId in action_field
return true
else if user.consumerKey == annotation.consumer and user.admin
return true
else
return false
else
return false
# Default permissions for all annotations. Anyone can
# read, but only annotation owners can update/delete/admin.
permissions: {
'read': ['group:__world__']
'update': []
'delete': []
'admin': []
}
# Event callback: Appends the @options.permissions, @options.user and
# @options.consumer objects to the provided annotation object.
#
# annotation - An annotation object.
#
# Examples
#
# annotation = {text: 'My comment'}
# permissions.addFieldsToAnnotation(annotation)
# console.log(annotation)
# # => {text: 'My comment', user: 'alice', consumer: 'annotateit', permissions: {...}}
#
# Returns nothing.
addFieldsToAnnotation: (annotation) =>
super
if annotation and @user
annotation.consumer = @user.consumerKey
# Field callback: Updates the state of the "anyone can…" checkboxes
#
# action - The action String, either "view" or "update"
# field - A DOM Element containing a form input.
# annotation - An annotation Object.
#
# Returns nothing.
updatePermissionsField: (action, field, annotation) =>
field = $(field).show()
input = field.find('input').removeAttr('disabled')
# Do not show field if current user is not admin.
field.hide() unless this.authorize('admin', annotation)
# See if we can authorise with any old user from this consumer
if @user and this.authorize(action, annotation || {}, {userId: '__nonexistentuser__', consumerKey: @user.consumerKey})
input.attr('checked', 'checked')
else
input.removeAttr('checked')
# Field callback: updates the annotation.permissions object based on the state
# of the field checkbox. If it is checked then permissions are set to world
# writable otherwise they use the original settings.
#
# action - The action String, either "view" or "update"
# field - A DOM Element representing the annotation editor.
# annotation - An annotation Object.
#
# Returns nothing.
updateAnnotationPermissions: (type, field, annotation) =>
annotation.permissions = @options.permissions unless annotation.permissions
dataKey = type + '-permissions'
if $(field).find('input').is(':checked')
annotation.permissions[type] = [if type == 'read' then @options.groups.world else @options.groups.consumer]
else
annotation.permissions[type] = []
# Sets the Permissions#user property on the basis of a received authToken. This plugin
# simply uses the entire token to represent the user.
#
# token - the authToken received by the Auth plugin
#
# Returns nothing.
_setAuthFromToken: (token) =>
this.setUser(token)
# 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()
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
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._getScholar()
this._getDublinCore()
this._getOpenGraph()
this._getFavicon()
# extract out/normalize some things
this._getTitle()
this._getLinks()
return @metadata
_getScholar: =>
@metadata.scholar = {}
for meta in $("meta")
name = $(meta).prop("name")
content = $(meta).prop("content")
if name.match(/^citation_/)
if @metadata.scholar[name]
@metadata.scholar[name].push(content)
else
@metadata.scholar[name] = [content]
_getDublinCore: =>
@metadata.dc = {}
for meta in $("meta")
name = $(meta).prop("name")
content = $(meta).prop("content")
nameParts = name.split(".")
if nameParts.length == 2 and nameParts[0].toLowerCase() == "dc"
n = nameParts[1]
if @metadata.dc[n]
@metadata.dc[n].push(content)
else
@metadata.dc[n] = [content]
_getOpenGraph: =>
@metadata.og = {}
for meta in $("meta")
property = $(meta).attr("property")
content = $(meta).prop("content")
if property
match = property.match(/^og:(.+)$/)
if match
n = match[1]
if @metadata.og[n]
@metadata.og[n].push(content)
else
@metadata.og[n] = [content]
_getTitle: =>
if @metadata.scholar.citation_title
@metadata.title = @metadata.scholar.citation_title[0]
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')
if rel in ["alternate", "canonical", "bookmark"] and type not in ["application/rss+xml", "application/atom+xml"]
@metadata.link.push(href: href, rel: rel, type: type)
# look for links in scholar metadata
for name, values of @metadata.scholar
if name == "citation_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 == "citation_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)
# hack to get a absolute url from a possibly relative one
_absoluteUrl: (url) ->
img = $("<img src='#{ url }'>")
url = img.prop('src')
img.prop('src', null)
return url
class Annotator.Plugin.Filter extends Annotator.Plugin
# Events and callbacks to bind to the Filter#element.
events:
".annotator-filter-property input focus": "_onFilterFocus"
".annotator-filter-property input blur": "_onFilterBlur"
".annotator-filter-property input keyup": "_onFilterKeyup"
".annotator-filter-previous click": "_onPreviousClick"
".annotator-filter-next click": "_onNextClick"
".annotator-filter-clear click": "_onClearClick"
# Common classes used to change plugin state.
classes:
active: 'annotator-filter-active'
hl:
hide: 'annotator-hl-filtered'
active: 'annotator-hl-active'
# HTML templates for the plugin UI.
html:
element: """
<div class="annotator-filter">
<strong>""" + Annotator._t('Navigate:') + """</strong>
<span class="annotator-filter-navigation">
<button class="annotator-filter-previous">""" + Annotator._t('Previous') + """</button>
<button class="annotator-filter-next">""" + Annotator._t('Next') + """</button>
</span>
<strong>""" + Annotator._t('Filter by:') + """</strong>
</div>
"""
filter: """
<span class="annotator-filter-property">
<label></label>
<input/>
<button class="annotator-filter-clear">""" + Annotator._t('Clear') + """</button>
</span>
"""
# Default options for the plugin.
options:
# A CSS selector or Element to append the plugin toolbar to.
appendTo: 'body'
# An array of filters can be provided on initialisation.
filters: []
# Adds a default filter on annotations.
addAnnotationFilter: true
# Public: Determines if the property is contained within the provided
# annotation property. Default is to split the string on spaces and only
# return true if all keywords are contained in the string. This method
# can be overridden by the user when initialising the plugin.
#
# string - An input String from the fitler.
# property - The annotation propery to query.
#
# Examples
#
# plugin.option.getKeywords('hello', 'hello world how are you?')
# # => Returns true
#
# plugin.option.getKeywords('hello bill', 'hello world how are you?')
# # => Returns false
#
# Returns an Array of keyword Strings.
isFiltered: (input, property) ->
return false unless input and property
for keyword in (input.split /\s*/)
return false if property.indexOf(keyword) == -1
return true
# Public: Creates a new instance of the Filter plugin.
#
# element - The Annotator element (this is ignored by the plugin).
# options - An Object literal of options.
#
# Examples
#
# filter = new Annotator.Plugin.Filter(annotator.element)
#
# Returns a new instance of the Filter plugin.
constructor: (element, options) ->
# As most events for this plugin are relative to the toolbar which is
# not inside the Annotator#Element we override the element property.
# Annotator#Element can still be accessed via @annotator.element.
element = $(@html.element).appendTo(options?.appendTo or @options.appendTo)
super element, options
@options.filters or= []
@filter = $(@html.filter)
@filters = []
@current = 0
# Public: Adds new filters. Updates the @highlights cache and creates event
# listeners on the annotator object.
#
# Returns nothing.
pluginInit: ->
for filter in @options.filters
this.addFilter(filter)
this.updateHighlights()
this._setupListeners()._insertSpacer()
if @options.addAnnotationFilter == true
this.addFilter {label: Annotator._t('Annotation'), property: 'text'}
# Adds margin to the current document to ensure that the annotation toolbar
# doesn't cover the page when not scrolled.
#
# Returns itself
_insertSpacer: ->
html = $('html')
currentMargin = parseInt(html.css('padding-top'), 10) || 0
html.css('padding-top', currentMargin + @element.outerHeight())
this
# Listens to annotation change events on the Annotator in order to refresh
# the @annotations collection.
# TODO: Make this more granular so the entire collection isn't reloaded for
# every single change.
#
# Returns itself.
_setupListeners: ->
events = [
'annotationsLoaded', 'annotationCreated',
'annotationUpdated', 'annotationDeleted'
]
for event in events
@annotator.subscribe event, this.updateHighlights
this
# Public: Adds a filter to the toolbar. The filter must have both a label
# and a property of an annotation object to filter on.
#
# options - An Object literal containing the filters options.
# label - A public facing String to represent the filter.
# property - An annotation property String to filter on.
# isFiltered - A callback Function that recieves the field input
# value and the annotation property value. See
# @options.isFiltered() for details.
#
# Examples
#
# # Set up a filter to filter on the annotation.user property.
# filter.addFilter({
# label: User,
# property: 'user'
# })
#
# Returns itself to allow chaining.
addFilter: (options) ->
filter = $.extend({
label: ''
property: ''
isFiltered: @options.isFiltered
}, options)
# Skip if a filter for this property has been loaded.
unless (f for f in @filters when f.property == filter.property).length
filter.id = 'annotator-filter-' + filter.property
filter.annotations = []
filter.element = @filter.clone().appendTo(@element)
filter.element.find('label')
.html(filter.label)
.attr('for', filter.id)
filter.element.find('input')
.attr({
id: filter.id
placeholder: Annotator._t('Filter by ') + filter.label + '\u2026'
})
filter.element.find('button').hide()
# Add the filter to the elements data store.
filter.element.data 'filter', filter
@filters.push filter
this
# Public: Updates the filter.annotations property. Then updates the state
# of the elements in the DOM. Calls the filter.isFiltered() method to
# determine if the annotation should remain.
#
# filter - A filter Object from @filters
#
# Examples
#
# filter.updateFilter(myFilter)
#
# Returns itself for chaining
updateFilter: (filter) ->
filter.annotations = []
this.updateHighlights()
this.resetHighlights()
input = $.trim filter.element.find('input').val()
if input
annotations = @highlights.map -> $(this).data('annotation')
for annotation in $.makeArray(annotations)
property = annotation[filter.property]
if filter.isFiltered input, property
filter.annotations.push annotation
this.filterHighlights()
# Public: Updates the @highlights property with the latest highlight
# elements in the DOM.
#
# Returns a jQuery collection of the highlight elements.
updateHighlights: =>
# Ignore any hidden highlights.
@highlights = @annotator.element.find('.annotator-hl:visible')
@filtered = @highlights.not(@classes.hl.hide)
# Public: Runs through each of the filters and removes all highlights not
# currently in scope.
#
# Returns itself for chaining.
filterHighlights: ->
activeFilters = $.grep @filters, (filter) -> !!filter.annotations.length
filtered = activeFilters[0]?.annotations || []
if activeFilters.length > 1
# If there are more than one filter then only annotations matched in every
# filter should remain.
annotations = []
$.each activeFilters, ->
$.merge(annotations, this.annotations)
uniques = []
filtered = []
$.each annotations, ->
if $.inArray(this, uniques) == -1
uniques.push this
else
filtered.push this
highlights = @highlights
for annotation, index in filtered
highlights = highlights.not(annotation.highlights)
highlights.addClass(@classes.hl.hide)
@filtered = @highlights.not(@classes.hl.hide)
this
# Public: Removes hidden class from all annotations.
#
# Returns itself for chaining.
resetHighlights: ->
@highlights.removeClass(@classes.hl.hide)
@filtered = @highlights
this
# Updates the filter field on focus.
#
# event - A focus Event object.
#
# Returns nothing
_onFilterFocus: (event) =>
input = $(event.target)
input.parent().addClass(@classes.active)
input.next('button').show()
# Updates the filter field on blur.
#
# event - A blur Event object.
#
# Returns nothing.
_onFilterBlur: (event) =>
unless event.target.value
input = $(event.target)
input.parent().removeClass(@classes.active)
input.next('button').hide()
# Updates the filter based on the id of the filter element.
#
# event - A keyup Event
#
# Returns nothing.
_onFilterKeyup: (event) =>
filter = $(event.target).parent().data('filter')
this.updateFilter filter if filter
# Locates the next/previous highlighted element in @highlights from the
# current one or goes to the very first/last element respectively.
#
# previous - If true finds the previously highlighted element.
#
# Returns itself.
_findNextHighlight: (previous) ->
return this unless @highlights.length
offset = if previous then 0 else -1
resetOffset = if previous then -1 else 0
operator = if previous then 'lt' else 'gt'
active = @highlights.not('.' + @classes.hl.hide)
current = active.filter('.' + @classes.hl.active)
current = active.eq(offset) unless current.length
annotation = current.data 'annotation'
index = active.index current[0]
next = active.filter(":#{operator}(#{index})").not(annotation.highlights).eq(resetOffset)
next = active.eq(resetOffset) unless next.length
this._scrollToHighlight next.data('annotation').highlights
# Locates the next highlighted element in @highlights from the current one
# or goes to the very first element.
#
# event - A click Event.
#
# Returns nothing
_onNextClick: (event) =>
this._findNextHighlight()
# Locates the previous highlighted element in @highlights from the current one
# or goes to the very last element.
#
# event - A click Event.
#
# Returns nothing
_onPreviousClick: (event) =>
this._findNextHighlight true
# Scrolls to the highlight provided. An adds an active class to it.
#
# highlight - Either highlight Element or an Array of elements. This value
# is usually retrieved from annotation.highlights.
#
# Returns nothing.
_scrollToHighlight: (highlight) ->
highlight = $(highlight)
@highlights.removeClass(@classes.hl.active)
highlight.addClass(@classes.hl.active)
$('html, body').animate({
scrollTop: highlight.offset().top - (@element.height() + 20)
}, 150);
# Clears the relevant input when the clear button is clicked.
#
# event - A click Event object.
#
# Returns nothing.
_onClearClick: (event) ->
$(event.target).prev('input').val('').keyup().blur()
# Public: A initialization function that sets up the Annotator and some of the
# default plugins. Intended for use with the annotator-full package.
#
# NOTE: This method is intened to be called via the jQuery .annotator() method
# although it is available directly on the Annotator instance.
#
# config - An object containing config options for the AnnotateIt store.
# storeUrl: API endpoint for the store (default: "http://annotateit.org/api")
# tokenUrl: API endpoint for auth token provider (default: "http://annotateit.org/api/token")
#
# options - An object containing plugin settings to override the defaults.
# If a plugin is entered with a 'falsy' value, the plugin will not be loaded.
#
# Examples
#
# $('#content').annotator().annotator('setupPlugins');
#
# // Only display a filter for the user field and disable tags.
# $('#content').annotator().annotator('setupPlugins', null, {
# Tags: false,
# Filter: {
# filters: [{label: 'User', property: 'user'}],
# addAnnotationFilter: false
# }
# });
#
# Returns itself for chaining.
Annotator::setupPlugins = (config={}, options={}) ->
win = util.getGlobal()
# Set up the default plugins.
plugins = ['Unsupported', 'Auth', 'Tags', 'Filter', 'Store', 'AnnotateItPermissions']
# If Showdown is included add the Markdown plugin.
if win.Showdown
plugins.push('Markdown')
# Check the config for store credentials and add relevant plugins.
uri = win.location.href.split(/#|\?/).shift() or ''
pluginConfig =
Tags: {}
Filter:
filters: [
{label: Annotator._t('User'), property: 'user'}
{label: Annotator._t('Tags'), property: 'tags'}
]
Auth:
tokenUrl: config.tokenUrl or 'http://annotateit.org/api/token'
Store:
prefix: config.storeUrl or 'http://annotateit.org/api'
annotationData:
uri: uri
loadFromSearch:
uri: uri
for own name, opts of options
if name not in plugins
plugins.push(name)
$.extend true, pluginConfig, options
for name in plugins
if name not of pluginConfig or pluginConfig[name]
this.addPlugin(name, pluginConfig[name])
# Plugin that renders annotation comments displayed in the Viewer in Markdown.
# Requires Showdown library to be present in the page when initialised.
class Annotator.Plugin.Markdown extends Annotator.Plugin
# Events to be bound to the @element.
events:
'annotationViewerTextField': 'updateTextField'
# Public: Initailises an instance of the Markdown plugin.
#
# element - The Annotator#element.
# options - An options Object (there are currently no options).
#
# Examples
#
# plugin = new Annotator.Plugin.Markdown(annotator.element)
#
# Returns a new instance of Annotator.Plugin.Markdown.
constructor: (element, options) ->
if Showdown?.converter?
super
@converter = new Showdown.converter()
else
console.error Annotator._t("To use the Markdown plugin, you must include Showdown into the page first.")
# Annotator event callback. Displays the annotation.text as a Markdown
# rendered version.
#
# field - The viewer field Element.
# annotation - The annotation Object being displayed.
#
# Examples
#
# # Normally called by Annotator#viewer()
# plugin.updateTextField(field, {text: 'My _markdown_ comment'})
# $(field).html() # => Returns "My <em>markdown</em> comment"
#
# Returns nothing
updateTextField: (field, annotation) =>
# Escape any HTML in the text to prevent XSS.
text = Annotator.Util.escape(annotation.text || '')
$(field).html(this.convert(text))
# Converts provided text into markdown.
#
# text - A String of Markdown to render as HTML.
#
# Examples
#
# plugin.convert('This is _very_ basic [Markdown](http://daringfireball.com)')
# # => Returns "This is <em>very<em> basic <a href="http://...">Markdown</a>"
#
# Returns HTML string.
convert: (text) ->
@converter.makeHtml text
# Public: Plugin for setting permissions on newly created annotations as well as
# managing user permissions such as viewing/editing/deleting annotions.
#
# element - A DOM Element upon which events are bound. When initialised by
# the Annotator it is the Annotator element.
# options - An Object literal containing custom options.
#
# Examples
#
# new Annotator.plugin.Permissions(annotator.element, {
# user: 'Alice'
# })
#
# Returns a new instance of the Permissions Object.
class Annotator.Plugin.Permissions extends Annotator.Plugin
# A Object literal consisting of event/method pairs to be bound to
# @element. See Delegator#addEvents() for details.
events:
'beforeAnnotationCreated': 'addFieldsToAnnotation'
# A Object literal of default options for the class.
options:
# Displays an "Anyone can view this annotation" checkbox in the Editor.
showViewPermissionsCheckbox: true
# Displays an "Anyone can edit this annotation" checkbox in the Editor.
showEditPermissionsCheckbox: true
# Public: Used by the plugin to determine a unique id for the @user property.
# By default this accepts and returns the user String but can be over-
# ridden in the @options object passed into the constructor.
#
# user - A String username or null if no user is set.
#
# Returns the String provided as user object.
userId: (user) -> user
# Public: Used by the plugin to determine a display name for the @user
# property. By default this accepts and returns the user String but can be
# over-ridden in the @options object passed into the constructor.
#
# user - A String username or null if no user is set.
#
# Returns the String provided as user object
userString: (user) -> user
# Public: Used by Permissions#authorize to determine whether a user can
# perform an action on an annotation. Overriding this function allows
# a far more complex permissions sysyem.
#
# By default this authorizes the action if any of three scenarios are true:
#
# 1) the annotation has a 'permissions' object, and either the field for
# the specified action is missing, empty, or contains the userId of the
# current user, i.e. @options.userId(@user)
#
# 2) the annotation has a 'user' property, and @options.userId(@user) matches
# 'annotation.user'
#
# 3) the annotation has no 'permissions' or 'user' properties
#
# annotation - The annotation on which the action is being requested.
# action - The action being requested: e.g. 'update', 'delete'.
# user - The user object (or string) requesting the action. This is usually
# automatically passed by Permissions#authorize as the current user (@user)
#
# permissions.setUser(null)
# permissions.authorize('update', {})
# # => true
#
# permissions.setUser('alice')
# permissions.authorize('update', {user: 'alice'})
# # => true
# permissions.authorize('update', {user: 'bob'})
# # => false
#
# permissions.setUser('alice')
# permissions.authorize('update', {
# user: 'bob',
# permissions: ['update': ['alice', 'bob']]
# })
# # => true
# permissions.authorize('destroy', {
# user: 'bob',
# permissions: [
# 'update': ['alice', 'bob']
# 'destroy': ['bob']
# ]
# })
# # => false
#
# Returns a Boolean, true if the user is authorised for the token provided.
userAuthorize: (action, annotation, user) ->
# Fine-grained custom authorization
if annotation.permissions
tokens = annotation.permissions[action] || []
if tokens.length == 0
# Empty or missing tokens array: anyone can perform action.
return true
for token in tokens
if this.userId(user) == token
return true
# No tokens matched: action should not be performed.
return false
# Coarse-grained authorization
else if annotation.user
if user
return this.userId(user) == this.userId(annotation.user)
else
return false
# No authorization info on annotation: free-for-all!
true
# Default user object.
user: ''
# Default permissions for all annotations. Anyone can do anything
# (assuming default userAuthorize function).
permissions: {
'read': []
'update': []
'delete': []
'admin': []
}
# The constructor called when a new instance of the Permissions
# plugin is created. See class documentation for usage.
#
# element - A DOM Element upon which events are bound..
# options - An Object literal containing custom options.
#
# Returns an instance of the Permissions object.
constructor: (element, options) ->
super
if @options.user
this.setUser(@options.user)
delete @options.user
# Public: Initializes the plugin and registers fields with the
# Annotator.Editor and Annotator.Viewer.
#
# Returns nothing.
pluginInit: ->
return unless Annotator.supported()
self = this
createCallback = (method, type) ->
(field, annotation) -> self[method].call(self, type, field, annotation)
# Set up user and default permissions from auth token if none currently given
if !@user and @annotator.plugins.Auth
@annotator.plugins.Auth.withToken(this._setAuthFromToken)
if @options.showViewPermissionsCheckbox == true
@annotator.editor.addField({
type: 'checkbox'
label: Annotator._t('Allow anyone to <strong>view</strong> this annotation')
load: createCallback('updatePermissionsField', 'read')
submit: createCallback('updateAnnotationPermissions', 'read')
})
if @options.showEditPermissionsCheckbox == true
@annotator.editor.addField({
type: 'checkbox'
label: Annotator._t('Allow anyone to <strong>edit</strong> this annotation')
load: createCallback('updatePermissionsField', 'update')
submit: createCallback('updateAnnotationPermissions', 'update')
})
# Setup the display of annotations in the Viewer.
@annotator.viewer.addField({
load: this.updateViewer
})
# Add a filter to the Filter plugin if loaded.
if @annotator.plugins.Filter
@annotator.plugins.Filter.addFilter({
label: Annotator._t('User')
property: 'user'
isFiltered: (input, user) =>
user = @options.userString(user)
return false unless input and user
for keyword in (input.split /\s*/)
return false if user.indexOf(keyword) == -1
return true
})
# Public: Sets the Permissions#user property.
#
# user - A String or Object to represent the current user.
#
# Examples
#
# permissions.setUser('Alice')
#
# permissions.setUser({id: 35, name: 'Alice'})
#
# Returns nothing.
setUser: (user) ->
@user = user
# Event callback: Appends the @user and @options.permissions objects to the
# provided annotation object. Only appends the user if one has been set.
#
# annotation - An annotation object.
#
# Examples
#
# annotation = {text: 'My comment'}
# permissions.addFieldsToAnnotation(annotation)
# console.log(annotation)
# # => {text: 'My comment', permissions: {...}}
#
# Returns nothing.
addFieldsToAnnotation: (annotation) =>
if annotation
annotation.permissions = @options.permissions
if @user
annotation.user = @user
# Public: Determines whether the provided action can be performed on the
# annotation. This uses the user-configurable 'userAuthorize' method to
# determine if an annotation is annotatable. See the default method for
# documentation on its behaviour.
#
# Returns a Boolean, true if the action can be performed on the annotation.
authorize: (action, annotation, user) ->
user = @user if user == undefined
if @options.userAuthorize
return @options.userAuthorize.call(@options, action, annotation, user)
else # userAuthorize nulled out: free-for-all!
return true
# Field callback: Updates the state of the "anyone can…" checkboxes
#
# action - The action String, either "view" or "update"
# field - A DOM Element containing a form input.
# annotation - An annotation Object.
#
# Returns nothing.
updatePermissionsField: (action, field, annotation) =>
field = $(field).show()
input = field.find('input').removeAttr('disabled')
# Do not show field if current user is not admin.
field.hide() unless this.authorize('admin', annotation)
# See if we can authorise without a user.
if this.authorize(action, annotation || {}, null)
input.attr('checked', 'checked')
else
input.removeAttr('checked')
# Field callback: updates the annotation.permissions object based on the state
# of the field checkbox. If it is checked then permissions are set to world
# writable otherwise they use the original settings.
#
# action - The action String, either "view" or "update"
# field - A DOM Element representing the annotation editor.
# annotation - An annotation Object.
#
# Returns nothing.
updateAnnotationPermissions: (type, field, annotation) =>
annotation.permissions = @options.permissions unless annotation.permissions
dataKey = type + '-permissions'
if $(field).find('input').is(':checked')
annotation.permissions[type] = []
else
# Clearly, the permissions model allows for more complex entries than this,
# but our UI presents a checkbox, so we can only interpret "prevent others
# from viewing" as meaning "allow only me to view". This may want changing
# in the future.
annotation.permissions[type] = [@user]
# Field callback: updates the annotation viewer to inlude the display name
# for the user obtained through Permissions#options.userString().
#
# field - A DIV Element representing the annotation field.
# annotation - An annotation Object to display.
# controls - A control Object to toggle the display of annotation controls.
#
# Returns nothing.
updateViewer: (field, annotation, controls) =>
field = $(field)
username = @options.userString annotation.user
if annotation.user and username and typeof username == 'string'
user = Annotator.Util.escape(@options.userString(annotation.user))
field.html(user).addClass('annotator-user')
else
field.remove()
if controls
controls.hideEdit() unless this.authorize('update', annotation)
controls.hideDelete() unless this.authorize('delete', annotation)
# Sets the Permissions#user property on the basis of a received authToken.
#
# token - the authToken received by the Auth plugin
#
# Returns nothing.
_setAuthFromToken: (token) =>
this.setUser(token.userId)
# Public: The Store plugin can be used to persist annotations to a database
# running on your server. It has a simple customisable interface that can be
# implemented with any web framework. It works by listening to events published
# by the Annotator and making appropriate requests to the server depending on
# the event.
#
# The store handles five distinct actions "read", "search", "create", "update"
# and "destory". The requests made can be customised with options when the
# plugin is added to the Annotator. Custom headers can also be sent with every
# request by setting a $.data key on the Annotation#element containing headers
# to send. e.g:
#
# annotator.element.data('annotation:headers', {
# 'X-My-Custom-Header': 'MyCustomValue'
# })
#
# This header will now be sent with every request.
class Annotator.Plugin.Store extends Annotator.Plugin
# The store listens for the following events published by the Annotator.
# - annotationCreated: A new annotation has been created.
# - annotationUpdated: An annotation has been updated.
# - annotationDeleted: An annotation has been deleted.
events:
'annotationCreated': 'annotationCreated'
'annotationDeleted': 'annotationDeleted'
'annotationUpdated': 'annotationUpdated'
# User customisable options available.
options:
# Custom meta data that will be attached to every annotation that is sent
# to the server. This _will_ override previous values.
annotationData: {}
# Should the plugin emulate HTTP methods like PUT and DELETE for
# interaction with legacy web servers? Setting this to `true` will fake
# HTTP `PUT` and `DELETE` requests with an HTTP `POST`, and will set the
# request header `X-HTTP-Method-Override` with the name of the desired
# method.
emulateHTTP: false
# If loadFromSearch is set, then we load the first batch of
# annotations from the 'search' URL as set in `options.urls`
# instead of the registry path 'prefix/read'.
#
# loadFromSearch: {
# 'limit': 0,
# 'all_fields': 1
# 'uri': 'http://this/document/only'
# }
loadFromSearch: false
# This is the API endpoint. If the server supports Cross Origin Resource
# Sharing (CORS) a full URL can be used here.
prefix: '/store'
# The server URLs for each available action. These URLs can be anything but
# must respond to the appropraite HTTP method. The token ":id" can be used
# anywhere in the URL and will be replaced with the annotation id.
#
# read: GET
# create: POST
# update: PUT
# destroy: DELETE
# search: GET
urls:
create: '/annotations'
read: '/annotations/:id'
update: '/annotations/:id'
destroy: '/annotations/:id'
search: '/search'
# Public: The contsructor initailases the Store instance. It requires the
# Annotator#element and an Object of options.
#
# element - This must be the Annotator#element in order to listen for events.
# options - An Object of key/value user options.
#
# Examples
#
# store = new Annotator.Plugin.Store(Annotator.element, {
# prefix: 'http://annotateit.org',
# annotationData: {
# uri: window.location.href
# }
# })
#
# Returns a new instance of Store.
constructor: (element, options) ->
super
@annotations = []
# Public: Initialises the plugin and loads the latest annotations. If the
# Auth plugin is also present it will request an auth token before loading
# any annotations.
#
# Examples
#
# store.pluginInit()
#
# Returns nothing.
pluginInit: ->
return unless Annotator.supported()
if @annotator.plugins.Auth
@annotator.plugins.Auth.withToken(this._getAnnotations)
else
this._getAnnotations()
# Checks the loadFromSearch option and if present loads annotations using
# the Store#loadAnnotationsFromSearch method rather than Store#loadAnnotations.
#
# Returns nothing.
_getAnnotations: =>
if @options.loadFromSearch
this.loadAnnotationsFromSearch(@options.loadFromSearch)
else
this.loadAnnotations()
# Public: Callback method for annotationCreated event. Receives an annotation
# and sends a POST request to the sever using the URI for the "create" action.
#
# annotation - An annotation Object that was created.
#
# Examples
#
# store.annotationCreated({text: "my new annotation comment"})
# # => Results in an HTTP POST request to the server containing the
# # annotation as serialised JSON.
#
# Returns nothing.
annotationCreated: (annotation) ->
# Pre-register the annotation so as to save the list of highlight
# elements.
if annotation not in @annotations
this.registerAnnotation(annotation)
this._apiRequest('create', annotation, (data) =>
# Update with (e.g.) ID from server.
if not data.id?
console.warn Annotator._t("Warning: No ID returned from server for annotation "), annotation
this.updateAnnotation annotation, data
)
else
# This is called to update annotations created at load time with
# the highlight elements created by Annotator.
this.updateAnnotation annotation, {}
# Public: Callback method for annotationUpdated event. Receives an annotation
# and sends a PUT request to the sever using the URI for the "update" action.
#
# annotation - An annotation Object that was updated.
#
# Examples
#
# store.annotationUpdated({id: "blah", text: "updated annotation comment"})
# # => Results in an HTTP PUT request to the server containing the
# # annotation as serialised JSON.
#
# Returns nothing.
annotationUpdated: (annotation) ->
if annotation in this.annotations
this._apiRequest 'update', annotation, ((data) => this.updateAnnotation(annotation, data))
# Public: Callback method for annotationDeleted event. Receives an annotation
# and sends a DELETE request to the server using the URI for the destroy
# action.
#
# annotation - An annotation Object that was deleted.
#
# Examples
#
# store.annotationDeleted({text: "my new annotation comment"})
# # => Results in an HTTP DELETE request to the server.
#
# Returns nothing.
annotationDeleted: (annotation) ->
if annotation in this.annotations
this._apiRequest 'destroy', annotation, (() => this.unregisterAnnotation(annotation))
# Public: Registers an annotation with the Store. Used to check whether an
# annotation has already been created when using Store#annotationCreated().
#
# NB: registerAnnotation and unregisterAnnotation do no error-checking/
# duplication avoidance of their own. Use with care.
#
# annotation - An annotation Object to resister.
#
# Examples
#
# store.registerAnnotation({id: "annotation"})
#
# Returns registed annotations.
registerAnnotation: (annotation) ->
@annotations.push(annotation)
# Public: Unregisters an annotation with the Store.
#
# NB: registerAnnotation and unregisterAnnotation do no error-checking/
# duplication avoidance of their own. Use with care.
#
# annotation - An annotation Object to unresister.
#
# Examples
#
# store.unregisterAnnotation({id: "annotation"})
#
# Returns remaining registed annotations.
unregisterAnnotation: (annotation) ->
@annotations.splice(@annotations.indexOf(annotation), 1)
# Public: Extends the provided annotation with the contents of the data
# Object. Will only extend annotations that have been registered with the
# store. Also updates the annotation object stored in the 'annotation' data
# store.
#
# annotation - An annotation Object to extend.
# data - An Object containing properties to add to the annotation.
#
# Examples
#
# annotation = $('.annotation-hl:first').data('annotation')
# store.updateAnnotation(annotation, {extraProperty: "bacon sarnie"})
# console.log($('.annotation-hl:first').data('annotation').extraProperty)
# # => Outputs "bacon sarnie"
#
# Returns nothing.
updateAnnotation: (annotation, data) ->
if annotation not in this.annotations
console.error Annotator._t("Trying to update unregistered annotation!")
else
$.extend(annotation, data)
# Update the elements with our copies of the annotation objects (e.g.
# with ids from the server).
$(annotation.highlights).data('annotation', annotation)
# Public: Makes a request to the server for all annotations.
#
# Examples
#
# store.loadAnnotations()
#
# Returns nothing.
loadAnnotations: () ->
this._apiRequest 'read', null, this._onLoadAnnotations
# Callback method for Store#loadAnnotations(). Processes the data
# returned from the server (a JSON array of annotation Objects) and updates
# the registry as well as loading them into the Annotator.
#
# data - An Array of annotation Objects
#
# Examples
#
# console.log @annotation # => []
# store._onLoadAnnotations([{}, {}, {}])
# console.log @annotation # => [{}, {}, {}]
#
# Returns nothing.
_onLoadAnnotations: (data=[]) =>
annotationMap = {}
for a in @annotations
annotationMap[a.id] = a
newData = []
for a in data
if annotationMap[a.id]
annotation = annotationMap[a.id]
this.updateAnnotation annotation, a
else
newData.push(a)
@annotations = @annotations.concat(newData)
@annotator.loadAnnotations(newData.slice()) # Clone array
# Public: Performs the same task as Store.#loadAnnotations() but calls the
# 'search' URI with an optional query string.
#
# searchOptions - Object literal of query string parameters.
#
# Examples
#
# store.loadAnnotationsFromSearch({
# limit: 100,
# uri: window.location.href
# })
#
# Returns nothing.
loadAnnotationsFromSearch: (searchOptions) ->
this._apiRequest 'search', searchOptions, this._onLoadAnnotationsFromSearch
# Callback method for Store#loadAnnotationsFromSearch(). Processes the data
# returned from the server (a JSON array of annotation Objects) and updates
# the registry as well as loading them into the Annotator.
#
# data - An Array of annotation Objects
#
# Returns nothing.
_onLoadAnnotationsFromSearch: (data={}) =>
this._onLoadAnnotations(data.rows || [])
# Public: Dump an array of serialized annotations
#
# param - comment
#
# Examples
#
# example
#
# Returns
dumpAnnotations: ->
(JSON.parse(this._dataFor(ann)) for ann in @annotations)
# Callback method for Store#loadAnnotationsFromSearch(). Processes the data
# returned from the server (a JSON array of annotation Objects) and updates
# the registry as well as loading them into the Annotator.
# Returns the jQuery XMLHttpRequest wrapper enabling additional callbacks to
# be applied as well as custom error handling.
#
# action - The action String eg. "read", "search", "create", "update"
# or "destory".
# obj - The data to be sent, either annotation object or query string.
# onSuccess - A callback Function to call on successful request.
#
# Examples:
#
# store._apiRequest('read', {id: 4}, (data) -> console.log(data))
# # => Outputs the annotation returned from the server.
#
# Returns jXMLHttpRequest object.
_apiRequest: (action, obj, onSuccess) ->
id = obj && obj.id
url = this._urlFor(action, id)
options = this._apiRequestOptions(action, obj, onSuccess)
request = $.ajax(url, options)
# Append the id and action to the request object
# for use in the error callback.
request._id = id
request._action = action
request
# Builds an options object suitable for use in a jQuery.ajax() call.
#
# action - The action String eg. "read", "search", "create", "update"
# or "destory".
# obj - The data to be sent, either annotation object or query string.
# onSuccess - A callback Function to call on successful request.
#
# Also extracts any custom headers from data stored on the Annotator#element
# under the 'annotator:headers' key. These headers should be stored as key/
# value pairs and will be sent with every request.
#
# Examples
#
# annotator.element.data('annotator:headers', {
# 'X-My-Custom-Header': 'CustomValue',
# 'X-Auth-User-Id': 'bill'
# })
#
# Returns Object literal of $.ajax() options.
_apiRequestOptions: (action, obj, onSuccess) ->
method = this._methodFor(action)
opts = {
type: method,
headers: @element.data('annotator:headers'),
dataType: "json",
success: (onSuccess or ->),
error: this._onError
}
# If emulateHTTP is enabled, we send a POST and put the real method in an
# HTTP request header.
if @options.emulateHTTP and method in ['PUT', 'DELETE']
opts.headers = $.extend(opts.headers, {'X-HTTP-Method-Override': method})
opts.type = 'POST'
# Don't JSONify obj if making search request.
if action is "search"
opts = $.extend(opts, data: obj)
return opts
data = obj && this._dataFor(obj)
# If emulateJSON is enabled, we send a form request (the correct
# contentType will be set automatically by jQuery), and put the
# JSON-encoded payload in the "json" key.
if @options.emulateJSON
opts.data = {json: data}
if @options.emulateHTTP
opts.data._method = method
return opts
opts = $.extend(opts, {
data: data
contentType: "application/json; charset=utf-8"
})
return opts
# Builds the appropriate URL from the options for the action provided.
#
# action - The action String.
# id - The annotation id as a String or Number.
#
# Examples
#
# store._urlFor('update', 34)
# # => Returns "/store/annotations/34"
#
# store._urlFor('search')
# # => Returns "/store/search"
#
# Returns URL String.
_urlFor: (action, id) ->
url = if @options.prefix? then @options.prefix else ''
url += @options.urls[action]
# If there's a '/:id' in the URL, either fill in the ID or remove the
# slash:
url = url.replace(/\/:id/, if id? then '/' + id else '')
# If there's a bare ':id' in the URL, then substitute directly:
url = url.replace(/:id/, if id? then id else '')
url
# Maps an action to an HTTP method.
#
# action - The action String.
#
# Examples
#
# store._methodFor('read') # => "GET"
# store._methodFor('update') # => "PUT"
# store._methodFor('destroy') # => "DELETE"
#
# Returns HTTP method String.
_methodFor: (action) ->
table = {
'create': 'POST'
'read': 'GET'
'update': 'PUT'
'destroy': 'DELETE'
'search': 'GET'
}
table[action]
# Creates a JSON serialisation of an annotation.
#
# annotation - An annotation Object to serialise.
#
# Examples
#
# store._dataFor({id: 32, text: 'my annotation comment'})
# # => Returns '{"id": 32, "text":"my annotation comment"}'
#
# Returns
_dataFor: (annotation) ->
# Store a reference to the highlights array. We can't serialize
# a list of HTMLElement objects.
highlights = annotation.highlights
delete annotation.highlights
# Preload with extra data.
$.extend(annotation, @options.annotationData)
data = JSON.stringify(annotation)
# Restore the highlights array.
annotation.highlights = highlights if highlights
data
# jQuery.ajax() callback. Displays an error notification to the user if
# the request failed.
#
# xhr - The jXMLHttpRequest object.
#
# Returns nothing.
_onError: (xhr) =>
action = xhr._action
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" this annotation")
if xhr._action == 'search'
message = Annotator._t("Sorry we could not search the store for annotations")
else if xhr._action == 'read' && !xhr._id
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" the annotations from the store")
switch xhr.status
when 401 then message = Annotator._t("Sorry you are not allowed to ") + action + Annotator._t(" this annotation")
when 404 then message = Annotator._t("Sorry we could not connect to the annotations store")
when 500 then message = Annotator._t("Sorry something went wrong with the annotation store")
Annotator.showNotification message, Annotator.Notification.ERROR
console.error Annotator._t("API request failed:") + " '#{xhr.status}'"
# Public: Tags plugin allows users to tag thier annotations with metadata
# stored in an Array on the annotation as tags.
class Annotator.Plugin.Tags extends Annotator.Plugin
options:
# Configurable function which accepts a string (the contents)
# of the tags input as an argument, and returns an array of
# tags.
parseTags: (string) ->
string = $.trim(string)
tags = []
tags = string.split(/\s+/) if string
tags
# Configurable function which accepts an array of tags and
# returns a string which will be used to fill the tags input.
stringifyTags: (array) ->
array.join(" ")
# The field element added to the Annotator.Editor wrapped in jQuery. Cached to
# save having to recreate it everytime the editor is displayed.
field: null
# The input element added to the Annotator.Editor wrapped in jQuery. Cached to
# save having to recreate it everytime the editor is displayed.
input: null
# Public: Initialises the plugin and adds custom fields to both the
# annotator viewer and editor. The plugin also checks if the annotator is
# supported by the current browser.
#
# Returns nothing.
pluginInit: ->
return unless Annotator.supported()
@field = @annotator.editor.addField({
label: Annotator._t('Add some tags here') + '\u2026'
load: this.updateField
submit: this.setAnnotationTags
})
@annotator.viewer.addField({
load: this.updateViewer
})
# Add a filter to the Filter plugin if loaded.
if @annotator.plugins.Filter
@annotator.plugins.Filter.addFilter
label: Annotator._t('Tag')
property: 'tags'
isFiltered: Annotator.Plugin.Tags.filterCallback
@input = $(@field).find(':input')
# Public: Extracts tags from the provided String.
#
# string - A String of tags seperated by spaces.
#
# Examples
#
# plugin.parseTags('cake chocolate cabbage')
# # => ['cake', 'chocolate', 'cabbage']
#
# Returns Array of parsed tags.
parseTags: (string) ->
@options.parseTags(string)
# Public: Takes an array of tags and serialises them into a String.
#
# array - An Array of tags.
#
# Examples
#
# plugin.stringifyTags(['cake', 'chocolate', 'cabbage'])
# # => 'cake chocolate cabbage'
#
# Returns Array of parsed tags.
stringifyTags: (array) ->
@options.stringifyTags(array)
# Annotator.Editor callback function. Updates the @input field with the
# tags attached to the provided annotation.
#
# field - The tags field Element containing the input Element.
# annotation - An annotation object to be edited.
#
# Examples
#
# field = $('<li><input /></li>')[0]
# plugin.updateField(field, {tags: ['apples', 'oranges', 'cake']})
# field.value # => Returns 'apples oranges cake'
#
# Returns nothing.
updateField: (field, annotation) =>
value = ''
value = this.stringifyTags(annotation.tags) if annotation.tags
@input.val(value)
# Annotator.Editor callback function. Updates the annotation field with the
# data retrieved from the @input property.
#
# field - The tags field Element containing the input Element.
# annotation - An annotation object to be updated.
#
# Examples
#
# annotation = {}
# field = $('<li><input value="cake chocolate cabbage" /></li>')[0]
#
# plugin.setAnnotationTags(field, annotation)
# annotation.tags # => Returns ['cake', 'chocolate', 'cabbage']
#
# Returns nothing.
setAnnotationTags: (field, annotation) =>
annotation.tags = this.parseTags(@input.val())
# Annotator.Viewer callback function. Updates the annotation display with tags
# removes the field from the Viewer if there are no tags to display.
#
# field - The Element to populate with tags.
# annotation - An annotation object to be display.
#
# Examples
#
# field = $('<div />')[0]
# plugin.updateField(field, {tags: ['apples']})
# field.innerHTML # => Returns '<span class="annotator-tag">apples</span>'
#
# Returns nothing.
updateViewer: (field, annotation) ->
field = $(field)
if annotation.tags and $.isArray(annotation.tags) and annotation.tags.length
field.addClass('annotator-tags').html(->
string = $.map(annotation.tags,(tag) ->
'<span class="annotator-tag">' + Annotator.Util.escape(tag) + '</span>'
).join(' ')
)
else
field.remove()
# Checks an input string of keywords against an array of tags. If the keywords
# match _all_ tags the function returns true. This should be used as a callback
# in the Filter plugin.
#
# input - A String of keywords from a input field.
#
# Examples
#
# Tags.filterCallback('cat dog mouse', ['cat', 'dog', 'mouse']) //=> true
# Tags.filterCallback('cat dog', ['cat', 'dog', 'mouse']) //=> true
# Tags.filterCallback('cat dog', ['cat']) //=> false
#
# Returns true if the input keywords match all tags.
Annotator.Plugin.Tags.filterCallback = (input, tags = []) ->
matches = 0
keywords = []
if input
keywords = input.split(/\s+/g)
for keyword in keywords when tags.length
matches += 1 for tag in tags when tag.indexOf(keyword) != -1
matches == keywords.length
# Plugin that will display a notification to the user if thier browser does
# not support the Annotator.
class Annotator.Plugin.Unsupported extends Annotator.Plugin
# Options Object, message sets the message displayed in the browser.
options:
message: Annotator._t("Sorry your current browser does not support the Annotator")
# Public: Checks the Annotator.supported() method and if unsupported displays
# @options.message in a notification.
#
# Returns nothing.
pluginInit: ->
unless Annotator.supported()
$(=>
# On document load display notification.
Annotator.showNotification(@options.message)
# Add a class if we're in IE6. A bit of a hack but we need to be able
# to set the notification position in the CSS.
if (window.XMLHttpRequest == undefined) and (ActiveXObject != undefined)
$('html').addClass('ie6')
)
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"
# Annotator <= 1.2.6 upgrade code
new Range.SerializedRange
startContainer: r.start
startOffset: r.startOffset
endContainer: r.end
endOffset: r.endOffset
else if typeof r.startContainer 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: (@message, @range, @type, @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 = {}
nr = {}
for p in ['start', 'end']
node = this[p + 'Container']
offset = this[p + 'Offset']
# console.log p + " node: " + node + "; offset: " + offset
if node.nodeType is Node.ELEMENT_NODE
# Get specified node.
it = node.childNodes[offset]
# If it doesn't exist, that means we need the end of the
# previous one.
node = it or node.childNodes[offset - 1]
# Is this an IMG?
isImg = node.nodeType is Node.ELEMENT_NODE and node.tagName.toLowerCase() is "img"
if isImg
# This is an img. Don't do anything.
offset = 0
else
# if node doesn't have any children, it's a <br> or <hr> or
# other self-closing tag, and we actually want the textNode
# that ends just before it
while node.nodeType is Node.ELEMENT_NODE and not node.firstChild and not isImg
it = null # null out ref to node so offset is correctly calculated below.
node = node.previousSibling
# Try to find a text child
while (node.nodeType isnt Node.TEXT_NODE)
node = node.firstChild
offset = if it then 0 else node.nodeValue.length
r[p] = node
r[p + 'Offset'] = offset
r[p + 'Img'] = isImg
changed = false
if r.startOffset > 0
if r.start.data.length > r.startOffset
nr.start = r.start.splitText r.startOffset
# console.log "Had to split element at start, at offset " + r.startOffset
changed = true
else
nr.start = r.start.nextSibling
# console.log "No split neaded at start, already cut."
else
nr.start = r.start
# console.log "No split needed at start, offset is 0."
if r.start is r.end and not r.startImg
if (r.endOffset - r.startOffset) < nr.start.nodeValue.length
nr.start.splitText(r.endOffset - r.startOffset)
# console.log "But had to split element at end at offset " +
# (r.endOffset - r.startOffset)
changed = true
else
# console.log "End is clean, too."
nr.end = nr.start
else
if r.endOffset < r.end.nodeValue.length and not r.endImg
r.end.splitText r.endOffset
# console.log "Besides start, had to split element at end at offset" +
# r.endOffset
changed = true
else
# console.log "End is clean."
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 window.DomTextMapper? and changed
# console.log "Ranged normalization changed the DOM, updating d-t-m"
window.DomTextMapper.changed nr.commonAncestor, "range normalization"
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
isImg = node.nodeType is 1 and node.tagName.toLowerCase() is "img"
if isEnd and not isImg then [xpath, offset + node.nodeValue.length] else [xpath, offset]
start = serialization(@start)
end = serialization(@end, true)
new Range.SerializedRange({
# XPath strings
startContainer: start[0]
endContainer: 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.
# startContainer: 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.
# endContainer: 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) ->
@startContainer = obj.startContainer
@startOffset = obj.startOffset
@endContainer = obj.endContainer
@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']
xpath = this[p + 'Container']
try
node = Range.nodeFromXPath(xpath, root)
catch e
throw new Range.RangeError(
"Error while finding #{p} node: #{xpath}: #{e}", range, p, e
)
if not node
throw new Range.RangeError("Couldn't find #{p} node: #{xpath}", range, 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'] + (if p is "start" then 1 else 0)
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
# console.log "Going on, because this ends at " +
# (length + tn.nodeValue.length) + ", and we are looking for " +
# targetOffset
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: ->
{
startContainer: @startContainer
startOffset: @startOffset
endContainer: @endContainer
endOffset: @endOffset
}
# 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) =>
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) =>
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
# 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
checkOrientation: ->
this.resetOrientation()
window = $(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
# 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
// Generated by CoffeeScript 1.6.3
/*
** Annotator 1.2.6-dev-794ee6a
** Annotator 1.2.6-dev-f58a5f3
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-07-25 10:20:52Z
** Built at: 2013-09-04 16:14:21Z
*/
/*
//
*/
// Generated by CoffeeScript 1.6.3
(function() {
var base64Decode, base64UrlDecode, createDateFromISO8601, parseToken,
__hasProp = {}.hasOwnProperty,
......@@ -223,3 +230,6 @@
})(Annotator.Plugin);
}).call(this);
//
//@ sourceMappingURL=annotator.auth.map
\ No newline at end of file
{"version":3,"file":"annotator.auth.js","sources":["_preamble.coffee","_annotator_mapsrc/src/plugin/auth.coffee"],"names":[],"mappings":"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"}
\ No newline at end of file
// Generated by CoffeeScript 1.6.3
/*
** Annotator 1.2.6-dev-b61d9f7
** Annotator 1.2.6-dev-f58a5f3
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-05-28 12:14:39Z
** Built at: 2013-09-04 16:14:22Z
*/
/*
//
*/
// Generated by CoffeeScript 1.6.3
(function() {
var _ref,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
......@@ -31,7 +38,8 @@
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);
this.uri = __bind(this.uri, this);
_ref = Document.__super__.constructor.apply(this, arguments);
return _ref;
}
......@@ -47,7 +55,6 @@
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++) {
......@@ -61,7 +68,6 @@
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++) {
......@@ -72,7 +78,6 @@
}
return (function() {
var _results;
_results = [];
for (href in uniqueUrls) {
_results.push(href);
......@@ -98,7 +103,6 @@
Document.prototype._getScholar = function() {
var content, meta, name, _i, _len, _ref1, _results;
this.metadata.scholar = {};
_ref1 = $("meta");
_results = [];
......@@ -121,7 +125,6 @@
Document.prototype._getDublinCore = function() {
var content, meta, n, name, nameParts, _i, _len, _ref1, _results;
this.metadata.dc = {};
_ref1 = $("meta");
_results = [];
......@@ -146,7 +149,6 @@
Document.prototype._getOpenGraph = function() {
var content, match, meta, n, property, _i, _len, _ref1, _results;
this.metadata.og = {};
_ref1 = $("meta");
_results = [];
......@@ -185,7 +187,6 @@
Document.prototype._getLinks = function() {
var doi, href, id, l, link, name, rel, type, url, values, _i, _j, _k, _len, _len1, _len2, _ref1, _ref2, _ref3, _results;
this.metadata.link = [
{
href: document.location.href
......@@ -237,7 +238,6 @@
if (name === "identifier") {
_results.push((function() {
var _l, _len3, _results1;
_results1 = [];
for (_l = 0, _len3 = values.length; _l < _len3; _l++) {
id = values[_l];
......@@ -260,7 +260,6 @@
Document.prototype._getFavicon = function() {
var link, _i, _len, _ref1, _ref2, _results;
_ref1 = $("link");
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
......@@ -276,7 +275,6 @@
Document.prototype._absoluteUrl = function(url) {
var img;
img = $("<img src='" + url + "'>");
url = img.prop('src');
img.prop('src', null);
......@@ -288,3 +286,6 @@
})(Annotator.Plugin);
}).call(this);
//
//@ sourceMappingURL=annotator.document.map
\ No newline at end of file
{"version":3,"file":"annotator.document.js","sources":["_preamble.coffee","_annotator_mapsrc/src/plugin/document.coffee"],"names":[],"mappings":"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"}
\ No newline at end of file
// Generated by CoffeeScript 1.6.3
/*
** Annotator 1.2.6-dev-f85315e
** Annotator 1.2.6-dev-f58a5f3
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-08-28 02:11:19Z
** Built at: 2013-09-04 16:14:18Z
*/
/*
//
*/
// Generated by CoffeeScript 1.6.3
(function() {
var $, Annotator, Delegator, LinkParser, Range, Util, findChild, fn, functions, g, getNodeName, getNodePosition, gettext, simpleXPathJQuery, simpleXPathPure, util, _Annotator, _gettext, _i, _j, _len, _len1, _ref, _ref1, _t,
__slice = [].slice,
......@@ -2116,3 +2123,6 @@
});
}).call(this);
//
//@ sourceMappingURL=annotator.map
\ No newline at end of file
{"version":3,"file":"annotator.js","sources":["_preamble.coffee","_annotator_mapsrc/src/xpath.coffee","_annotator_mapsrc/src/extensions.coffee","_annotator_mapsrc/src/console.coffee","_annotator_mapsrc/src/class.coffee","_annotator_mapsrc/src/range.coffee","_annotator_mapsrc/src/annotator.coffee","_annotator_mapsrc/src/widget.coffee","_annotator_mapsrc/src/editor.coffee","_annotator_mapsrc/src/viewer.coffee","_annotator_mapsrc/src/notification.coffee"],"names":[],"mappings":"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"}
\ No newline at end of file
// Generated by CoffeeScript 1.6.3
/*
** Annotator 1.2.7-dev-9612762
** Annotator 1.2.6-dev-f58a5f3
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-07-17 12:06:57Z
** Built at: 2013-09-04 16:14:25Z
*/
......@@ -221,8 +221,5 @@
}).call(this);
/*
//
*/
//@ sourceMappingURL=annotator.permissions.map
\ No newline at end of file
{"version":3,"file":"annotator.permissions.js","sources":["_preamble.coffee","_annotator_mapsrc/src/plugin/permissions.coffee"],"names":[],"mappings":"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"}
\ No newline at end of file
// Generated by CoffeeScript 1.6.3
/*
** Annotator 1.2.7-dev-48fa9e6
** Annotator 1.2.6-dev-f58a5f3
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-07-16 23:25:13Z
** Built at: 2013-09-04 16:14:26Z
*/
......@@ -287,8 +287,5 @@
}).call(this);
/*
//
*/
//@ sourceMappingURL=annotator.store.map
\ No newline at end of file
{"version":3,"file":"annotator.store.js","sources":["_preamble.coffee","_annotator_mapsrc/src/plugin/store.coffee"],"names":[],"mappings":"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"}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment