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') ...@@ -3,8 +3,8 @@ extend = require('extend')
raf = require('raf') raf = require('raf')
scrollIntoView = require('scroll-into-view') scrollIntoView = require('scroll-into-view')
Annotator = require('annotator') Delegator = require('./delegator')
$ = Annotator.$ $ = require('jquery')
adder = require('./adder') adder = require('./adder')
highlighter = require('./highlighter') highlighter = require('./highlighter')
...@@ -31,7 +31,7 @@ normalizeURI = (uri, baseURI) -> ...@@ -31,7 +31,7 @@ normalizeURI = (uri, baseURI) ->
# See https://github.com/hypothesis/h/issues/3471#issuecomment-226713750 # See https://github.com/hypothesis/h/issues/3471#issuecomment-226713750
return url.toString().replace(/#.*/, ''); return url.toString().replace(/#.*/, '');
module.exports = class Guest extends Annotator module.exports = class Guest extends Delegator
SHOW_HIGHLIGHTS_CLASS = 'annotator-highlights-always-on' SHOW_HIGHLIGHTS_CLASS = 'annotator-highlights-always-on'
# Events to be bound on Annotator#element. # Events to be bound on Annotator#element.
...@@ -48,24 +48,41 @@ module.exports = class Guest extends Annotator ...@@ -48,24 +48,41 @@ module.exports = class Guest extends Annotator
anchoring: require('./anchoring/html') anchoring: require('./anchoring/html')
# Internal state # Internal state
plugins: null
anchors: null anchors: null
visibleHighlights: false visibleHighlights: false
html: extend {}, Annotator::html, html:
adder: '<hypothesis-adder></hypothesis-adder>'; 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) -> constructor: (element, options) ->
super super
this.adder = $(this.html.adder).appendTo(@element).hide()
self = this self = this
this.adderCtrl = new adder.Adder(@adder[0], { this.adderCtrl = new adder.Adder(@adder[0], {
onAnnotate: -> onAnnotate: ->
self.createAnnotation() self.createAnnotation()
Annotator.Util.getGlobal().getSelection().removeAllRanges() document.getSelection().removeAllRanges()
onHighlight: -> onHighlight: ->
self.setVisibleHighlights(true) self.setVisibleHighlights(true)
self.createHighlight() self.createHighlight()
Annotator.Util.getGlobal().getSelection().removeAllRanges() document.getSelection().removeAllRanges()
}) })
this.selections = selections(document).subscribe this.selections = selections(document).subscribe
next: (range) -> next: (range) ->
...@@ -74,6 +91,7 @@ module.exports = class Guest extends Annotator ...@@ -74,6 +91,7 @@ module.exports = class Guest extends Annotator
else else
self._onClearSelection() self._onClearSelection()
this.plugins = {}
this.anchors = [] this.anchors = []
cfOptions = cfOptions =
...@@ -91,7 +109,7 @@ module.exports = class Guest extends Annotator ...@@ -91,7 +109,7 @@ module.exports = class Guest extends Annotator
# Load plugins # Load plugins
for own name, opts of @options 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) this.addPlugin(name, opts)
# Get the document info # Get the document info
...@@ -142,16 +160,6 @@ module.exports = class Guest extends Annotator ...@@ -142,16 +160,6 @@ module.exports = class Guest extends Annotator
crossframe.on 'setVisibleHighlights', (state) => crossframe.on 'setVisibleHighlights', (state) =>
this.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: -> destroy: ->
$('#annotator-dynamic-style').remove() $('#annotator-dynamic-style').remove()
...@@ -167,7 +175,7 @@ module.exports = class Guest extends Annotator ...@@ -167,7 +175,7 @@ module.exports = class Guest extends Annotator
for name, plugin of @plugins for name, plugin of @plugins
@plugins[name].destroy() @plugins[name].destroy()
this.removeEvents() super
anchor: (annotation) -> anchor: (annotation) ->
self = this self = this
...@@ -348,6 +356,20 @@ module.exports = class Guest extends Annotator ...@@ -348,6 +356,20 @@ module.exports = class Guest extends Annotator
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?
$(h).replaceWith(h.childNodes)
this.publish('annotationDeleted', [annotation])
annotation
showAnnotations: (annotations) -> showAnnotations: (annotations) ->
tags = (a.$tag for a in annotations) tags = (a.$tag for a in annotations)
@crossframe?.call('showAnnotations', tags) @crossframe?.call('showAnnotations', tags)
...@@ -365,7 +387,7 @@ module.exports = class Guest extends Annotator ...@@ -365,7 +387,7 @@ module.exports = class Guest extends Annotator
@crossframe?.call('focusAnnotations', tags) @crossframe?.call('focusAnnotations', tags)
_onSelection: (range) -> _onSelection: (range) ->
selection = Annotator.Util.getGlobal().getSelection() selection = document.getSelection()
isBackwards = rangeUtil.isSelectionBackwards(selection) isBackwards = rangeUtil.isSelectionBackwards(selection)
focusRect = rangeUtil.selectionFocusRect(selection) focusRect = rangeUtil.selectionFocusRect(selection)
if !focusRect if !focusRect
...@@ -375,7 +397,7 @@ module.exports = class Guest extends Annotator ...@@ -375,7 +397,7 @@ module.exports = class Guest extends Annotator
@selectedRanges = [range] @selectedRanges = [range]
Annotator.$('.annotator-toolbar .h-icon-note') $('.annotator-toolbar .h-icon-note')
.attr('title', 'New Annotation') .attr('title', 'New Annotation')
.removeClass('h-icon-note') .removeClass('h-icon-note')
.addClass('h-icon-annotate'); .addClass('h-icon-annotate');
...@@ -387,7 +409,7 @@ module.exports = class Guest extends Annotator ...@@ -387,7 +409,7 @@ module.exports = class Guest extends Annotator
this.adderCtrl.hide() this.adderCtrl.hide()
@selectedRanges = [] @selectedRanges = []
Annotator.$('.annotator-toolbar .h-icon-annotate') $('.annotator-toolbar .h-icon-annotate')
.attr('title', 'New Page Note') .attr('title', 'New Page Note')
.removeClass('h-icon-annotate') .removeClass('h-icon-annotate')
.addClass('h-icon-note'); .addClass('h-icon-note');
......
Annotator = require('annotator') $ = require('jquery')
$ = Annotator.$
Guest = require('./guest') Guest = require('./guest')
module.exports = class Host extends Guest module.exports = class Host extends Guest
constructor: (element, options) -> 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( 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 if options.app and '?' in options.app
options.app += '&' + configParam options.app += '&' + configParam
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
require('../shared/polyfills'); require('../shared/polyfills');
var Annotator = require('annotator');
// Polyfills // Polyfills
...@@ -13,44 +12,44 @@ var Annotator = require('annotator'); ...@@ -13,44 +12,44 @@ var Annotator = require('annotator');
if (!window.document.evaluate) { if (!window.document.evaluate) {
require('./vendor/wgxpath.install'); require('./vendor/wgxpath.install');
} }
var g = Annotator.Util.getGlobal(); if (window.wgxpath) {
if (g.wgxpath) { window.wgxpath.install();
g.wgxpath.install();
} }
var $ = require('jquery');
// Applications // Applications
Annotator.Guest = require('./guest'); var Sidebar = require('./sidebar');
Annotator.Host = require('./host'); var PdfSidebar = require('./pdf-sidebar');
Annotator.Sidebar = require('./sidebar');
Annotator.PdfSidebar = require('./pdf-sidebar');
// UI plugins var pluginClasses = {
Annotator.Plugin.BucketBar = require('./plugin/bucket-bar'); // UI plugins
Annotator.Plugin.Toolbar = require('./plugin/toolbar'); BucketBar: require('./plugin/bucket-bar'),
Toolbar: require('./plugin/toolbar'),
// Document type plugins // Document type plugins
Annotator.Plugin.PDF = require('./plugin/pdf'); PDF: require('./plugin/pdf'),
require('./vendor/annotator.document'); // Does not export the plugin :( Document: require('./plugin/document'),
// Cross-frame communication // Cross-frame communication
Annotator.Plugin.CrossFrame = require('./plugin/cross-frame'); 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');
var appLinkEl = var appLinkEl =
document.querySelector('link[type="application/annotator+html"]'); document.querySelector('link[type="application/annotator+html"]');
var options = require('./config')(window); var options = require('./config')(window);
Annotator.noConflict().$.noConflict(true)(function() { $.noConflict(true)(function() {
var Klass = window.PDFViewerApplication ? var Klass = window.PDFViewerApplication ?
Annotator.PdfSidebar : PdfSidebar :
Annotator.Sidebar; Sidebar;
if (options.hasOwnProperty('constructor')) { if (options.hasOwnProperty('constructor')) {
Klass = options.constructor; Klass = options.constructor;
delete options.constructor; delete options.constructor;
} }
options.pluginClasses = pluginClasses;
window.annotator = new Klass(document.body, options); window.annotator = new Klass(document.body, options);
appLinkEl.addEventListener('destroy', function () { appLinkEl.addEventListener('destroy', function () {
appLinkEl.parentElement.removeChild(appLinkEl); appLinkEl.parentElement.removeChild(appLinkEl);
......
Delegator = require('./delegator')
module.exports = class Plugin extends Delegator
constructor: (element, options) ->
super
pluginInit: () ->
raf = require('raf') raf = require('raf')
Annotator = require('annotator') $ = require('jquery')
$ = Annotator.$ Plugin = require('../plugin')
scrollIntoView = require('scroll-into-view') scrollIntoView = require('scroll-into-view')
...@@ -42,7 +42,7 @@ scrollToClosest = (anchors, direction) -> ...@@ -42,7 +42,7 @@ scrollToClosest = (anchors, direction) ->
scrollIntoView(next.highlights[0]) scrollIntoView(next.highlights[0])
module.exports = class BucketBar extends Annotator.Plugin module.exports = class BucketBar extends Plugin
# svg skeleton # svg skeleton
html: """ html: """
<div class="annotator-bucket-bar"> <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. # Extracts individual keys from an object and returns a new one.
extract = extract = (obj, keys...) -> extract = extract = (obj, keys...) ->
...@@ -11,17 +16,17 @@ 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 # 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 # can also be used to send messages through to the sidebar using the
# call method. # call method.
module.exports = class CrossFrame extends Annotator.Plugin module.exports = class CrossFrame extends Plugin
constructor: (elem, options) -> constructor: (elem, options) ->
super super
opts = extract(options, 'server') 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') opts = extract(options, 'on', 'emit')
annotationSync = new CrossFrame.AnnotationSync(bridge, opts) annotationSync = new AnnotationSync(bridge, opts)
this.pluginInit = -> this.pluginInit = ->
onDiscoveryCallback = (source, origin, token) -> onDiscoveryCallback = (source, origin, token) ->
...@@ -30,7 +35,7 @@ module.exports = class CrossFrame extends Annotator.Plugin ...@@ -30,7 +35,7 @@ module.exports = class CrossFrame extends Annotator.Plugin
this.destroy = -> this.destroy = ->
# super doesnt work here :( # super doesnt work here :(
Annotator.Plugin::destroy.apply(this, arguments) Plugin::destroy.apply(this, arguments)
bridge.destroy() bridge.destroy()
discovery.stopDiscovery() 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') Plugin = require('../plugin')
Annotator = require('annotator')
RenderingStates = require('../pdfjs-rendering-states') RenderingStates = require('../pdfjs-rendering-states')
module.exports = class PDF extends Annotator.Plugin module.exports = class PDF extends Plugin
documentLoaded: null documentLoaded: null
observer: null observer: null
pdfViewer: null pdfViewer: null
......
CrossFrame = require('../cross-frame') proxyquire = require('proxyquire')
Plugin = require('../../plugin')
CrossFrame = null
describe 'Annotator.Plugin.CrossFrame', -> describe 'Annotator.Plugin.CrossFrame', ->
fakeDiscovery = null fakeDiscovery = null
fakeBridge = null fakeBridge = null
fakeAnnotationSync = null fakeAnnotationSync = null
proxyDiscovery = null
proxyBridge = null
proxyAnnotationSync = null
sandbox = sinon.sandbox.create() sandbox = sinon.sandbox.create()
createCrossFrame = (options) -> createCrossFrame = (options) ->
...@@ -28,9 +36,17 @@ describe 'Annotator.Plugin.CrossFrame', -> ...@@ -28,9 +36,17 @@ describe 'Annotator.Plugin.CrossFrame', ->
fakeAnnotationSync = fakeAnnotationSync =
sync: sandbox.stub() sync: sandbox.stub()
CrossFrame.AnnotationSync = sandbox.stub().returns(fakeAnnotationSync) proxyAnnotationSync = sandbox.stub().returns(fakeAnnotationSync)
CrossFrame.Discovery = sandbox.stub().returns(fakeDiscovery) proxyDiscovery = sandbox.stub().returns(fakeDiscovery)
CrossFrame.Bridge = sandbox.stub().returns(fakeBridge) proxyBridge = sandbox.stub().returns(fakeBridge)
CrossFrame = proxyquire('../cross-frame', {
'../plugin': Plugin,
'../annotation-sync': proxyAnnotationSync,
'../../shared/bridge': proxyBridge,
'../../shared/discovery': proxyDiscovery
})
afterEach -> afterEach ->
sandbox.restore() sandbox.restore()
...@@ -38,23 +54,23 @@ describe 'Annotator.Plugin.CrossFrame', -> ...@@ -38,23 +54,23 @@ describe 'Annotator.Plugin.CrossFrame', ->
describe 'CrossFrame constructor', -> describe 'CrossFrame constructor', ->
it 'instantiates the Discovery component', -> it 'instantiates the Discovery component', ->
createCrossFrame() createCrossFrame()
assert.calledWith(CrossFrame.Discovery, window) assert.calledWith(proxyDiscovery, window)
it 'passes the options along to the bridge', -> it 'passes the options along to the bridge', ->
createCrossFrame(server: true) createCrossFrame(server: true)
assert.calledWith(CrossFrame.Discovery, window, server: true) assert.calledWith(proxyDiscovery, window, server: true)
it 'instantiates the CrossFrame component', -> it 'instantiates the CrossFrame component', ->
createCrossFrame() createCrossFrame()
assert.calledWith(CrossFrame.Discovery) assert.calledWith(proxyDiscovery)
it 'instantiates the AnnotationSync component', -> it 'instantiates the AnnotationSync component', ->
createCrossFrame() createCrossFrame()
assert.called(CrossFrame.AnnotationSync) assert.called(proxyAnnotationSync)
it 'passes along options to AnnotationSync', -> it 'passes along options to AnnotationSync', ->
createCrossFrame() createCrossFrame()
assert.calledWith(CrossFrame.AnnotationSync, fakeBridge, { assert.calledWith(proxyAnnotationSync, fakeBridge, {
on: sinon.match.func on: sinon.match.func
emit: sinon.match.func emit: sinon.match.func
}) })
......
Annotator = require('annotator') Plugin = require('../plugin')
$ = Annotator.$ $ = require('jquery')
makeButton = (item) -> makeButton = (item) ->
anchor = $('<button></button>') anchor = $('<button></button>')
...@@ -12,7 +12,7 @@ makeButton = (item) -> ...@@ -12,7 +12,7 @@ makeButton = (item) ->
button = $('<li></li>').append(anchor) button = $('<li></li>').append(anchor)
return button[0] return button[0]
module.exports = class Toolbar extends Annotator.Plugin module.exports = class Toolbar extends Plugin
HIDE_CLASS = 'annotator-hide' HIDE_CLASS = 'annotator-hide'
events: events:
......
...@@ -43,6 +43,7 @@ module.exports = class Sidebar extends Host ...@@ -43,6 +43,7 @@ module.exports = class Sidebar extends Host
@onSignupRequest = serviceConfig.onSignupRequest @onSignupRequest = serviceConfig.onSignupRequest
this._setupSidebarEvents() this._setupSidebarEvents()
this._setupDocumentEvents()
_setupDocumentEvents: -> _setupDocumentEvents: ->
sidebarTrigger(document.body, => this.show()) sidebarTrigger(document.body, => this.show())
......
Annotator = require('annotator')
proxyquire = require('proxyquire') proxyquire = require('proxyquire')
adder = require('../adder') adder = require('../adder')
Observable = require('../util/observable').Observable Observable = require('../util/observable').Observable
Plugin = require('../plugin')
$ = Annotator.$ Delegator = require('../delegator')
Annotator['@noCallThru'] = true; $ = require('jquery')
Delegator['@noCallThru'] = true
Guest = null Guest = null
anchoring = {} anchoring = {}
...@@ -29,6 +30,18 @@ class FakeAdder ...@@ -29,6 +30,18 @@ class FakeAdder
this.showAt = sinon.stub() this.showAt = sinon.stub()
this.target = 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 # A little helper which returns a promise that resolves after a timeout
timeoutPromise = (millis = 0) -> timeoutPromise = (millis = 0) ->
new Promise((resolve) -> setTimeout(resolve, millis)) new Promise((resolve) -> setTimeout(resolve, millis))
...@@ -37,10 +50,12 @@ describe 'Guest', -> ...@@ -37,10 +50,12 @@ describe 'Guest', ->
sandbox = sinon.sandbox.create() sandbox = sinon.sandbox.create()
CrossFrame = null CrossFrame = null
fakeCrossFrame = null fakeCrossFrame = null
guestOptions = null
createGuest = (options) -> createGuest = (options={}) ->
options = Object.assign({}, guestOptions, options)
element = document.createElement('div') element = document.createElement('div')
return new Guest(element, options || {}) return new Guest(element, options)
beforeEach -> beforeEach ->
FakeAdder::instance = null FakeAdder::instance = null
...@@ -49,6 +64,7 @@ describe 'Guest', -> ...@@ -49,6 +64,7 @@ describe 'Guest', ->
selectionFocusRect: sinon.stub() selectionFocusRect: sinon.stub()
} }
selections = null selections = null
guestOptions = {pluginClasses: {}}
Guest = proxyquire('../guest', { Guest = proxyquire('../guest', {
'./adder': {Adder: FakeAdder}, './adder': {Adder: FakeAdder},
...@@ -60,7 +76,7 @@ describe 'Guest', -> ...@@ -60,7 +76,7 @@ describe 'Guest', ->
selections = obs selections = obs
return () -> return () ->
) )
'annotator': Annotator, './delegator': Delegator,
'raf': raf, 'raf': raf,
'scroll-into-view': scrollIntoView, 'scroll-into-view': scrollIntoView,
}) })
...@@ -69,15 +85,39 @@ describe 'Guest', -> ...@@ -69,15 +85,39 @@ describe 'Guest', ->
onConnect: sinon.stub() onConnect: sinon.stub()
on: sinon.stub() on: sinon.stub()
sync: sinon.stub() sync: sinon.stub()
destroy: sinon.stub()
} }
CrossFrame = sandbox.stub() CrossFrame = sandbox.stub().returns(fakeCrossFrame)
CrossFrame.returns(fakeCrossFrame) guestOptions.pluginClasses['CrossFrame'] = CrossFrame
Annotator.Plugin.CrossFrame = CrossFrame
afterEach -> afterEach ->
sandbox.restore() 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', -> describe 'cross frame', ->
......
Annotator = require('annotator')
proxyquire = require('proxyquire') proxyquire = require('proxyquire')
Host = proxyquire('../host', { Host = proxyquire('../host', {})
'annotator': Annotator,
})
describe 'Host', -> describe 'Host', ->
sandbox = sinon.sandbox.create() sandbox = sinon.sandbox.create()
CrossFrame = null CrossFrame = null
fakeCrossFrame = null fakeCrossFrame = null
hostOptions = {pluginClasses: {}}
createHost = (options={}, element=null) -> 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 if !element
element = document.createElement('div') element = document.createElement('div')
return new Host(element, options) return new Host(element, options)
...@@ -27,11 +24,10 @@ describe 'Host', -> ...@@ -27,11 +24,10 @@ describe 'Host', ->
CrossFrame = sandbox.stub() CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame) CrossFrame.returns(fakeCrossFrame)
Annotator.Plugin.CrossFrame = CrossFrame hostOptions.pluginClasses['CrossFrame'] = CrossFrame
afterEach -> afterEach ->
sandbox.restore() sandbox.restore()
delete Annotator.Plugin.CrossFrame
describe 'widget visibility', -> describe 'widget visibility', ->
it 'starts hidden', -> it 'starts hidden', ->
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
'use strict'; 'use strict';
var Annotator = require('annotator');
var unroll = require('../../../shared/test/util').unroll; var unroll = require('../../../shared/test/util').unroll;
var Guest = require('../../guest'); var Guest = require('../../guest');
...@@ -48,17 +46,18 @@ function FakeCrossFrame() { ...@@ -48,17 +46,18 @@ function FakeCrossFrame() {
describe('anchoring', function () { describe('anchoring', function () {
var guest; var guest;
var guestOptions;
var container; var container;
before(function () { before(function () {
Annotator.Plugin.CrossFrame = FakeCrossFrame; guestOptions = {pluginClasses: {CrossFrame: FakeCrossFrame}};
}); });
beforeEach(function () { beforeEach(function () {
container = document.createElement('div'); container = document.createElement('div');
container.innerHTML = require('./test-page.html'); container.innerHTML = require('./test-page.html');
document.body.appendChild(container); document.body.appendChild(container);
guest = new Guest(container); guest = new Guest(container, guestOptions);
}); });
afterEach(function () { afterEach(function () {
......
Annotator = require('annotator')
events = require('../../shared/bridge-events') events = require('../../shared/bridge-events')
proxyquire = require('proxyquire') proxyquire = require('proxyquire')
Sidebar = proxyquire('../sidebar', { Sidebar = proxyquire('../sidebar', {})
'annotator': Annotator,
})
describe 'Sidebar', -> describe 'Sidebar', ->
sandbox = sinon.sandbox.create() sandbox = sinon.sandbox.create()
CrossFrame = null CrossFrame = null
fakeCrossFrame = null fakeCrossFrame = null
sidebarOptions = {pluginClasses: {}}
createSidebar = (options={}) -> createSidebar = (options={}) ->
options = Object.assign({}, sidebarOptions, options)
element = document.createElement('div') element = document.createElement('div')
return new Sidebar(element, options) return new Sidebar(element, options)
...@@ -20,14 +19,14 @@ describe 'Sidebar', -> ...@@ -20,14 +19,14 @@ describe 'Sidebar', ->
fakeCrossFrame.onConnect = sandbox.stub().returns(fakeCrossFrame) fakeCrossFrame.onConnect = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.on = sandbox.stub().returns(fakeCrossFrame) fakeCrossFrame.on = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.call = sandbox.spy() fakeCrossFrame.call = sandbox.spy()
fakeCrossFrame.destroy = sandbox.stub()
CrossFrame = sandbox.stub() CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame) CrossFrame.returns(fakeCrossFrame)
Annotator.Plugin.CrossFrame = CrossFrame sidebarOptions.pluginClasses['CrossFrame'] = CrossFrame
afterEach -> afterEach ->
sandbox.restore() sandbox.restore()
delete Annotator.Plugin.CrossFrame
describe 'crossframe listeners', -> describe 'crossframe listeners', ->
emitEvent = (event, args...) -> emitEvent = (event, args...) ->
...@@ -196,3 +195,14 @@ describe 'Sidebar', -> ...@@ -196,3 +195,14 @@ describe 'Sidebar', ->
sidebar.show() sidebar.show()
sidebar.element.trigger(event) sidebar.element.trigger(event)
assert.isFalse(sidebar.isOpen()) 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
This diff is collapsed.
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