Commit 4efa180a authored by Sean Roberts's avatar Sean Roberts Committed by GitHub

Merge pull request #380 from evidentpoint/remove-annotator-dep

Remove dependency Annotator.js
parents 5e284a96 54d8378f
$ = require('jquery')
###
** Adapted from:
** https://github.com/openannotation/annotator/blob/v1.2.x/src/class.coffee
**
** Annotator v1.2.10
** https://github.com/openannotation/annotator
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/openannotation/annotator/blob/master/LICENSE
###
# 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.
module.exports = 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 instance represents.
# options - An Object literal of options.
#
# Examples
#
# element = document.getElementById('my-element')
# instance = new Delegator(element, {
# option: 'my-option'
# })
#
# Returns a new instance of Delegator.
constructor: (element, options) ->
@options = $.extend(true, {}, @options, options)
@element = $(element)
# Delegator creates closures for each event it binds. This is a private
# registry of created closures, used to enable event unbinding.
@_closures = {}
this.on = this.subscribe
this.addEvents()
# Public: Destroy the instance, unbinding all events.
#
# Returns nothing.
destroy: ->
this.removeEvents()
# Public: binds the function names in the @events Object to their events.
#
# The @events Object should be a set of key/value pairs where the key is the
# event name with optional CSS selector. The value should be a String method
# name on the current class.
#
# This is called by the default Delegator constructor and so shouldn't usually
# need to be called by the user.
#
# Examples
#
# # This will bind the clickedElement() method to the click event on @element.
# @options = {"click": "clickedElement"}
#
# # This will delegate the submitForm() method to the submit event on the
# # form within the @element.
# @options = {"form submit": "submitForm"}
#
# # This will bind the updateAnnotationStore() method to the custom
# # annotation:save event. NOTE: Because this is a custom event the
# # Delegator#subscribe() method will be used and updateAnnotationStore()
# # will not receive an event parameter like the previous two examples.
# @options = {"annotation:save": "updateAnnotationStore"}
#
# Returns nothing.
addEvents: ->
for event in Delegator._parseEvents(@events)
this._addEvent event.selector, event.event, event.functionName
# Public: unbinds functions previously bound to events by addEvents().
#
# The @events Object should be a set of key/value pairs where the key is the
# event name with optional CSS selector. The value should be a String method
# name on the current class.
#
# Returns nothing.
removeEvents: ->
for event in Delegator._parseEvents(@events)
this._removeEvent event.selector, event.event, event.functionName
# Binds an event to a callback function represented by a String. A selector
# can be provided in order to watch for events on a child element.
#
# The event can be any standard event supported by jQuery or a custom String.
# If a custom string is used the callback function will not recieve an
# event object as it's first parameter.
#
# selector - Selector String matching child elements. (default: '')
# event - The event to listen for.
# functionName - A String function name to bind to the event.
#
# Examples
#
# # Listens for all click events on instance.element.
# instance._addEvent('', 'click', 'onClick')
#
# # Delegates the instance.onInputFocus() method to focus events on all
# # form inputs within instance.element.
# instance._addEvent('form :input', 'focus', 'onInputFocus')
#
# Returns itself.
_addEvent: (selector, event, functionName) ->
closure = => this[functionName].apply(this, arguments)
if selector == '' and Delegator._isCustomEvent(event)
this.subscribe(event, closure)
else
@element.delegate(selector, event, closure)
@_closures["#{selector}/#{event}/#{functionName}"] = closure
this
# Unbinds a function previously bound to an event by the _addEvent method.
#
# Takes the same arguments as _addEvent(), and an event will only be
# successfully unbound if the arguments to removeEvent() are exactly the same
# as the original arguments to _addEvent(). This would usually be called by
# _removeEvents().
#
# selector - Selector String matching child elements. (default: '')
# event - The event to listen for.
# functionName - A String function name to bind to the event.
#
# Returns itself.
_removeEvent: (selector, event, functionName) ->
closure = @_closures["#{selector}/#{event}/#{functionName}"]
if selector == '' and Delegator._isCustomEvent(event)
this.unsubscribe(event, closure)
else
@element.undelegate(selector, event, closure)
delete @_closures["#{selector}/#{event}/#{functionName}"]
this
# Public: Fires an event and calls all subscribed callbacks with any parameters
# provided. This is essentially an alias of @element.triggerHandler() but
# should be used to fire custom events.
#
# NOTE: Events fired using .publish() will not bubble up the DOM.
#
# event - A String event name.
# params - An Array of parameters to provide to callbacks.
#
# Examples
#
# instance.subscribe('annotation:save', (msg) -> console.log(msg))
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# Returns itself.
publish: () ->
@element.triggerHandler.apply @element, arguments
this
# Public: Listens for custom event which when published will call the provided
# callback. This is essentially a wrapper around @element.bind() but removes
# the event parameter that jQuery event callbacks always recieve. These
# parameters are unnessecary for custom events.
#
# event - A String event name.
# callback - A callback function called when the event is published.
#
# Examples
#
# instance.subscribe('annotation:save', (msg) -> console.log(msg))
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# Returns itself.
subscribe: (event, callback) ->
closure = -> callback.apply(this, [].slice.call(arguments, 1))
# Ensure both functions have the same unique id so that jQuery will accept
# callback when unbinding closure.
closure.guid = callback.guid = ($.guid += 1)
@element.bind event, closure
this
# Public: Unsubscribes a callback from an event. The callback will no longer
# be called when the event is published.
#
# event - A String event name.
# callback - A callback function to be removed.
#
# Examples
#
# callback = (msg) -> console.log(msg)
# instance.subscribe('annotation:save', callback)
# instance.publish('annotation:save', ['Hello World'])
# # => Outputs "Hello World"
#
# instance.unsubscribe('annotation:save', callback)
# instance.publish('annotation:save', ['Hello Again'])
# # => No output.
#
# Returns itself.
unsubscribe: ->
@element.unbind.apply @element, arguments
this
# Parse the @events object of a Delegator into an array of objects containing
# string-valued "selector", "event", and "func" keys.
Delegator._parseEvents = (eventsObj) ->
events = []
for sel, functionName of eventsObj
[selector..., event] = sel.split ' '
events.push({
selector: selector.join(' '),
event: event,
functionName: functionName
})
return events
# Native jQuery events that should recieve an event object. Plugins can
# add their own methods to this if required.
Delegator.natives = do ->
specials = (key for own key, val of $.event.special)
"""
blur focus focusin focusout load resize scroll unload click dblclick
mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave
change select submit keydown keypress keyup error
""".split(/[^a-z]+/).concat(specials)
# Checks to see if the provided event is a DOM event supported by jQuery or
# a custom user event.
#
# event - String event name.
#
# Examples
#
# Delegator._isCustomEvent('click') # => false
# Delegator._isCustomEvent('mousedown') # => false
# Delegator._isCustomEvent('annotation:created') # => true
#
# Returns true if event is a custom user event.
Delegator._isCustomEvent = (event) ->
[event] = event.split('.')
$.inArray(event, Delegator.natives) == -1
......@@ -3,8 +3,8 @@ extend = require('extend')
raf = require('raf')
scrollIntoView = require('scroll-into-view')
Annotator = require('annotator')
$ = Annotator.$
Delegator = require('./delegator')
$ = require('jquery')
adder = require('./adder')
highlighter = require('./highlighter')
......@@ -31,7 +31,7 @@ normalizeURI = (uri, baseURI) ->
# See https://github.com/hypothesis/h/issues/3471#issuecomment-226713750
return url.toString().replace(/#.*/, '');
module.exports = class Guest extends Annotator
module.exports = class Guest extends Delegator
SHOW_HIGHLIGHTS_CLASS = 'annotator-highlights-always-on'
# Events to be bound on Annotator#element.
......@@ -48,24 +48,41 @@ module.exports = class Guest extends Annotator
anchoring: require('./anchoring/html')
# Internal state
plugins: null
anchors: null
visibleHighlights: false
html: extend {}, Annotator::html,
adder: '<hypothesis-adder></hypothesis-adder>';
html:
adder: '<hypothesis-adder></hypothesis-adder>'
wrapper: '<div class="annotator-wrapper"></div>'
addPlugin: (name, options) ->
if @plugins[name]
console.error("You cannot have more than one instance of any plugin.")
else
klass = @options.pluginClasses[name]
if typeof klass is 'function'
@plugins[name] = new klass(@element[0], options)
@plugins[name].annotator = this
@plugins[name].pluginInit?()
else
console.error("Could not load " + name + " plugin. Have you included the appropriate <script> tag?")
this # allow chaining
constructor: (element, options) ->
super
this.adder = $(this.html.adder).appendTo(@element).hide()
self = this
this.adderCtrl = new adder.Adder(@adder[0], {
onAnnotate: ->
self.createAnnotation()
Annotator.Util.getGlobal().getSelection().removeAllRanges()
document.getSelection().removeAllRanges()
onHighlight: ->
self.setVisibleHighlights(true)
self.createHighlight()
Annotator.Util.getGlobal().getSelection().removeAllRanges()
document.getSelection().removeAllRanges()
})
this.selections = selections(document).subscribe
next: (range) ->
......@@ -74,6 +91,7 @@ module.exports = class Guest extends Annotator
else
self._onClearSelection()
this.plugins = {}
this.anchors = []
cfOptions =
......@@ -91,7 +109,7 @@ module.exports = class Guest extends Annotator
# Load plugins
for own name, opts of @options
if not @plugins[name] and Annotator.Plugin[name]
if not @plugins[name] and @options.pluginClasses[name]
this.addPlugin(name, opts)
# Get the document info
......@@ -142,16 +160,6 @@ module.exports = class Guest extends Annotator
crossframe.on 'setVisibleHighlights', (state) =>
this.setVisibleHighlights(state)
_setupWrapper: ->
@wrapper = @element
this
# These methods aren't used in the iframe-hosted configuration of Annotator.
_setupViewer: -> this
_setupEditor: -> this
_setupDocumentEvents: -> this
_setupDynamicStyle: -> this
destroy: ->
$('#annotator-dynamic-style').remove()
......@@ -167,7 +175,7 @@ module.exports = class Guest extends Annotator
for name, plugin of @plugins
@plugins[name].destroy()
this.removeEvents()
super
anchor: (annotation) ->
self = this
......@@ -348,6 +356,20 @@ module.exports = class Guest extends Annotator
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?
$(h).replaceWith(h.childNodes)
this.publish('annotationDeleted', [annotation])
annotation
showAnnotations: (annotations) ->
tags = (a.$tag for a in annotations)
@crossframe?.call('showAnnotations', tags)
......@@ -365,7 +387,7 @@ module.exports = class Guest extends Annotator
@crossframe?.call('focusAnnotations', tags)
_onSelection: (range) ->
selection = Annotator.Util.getGlobal().getSelection()
selection = document.getSelection()
isBackwards = rangeUtil.isSelectionBackwards(selection)
focusRect = rangeUtil.selectionFocusRect(selection)
if !focusRect
......@@ -375,7 +397,7 @@ module.exports = class Guest extends Annotator
@selectedRanges = [range]
Annotator.$('.annotator-toolbar .h-icon-note')
$('.annotator-toolbar .h-icon-note')
.attr('title', 'New Annotation')
.removeClass('h-icon-note')
.addClass('h-icon-annotate');
......@@ -387,7 +409,7 @@ module.exports = class Guest extends Annotator
this.adderCtrl.hide()
@selectedRanges = []
Annotator.$('.annotator-toolbar .h-icon-annotate')
$('.annotator-toolbar .h-icon-annotate')
.attr('title', 'New Page Note')
.removeClass('h-icon-annotate')
.addClass('h-icon-note');
......
Annotator = require('annotator')
$ = Annotator.$
$ = require('jquery')
Guest = require('./guest')
module.exports = class Host extends Guest
constructor: (element, options) ->
# Make a copy of all options except `options.app`, the app base URL.
# Make a copy of all options except `options.app`, the app base URL, and `options.pluginClasses`
configParam = 'config=' + encodeURIComponent(
JSON.stringify(Object.assign({}, options, {app:undefined}))
JSON.stringify(Object.assign({}, options, {app:undefined, pluginClasses: undefined }))
)
if options.app and '?' in options.app
options.app += '&' + configParam
......
......@@ -2,7 +2,6 @@
require('../shared/polyfills');
var Annotator = require('annotator');
// Polyfills
......@@ -13,44 +12,44 @@ var Annotator = require('annotator');
if (!window.document.evaluate) {
require('./vendor/wgxpath.install');
}
var g = Annotator.Util.getGlobal();
if (g.wgxpath) {
g.wgxpath.install();
if (window.wgxpath) {
window.wgxpath.install();
}
var $ = require('jquery');
// Applications
Annotator.Guest = require('./guest');
Annotator.Host = require('./host');
Annotator.Sidebar = require('./sidebar');
Annotator.PdfSidebar = require('./pdf-sidebar');
var Sidebar = require('./sidebar');
var PdfSidebar = require('./pdf-sidebar');
// UI plugins
Annotator.Plugin.BucketBar = require('./plugin/bucket-bar');
Annotator.Plugin.Toolbar = require('./plugin/toolbar');
var pluginClasses = {
// UI plugins
BucketBar: require('./plugin/bucket-bar'),
Toolbar: require('./plugin/toolbar'),
// Document type plugins
Annotator.Plugin.PDF = require('./plugin/pdf');
require('./vendor/annotator.document'); // Does not export the plugin :(
// Document type plugins
PDF: require('./plugin/pdf'),
Document: require('./plugin/document'),
// Cross-frame communication
Annotator.Plugin.CrossFrame = require('./plugin/cross-frame');
Annotator.Plugin.CrossFrame.AnnotationSync = require('./annotation-sync');
Annotator.Plugin.CrossFrame.Bridge = require('../shared/bridge');
Annotator.Plugin.CrossFrame.Discovery = require('../shared/discovery');
// Cross-frame communication
CrossFrame: require('./plugin/cross-frame'),
};
var appLinkEl =
document.querySelector('link[type="application/annotator+html"]');
var options = require('./config')(window);
Annotator.noConflict().$.noConflict(true)(function() {
$.noConflict(true)(function() {
var Klass = window.PDFViewerApplication ?
Annotator.PdfSidebar :
Annotator.Sidebar;
PdfSidebar :
Sidebar;
if (options.hasOwnProperty('constructor')) {
Klass = options.constructor;
delete options.constructor;
}
options.pluginClasses = pluginClasses;
window.annotator = new Klass(document.body, options);
appLinkEl.addEventListener('destroy', function () {
appLinkEl.parentElement.removeChild(appLinkEl);
......
Delegator = require('./delegator')
module.exports = class Plugin extends Delegator
constructor: (element, options) ->
super
pluginInit: () ->
raf = require('raf')
Annotator = require('annotator')
$ = Annotator.$
$ = require('jquery')
Plugin = require('../plugin')
scrollIntoView = require('scroll-into-view')
......@@ -42,7 +42,7 @@ scrollToClosest = (anchors, direction) ->
scrollIntoView(next.highlights[0])
module.exports = class BucketBar extends Annotator.Plugin
module.exports = class BucketBar extends Plugin
# svg skeleton
html: """
<div class="annotator-bucket-bar">
......
Annotator = require('annotator')
Plugin = require('../plugin')
AnnotationSync = require('../annotation-sync')
Bridge = require('../../shared/bridge')
Discovery = require('../../shared/discovery')
# Extracts individual keys from an object and returns a new one.
extract = extract = (obj, keys...) ->
......@@ -11,17 +16,17 @@ extract = extract = (obj, keys...) ->
# frame acts as the bridge client, the sidebar is the server. This plugin
# can also be used to send messages through to the sidebar using the
# call method.
module.exports = class CrossFrame extends Annotator.Plugin
module.exports = class CrossFrame extends Plugin
constructor: (elem, options) ->
super
opts = extract(options, 'server')
discovery = new CrossFrame.Discovery(window, opts)
discovery = new Discovery(window, opts)
bridge = new CrossFrame.Bridge()
bridge = new Bridge()
opts = extract(options, 'on', 'emit')
annotationSync = new CrossFrame.AnnotationSync(bridge, opts)
annotationSync = new AnnotationSync(bridge, opts)
this.pluginInit = ->
onDiscoveryCallback = (source, origin, token) ->
......@@ -30,7 +35,7 @@ module.exports = class CrossFrame extends Annotator.Plugin
this.destroy = ->
# super doesnt work here :(
Annotator.Plugin::destroy.apply(this, arguments)
Plugin::destroy.apply(this, arguments)
bridge.destroy()
discovery.stopDiscovery()
......
$ = require('jquery')
Plugin = require('../plugin')
###
** Adapted from:
** https://github.com/openannotation/annotator/blob/v1.2.x/src/plugin/document.coffee
**
** Annotator v1.2.10
** https://github.com/openannotation/annotator
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/openannotation/annotator/blob/master/LICENSE
###
module.exports = class Document extends Plugin
events:
'beforeAnnotationCreated': 'beforeAnnotationCreated'
pluginInit: ->
this.getDocumentMetadata()
# returns the primary URI for the document being annotated
uri: =>
uri = decodeURIComponent document.location.href
for link in @metadata.link
if link.rel == "canonical"
uri = link.href
return uri
# returns all uris for the document being annotated
uris: =>
uniqueUrls = {}
for link in @metadata.link
uniqueUrls[link.href] = true if link.href
return (href for href of uniqueUrls)
beforeAnnotationCreated: (annotation) =>
annotation.document = @metadata
getDocumentMetadata: =>
@metadata = {}
# first look for some common metadata types
# TODO: look for microdata/rdfa?
this._getHighwire()
this._getDublinCore()
this._getFacebook()
this._getEprints()
this._getPrism()
this._getTwitter()
this._getFavicon()
# extract out/normalize some things
this._getTitle()
this._getLinks()
return @metadata
_getHighwire: =>
return @metadata.highwire = this._getMetaTags("citation", "name", "_")
_getFacebook: =>
return @metadata.facebook = this._getMetaTags("og", "property", ":")
_getTwitter: =>
return @metadata.twitter = this._getMetaTags("twitter", "name", ":")
_getDublinCore: =>
return @metadata.dc = this._getMetaTags("dc", "name", ".")
_getPrism: =>
return @metadata.prism = this._getMetaTags("prism", "name", ".")
_getEprints: =>
return @metadata.eprints = this._getMetaTags("eprints", "name", ".")
_getMetaTags: (prefix, attribute, delimiter) =>
tags = {}
for meta in $("meta")
name = $(meta).attr(attribute)
content = $(meta).prop("content")
if name
match = name.match(RegExp("^#{prefix}#{delimiter}(.+)$", "i"))
if match
n = match[1]
if tags[n]
tags[n].push(content)
else
tags[n] = [content]
return tags
_getTitle: =>
if @metadata.highwire.title
@metadata.title = @metadata.highwire.title[0]
else if @metadata.eprints.title
@metadata.title = @metadata.eprints.title[0]
else if @metadata.prism.title
@metadata.title = @metadata.prism.title[0]
else if @metadata.facebook.title
@metadata.title = @metadata.facebook.title[0]
else if @metadata.twitter.title
@metadata.title = @metadata.twitter.title[0]
else if @metadata.dc.title
@metadata.title = @metadata.dc.title[0]
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')
lang = l.prop('hreflang')
if rel not in ["alternate", "canonical", "bookmark", "shortlink"] then continue
if rel is 'alternate'
# Ignore feeds resources
if type and type.match /^application\/(rss|atom)\+xml/ then continue
# Ignore alternate languages
if lang then continue
@metadata.link.push(href: href, rel: rel, type: type)
# look for links in scholar metadata
for name, values of @metadata.highwire
if name == "pdf_url"
for url in values
@metadata.link.push
href: this._absoluteUrl(url)
type: "application/pdf"
# kind of a hack to express DOI identifiers as links but it's a
# convenient place to look them up later, and somewhat sane since
# they don't have a type
if name == "doi"
for doi in values
if doi[0..3] != "doi:"
doi = "doi:" + doi
@metadata.link.push(href: doi)
# look for links in dublincore data
for name, values of @metadata.dc
if name == "identifier"
for id in values
if id[0..3] == "doi:"
@metadata.link.push(href: id)
_getFavicon: =>
for link in $("link")
if $(link).prop("rel") in ["shortcut icon", "icon"]
@metadata["favicon"] = this._absoluteUrl(link.href)
# hack to get a absolute url from a possibly relative one
_absoluteUrl: (url) ->
d = document.createElement('a')
d.href = url
d.href
extend = require('extend')
Annotator = require('annotator')
Plugin = require('../plugin')
RenderingStates = require('../pdfjs-rendering-states')
module.exports = class PDF extends Annotator.Plugin
module.exports = class PDF extends Plugin
documentLoaded: null
observer: null
pdfViewer: null
......
CrossFrame = require('../cross-frame')
proxyquire = require('proxyquire')
Plugin = require('../../plugin')
CrossFrame = null
describe 'Annotator.Plugin.CrossFrame', ->
fakeDiscovery = null
fakeBridge = null
fakeAnnotationSync = null
proxyDiscovery = null
proxyBridge = null
proxyAnnotationSync = null
sandbox = sinon.sandbox.create()
createCrossFrame = (options) ->
......@@ -28,9 +36,17 @@ describe 'Annotator.Plugin.CrossFrame', ->
fakeAnnotationSync =
sync: sandbox.stub()
CrossFrame.AnnotationSync = sandbox.stub().returns(fakeAnnotationSync)
CrossFrame.Discovery = sandbox.stub().returns(fakeDiscovery)
CrossFrame.Bridge = sandbox.stub().returns(fakeBridge)
proxyAnnotationSync = sandbox.stub().returns(fakeAnnotationSync)
proxyDiscovery = sandbox.stub().returns(fakeDiscovery)
proxyBridge = sandbox.stub().returns(fakeBridge)
CrossFrame = proxyquire('../cross-frame', {
'../plugin': Plugin,
'../annotation-sync': proxyAnnotationSync,
'../../shared/bridge': proxyBridge,
'../../shared/discovery': proxyDiscovery
})
afterEach ->
sandbox.restore()
......@@ -38,23 +54,23 @@ describe 'Annotator.Plugin.CrossFrame', ->
describe 'CrossFrame constructor', ->
it 'instantiates the Discovery component', ->
createCrossFrame()
assert.calledWith(CrossFrame.Discovery, window)
assert.calledWith(proxyDiscovery, window)
it 'passes the options along to the bridge', ->
createCrossFrame(server: true)
assert.calledWith(CrossFrame.Discovery, window, server: true)
assert.calledWith(proxyDiscovery, window, server: true)
it 'instantiates the CrossFrame component', ->
createCrossFrame()
assert.calledWith(CrossFrame.Discovery)
assert.calledWith(proxyDiscovery)
it 'instantiates the AnnotationSync component', ->
createCrossFrame()
assert.called(CrossFrame.AnnotationSync)
assert.called(proxyAnnotationSync)
it 'passes along options to AnnotationSync', ->
createCrossFrame()
assert.calledWith(CrossFrame.AnnotationSync, fakeBridge, {
assert.calledWith(proxyAnnotationSync, fakeBridge, {
on: sinon.match.func
emit: sinon.match.func
})
......
Annotator = require('annotator')
$ = Annotator.$
Plugin = require('../plugin')
$ = require('jquery')
makeButton = (item) ->
anchor = $('<button></button>')
......@@ -12,7 +12,7 @@ makeButton = (item) ->
button = $('<li></li>').append(anchor)
return button[0]
module.exports = class Toolbar extends Annotator.Plugin
module.exports = class Toolbar extends Plugin
HIDE_CLASS = 'annotator-hide'
events:
......
......@@ -43,6 +43,7 @@ module.exports = class Sidebar extends Host
@onSignupRequest = serviceConfig.onSignupRequest
this._setupSidebarEvents()
this._setupDocumentEvents()
_setupDocumentEvents: ->
sidebarTrigger(document.body, => this.show())
......
Annotator = require('annotator')
proxyquire = require('proxyquire')
adder = require('../adder')
Observable = require('../util/observable').Observable
Plugin = require('../plugin')
$ = Annotator.$
Annotator['@noCallThru'] = true;
Delegator = require('../delegator')
$ = require('jquery')
Delegator['@noCallThru'] = true
Guest = null
anchoring = {}
......@@ -29,6 +30,18 @@ class FakeAdder
this.showAt = sinon.stub()
this.target = sinon.stub()
class FakePlugin extends Plugin
instance: null
events:
'customEvent': 'customEventHandler'
constructor: ->
FakePlugin::instance = this
super
pluginInit: sinon.stub()
customEventHandler: sinon.stub()
# A little helper which returns a promise that resolves after a timeout
timeoutPromise = (millis = 0) ->
new Promise((resolve) -> setTimeout(resolve, millis))
......@@ -37,10 +50,12 @@ describe 'Guest', ->
sandbox = sinon.sandbox.create()
CrossFrame = null
fakeCrossFrame = null
guestOptions = null
createGuest = (options) ->
createGuest = (options={}) ->
options = Object.assign({}, guestOptions, options)
element = document.createElement('div')
return new Guest(element, options || {})
return new Guest(element, options)
beforeEach ->
FakeAdder::instance = null
......@@ -49,6 +64,7 @@ describe 'Guest', ->
selectionFocusRect: sinon.stub()
}
selections = null
guestOptions = {pluginClasses: {}}
Guest = proxyquire('../guest', {
'./adder': {Adder: FakeAdder},
......@@ -60,7 +76,7 @@ describe 'Guest', ->
selections = obs
return () ->
)
'annotator': Annotator,
'./delegator': Delegator,
'raf': raf,
'scroll-into-view': scrollIntoView,
})
......@@ -69,15 +85,39 @@ describe 'Guest', ->
onConnect: sinon.stub()
on: sinon.stub()
sync: sinon.stub()
destroy: sinon.stub()
}
CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame)
Annotator.Plugin.CrossFrame = CrossFrame
CrossFrame = sandbox.stub().returns(fakeCrossFrame)
guestOptions.pluginClasses['CrossFrame'] = CrossFrame
afterEach ->
sandbox.restore()
delete Annotator.Plugin.CrossFrame
describe 'plugins', ->
fakePlugin = null
guest = null
beforeEach ->
FakePlugin::instance = null
guestOptions.pluginClasses['FakePlugin'] = FakePlugin
guest = createGuest(FakePlugin: {})
fakePlugin = FakePlugin::instance
it 'load and "pluginInit" gets called', ->
assert.calledOnce(fakePlugin.pluginInit)
it 'hold reference to instance', ->
assert.equal(fakePlugin.annotator, guest)
it 'subscribe to events', ->
guest.publish('customEvent', ['1', '2'])
assert.calledWith(fakePlugin.customEventHandler, '1', '2')
it 'destroy when instance is destroyed', ->
sandbox.spy(fakePlugin, 'destroy')
guest.destroy()
assert.called(fakePlugin.destroy)
describe 'cross frame', ->
......
Annotator = require('annotator')
proxyquire = require('proxyquire')
Host = proxyquire('../host', {
'annotator': Annotator,
})
Host = proxyquire('../host', {})
describe 'Host', ->
sandbox = sinon.sandbox.create()
CrossFrame = null
fakeCrossFrame = null
hostOptions = {pluginClasses: {}}
createHost = (options={}, element=null) ->
options = Object.assign({app: '/base/annotator/test/empty.html'}, options)
options = Object.assign({app: '/base/annotator/test/empty.html'}, hostOptions, options)
if !element
element = document.createElement('div')
return new Host(element, options)
......@@ -27,11 +24,10 @@ describe 'Host', ->
CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame)
Annotator.Plugin.CrossFrame = CrossFrame
hostOptions.pluginClasses['CrossFrame'] = CrossFrame
afterEach ->
sandbox.restore()
delete Annotator.Plugin.CrossFrame
describe 'widget visibility', ->
it 'starts hidden', ->
......
......@@ -3,8 +3,6 @@
'use strict';
var Annotator = require('annotator');
var unroll = require('../../../shared/test/util').unroll;
var Guest = require('../../guest');
......@@ -48,17 +46,18 @@ function FakeCrossFrame() {
describe('anchoring', function () {
var guest;
var guestOptions;
var container;
before(function () {
Annotator.Plugin.CrossFrame = FakeCrossFrame;
guestOptions = {pluginClasses: {CrossFrame: FakeCrossFrame}};
});
beforeEach(function () {
container = document.createElement('div');
container.innerHTML = require('./test-page.html');
document.body.appendChild(container);
guest = new Guest(container);
guest = new Guest(container, guestOptions);
});
afterEach(function () {
......
Annotator = require('annotator')
events = require('../../shared/bridge-events')
proxyquire = require('proxyquire')
Sidebar = proxyquire('../sidebar', {
'annotator': Annotator,
})
Sidebar = proxyquire('../sidebar', {})
describe 'Sidebar', ->
sandbox = sinon.sandbox.create()
CrossFrame = null
fakeCrossFrame = null
sidebarOptions = {pluginClasses: {}}
createSidebar = (options={}) ->
options = Object.assign({}, sidebarOptions, options)
element = document.createElement('div')
return new Sidebar(element, options)
......@@ -20,14 +19,14 @@ describe 'Sidebar', ->
fakeCrossFrame.onConnect = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.on = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.call = sandbox.spy()
fakeCrossFrame.destroy = sandbox.stub()
CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame)
Annotator.Plugin.CrossFrame = CrossFrame
sidebarOptions.pluginClasses['CrossFrame'] = CrossFrame
afterEach ->
sandbox.restore()
delete Annotator.Plugin.CrossFrame
describe 'crossframe listeners', ->
emitEvent = (event, args...) ->
......@@ -196,3 +195,14 @@ describe 'Sidebar', ->
sidebar.show()
sidebar.element.trigger(event)
assert.isFalse(sidebar.isOpen())
describe 'destruction', ->
sidebar = null
beforeEach ->
sidebar = createSidebar({})
it 'the sidebar is destroyed and the frame is detached', ->
sidebar.destroy()
assert.called(fakeCrossFrame.destroy)
assert.equal(sidebar.frame[0].parentElement, null)
/*
** Annotator v1.2.10-dev-6536160
** https://github.com/okfn/annotator/
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2015-05-11 18:53:38Z
*/
//
// Generated by CoffeeScript 1.6.3
(function() {
var _ref,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Annotator.Plugin.Document = (function(_super) {
var $;
__extends(Document, _super);
function Document() {
this._getFavicon = __bind(this._getFavicon, this);
this._getLinks = __bind(this._getLinks, this);
this._getTitle = __bind(this._getTitle, this);
this._getMetaTags = __bind(this._getMetaTags, this);
this._getEprints = __bind(this._getEprints, this);
this._getPrism = __bind(this._getPrism, this);
this._getDublinCore = __bind(this._getDublinCore, this);
this._getTwitter = __bind(this._getTwitter, this);
this._getFacebook = __bind(this._getFacebook, this);
this._getHighwire = __bind(this._getHighwire, this);
this.getDocumentMetadata = __bind(this.getDocumentMetadata, this);
this.beforeAnnotationCreated = __bind(this.beforeAnnotationCreated, this);
this.uris = __bind(this.uris, this);
this.uri = __bind(this.uri, this);
_ref = Document.__super__.constructor.apply(this, arguments);
return _ref;
}
$ = Annotator.$;
Document.prototype.events = {
'beforeAnnotationCreated': 'beforeAnnotationCreated'
};
Document.prototype.pluginInit = function() {
return this.getDocumentMetadata();
};
Document.prototype.uri = function() {
var link, uri, _i, _len, _ref1;
uri = decodeURIComponent(document.location.href);
_ref1 = this.metadata.link;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
link = _ref1[_i];
if (link.rel === "canonical") {
uri = link.href;
}
}
return uri;
};
Document.prototype.uris = function() {
var href, link, uniqueUrls, _i, _len, _ref1;
uniqueUrls = {};
_ref1 = this.metadata.link;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
link = _ref1[_i];
if (link.href) {
uniqueUrls[link.href] = true;
}
}
return (function() {
var _results;
_results = [];
for (href in uniqueUrls) {
_results.push(href);
}
return _results;
})();
};
Document.prototype.beforeAnnotationCreated = function(annotation) {
return annotation.document = this.metadata;
};
Document.prototype.getDocumentMetadata = function() {
this.metadata = {};
this._getHighwire();
this._getDublinCore();
this._getFacebook();
this._getEprints();
this._getPrism();
this._getTwitter();
this._getFavicon();
this._getTitle();
this._getLinks();
return this.metadata;
};
Document.prototype._getHighwire = function() {
return this.metadata.highwire = this._getMetaTags("citation", "name", "_");
};
Document.prototype._getFacebook = function() {
return this.metadata.facebook = this._getMetaTags("og", "property", ":");
};
Document.prototype._getTwitter = function() {
return this.metadata.twitter = this._getMetaTags("twitter", "name", ":");
};
Document.prototype._getDublinCore = function() {
return this.metadata.dc = this._getMetaTags("dc", "name", ".");
};
Document.prototype._getPrism = function() {
return this.metadata.prism = this._getMetaTags("prism", "name", ".");
};
Document.prototype._getEprints = function() {
return this.metadata.eprints = this._getMetaTags("eprints", "name", ".");
};
Document.prototype._getMetaTags = function(prefix, attribute, delimiter) {
var content, match, meta, n, name, tags, _i, _len, _ref1;
tags = {};
_ref1 = $("meta");
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
meta = _ref1[_i];
name = $(meta).attr(attribute);
content = $(meta).prop("content");
if (name) {
match = name.match(RegExp("^" + prefix + delimiter + "(.+)$", "i"));
if (match) {
n = match[1];
if (tags[n]) {
tags[n].push(content);
} else {
tags[n] = [content];
}
}
}
}
return tags;
};
Document.prototype._getTitle = function() {
if (this.metadata.highwire.title) {
return this.metadata.title = this.metadata.highwire.title[0];
} else if (this.metadata.eprints.title) {
return this.metadata.title = this.metadata.eprints.title[0];
} else if (this.metadata.prism.title) {
return this.metadata.title = this.metadata.prism.title[0];
} else if (this.metadata.facebook.title) {
return this.metadata.title = this.metadata.facebook.title[0];
} else if (this.metadata.twitter.title) {
return this.metadata.title = this.metadata.twitter.title[0];
} else if (this.metadata.dc.title) {
return this.metadata.title = this.metadata.dc.title[0];
} else {
return this.metadata.title = $("head title").text();
}
};
Document.prototype._getLinks = function() {
var doi, href, id, l, lang, link, name, rel, type, url, values, _i, _j, _k, _len, _len1, _len2, _ref1, _ref2, _ref3, _results;
this.metadata.link = [
{
href: document.location.href
}
];
_ref1 = $("link");
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
link = _ref1[_i];
l = $(link);
href = this._absoluteUrl(l.prop('href'));
rel = l.prop('rel');
type = l.prop('type');
lang = l.prop('hreflang');
if (rel !== "alternate" && rel !== "canonical" && rel !== "bookmark" && rel !== "shortlink") {
continue;
}
if (rel === 'alternate') {
if (type && type.match(/^application\/(rss|atom)\+xml/)) {
continue;
}
if (lang) {
continue;
}
}
this.metadata.link.push({
href: href,
rel: rel,
type: type
});
}
_ref2 = this.metadata.highwire;
for (name in _ref2) {
values = _ref2[name];
if (name === "pdf_url") {
for (_j = 0, _len1 = values.length; _j < _len1; _j++) {
url = values[_j];
this.metadata.link.push({
href: this._absoluteUrl(url),
type: "application/pdf"
});
}
}
if (name === "doi") {
for (_k = 0, _len2 = values.length; _k < _len2; _k++) {
doi = values[_k];
if (doi.slice(0, 4) !== "doi:") {
doi = "doi:" + doi;
}
this.metadata.link.push({
href: doi
});
}
}
}
_ref3 = this.metadata.dc;
_results = [];
for (name in _ref3) {
values = _ref3[name];
if (name === "identifier") {
_results.push((function() {
var _l, _len3, _results1;
_results1 = [];
for (_l = 0, _len3 = values.length; _l < _len3; _l++) {
id = values[_l];
if (id.slice(0, 4) === "doi:") {
_results1.push(this.metadata.link.push({
href: id
}));
} else {
_results1.push(void 0);
}
}
return _results1;
}).call(this));
} else {
_results.push(void 0);
}
}
return _results;
};
Document.prototype._getFavicon = function() {
var link, _i, _len, _ref1, _ref2, _results;
_ref1 = $("link");
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
link = _ref1[_i];
if ((_ref2 = $(link).prop("rel")) === "shortcut icon" || _ref2 === "icon") {
_results.push(this.metadata["favicon"] = this._absoluteUrl(link.href));
} else {
_results.push(void 0);
}
}
return _results;
};
Document.prototype._absoluteUrl = function(url) {
var d;
d = document.createElement('a');
d.href = url;
return d.href;
};
return Document;
})(Annotator.Plugin);
}).call(this);
//
//# sourceMappingURL=annotator.document.map
\ No newline at end of file
/*
** Annotator v1.2.10-dev-6536160
** https://github.com/okfn/annotator/
**
** Copyright 2015, the Annotator project contributors.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2015-05-11 18:53:38Z
*/
//
// Generated by CoffeeScript 1.6.3
(function() {
var $, Annotator, Delegator, LinkParser, Range, Util, findChild, fn, functions, g, getNodeName, getNodePosition, gettext, simpleXPathJQuery, simpleXPathPure, _Annotator, _gettext, _i, _j, _len, _len1, _ref, _ref1, _t,
__slice = [].slice,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
simpleXPathJQuery = function(relativeRoot) {
var jq;
jq = this.map(function() {
var elem, idx, path, tagName;
path = '';
elem = this;
while ((elem != null ? elem.nodeType : void 0) === Node.ELEMENT_NODE && elem !== relativeRoot) {
tagName = elem.tagName.replace(":", "\\:");
idx = $(elem.parentNode).children(tagName).index(elem) + 1;
idx = "[" + idx + "]";
path = "/" + elem.tagName.toLowerCase() + idx + path;
elem = elem.parentNode;
}
return path;
});
return jq.get();
};
simpleXPathPure = function(relativeRoot) {
var getPathSegment, getPathTo, jq, rootNode;
getPathSegment = function(node) {
var name, pos;
name = getNodeName(node);
pos = getNodePosition(node);
return "" + name + "[" + pos + "]";
};
rootNode = relativeRoot;
getPathTo = function(node) {
var xpath;
xpath = '';
while (node !== rootNode) {
if (node == null) {
throw new Error("Called getPathTo on a node which was not a descendant of @rootNode. " + rootNode);
}
xpath = (getPathSegment(node)) + '/' + xpath;
node = node.parentNode;
}
xpath = '/' + xpath;
xpath = xpath.replace(/\/$/, '');
return xpath;
};
jq = this.map(function() {
var path;
path = getPathTo(this);
return path;
});
return jq.get();
};
findChild = function(node, type, index) {
var child, children, found, name, _i, _len;
if (!node.hasChildNodes()) {
throw new Error("XPath error: node has no children!");
}
children = node.childNodes;
found = 0;
for (_i = 0, _len = children.length; _i < _len; _i++) {
child = children[_i];
name = getNodeName(child);
if (name === type) {
found += 1;
if (found === index) {
return child;
}
}
}
throw new Error("XPath error: wanted child not found.");
};
getNodeName = function(node) {
var nodeName;
nodeName = node.nodeName.toLowerCase();
switch (nodeName) {
case "#text":
return "text()";
case "#comment":
return "comment()";
case "#cdata-section":
return "cdata-section()";
default:
return nodeName;
}
};
getNodePosition = function(node) {
var pos, tmp;
pos = 0;
tmp = node;
while (tmp) {
if (tmp.nodeName === node.nodeName) {
pos++;
}
tmp = tmp.previousSibling;
}
return pos;
};
gettext = null;
if (typeof Gettext !== "undefined" && Gettext !== null) {
_gettext = new Gettext({
domain: "annotator"
});
gettext = function(msgid) {
return _gettext.gettext(msgid);
};
} else {
gettext = function(msgid) {
return msgid;
};
}
_t = function(msgid) {
return gettext(msgid);
};
if (!(typeof jQuery !== "undefined" && jQuery !== null ? (_ref = jQuery.fn) != null ? _ref.jquery : void 0 : void 0)) {
console.error(_t("Annotator requires jQuery: have you included lib/vendor/jquery.js?"));
}
if (!(JSON && JSON.parse && JSON.stringify)) {
console.error(_t("Annotator requires a JSON implementation: have you included lib/vendor/json2.js?"));
}
$ = jQuery;
Util = {};
Util.flatten = function(array) {
var flatten;
flatten = function(ary) {
var el, flat, _i, _len;
flat = [];
for (_i = 0, _len = ary.length; _i < _len; _i++) {
el = ary[_i];
flat = flat.concat(el && $.isArray(el) ? flatten(el) : el);
}
return flat;
};
return flatten(array);
};
Util.contains = function(parent, child) {
var node;
node = child;
while (node != null) {
if (node === parent) {
return true;
}
node = node.parentNode;
}
return false;
};
Util.getTextNodes = function(jq) {
var getTextNodes;
getTextNodes = function(node) {
var nodes;
if (node && node.nodeType !== Node.TEXT_NODE) {
nodes = [];
if (node.nodeType !== Node.COMMENT_NODE) {
node = node.lastChild;
while (node) {
nodes.push(getTextNodes(node));
node = node.previousSibling;
}
}
return nodes.reverse();
} else {
return node;
}
};
return jq.map(function() {
return Util.flatten(getTextNodes(this));
});
};
Util.getLastTextNodeUpTo = function(n) {
var result;
switch (n.nodeType) {
case Node.TEXT_NODE:
return n;
case Node.ELEMENT_NODE:
if (n.lastChild != null) {
result = Util.getLastTextNodeUpTo(n.lastChild);
if (result != null) {
return result;
}
}
break;
}
n = n.previousSibling;
if (n != null) {
return Util.getLastTextNodeUpTo(n);
} else {
return null;
}
};
Util.getFirstTextNodeNotBefore = function(n) {
var result;
switch (n.nodeType) {
case Node.TEXT_NODE:
return n;
case Node.ELEMENT_NODE:
if (n.firstChild != null) {
result = Util.getFirstTextNodeNotBefore(n.firstChild);
if (result != null) {
return result;
}
}
break;
}
n = n.nextSibling;
if (n != null) {
return Util.getFirstTextNodeNotBefore(n);
} else {
return null;
}
};
Util.readRangeViaSelection = function(range) {
var sel;
sel = Util.getGlobal().getSelection();
sel.removeAllRanges();
sel.addRange(range.toRange());
return sel.toString();
};
Util.xpathFromNode = function(el, relativeRoot) {
var exception, result;
try {
result = simpleXPathJQuery.call(el, relativeRoot);
} catch (_error) {
exception = _error;
console.log("jQuery-based XPath construction failed! Falling back to manual.");
result = simpleXPathPure.call(el, relativeRoot);
}
return result;
};
Util.nodeFromXPath = function(xp, root) {
var idx, name, node, step, steps, _i, _len, _ref1;
steps = xp.substring(1).split("/");
node = root;
for (_i = 0, _len = steps.length; _i < _len; _i++) {
step = steps[_i];
_ref1 = step.split("["), name = _ref1[0], idx = _ref1[1];
idx = idx != null ? parseInt((idx != null ? idx.split("]") : void 0)[0]) : 1;
node = findChild(node, name.toLowerCase(), idx);
}
return node;
};
Util.escape = function(html) {
return html.replace(/&(?!\w+;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
Util.uuid = (function() {
var counter;
counter = 0;
return function() {
return counter++;
};
})();
Util.getGlobal = function() {
return (function() {
return this;
})();
};
Util.maxZIndex = function($elements) {
var all, el;
all = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = $elements.length; _i < _len; _i++) {
el = $elements[_i];
if ($(el).css('position') === 'static') {
_results.push(-1);
} else {
_results.push(parseFloat($(el).css('z-index')) || -1);
}
}
return _results;
})();
return Math.max.apply(Math, all);
};
Util.mousePosition = function(e, offsetEl) {
var offset, _ref1;
if ((_ref1 = $(offsetEl).css('position')) !== 'absolute' && _ref1 !== 'fixed' && _ref1 !== 'relative') {
offsetEl = $(offsetEl).offsetParent()[0];
}
offset = $(offsetEl).offset();
return {
top: e.pageY - offset.top,
left: e.pageX - offset.left
};
};
Util.preventEventDefault = function(event) {
return event != null ? typeof event.preventDefault === "function" ? event.preventDefault() : void 0 : void 0;
};
functions = ["log", "debug", "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "time", "timeEnd", "profile", "profileEnd", "count", "clear", "table", "error", "notifyFirebug", "firebug", "userObjects"];
if (typeof console !== "undefined" && console !== null) {
if (console.group == null) {
console.group = function(name) {
return console.log("GROUP: ", name);
};
}
if (console.groupCollapsed == null) {
console.groupCollapsed = console.group;
}
for (_i = 0, _len = functions.length; _i < _len; _i++) {
fn = functions[_i];
if (console[fn] == null) {
console[fn] = function() {
return console.log(_t("Not implemented:") + (" console." + name));
};
}
}
} else {
this.console = {};
for (_j = 0, _len1 = functions.length; _j < _len1; _j++) {
fn = functions[_j];
this.console[fn] = function() {};
}
this.console['error'] = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return alert("ERROR: " + (args.join(', ')));
};
this.console['warn'] = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return alert("WARNING: " + (args.join(', ')));
};
}
Delegator = (function() {
Delegator.prototype.events = {};
Delegator.prototype.options = {};
Delegator.prototype.element = null;
function Delegator(element, options) {
this.options = $.extend(true, {}, this.options, options);
this.element = $(element);
this._closures = {};
this.on = this.subscribe;
this.addEvents();
}
Delegator.prototype.destroy = function() {
return this.removeEvents();
};
Delegator.prototype.addEvents = function() {
var event, _k, _len2, _ref1, _results;
_ref1 = Delegator._parseEvents(this.events);
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
event = _ref1[_k];
_results.push(this._addEvent(event.selector, event.event, event.functionName));
}
return _results;
};
Delegator.prototype.removeEvents = function() {
var event, _k, _len2, _ref1, _results;
_ref1 = Delegator._parseEvents(this.events);
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
event = _ref1[_k];
_results.push(this._removeEvent(event.selector, event.event, event.functionName));
}
return _results;
};
Delegator.prototype._addEvent = function(selector, event, functionName) {
var closure,
_this = this;
closure = function() {
return _this[functionName].apply(_this, arguments);
};
if (selector === '' && Delegator._isCustomEvent(event)) {
this.subscribe(event, closure);
} else {
this.element.delegate(selector, event, closure);
}
this._closures["" + selector + "/" + event + "/" + functionName] = closure;
return this;
};
Delegator.prototype._removeEvent = function(selector, event, functionName) {
var closure;
closure = this._closures["" + selector + "/" + event + "/" + functionName];
if (selector === '' && Delegator._isCustomEvent(event)) {
this.unsubscribe(event, closure);
} else {
this.element.undelegate(selector, event, closure);
}
delete this._closures["" + selector + "/" + event + "/" + functionName];
return this;
};
Delegator.prototype.publish = function() {
this.element.triggerHandler.apply(this.element, arguments);
return this;
};
Delegator.prototype.subscribe = function(event, callback) {
var closure;
closure = function() {
return callback.apply(this, [].slice.call(arguments, 1));
};
closure.guid = callback.guid = ($.guid += 1);
this.element.bind(event, closure);
return this;
};
Delegator.prototype.unsubscribe = function() {
this.element.unbind.apply(this.element, arguments);
return this;
};
return Delegator;
})();
Delegator._parseEvents = function(eventsObj) {
var event, events, functionName, sel, selector, _k, _ref1;
events = [];
for (sel in eventsObj) {
functionName = eventsObj[sel];
_ref1 = sel.split(' '), selector = 2 <= _ref1.length ? __slice.call(_ref1, 0, _k = _ref1.length - 1) : (_k = 0, []), event = _ref1[_k++];
events.push({
selector: selector.join(' '),
event: event,
functionName: functionName
});
}
return events;
};
Delegator.natives = (function() {
var key, specials, val;
specials = (function() {
var _ref1, _results;
_ref1 = jQuery.event.special;
_results = [];
for (key in _ref1) {
if (!__hasProp.call(_ref1, key)) continue;
val = _ref1[key];
_results.push(key);
}
return _results;
})();
return "blur focus focusin focusout load resize scroll unload click dblclick\nmousedown mouseup mousemove mouseover mouseout mouseenter mouseleave\nchange select submit keydown keypress keyup error".split(/[^a-z]+/).concat(specials);
})();
Delegator._isCustomEvent = function(event) {
event = event.split('.')[0];
return $.inArray(event, Delegator.natives) === -1;
};
Range = {};
Range.sniff = function(r) {
if (r.commonAncestorContainer != null) {
return new Range.BrowserRange(r);
} else if (typeof r.start === "string") {
return new Range.SerializedRange(r);
} else if (r.start && typeof r.start === "object") {
return new Range.NormalizedRange(r);
} else {
console.error(_t("Could not sniff range type"));
return false;
}
};
Range.nodeFromXPath = function(xpath, root) {
var customResolver, evaluateXPath, namespace, node, segment;
if (root == null) {
root = document;
}
evaluateXPath = function(xp, nsResolver) {
var exception;
if (nsResolver == null) {
nsResolver = null;
}
try {
return document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
} catch (_error) {
exception = _error;
console.log("XPath evaluation failed.");
console.log("Trying fallback...");
return Util.nodeFromXPath(xp, root);
}
};
if (!$.isXMLDoc(document.documentElement)) {
return evaluateXPath(xpath);
} else {
customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement);
node = evaluateXPath(xpath, customResolver);
if (!node) {
xpath = ((function() {
var _k, _len2, _ref1, _results;
_ref1 = xpath.split('/');
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
segment = _ref1[_k];
if (segment && segment.indexOf(':') === -1) {
_results.push(segment.replace(/^([a-z]+)/, 'xhtml:$1'));
} else {
_results.push(segment);
}
}
return _results;
})()).join('/');
namespace = document.lookupNamespaceURI(null);
customResolver = function(ns) {
if (ns === 'xhtml') {
return namespace;
} else {
return document.documentElement.getAttribute('xmlns:' + ns);
}
};
node = evaluateXPath(xpath, customResolver);
}
return node;
}
};
Range.RangeError = (function(_super) {
__extends(RangeError, _super);
function RangeError(type, message, parent) {
this.type = type;
this.message = message;
this.parent = parent != null ? parent : null;
RangeError.__super__.constructor.call(this, this.message);
}
return RangeError;
})(Error);
Range.BrowserRange = (function() {
function BrowserRange(obj) {
this.commonAncestorContainer = obj.commonAncestorContainer;
this.startContainer = obj.startContainer;
this.startOffset = obj.startOffset;
this.endContainer = obj.endContainer;
this.endOffset = obj.endOffset;
}
BrowserRange.prototype.normalize = function(root) {
var n, node, nr, r;
if (this.tainted) {
console.error(_t("You may only call normalize() once on a BrowserRange!"));
return false;
} else {
this.tainted = true;
}
r = {};
if (this.startContainer.nodeType === Node.ELEMENT_NODE) {
r.start = Util.getFirstTextNodeNotBefore(this.startContainer.childNodes[this.startOffset]);
r.startOffset = 0;
} else {
r.start = this.startContainer;
r.startOffset = this.startOffset;
}
if (this.endContainer.nodeType === Node.ELEMENT_NODE) {
node = this.endContainer.childNodes[this.endOffset];
if (node != null) {
n = node;
while ((n != null) && (n.nodeType !== Node.TEXT_NODE)) {
n = n.firstChild;
}
if (n != null) {
r.end = n;
r.endOffset = 0;
}
}
if (r.end == null) {
node = this.endContainer.childNodes[this.endOffset - 1];
r.end = Util.getLastTextNodeUpTo(node);
r.endOffset = r.end.nodeValue.length;
}
} else {
r.end = this.endContainer;
r.endOffset = this.endOffset;
}
nr = {};
if (r.startOffset > 0) {
if (!r.start.nextSibling || r.start.nodeValue.length > r.startOffset) {
nr.start = r.start.splitText(r.startOffset);
} else {
nr.start = r.start.nextSibling;
}
} else {
nr.start = r.start;
}
if (r.start === r.end) {
if (nr.start.nodeValue.length > (r.endOffset - r.startOffset)) {
nr.start.splitText(r.endOffset - r.startOffset);
}
nr.end = nr.start;
} else {
if (r.end.nodeValue.length > r.endOffset) {
r.end.splitText(r.endOffset);
}
nr.end = r.end;
}
nr.commonAncestor = this.commonAncestorContainer;
while (nr.commonAncestor.nodeType !== Node.ELEMENT_NODE) {
nr.commonAncestor = nr.commonAncestor.parentNode;
}
return new Range.NormalizedRange(nr);
};
BrowserRange.prototype.serialize = function(root, ignoreSelector) {
return this.normalize(root).serialize(root, ignoreSelector);
};
return BrowserRange;
})();
Range.NormalizedRange = (function() {
function NormalizedRange(obj) {
this.commonAncestor = obj.commonAncestor;
this.start = obj.start;
this.end = obj.end;
}
NormalizedRange.prototype.normalize = function(root) {
return this;
};
NormalizedRange.prototype.limit = function(bounds) {
var nodes, parent, startParents, _k, _len2, _ref1;
nodes = $.grep(this.textNodes(), function(node) {
return node.parentNode === bounds || $.contains(bounds, node.parentNode);
});
if (!nodes.length) {
return null;
}
this.start = nodes[0];
this.end = nodes[nodes.length - 1];
startParents = $(this.start).parents();
_ref1 = $(this.end).parents();
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
parent = _ref1[_k];
if (startParents.index(parent) !== -1) {
this.commonAncestor = parent;
break;
}
}
return this;
};
NormalizedRange.prototype.serialize = function(root, ignoreSelector) {
var end, serialization, start;
serialization = function(node, isEnd) {
var n, nodes, offset, origParent, textNodes, xpath, _k, _len2;
if (ignoreSelector) {
origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0);
} else {
origParent = $(node).parent();
}
xpath = Util.xpathFromNode(origParent, root)[0];
textNodes = Util.getTextNodes(origParent);
nodes = textNodes.slice(0, textNodes.index(node));
offset = 0;
for (_k = 0, _len2 = nodes.length; _k < _len2; _k++) {
n = nodes[_k];
offset += n.nodeValue.length;
}
if (isEnd) {
return [xpath, offset + node.nodeValue.length];
} else {
return [xpath, offset];
}
};
start = serialization(this.start);
end = serialization(this.end, true);
return new Range.SerializedRange({
start: start[0],
end: end[0],
startOffset: start[1],
endOffset: end[1]
});
};
NormalizedRange.prototype.text = function() {
var node;
return ((function() {
var _k, _len2, _ref1, _results;
_ref1 = this.textNodes();
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
node = _ref1[_k];
_results.push(node.nodeValue);
}
return _results;
}).call(this)).join('');
};
NormalizedRange.prototype.textNodes = function() {
var end, start, textNodes, _ref1;
textNodes = Util.getTextNodes($(this.commonAncestor));
_ref1 = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref1[0], end = _ref1[1];
return $.makeArray(textNodes.slice(start, +end + 1 || 9e9));
};
NormalizedRange.prototype.toRange = function() {
var range;
range = document.createRange();
range.setStartBefore(this.start);
range.setEndAfter(this.end);
return range;
};
return NormalizedRange;
})();
Range.SerializedRange = (function() {
function SerializedRange(obj) {
this.start = obj.start;
this.startOffset = obj.startOffset;
this.end = obj.end;
this.endOffset = obj.endOffset;
}
SerializedRange.prototype.normalize = function(root) {
var contains, e, length, node, p, range, targetOffset, tn, _k, _l, _len2, _len3, _ref1, _ref2;
range = {};
_ref1 = ['start', 'end'];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
p = _ref1[_k];
try {
node = Range.nodeFromXPath(this[p], root);
} catch (_error) {
e = _error;
throw new Range.RangeError(p, ("Error while finding " + p + " node: " + this[p] + ": ") + e, e);
}
if (!node) {
throw new Range.RangeError(p, "Couldn't find " + p + " node: " + this[p]);
}
length = 0;
targetOffset = this[p + 'Offset'];
if (p === 'end') {
targetOffset--;
}
_ref2 = Util.getTextNodes($(node));
for (_l = 0, _len3 = _ref2.length; _l < _len3; _l++) {
tn = _ref2[_l];
if (length + tn.nodeValue.length > targetOffset) {
range[p + 'Container'] = tn;
range[p + 'Offset'] = this[p + 'Offset'] - length;
break;
} else {
length += tn.nodeValue.length;
}
}
if (range[p + 'Offset'] == null) {
throw new Range.RangeError("" + p + "offset", "Couldn't find offset " + this[p + 'Offset'] + " in element " + this[p]);
}
}
contains = document.compareDocumentPosition == null ? function(a, b) {
return a.contains(b);
} : function(a, b) {
return a.compareDocumentPosition(b) & 16;
};
$(range.startContainer).parents().each(function() {
if (contains(this, range.endContainer)) {
range.commonAncestorContainer = this;
return false;
}
});
return new Range.BrowserRange(range).normalize(root);
};
SerializedRange.prototype.serialize = function(root, ignoreSelector) {
return this.normalize(root).serialize(root, ignoreSelector);
};
SerializedRange.prototype.toObject = function() {
return {
start: this.start,
startOffset: this.startOffset,
end: this.end,
endOffset: this.endOffset
};
};
return SerializedRange;
})();
_Annotator = this.Annotator;
Annotator = (function(_super) {
__extends(Annotator, _super);
Annotator.prototype.events = {
".annotator-adder button click": "onAdderClick",
".annotator-adder button mousedown": "onAdderMousedown",
".annotator-hl mouseover": "onHighlightMouseover",
".annotator-hl mouseout": "startViewerHideTimer"
};
Annotator.prototype.html = {
adder: '<div class="annotator-adder"><button>' + _t('Annotate') + '</button></div>',
wrapper: '<div class="annotator-wrapper"></div>'
};
Annotator.prototype.options = {
readOnly: false
};
Annotator.prototype.plugins = {};
Annotator.prototype.editor = null;
Annotator.prototype.viewer = null;
Annotator.prototype.selectedRanges = null;
Annotator.prototype.mouseIsDown = false;
Annotator.prototype.ignoreMouseup = false;
Annotator.prototype.viewerHideTimer = null;
function Annotator(element, options) {
this.onDeleteAnnotation = __bind(this.onDeleteAnnotation, this);
this.onEditAnnotation = __bind(this.onEditAnnotation, this);
this.onAdderClick = __bind(this.onAdderClick, this);
this.onAdderMousedown = __bind(this.onAdderMousedown, this);
this.onHighlightMouseover = __bind(this.onHighlightMouseover, this);
this.checkForEndSelection = __bind(this.checkForEndSelection, this);
this.checkForStartSelection = __bind(this.checkForStartSelection, this);
this.clearViewerHideTimer = __bind(this.clearViewerHideTimer, this);
this.startViewerHideTimer = __bind(this.startViewerHideTimer, this);
this.showViewer = __bind(this.showViewer, this);
this.onEditorSubmit = __bind(this.onEditorSubmit, this);
this.onEditorHide = __bind(this.onEditorHide, this);
this.showEditor = __bind(this.showEditor, this);
Annotator.__super__.constructor.apply(this, arguments);
this.plugins = {};
if (!Annotator.supported()) {
return this;
}
if (!this.options.readOnly) {
this._setupDocumentEvents();
}
this._setupWrapper()._setupViewer()._setupEditor();
this._setupDynamicStyle();
this.adder = $(this.html.adder).appendTo(this.wrapper).hide();
Annotator._instances.push(this);
}
Annotator.prototype._setupWrapper = function() {
this.wrapper = $(this.html.wrapper);
this.element.find('script').remove();
this.element.wrapInner(this.wrapper);
this.wrapper = this.element.find('.annotator-wrapper');
return this;
};
Annotator.prototype._setupViewer = function() {
var _this = this;
this.viewer = new Annotator.Viewer({
readOnly: this.options.readOnly
});
this.viewer.hide().on("edit", this.onEditAnnotation).on("delete", this.onDeleteAnnotation).addField({
load: function(field, annotation) {
if (annotation.text) {
$(field).html(Util.escape(annotation.text));
} else {
$(field).html("<i>" + (_t('No Comment')) + "</i>");
}
return _this.publish('annotationViewerTextField', [field, annotation]);
}
}).element.appendTo(this.wrapper).bind({
"mouseover": this.clearViewerHideTimer,
"mouseout": this.startViewerHideTimer
});
return this;
};
Annotator.prototype._setupEditor = function() {
this.editor = new Annotator.Editor();
this.editor.hide().on('hide', this.onEditorHide).on('save', this.onEditorSubmit).addField({
type: 'textarea',
label: _t('Comments') + '\u2026',
load: function(field, annotation) {
return $(field).find('textarea').val(annotation.text || '');
},
submit: function(field, annotation) {
return annotation.text = $(field).find('textarea').val();
}
});
this.editor.element.appendTo(this.wrapper);
return this;
};
Annotator.prototype._setupDocumentEvents = function() {
$(document).bind({
"mouseup": this.checkForEndSelection,
"mousedown": this.checkForStartSelection
});
return this;
};
Annotator.prototype._setupDynamicStyle = function() {
var max, sel, style, x;
style = $('#annotator-dynamic-style');
if (!style.length) {
style = $('<style id="annotator-dynamic-style"></style>').appendTo(document.head);
}
sel = '*' + ((function() {
var _k, _len2, _ref1, _results;
_ref1 = ['adder', 'outer', 'notice', 'filter'];
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
x = _ref1[_k];
_results.push(":not(.annotator-" + x + ")");
}
return _results;
})()).join('');
max = Util.maxZIndex($(document.body).find(sel));
max = Math.max(max, 1000);
style.text([".annotator-adder, .annotator-outer, .annotator-notice {", " z-index: " + (max + 20) + ";", "}", ".annotator-filter {", " z-index: " + (max + 10) + ";", "}"].join("\n"));
return this;
};
Annotator.prototype.destroy = function() {
var idx, name, plugin, _base, _ref1;
Annotator.__super__.destroy.apply(this, arguments);
$(document).unbind({
"mouseup": this.checkForEndSelection,
"mousedown": this.checkForStartSelection
});
$('#annotator-dynamic-style').remove();
this.adder.remove();
this.viewer.destroy();
this.editor.destroy();
this.wrapper.find('.annotator-hl').each(function() {
$(this).contents().insertBefore(this);
return $(this).remove();
});
this.wrapper.contents().insertBefore(this.wrapper);
this.wrapper.remove();
this.element.data('annotator', null);
_ref1 = this.plugins;
for (name in _ref1) {
plugin = _ref1[name];
if (typeof (_base = this.plugins[name]).destroy === "function") {
_base.destroy();
}
}
idx = Annotator._instances.indexOf(this);
if (idx !== -1) {
return Annotator._instances.splice(idx, 1);
}
};
Annotator.prototype.getSelectedRanges = function() {
var browserRange, i, normedRange, r, ranges, rangesToIgnore, selection, _k, _len2;
selection = Util.getGlobal().getSelection();
ranges = [];
rangesToIgnore = [];
if (!selection.isCollapsed) {
ranges = (function() {
var _k, _ref1, _results;
_results = [];
for (i = _k = 0, _ref1 = selection.rangeCount; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
r = selection.getRangeAt(i);
browserRange = new Range.BrowserRange(r);
normedRange = browserRange.normalize().limit(this.wrapper[0]);
if (normedRange === null) {
rangesToIgnore.push(r);
}
_results.push(normedRange);
}
return _results;
}).call(this);
selection.removeAllRanges();
}
for (_k = 0, _len2 = rangesToIgnore.length; _k < _len2; _k++) {
r = rangesToIgnore[_k];
selection.addRange(r);
}
return $.grep(ranges, function(range) {
if (range) {
selection.addRange(range.toRange());
}
return range;
});
};
Annotator.prototype.createAnnotation = function() {
var annotation;
annotation = {};
this.publish('beforeAnnotationCreated', [annotation]);
return annotation;
};
Annotator.prototype.setupAnnotation = function(annotation) {
var e, normed, normedRanges, r, root, _k, _l, _len2, _len3, _ref1;
root = this.wrapper[0];
annotation.ranges || (annotation.ranges = this.selectedRanges);
normedRanges = [];
_ref1 = annotation.ranges;
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
r = _ref1[_k];
try {
normedRanges.push(Range.sniff(r).normalize(root));
} catch (_error) {
e = _error;
if (e instanceof Range.RangeError) {
this.publish('rangeNormalizeFail', [annotation, r, e]);
} else {
throw e;
}
}
}
annotation.quote = [];
annotation.ranges = [];
annotation.highlights = [];
for (_l = 0, _len3 = normedRanges.length; _l < _len3; _l++) {
normed = normedRanges[_l];
annotation.quote.push($.trim(normed.text()));
annotation.ranges.push(normed.serialize(this.wrapper[0], '.annotator-hl'));
$.merge(annotation.highlights, this.highlightRange(normed));
}
annotation.quote = annotation.quote.join(' / ');
$(annotation.highlights).data('annotation', annotation);
$(annotation.highlights).attr('data-annotation-id', annotation.id);
return annotation;
};
Annotator.prototype.updateAnnotation = function(annotation) {
this.publish('beforeAnnotationUpdated', [annotation]);
$(annotation.highlights).attr('data-annotation-id', annotation.id);
this.publish('annotationUpdated', [annotation]);
return annotation;
};
Annotator.prototype.deleteAnnotation = function(annotation) {
var child, h, _k, _len2, _ref1;
if (annotation.highlights != null) {
_ref1 = annotation.highlights;
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
h = _ref1[_k];
if (!(h.parentNode != null)) {
continue;
}
child = h.childNodes[0];
$(h).replaceWith(h.childNodes);
}
}
this.publish('annotationDeleted', [annotation]);
return annotation;
};
Annotator.prototype.loadAnnotations = function(annotations) {
var clone, loader,
_this = this;
if (annotations == null) {
annotations = [];
}
loader = function(annList) {
var n, now, _k, _len2;
if (annList == null) {
annList = [];
}
now = annList.splice(0, 10);
for (_k = 0, _len2 = now.length; _k < _len2; _k++) {
n = now[_k];
_this.setupAnnotation(n);
}
if (annList.length > 0) {
return setTimeout((function() {
return loader(annList);
}), 10);
} else {
return _this.publish('annotationsLoaded', [clone]);
}
};
clone = annotations.slice();
loader(annotations);
return this;
};
Annotator.prototype.dumpAnnotations = function() {
if (this.plugins['Store']) {
return this.plugins['Store'].dumpAnnotations();
} else {
console.warn(_t("Can't dump annotations without Store plugin."));
return false;
}
};
Annotator.prototype.highlightRange = function(normedRange, cssClass) {
var hl, node, white, _k, _len2, _ref1, _results;
if (cssClass == null) {
cssClass = 'annotator-hl';
}
white = /^\s*$/;
hl = $("<span class='" + cssClass + "'></span>");
_ref1 = normedRange.textNodes();
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
node = _ref1[_k];
if (!white.test(node.nodeValue)) {
_results.push($(node).wrapAll(hl).parent().show()[0]);
}
}
return _results;
};
Annotator.prototype.highlightRanges = function(normedRanges, cssClass) {
var highlights, r, _k, _len2;
if (cssClass == null) {
cssClass = 'annotator-hl';
}
highlights = [];
for (_k = 0, _len2 = normedRanges.length; _k < _len2; _k++) {
r = normedRanges[_k];
$.merge(highlights, this.highlightRange(r, cssClass));
}
return highlights;
};
Annotator.prototype.addPlugin = function(name, options) {
var klass, _base;
if (this.plugins[name]) {
console.error(_t("You cannot have more than one instance of any plugin."));
} else {
klass = Annotator.Plugin[name];
if (typeof klass === 'function') {
this.plugins[name] = new klass(this.element[0], options);
this.plugins[name].annotator = this;
if (typeof (_base = this.plugins[name]).pluginInit === "function") {
_base.pluginInit();
}
} else {
console.error(_t("Could not load ") + name + _t(" plugin. Have you included the appropriate <script> tag?"));
}
}
return this;
};
Annotator.prototype.showEditor = function(annotation, location) {
this.editor.element.css(location);
this.editor.load(annotation);
this.publish('annotationEditorShown', [this.editor, annotation]);
return this;
};
Annotator.prototype.onEditorHide = function() {
this.publish('annotationEditorHidden', [this.editor]);
return this.ignoreMouseup = false;
};
Annotator.prototype.onEditorSubmit = function(annotation) {
return this.publish('annotationEditorSubmit', [this.editor, annotation]);
};
Annotator.prototype.showViewer = function(annotations, location) {
this.viewer.element.css(location);
this.viewer.load(annotations);
return this.publish('annotationViewerShown', [this.viewer, annotations]);
};
Annotator.prototype.startViewerHideTimer = function() {
if (!this.viewerHideTimer) {
return this.viewerHideTimer = setTimeout(this.viewer.hide, 250);
}
};
Annotator.prototype.clearViewerHideTimer = function() {
clearTimeout(this.viewerHideTimer);
return this.viewerHideTimer = false;
};
Annotator.prototype.checkForStartSelection = function(event) {
if (!(event && this.isAnnotator(event.target))) {
this.startViewerHideTimer();
}
return this.mouseIsDown = true;
};
Annotator.prototype.checkForEndSelection = function(event) {
var container, range, _k, _len2, _ref1;
this.mouseIsDown = false;
if (this.ignoreMouseup) {
return;
}
this.selectedRanges = this.getSelectedRanges();
_ref1 = this.selectedRanges;
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
range = _ref1[_k];
container = range.commonAncestor;
if (this.isAnnotator(container)) {
return;
}
}
if (event && this.selectedRanges.length) {
return this.adder.css(Util.mousePosition(event, this.wrapper[0])).show();
} else {
return this.adder.hide();
}
};
Annotator.prototype.isAnnotator = function(element) {
return !!$(element).parents().addBack().filter('[class^=annotator-]').not('[class=annotator-hl]').not(this.wrapper).length;
};
Annotator.prototype.onHighlightMouseover = function(event) {
var annotations;
this.clearViewerHideTimer();
if (this.mouseIsDown) {
return false;
}
if (this.viewer.isShown()) {
this.viewer.hide();
}
annotations = $(event.target).parents('.annotator-hl').addBack().map(function() {
return $(this).data("annotation");
}).toArray();
return this.showViewer(annotations, Util.mousePosition(event, this.wrapper[0]));
};
Annotator.prototype.onAdderMousedown = function(event) {
if (event != null) {
event.preventDefault();
}
return this.ignoreMouseup = true;
};
Annotator.prototype.onAdderClick = function(event) {
var annotation, cancel, cleanup, position, save,
_this = this;
if (event != null) {
event.preventDefault();
}
position = this.adder.position();
this.adder.hide();
annotation = this.setupAnnotation(this.createAnnotation());
$(annotation.highlights).addClass('annotator-hl-temporary');
save = function() {
cleanup();
$(annotation.highlights).removeClass('annotator-hl-temporary');
return _this.publish('annotationCreated', [annotation]);
};
cancel = function() {
cleanup();
return _this.deleteAnnotation(annotation);
};
cleanup = function() {
_this.unsubscribe('annotationEditorHidden', cancel);
return _this.unsubscribe('annotationEditorSubmit', save);
};
this.subscribe('annotationEditorHidden', cancel);
this.subscribe('annotationEditorSubmit', save);
return this.showEditor(annotation, position);
};
Annotator.prototype.onEditAnnotation = function(annotation) {
var cleanup, offset, update,
_this = this;
offset = this.viewer.element.position();
update = function() {
cleanup();
return _this.updateAnnotation(annotation);
};
cleanup = function() {
_this.unsubscribe('annotationEditorHidden', cleanup);
return _this.unsubscribe('annotationEditorSubmit', update);
};
this.subscribe('annotationEditorHidden', cleanup);
this.subscribe('annotationEditorSubmit', update);
this.viewer.hide();
return this.showEditor(annotation, offset);
};
Annotator.prototype.onDeleteAnnotation = function(annotation) {
this.viewer.hide();
return this.deleteAnnotation(annotation);
};
return Annotator;
})(Delegator);
Annotator.Plugin = (function(_super) {
__extends(Plugin, _super);
function Plugin(element, options) {
Plugin.__super__.constructor.apply(this, arguments);
}
Plugin.prototype.pluginInit = function() {};
return Plugin;
})(Delegator);
g = Util.getGlobal();
if (((_ref1 = g.document) != null ? _ref1.evaluate : void 0) == null) {
$.getScript('http://assets.annotateit.org/vendor/xpath.min.js');
}
if (g.getSelection == null) {
$.getScript('http://assets.annotateit.org/vendor/ierange.min.js');
}
if (g.JSON == null) {
$.getScript('http://assets.annotateit.org/vendor/json2.min.js');
}
if (g.Node == null) {
g.Node = {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
TEXT_NODE: 3,
CDATA_SECTION_NODE: 4,
ENTITY_REFERENCE_NODE: 5,
ENTITY_NODE: 6,
PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8,
DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11,
NOTATION_NODE: 12
};
}
Annotator.$ = $;
Annotator.Delegator = Delegator;
Annotator.Range = Range;
Annotator.Util = Util;
Annotator._instances = [];
Annotator._t = _t;
Annotator.supported = function() {
return (function() {
return !!this.getSelection;
})();
};
Annotator.noConflict = function() {
Util.getGlobal().Annotator = _Annotator;
return this;
};
$.fn.annotator = function(options) {
var args;
args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var instance;
instance = $.data(this, 'annotator');
if (options === 'destroy') {
$.removeData(this, 'annotator');
return instance != null ? instance.destroy(args) : void 0;
} else if (instance) {
return options && instance[options].apply(instance, args);
} else {
instance = new Annotator(this, options);
return $.data(this, 'annotator', instance);
}
});
};
this.Annotator = Annotator;
Annotator.Widget = (function(_super) {
__extends(Widget, _super);
Widget.prototype.classes = {
hide: 'annotator-hide',
invert: {
x: 'annotator-invert-x',
y: 'annotator-invert-y'
}
};
function Widget(element, options) {
Widget.__super__.constructor.apply(this, arguments);
this.classes = $.extend({}, Annotator.Widget.prototype.classes, this.classes);
}
Widget.prototype.destroy = function() {
this.removeEvents();
return this.element.remove();
};
Widget.prototype.checkOrientation = function() {
var current, offset, viewport, widget, window;
this.resetOrientation();
window = $(Annotator.Util.getGlobal());
widget = this.element.children(":first");
offset = widget.offset();
viewport = {
top: window.scrollTop(),
right: window.width() + window.scrollLeft()
};
current = {
top: offset.top,
right: offset.left + widget.width()
};
if ((current.top - viewport.top) < 0) {
this.invertY();
}
if ((current.right - viewport.right) > 0) {
this.invertX();
}
return this;
};
Widget.prototype.resetOrientation = function() {
this.element.removeClass(this.classes.invert.x).removeClass(this.classes.invert.y);
return this;
};
Widget.prototype.invertX = function() {
this.element.addClass(this.classes.invert.x);
return this;
};
Widget.prototype.invertY = function() {
this.element.addClass(this.classes.invert.y);
return this;
};
Widget.prototype.isInvertedY = function() {
return this.element.hasClass(this.classes.invert.y);
};
Widget.prototype.isInvertedX = function() {
return this.element.hasClass(this.classes.invert.x);
};
return Widget;
})(Delegator);
Annotator.Editor = (function(_super) {
__extends(Editor, _super);
Editor.prototype.events = {
"form submit": "submit",
".annotator-save click": "submit",
".annotator-cancel click": "hide",
".annotator-cancel mouseover": "onCancelButtonMouseover",
"textarea keydown": "processKeypress"
};
Editor.prototype.classes = {
hide: 'annotator-hide',
focus: 'annotator-focus'
};
Editor.prototype.html = "<div class=\"annotator-outer annotator-editor\">\n <form class=\"annotator-widget\">\n <ul class=\"annotator-listing\"></ul>\n <div class=\"annotator-controls\">\n <a href=\"#cancel\" class=\"annotator-cancel\">" + _t('Cancel') + "</a>\n<a href=\"#save\" class=\"annotator-save annotator-focus\">" + _t('Save') + "</a>\n </div>\n </form>\n</div>";
Editor.prototype.options = {};
function Editor(options) {
this.onCancelButtonMouseover = __bind(this.onCancelButtonMouseover, this);
this.processKeypress = __bind(this.processKeypress, this);
this.submit = __bind(this.submit, this);
this.load = __bind(this.load, this);
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Editor.__super__.constructor.call(this, $(this.html)[0], options);
this.fields = [];
this.annotation = {};
}
Editor.prototype.show = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.removeClass(this.classes.hide);
this.element.find('.annotator-save').addClass(this.classes.focus);
this.checkOrientation();
this.element.find(":input:first").focus();
this.setupDraggables();
return this.publish('show');
};
Editor.prototype.hide = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.addClass(this.classes.hide);
return this.publish('hide');
};
Editor.prototype.load = function(annotation) {
var field, _k, _len2, _ref2;
this.annotation = annotation;
this.publish('load', [this.annotation]);
_ref2 = this.fields;
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
field = _ref2[_k];
field.load(field.element, this.annotation);
}
return this.show();
};
Editor.prototype.submit = function(event) {
var field, _k, _len2, _ref2;
Annotator.Util.preventEventDefault(event);
_ref2 = this.fields;
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
field = _ref2[_k];
field.submit(field.element, this.annotation);
}
this.publish('save', [this.annotation]);
return this.hide();
};
Editor.prototype.addField = function(options) {
var element, field, input;
field = $.extend({
id: 'annotator-field-' + Annotator.Util.uuid(),
type: 'input',
label: '',
load: function() {},
submit: function() {}
}, options);
input = null;
element = $('<li class="annotator-item" />');
field.element = element[0];
switch (field.type) {
case 'textarea':
input = $('<textarea />');
break;
case 'input':
case 'checkbox':
input = $('<input />');
break;
case 'select':
input = $('<select />');
}
element.append(input);
input.attr({
id: field.id,
placeholder: field.label
});
if (field.type === 'checkbox') {
input[0].type = 'checkbox';
element.addClass('annotator-checkbox');
element.append($('<label />', {
"for": field.id,
html: field.label
}));
}
this.element.find('ul:first').append(element);
this.fields.push(field);
return field.element;
};
Editor.prototype.checkOrientation = function() {
var controls, list;
Editor.__super__.checkOrientation.apply(this, arguments);
list = this.element.find('ul');
controls = this.element.find('.annotator-controls');
if (this.element.hasClass(this.classes.invert.y)) {
controls.insertBefore(list);
} else if (controls.is(':first-child')) {
controls.insertAfter(list);
}
return this;
};
Editor.prototype.processKeypress = function(event) {
if (event.keyCode === 27) {
return this.hide();
} else if (event.keyCode === 13 && !event.shiftKey) {
return this.submit();
}
};
Editor.prototype.onCancelButtonMouseover = function() {
return this.element.find('.' + this.classes.focus).removeClass(this.classes.focus);
};
Editor.prototype.setupDraggables = function() {
var classes, controls, cornerItem, editor, mousedown, onMousedown, onMousemove, onMouseup, resize, textarea, throttle,
_this = this;
this.element.find('.annotator-resize').remove();
if (this.element.hasClass(this.classes.invert.y)) {
cornerItem = this.element.find('.annotator-item:last');
} else {
cornerItem = this.element.find('.annotator-item:first');
}
if (cornerItem) {
$('<span class="annotator-resize"></span>').appendTo(cornerItem);
}
mousedown = null;
classes = this.classes;
editor = this.element;
textarea = null;
resize = editor.find('.annotator-resize');
controls = editor.find('.annotator-controls');
throttle = false;
onMousedown = function(event) {
if (event.target === this) {
mousedown = {
element: this,
top: event.pageY,
left: event.pageX
};
textarea = editor.find('textarea:first');
$(window).bind({
'mouseup.annotator-editor-resize': onMouseup,
'mousemove.annotator-editor-resize': onMousemove
});
return event.preventDefault();
}
};
onMouseup = function() {
mousedown = null;
return $(window).unbind('.annotator-editor-resize');
};
onMousemove = function(event) {
var diff, directionX, directionY, height, width;
if (mousedown && throttle === false) {
diff = {
top: event.pageY - mousedown.top,
left: event.pageX - mousedown.left
};
if (mousedown.element === resize[0]) {
height = textarea.height();
width = textarea.width();
directionX = editor.hasClass(classes.invert.x) ? -1 : 1;
directionY = editor.hasClass(classes.invert.y) ? 1 : -1;
textarea.height(height + (diff.top * directionY));
textarea.width(width + (diff.left * directionX));
if (textarea.height() !== height) {
mousedown.top = event.pageY;
}
if (textarea.width() !== width) {
mousedown.left = event.pageX;
}
} else if (mousedown.element === controls[0]) {
editor.css({
top: parseInt(editor.css('top'), 10) + diff.top,
left: parseInt(editor.css('left'), 10) + diff.left
});
mousedown.top = event.pageY;
mousedown.left = event.pageX;
}
throttle = true;
return setTimeout(function() {
return throttle = false;
}, 1000 / 60);
}
};
resize.bind('mousedown', onMousedown);
return controls.bind('mousedown', onMousedown);
};
return Editor;
})(Annotator.Widget);
Annotator.Viewer = (function(_super) {
__extends(Viewer, _super);
Viewer.prototype.events = {
".annotator-edit click": "onEditClick",
".annotator-delete click": "onDeleteClick"
};
Viewer.prototype.classes = {
hide: 'annotator-hide',
showControls: 'annotator-visible'
};
Viewer.prototype.html = {
element: "<div class=\"annotator-outer annotator-viewer\">\n <ul class=\"annotator-widget annotator-listing\"></ul>\n</div>",
item: "<li class=\"annotator-annotation annotator-item\">\n <span class=\"annotator-controls\">\n <a href=\"#\" title=\"View as webpage\" class=\"annotator-link\">View as webpage</a>\n <button title=\"Edit\" class=\"annotator-edit\">Edit</button>\n <button title=\"Delete\" class=\"annotator-delete\">Delete</button>\n </span>\n</li>"
};
Viewer.prototype.options = {
readOnly: false
};
function Viewer(options) {
this.onDeleteClick = __bind(this.onDeleteClick, this);
this.onEditClick = __bind(this.onEditClick, this);
this.load = __bind(this.load, this);
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Viewer.__super__.constructor.call(this, $(this.html.element)[0], options);
this.item = $(this.html.item)[0];
this.fields = [];
this.annotations = [];
}
Viewer.prototype.show = function(event) {
var controls,
_this = this;
Annotator.Util.preventEventDefault(event);
controls = this.element.find('.annotator-controls').addClass(this.classes.showControls);
setTimeout((function() {
return controls.removeClass(_this.classes.showControls);
}), 500);
this.element.removeClass(this.classes.hide);
return this.checkOrientation().publish('show');
};
Viewer.prototype.isShown = function() {
return !this.element.hasClass(this.classes.hide);
};
Viewer.prototype.hide = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.addClass(this.classes.hide);
return this.publish('hide');
};
Viewer.prototype.load = function(annotations) {
var annotation, controller, controls, del, edit, element, field, item, link, links, list, _k, _l, _len2, _len3, _ref2, _ref3;
this.annotations = annotations || [];
list = this.element.find('ul:first').empty();
_ref2 = this.annotations;
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
annotation = _ref2[_k];
item = $(this.item).clone().appendTo(list).data('annotation', annotation);
controls = item.find('.annotator-controls');
link = controls.find('.annotator-link');
edit = controls.find('.annotator-edit');
del = controls.find('.annotator-delete');
links = new LinkParser(annotation.links || []).get('alternate', {
'type': 'text/html'
});
if (links.length === 0 || (links[0].href == null)) {
link.remove();
} else {
link.attr('href', links[0].href);
}
if (this.options.readOnly) {
edit.remove();
del.remove();
} else {
controller = {
showEdit: function() {
return edit.removeAttr('disabled');
},
hideEdit: function() {
return edit.attr('disabled', 'disabled');
},
showDelete: function() {
return del.removeAttr('disabled');
},
hideDelete: function() {
return del.attr('disabled', 'disabled');
}
};
}
_ref3 = this.fields;
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
field = _ref3[_l];
element = $(field.element).clone().appendTo(item)[0];
field.load(element, annotation, controller);
}
}
this.publish('load', [this.annotations]);
return this.show();
};
Viewer.prototype.addField = function(options) {
var field;
field = $.extend({
load: function() {}
}, options);
field.element = $('<div />')[0];
this.fields.push(field);
field.element;
return this;
};
Viewer.prototype.onEditClick = function(event) {
return this.onButtonClick(event, 'edit');
};
Viewer.prototype.onDeleteClick = function(event) {
return this.onButtonClick(event, 'delete');
};
Viewer.prototype.onButtonClick = function(event, type) {
var item;
item = $(event.target).parents('.annotator-annotation');
return this.publish(type, [item.data('annotation')]);
};
return Viewer;
})(Annotator.Widget);
LinkParser = (function() {
function LinkParser(data) {
this.data = data;
}
LinkParser.prototype.get = function(rel, cond) {
var d, k, keys, match, v, _k, _len2, _ref2, _results;
if (cond == null) {
cond = {};
}
cond = $.extend({}, cond, {
rel: rel
});
keys = (function() {
var _results;
_results = [];
for (k in cond) {
if (!__hasProp.call(cond, k)) continue;
v = cond[k];
_results.push(k);
}
return _results;
})();
_ref2 = this.data;
_results = [];
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
d = _ref2[_k];
match = keys.reduce((function(m, k) {
return m && (d[k] === cond[k]);
}), true);
if (match) {
_results.push(d);
} else {
continue;
}
}
return _results;
};
return LinkParser;
})();
Annotator = Annotator || {};
Annotator.Notification = (function(_super) {
__extends(Notification, _super);
Notification.prototype.events = {
"click": "hide"
};
Notification.prototype.options = {
html: "<div class='annotator-notice'></div>",
classes: {
show: "annotator-notice-show",
info: "annotator-notice-info",
success: "annotator-notice-success",
error: "annotator-notice-error"
}
};
function Notification(options) {
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Notification.__super__.constructor.call(this, $(this.options.html).appendTo(document.body)[0], options);
}
Notification.prototype.show = function(message, status) {
if (status == null) {
status = Annotator.Notification.INFO;
}
this.currentStatus = status;
$(this.element).addClass(this.options.classes.show).addClass(this.options.classes[this.currentStatus]).html(Util.escape(message || ""));
setTimeout(this.hide, 5000);
return this;
};
Notification.prototype.hide = function() {
if (this.currentStatus == null) {
this.currentStatus = Annotator.Notification.INFO;
}
$(this.element).removeClass(this.options.classes.show).removeClass(this.options.classes[this.currentStatus]);
return this;
};
return Notification;
})(Delegator);
Annotator.Notification.INFO = 'info';
Annotator.Notification.SUCCESS = 'success';
Annotator.Notification.ERROR = 'error';
$(function() {
var notification;
notification = new Annotator.Notification;
Annotator.showNotification = notification.show;
return Annotator.hideNotification = notification.hide;
});
}).call(this);
//
//# sourceMappingURL=annotator.map
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