(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.annotator = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o true }); ``` `RSVP.Promise.cast` is similar to `RSVP.resolve`, but `RSVP.Promise.cast` differs in the following ways: * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you have something that could either be a promise or a value. RSVP.resolve will have the same effect but will create a new promise wrapper if the argument is a promise. * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to promises of the exact class specified, so that the resulting object's `then` is ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise). @method cast @for RSVP @param {Object} object to be casted @return {Promise} promise that is fulfilled when all properties of `promises` have been fulfilled, or rejected if any of them become rejected. */ function cast(object) { /*jshint validthis:true */ if (object && typeof object === 'object' && object.constructor === this) { return object; } var Promise = this; return new Promise(function(resolve) { resolve(object); }); } exports.cast = cast; },{}],9:[function(require,module,exports){ "use strict"; var config = { instrument: false }; function configure(name, value) { if (arguments.length === 2) { config[name] = value; } else { return config[name]; } } exports.config = config; exports.configure = configure; },{}],10:[function(require,module,exports){ (function (global){ "use strict"; /*global self*/ var RSVPPromise = require("./promise").Promise; var isFunction = require("./utils").isFunction; function polyfill() { var local; if (typeof global !== 'undefined') { local = global; } else if (typeof window !== 'undefined' && window.document) { local = window; } else { local = self; } var es6PromiseSupport = "Promise" in local && // Some of these methods are missing from // Firefox/Chrome experimental implementations "cast" in local.Promise && "resolve" in local.Promise && "reject" in local.Promise && "all" in local.Promise && "race" in local.Promise && // Older version of the spec had a resolver object // as the arg rather than a function (function() { var resolve; new local.Promise(function(r) { resolve = r; }); return isFunction(resolve); }()); if (!es6PromiseSupport) { local.Promise = RSVPPromise; } } exports.polyfill = polyfill; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./promise":11,"./utils":15}],11:[function(require,module,exports){ "use strict"; var config = require("./config").config; var configure = require("./config").configure; var objectOrFunction = require("./utils").objectOrFunction; var isFunction = require("./utils").isFunction; var now = require("./utils").now; var cast = require("./cast").cast; var all = require("./all").all; var race = require("./race").race; var staticResolve = require("./resolve").resolve; var staticReject = require("./reject").reject; var asap = require("./asap").asap; var counter = 0; config.async = asap; // default async is asap; function Promise(resolver) { if (!isFunction(resolver)) { throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); } if (!(this instanceof Promise)) { throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); } this._subscribers = []; invokeResolver(resolver, this); } function invokeResolver(resolver, promise) { function resolvePromise(value) { resolve(promise, value); } function rejectPromise(reason) { reject(promise, reason); } try { resolver(resolvePromise, rejectPromise); } catch(e) { rejectPromise(e); } } function invokeCallback(settled, promise, callback, detail) { var hasCallback = isFunction(callback), value, error, succeeded, failed; if (hasCallback) { try { value = callback(detail); succeeded = true; } catch(e) { failed = true; error = e; } } else { value = detail; succeeded = true; } if (handleThenable(promise, value)) { return; } else if (hasCallback && succeeded) { resolve(promise, value); } else if (failed) { reject(promise, error); } else if (settled === FULFILLED) { resolve(promise, value); } else if (settled === REJECTED) { reject(promise, value); } } var PENDING = void 0; var SEALED = 0; var FULFILLED = 1; var REJECTED = 2; function subscribe(parent, child, onFulfillment, onRejection) { var subscribers = parent._subscribers; var length = subscribers.length; subscribers[length] = child; subscribers[length + FULFILLED] = onFulfillment; subscribers[length + REJECTED] = onRejection; } function publish(promise, settled) { var child, callback, subscribers = promise._subscribers, detail = promise._detail; for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i]; callback = subscribers[i + settled]; invokeCallback(settled, child, callback, detail); } promise._subscribers = null; } Promise.prototype = { constructor: Promise, _state: undefined, _detail: undefined, _subscribers: undefined, then: function(onFulfillment, onRejection) { var promise = this; var thenPromise = new this.constructor(function() {}); if (this._state) { var callbacks = arguments; config.async(function invokePromiseCallback() { invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail); }); } else { subscribe(this, thenPromise, onFulfillment, onRejection); } return thenPromise; }, 'catch': function(onRejection) { return this.then(null, onRejection); } }; Promise.all = all; Promise.cast = cast; Promise.race = race; Promise.resolve = staticResolve; Promise.reject = staticReject; function handleThenable(promise, value) { var then = null, resolved; try { if (promise === value) { throw new TypeError("A promises callback cannot return that same promise."); } if (objectOrFunction(value)) { then = value.then; if (isFunction(then)) { then.call(value, function(val) { if (resolved) { return true; } resolved = true; if (value !== val) { resolve(promise, val); } else { fulfill(promise, val); } }, function(val) { if (resolved) { return true; } resolved = true; reject(promise, val); }); return true; } } } catch (error) { if (resolved) { return true; } reject(promise, error); return true; } return false; } function resolve(promise, value) { if (promise === value) { fulfill(promise, value); } else if (!handleThenable(promise, value)) { fulfill(promise, value); } } function fulfill(promise, value) { if (promise._state !== PENDING) { return; } promise._state = SEALED; promise._detail = value; config.async(publishFulfillment, promise); } function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = SEALED; promise._detail = reason; config.async(publishRejection, promise); } function publishFulfillment(promise) { publish(promise, promise._state = FULFILLED); } function publishRejection(promise) { publish(promise, promise._state = REJECTED); } exports.Promise = Promise; },{"./all":6,"./asap":7,"./cast":8,"./config":9,"./race":12,"./reject":13,"./resolve":14,"./utils":15}],12:[function(require,module,exports){ "use strict"; /* global toString */ var isArray = require("./utils").isArray; /** `RSVP.race` allows you to watch a series of promises and act as soon as the first promise given to the `promises` argument fulfills or rejects. Example: ```javascript var promise1 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve("promise 1"); }, 200); }); var promise2 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve("promise 2"); }, 100); }); RSVP.race([promise1, promise2]).then(function(result){ // result === "promise 2" because it was resolved before promise1 // was resolved. }); ``` `RSVP.race` is deterministic in that only the state of the first completed promise matters. For example, even if other promises given to the `promises` array argument are resolved, but the first completed promise has become rejected before the other promises became fulfilled, the returned promise will become rejected: ```javascript var promise1 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ resolve("promise 1"); }, 200); }); var promise2 = new RSVP.Promise(function(resolve, reject){ setTimeout(function(){ reject(new Error("promise 2")); }, 100); }); RSVP.race([promise1, promise2]).then(function(result){ // Code here never runs because there are rejected promises! }, function(reason){ // reason.message === "promise2" because promise 2 became rejected before // promise 1 became fulfilled }); ``` @method race @for RSVP @param {Array} promises array of promises to observe @param {String} label optional string for describing the promise returned. Useful for tooling. @return {Promise} a promise that becomes fulfilled with the value the first completed promises is resolved with if the first completed promise was fulfilled, or rejected with the reason that the first completed promise was rejected with. */ function race(promises) { /*jshint validthis:true */ var Promise = this; if (!isArray(promises)) { throw new TypeError('You must pass an array to race.'); } return new Promise(function(resolve, reject) { var results = [], promise; for (var i = 0; i < promises.length; i++) { promise = promises[i]; if (promise && typeof promise.then === 'function') { promise.then(resolve, reject); } else { resolve(promise); } } }); } exports.race = race; },{"./utils":15}],13:[function(require,module,exports){ "use strict"; /** `RSVP.reject` returns a promise that will become rejected with the passed `reason`. `RSVP.reject` is essentially shorthand for the following: ```javascript var promise = new RSVP.Promise(function(resolve, reject){ reject(new Error('WHOOPS')); }); promise.then(function(value){ // Code here doesn't run because the promise is rejected! }, function(reason){ // reason.message === 'WHOOPS' }); ``` Instead of writing the above, your code now simply becomes the following: ```javascript var promise = RSVP.reject(new Error('WHOOPS')); promise.then(function(value){ // Code here doesn't run because the promise is rejected! }, function(reason){ // reason.message === 'WHOOPS' }); ``` @method reject @for RSVP @param {Any} reason value that the returned promise will be rejected with. @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise that will become rejected with the given `reason`. */ function reject(reason) { /*jshint validthis:true */ var Promise = this; return new Promise(function (resolve, reject) { reject(reason); }); } exports.reject = reject; },{}],14:[function(require,module,exports){ "use strict"; /** `RSVP.resolve` returns a promise that will become fulfilled with the passed `value`. `RSVP.resolve` is essentially shorthand for the following: ```javascript var promise = new RSVP.Promise(function(resolve, reject){ resolve(1); }); promise.then(function(value){ // value === 1 }); ``` Instead of writing the above, your code now simply becomes the following: ```javascript var promise = RSVP.resolve(1); promise.then(function(value){ // value === 1 }); ``` @method resolve @for RSVP @param {Any} value value that the returned promise will be resolved with @param {String} label optional string for identifying the returned promise. Useful for tooling. @return {Promise} a promise that will become fulfilled with the given `value` */ function resolve(value) { /*jshint validthis:true */ var Promise = this; return new Promise(function(resolve, reject) { resolve(value); }); } exports.resolve = resolve; },{}],15:[function(require,module,exports){ "use strict"; function objectOrFunction(x) { return isFunction(x) || (typeof x === "object" && x !== null); } function isFunction(x) { return typeof x === "function"; } function isArray(x) { return Object.prototype.toString.call(x) === "[object Array]"; } // Date.now is not available in browsers < IE9 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility var now = Date.now || function() { return new Date().getTime(); }; exports.objectOrFunction = objectOrFunction; exports.isFunction = isFunction; exports.isArray = isArray; exports.now = now; },{}],16:[function(require,module,exports){ var inserted = {}; module.exports = function (css, options) { if (inserted[css]) return; inserted[css] = true; var elem = document.createElement('style'); elem.setAttribute('type', 'text/css'); if ('textContent' in elem) { elem.textContent = css; } else { elem.styleSheet.cssText = css; } var head = document.getElementsByTagName('head')[0]; if (options && options.prepend) { head.insertBefore(elem, head.childNodes[0]); } else { head.appendChild(elem); } }; },{}],17:[function(require,module,exports){ },{}],18:[function(require,module,exports){ // Generated by CoffeeScript 1.7.1 (function() { module.exports = { xpath: require("./xpath"), Range: require("./range") }; }).call(this); },{"./range":19,"./xpath":21}],19:[function(require,module,exports){ // Generated by CoffeeScript 1.7.1 (function() { var Range, Util, xpath, __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; }; xpath = require('./xpath'); Util = require('./util'); 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("Could not sniff range type"); return false; } }; 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 nr, r; if (this.tainted) { console.error("You may only call normalize() once on a BrowserRange!"); return false; } else { this.tainted = true; } r = {}; this._normalizeStart(r); this._normalizeEnd(r); nr = {}; if (r.startOffset > 0) { if (r.start.nodeValue.length > r.startOffset) { nr.start = r.start.splitText(r.startOffset); } else { nr.start = r.start.nextSibling; } } else { nr.start = r.start; } if (r.start === r.end) { if (nr.start.nodeValue.length > (r.endOffset - r.startOffset)) { nr.start.splitText(r.endOffset - r.startOffset); } nr.end = nr.start; } else { if (r.end.nodeValue.length > r.endOffset) { r.end.splitText(r.endOffset); } nr.end = r.end; } nr.commonAncestor = this.commonAncestorContainer; while (nr.commonAncestor.nodeType !== Util.NodeTypes.ELEMENT_NODE) { nr.commonAncestor = nr.commonAncestor.parentNode; } return new Range.NormalizedRange(nr); }; BrowserRange.prototype._normalizeStart = function(r) { if (this.startContainer.nodeType === Util.NodeTypes.ELEMENT_NODE) { r.start = Util.getFirstTextNodeNotBefore(this.startContainer.childNodes[this.startOffset]); return r.startOffset = 0; } else { r.start = this.startContainer; return r.startOffset = this.startOffset; } }; BrowserRange.prototype._normalizeEnd = function(r) { var n, node; if (this.endContainer.nodeType === Util.NodeTypes.ELEMENT_NODE) { node = this.endContainer.childNodes[this.endOffset]; if (node != null) { n = node; while ((n != null) && (n.nodeType !== Util.NodeTypes.TEXT_NODE)) { n = n.firstChild; } if (n != null) { r.end = n; r.endOffset = 0; } } if (r.end == null) { if (this.endOffset) { node = this.endContainer.childNodes[this.endOffset - 1]; } else { node = this.endContainer.previousSibling; } r.end = Util.getLastTextNodeUpTo(node); return r.endOffset = r.end.nodeValue.length; } } else { r.end = this.endContainer; return r.endOffset = this.endOffset; } }; 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, _i, _len, _ref; 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(); _ref = $(this.end).parents(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { parent = _ref[_i]; 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, path, textNodes, _i, _len; if (ignoreSelector) { origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0); } else { origParent = $(node).parent(); } path = xpath.fromNode(origParent, root)[0]; textNodes = Util.getTextNodes(origParent); nodes = textNodes.slice(0, textNodes.index(node)); offset = 0; for (_i = 0, _len = nodes.length; _i < _len; _i++) { n = nodes[_i]; offset += n.nodeValue.length; } if (isEnd) { return [path, offset + node.nodeValue.length]; } else { return [path, 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 _i, _len, _ref, _results; _ref = this.textNodes(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { node = _ref[_i]; _results.push(node.nodeValue); } return _results; }).call(this)).join(''); }; NormalizedRange.prototype.textNodes = function() { var end, start, textNodes, _ref; textNodes = Util.getTextNodes($(this.commonAncestor)); _ref = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref[0], end = _ref[1]; return $.makeArray(textNodes.slice(start, +end + 1 || 9e9)); }; 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, _i, _j, _len, _len1, _ref, _ref1; range = {}; _ref = ['start', 'end']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { p = _ref[_i]; try { node = xpath.toNode(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 -= 1; } _ref1 = Util.getTextNodes($(node)); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { tn = _ref1[_j]; 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.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_CONTAINED_BY; } : function(a, b) { return a.contains(b); }; $(range.startContainer).parents().each(function() { var endContainer; if (range.endContainer.nodeType === Util.NodeTypes.TEXT_NODE) { endContainer = range.endContainer.parentNode; } else { endContainer = range.endContainer; } if (contains(this, 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; })(); module.exports = Range; }).call(this); },{"./util":20,"./xpath":21,"jquery":17}],20:[function(require,module,exports){ // Generated by CoffeeScript 1.7.1 (function() { var Util; Util = {}; Util.NodeTypes = { 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 }; Util.getFirstTextNodeNotBefore = function(n) { var result; switch (n.nodeType) { case Util.NodeTypes.TEXT_NODE: return n; case Util.NodeTypes.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.getLastTextNodeUpTo = function(n) { var result; switch (n.nodeType) { case Util.NodeTypes.TEXT_NODE: return n; case Util.NodeTypes.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.getTextNodes = function(jq) { var getTextNodes; getTextNodes = function(node) { var nodes; if (node && node.nodeType !== Util.NodeTypes.TEXT_NODE) { nodes = []; if (node.nodeType !== Util.NodeTypes.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.getGlobal = function() { return (function() { return this; })(); }; Util.contains = function(parent, child) { var node; node = child; while (node != null) { if (node === parent) { return true; } node = node.parentNode; } return false; }; 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); }; module.exports = Util; }).call(this); },{"jquery":17}],21:[function(require,module,exports){ // Generated by CoffeeScript 1.7.1 (function() { var Util, evaluateXPath, findChild, fromNode, getNodeName, getNodePosition, simpleXPathJQuery, simpleXPathPure, toNode; Util = require('./util'); evaluateXPath = function(xp, root, nsResolver) { var exception, idx, name, node, step, steps, _i, _len, _ref; if (root == null) { root = document; } 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..."); steps = xp.substring(1).split("/"); node = root; for (_i = 0, _len = steps.length; _i < _len; _i++) { step = steps[_i]; _ref = step.split("["), name = _ref[0], idx = _ref[1]; idx = idx != null ? parseInt((idx != null ? idx.split("]") : void 0)[0]) : 1; node = findChild(node, name.toLowerCase(), idx); } return node; } }; simpleXPathJQuery = function($el, relativeRoot) { var jq; jq = $el.map(function() { var elem, idx, path, tagName; path = ''; elem = this; while ((elem != null ? elem.nodeType : void 0) === Util.NodeTypes.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($el, 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 = $el.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 += 1; } tmp = tmp.previousSibling; } return pos; }; fromNode = function($el, relativeRoot) { var exception, result; try { result = simpleXPathJQuery($el, relativeRoot); } catch (_error) { exception = _error; console.log("jQuery-based XPath construction failed! Falling back to manual."); result = simpleXPathPure($el, relativeRoot); } return result; }; toNode = function(path, root) { var customResolver, namespace, node, segment; if (root == null) { root = document; } if (!$.isXMLDoc(document.documentElement)) { return evaluateXPath(path, root); } else { customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement); node = evaluateXPath(path, root, customResolver); if (!node) { path = ((function() { var _i, _len, _ref, _results; _ref = path.split('/'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; 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(path, root, customResolver); } return node; } }; module.exports = { fromNode: fromNode, toNode: toNode }; }).call(this); },{"./util":20,"jquery":17}],22:[function(require,module,exports){ /*package annotator */ "use strict"; var extend = require('backbone-extend-standalone'); var Promise = require('es6-promise').Promise; var authz = require('./authz'); var identity = require('./identity'); var notification = require('./notification'); var registry = require('./registry'); var storage = require('./storage'); /** * class:: App() * * App is the coordination point for all annotation functionality. App instances * manage the configuration of a particular annotation application, and are the * starting point for most deployments of Annotator. */ function App() { this.modules = []; this.registry = new registry.Registry(); this._started = false; // Register a bunch of default utilities this.registry.registerUtility(notification.defaultNotifier, 'notifier'); // And set up default components. this.include(authz.acl); this.include(identity.simple); this.include(storage.noop); } /** * function:: App.prototype.include(module[, options]) * * Include an extension module. If an `options` object is supplied, it will be * passed to the module at initialisation. * * If the returned module instance has a `configure` function, this will be * called with the application registry as a parameter. * * :param Object module: * :param Object options: * :returns: Itself. * :rtype: App */ App.prototype.include = function (module, options) { var mod = module(options); if (typeof mod.configure === 'function') { mod.configure(this.registry); } this.modules.push(mod); return this; }; /** * function:: App.prototype.start() * * Tell the app that configuration is complete. This binds the various * components passed to the registry to their canonical names so they can be * used by the rest of the application. * * Runs the 'start' module hook. * * :returns: A promise, resolved when all module 'start' hooks have completed. * :rtype: Promise */ App.prototype.start = function () { if (this._started) { return; } this._started = true; var self = this; var reg = this.registry; this.authz = reg.getUtility('authorizationPolicy'); this.ident = reg.getUtility('identityPolicy'); this.notify = reg.getUtility('notifier'); this.annotations = new storage.StorageAdapter( reg.getUtility('storage'), function () { return self.runHook.apply(self, arguments); } ); return this.runHook('start', [this]); }; /** * function:: App.prototype.destroy() * * Destroy the App. Unbinds all event handlers and runs the 'destroy' module * hook. * * :returns: A promise, resolved when destroyed. * :rtype: Promise */ App.prototype.destroy = function () { return this.runHook('destroy'); }; /** * function:: App.prototype.runHook(name[, args]) * * Run the named module hook and return a promise of the results of all the hook * functions. You won't usually need to run this yourself unless you are * extending the base functionality of App. * * Optionally accepts an array of argument (`args`) to pass to each hook * function. * * :returns: A promise, resolved when all hooks are complete. * :rtype: Promise */ App.prototype.runHook = function (name, args) { var results = []; for (var i = 0, len = this.modules.length; i < len; i++) { var mod = this.modules[i]; if (typeof mod[name] === 'function') { results.push(mod[name].apply(mod, args)); } } return Promise.all(results); }; /** * function:: App.extend(object) * * Create a new object that inherits from the App class. * * For example, here we create a ``CustomApp`` that will include the * hypothetical ``mymodules.foo.bar`` module depending on the options object * passed into the constructor:: * * var CustomApp = annotator.App.extend({ * constructor: function (options) { * App.apply(this); * if (options.foo === 'bar') { * this.include(mymodules.foo.bar); * } * } * }); * * var app = new CustomApp({foo: 'bar'}); * * :returns: The subclass constructor. * :rtype: Function */ App.extend = extend; exports.App = App; },{"./authz":23,"./identity":24,"./notification":25,"./registry":26,"./storage":27,"backbone-extend-standalone":3,"es6-promise":5}],23:[function(require,module,exports){ /*package annotator.authz */ "use strict"; var AclAuthzPolicy; /** * function:: acl() * * A module that configures and registers an instance of * :class:`annotator.authz.AclAuthzPolicy`. * */ exports.acl = function acl() { var authorization = new AclAuthzPolicy(); return { configure: function (registry) { registry.registerUtility(authorization, 'authorizationPolicy'); } }; }; /** * class:: AclAuthzPolicy() * * An authorization policy that permits actions based on access control lists. * */ AclAuthzPolicy = exports.AclAuthzPolicy = function AclAuthzPolicy() { }; /** * function:: AclAuthzPolicy.prototype.permits(action, context, identity) * * Determines whether the user identified by `identity` is permitted to * perform the specified action in the given context. * * If the context has a "permissions" object property, then actions will * be permitted if either of the following are true: * * a) permissions[action] is undefined or null, * b) permissions[action] is an Array containing the authorized userid * for the given identity. * * If the context has no permissions associated with it then all actions * will be permitted. * * If the annotation has a "user" property, then actions will be permitted * only if `identity` matches this "user" property. * * If the annotation has neither a "permissions" property nor a "user" * property, then all actions will be permitted. * * :param String action: The action to perform. * :param context: The permissions context for the authorization check. * :param identity: The identity whose authorization is being checked. * * :returns Boolean: Whether the action is permitted in this context for this * identity. */ AclAuthzPolicy.prototype.permits = function (action, context, identity) { var userid = this.authorizedUserId(identity); var permissions = context.permissions; if (permissions) { // Fine-grained authorization on permissions field var tokens = permissions[action]; if (typeof tokens === 'undefined' || tokens === null) { // Missing tokens array for this action: anyone can perform // action. return true; } for (var i = 0, len = tokens.length; i < len; i++) { if (userid === tokens[i]) { return true; } } // No tokens matched: action should not be performed. return false; } else if (context.user) { // Coarse-grained authorization return userid === context.user; } // No authorization info on context: free-for-all! return true; }; /** * function:: AclAuthzPolicy.prototype.authorizedUserId(identity) * * Returns the authorized userid for the user identified by `identity`. */ AclAuthzPolicy.prototype.authorizedUserId = function (identity) { return identity; }; },{}],24:[function(require,module,exports){ /*package annotator.identity */ "use strict"; var SimpleIdentityPolicy; /** * function:: simple() * * A module that configures and registers an instance of * :class:`annotator.identity.SimpleIdentityPolicy`. */ exports.simple = function simple() { var identity = new SimpleIdentityPolicy(); return { configure: function (registry) { registry.registerUtility(identity, 'identityPolicy'); } }; }; /** * class:: SimpleIdentityPolicy * * A simple identity policy that considers the identity to be an opaque * identifier. */ SimpleIdentityPolicy = function SimpleIdentityPolicy() { /** * data:: SimpleIdentityPolicy.identity * * Default identity. Defaults to `null`, which disables identity-related * functionality. * * This is not part of the identity policy public interface, but provides a * simple way for you to set a fixed current user:: * * app.ident.identity = 'bob'; */ this.identity = null; }; exports.SimpleIdentityPolicy = SimpleIdentityPolicy; /** * function:: SimpleIdentityPolicy.prototype.who() * * Returns the current user identity. */ SimpleIdentityPolicy.prototype.who = function () { return this.identity; }; },{}],25:[function(require,module,exports){ (function (global){ /*package annotator.notifier */ "use strict"; var util = require('./util'); var $ = util.$; var INFO = 'info', SUCCESS = 'success', ERROR = 'error'; var bannerTemplate = "
"; var bannerClasses = { show: "annotator-notice-show", info: "annotator-notice-info", success: "annotator-notice-success", error: "annotator-notice-error" }; /** * function:: banner(message[, severity=notification.INFO]) * * Creates a user-visible banner notification that can be used to display * information, warnings and errors to the user. * * :param String message: The notice message text. * :param severity: * The severity of the notice (one of `notification.INFO`, * `notification.SUCCESS`, or `notification.ERROR`) * * :returns: * An object with a `close` method that can be used to close the banner. */ function banner(message, severity) { if (typeof severity === 'undefined' || severity === null) { severity = INFO; } var element = $(bannerTemplate)[0]; var closed = false; var close = function () { if (closed) { return; } closed = true; $(element) .removeClass(bannerClasses.show) .removeClass(bannerClasses[severity]); // The removal of the above classes triggers a 400ms ease-out // transition, so we can dispose the element from the DOM after // 500ms. setTimeout(function () { $(element).remove(); }, 500); }; $(element) .addClass(bannerClasses.show) .addClass(bannerClasses[severity]) .html(util.escapeHtml(message || "")) .appendTo(global.document.body); $(element).on('click', close); // Hide the notifier after 5s setTimeout(close, 5000); return { close: close }; } exports.banner = banner; exports.defaultNotifier = banner; exports.INFO = INFO; exports.SUCCESS = SUCCESS; exports.ERROR = ERROR; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./util":39}],26:[function(require,module,exports){ /*package annotator.registry */ "use strict"; /** * class:: Registry() * * `Registry` is an application registry. It serves as a place to register and * find shared components in a running :class:`annotator.App`. * * You won't usually create your own `Registry` -- one will be created for you * by the :class:`~annotator.App`. If you are writing an Annotator module, you * can use the registry to provide or override a component of the Annotator * application. * * For example, if you are writing a module that overrides the "storage" * component, you will use the registry in your module's `configure` function to * register your component:: * * function myStorage () { * return { * configure: function (registry) { * registry.registerUtility(this, 'storage'); * }, * ... * }; * } */ function Registry() { this.utilities = {}; } /** * function:: Registry.prototype.registerUtility(component, iface) * * Register component `component` as an implementer of interface `iface`. * * :param component: The component to register. * :param string iface: The name of the interface. */ Registry.prototype.registerUtility = function (component, iface) { this.utilities[iface] = component; }; /** * function:: Registry.prototype.getUtility(iface) * * Get component implementing interface `iface`. * * :param string iface: The name of the interface. * :returns: Component matching `iface`. * :throws LookupError: If no component is found for interface `iface`. */ Registry.prototype.getUtility = function (iface) { var component = this.queryUtility(iface); if (component === null) { throw new LookupError(iface); } return component; }; /** * function:: Registry.prototype.queryUtility(iface) * * Get component implementing interface `iface`. Returns `null` if no matching * component is found. * * :param string iface: The name of the interface. * :returns: Component matching `iface`, if found; `null` otherwise. */ Registry.prototype.queryUtility = function (iface) { var component = this.utilities[iface]; if (typeof component === 'undefined' || component === null) { return null; } return component; }; /** * class:: LookupError(iface) * * The error thrown when a registry component lookup fails. */ function LookupError(iface) { this.name = 'LookupError'; this.message = 'No utility registered for interface "' + iface + '".'; } LookupError.prototype = Object.create(Error.prototype); LookupError.prototype.constructor = LookupError; exports.LookupError = LookupError; exports.Registry = Registry; },{}],27:[function(require,module,exports){ /*package annotator.storage */ "use strict"; var util = require('./util'); var $ = util.$; var _t = util.gettext; var Promise = util.Promise; // id returns an identifier unique within this session var id = (function () { var counter; counter = -1; return function () { return counter += 1; }; }()); /** * function:: debug() * * A storage component that can be used to print details of the annotation * persistence processes to the console when developing other parts of * Annotator. * * Use as an extension module:: * * app.include(annotator.storage.debug); * */ exports.debug = function () { function trace(action, annotation) { var copyAnno = JSON.parse(JSON.stringify(annotation)); console.debug("annotator.storage.debug: " + action, copyAnno); } return { create: function (annotation) { annotation.id = id(); trace('create', annotation); return annotation; }, update: function (annotation) { trace('update', annotation); return annotation; }, 'delete': function (annotation) { trace('destroy', annotation); return annotation; }, query: function (queryObj) { trace('query', queryObj); return {results: [], meta: {total: 0}}; }, configure: function (registry) { registry.registerUtility(this, 'storage'); } }; }; /** * function:: noop() * * A no-op storage component. It swallows all calls and does the bare minimum * needed. Needless to say, it does not provide any real persistence. * * Use as a extension module:: * * app.include(annotator.storage.noop); * */ exports.noop = function () { return { create: function (annotation) { if (typeof annotation.id === 'undefined' || annotation.id === null) { annotation.id = id(); } return annotation; }, update: function (annotation) { return annotation; }, 'delete': function (annotation) { return annotation; }, query: function () { return {results: []}; }, configure: function (registry) { registry.registerUtility(this, 'storage'); } }; }; var HttpStorage; /** * function:: http([options]) * * A module which configures an instance of * :class:`annotator.storage.HttpStorage` as the storage component. * * :param Object options: * Configuration options. For available options see * :attr:`~annotator.storage.HttpStorage.options`. */ exports.http = function http(options) { // This gets overridden on app start var notify = function () {}; if (typeof options === 'undefined' || options === null) { options = {}; } // Use the notifier unless an onError handler has been set. options.onError = options.onError || function (msg, xhr) { console.error(msg, xhr); notify(msg, 'error'); }; var storage = new HttpStorage(options); return { configure: function (registry) { registry.registerUtility(storage, 'storage'); }, start: function (app) { notify = app.notify; } }; }; /** * class:: HttpStorage([options]) * * HttpStorage is a storage component that talks to a remote JSON + HTTP API * that should be relatively easy to implement with any web application * framework. * * :param Object options: See :attr:`~annotator.storage.HttpStorage.options`. */ HttpStorage = exports.HttpStorage = function HttpStorage(options) { this.options = $.extend(true, {}, HttpStorage.options, options); this.onError = this.options.onError; }; /** * function:: HttpStorage.prototype.create(annotation) * * Create an annotation. * * **Examples**:: * * store.create({text: "my new annotation comment"}) * // => Results in an HTTP POST request to the server containing the * // annotation as serialised JSON. * * :param Object annotation: An annotation. * :returns: The request object. * :rtype: Promise */ HttpStorage.prototype.create = function (annotation) { return this._apiRequest('create', annotation); }; /** * function:: HttpStorage.prototype.update(annotation) * * Update an annotation. * * **Examples**:: * * store.update({id: "blah", text: "updated annotation comment"}) * // => Results in an HTTP PUT request to the server containing the * // annotation as serialised JSON. * * :param Object annotation: An annotation. Must contain an `id`. * :returns: The request object. * :rtype: Promise */ HttpStorage.prototype.update = function (annotation) { return this._apiRequest('update', annotation); }; /** * function:: HttpStorage.prototype.delete(annotation) * * Delete an annotation. * * **Examples**:: * * store.delete({id: "blah"}) * // => Results in an HTTP DELETE request to the server. * * :param Object annotation: An annotation. Must contain an `id`. * :returns: The request object. * :rtype: Promise */ HttpStorage.prototype['delete'] = function (annotation) { return this._apiRequest('destroy', annotation); }; /** * function:: HttpStorage.prototype.query(queryObj) * * Searches for annotations matching the specified query. * * :param Object queryObj: An object describing the query. * :returns: * A promise, resolves to an object containing query `results` and `meta`. * :rtype: Promise */ HttpStorage.prototype.query = function (queryObj) { return this._apiRequest('search', queryObj) .then(function (obj) { var rows = obj.rows; delete obj.rows; return {results: rows, meta: obj}; }); }; /** * function:: HttpStorage.prototype.setHeader(name, value) * * Set a custom HTTP header to be sent with every request. * * **Examples**:: * * store.setHeader('X-My-Custom-Header', 'MyCustomValue') * * :param string name: The header name. * :param string value: The header value. */ HttpStorage.prototype.setHeader = function (key, value) { this.options.headers[key] = value; }; /* * Helper method to build an XHR request for a specified action and * object. * * :param String action: The action: "search", "create", "update" or "destroy". * :param obj: The data to be sent, either annotation object or query string. * * :returns: The request object. * :rtype: jqXHR */ HttpStorage.prototype._apiRequest = function (action, obj) { var id = obj && obj.id; var url = this._urlFor(action, id); var options = this._apiRequestOptions(action, obj); var request = $.ajax(url, options); // Append the id and action to the request object // for use in the error callback. request._id = id; request._action = action; return request; }; /* * Builds an options object suitable for use in a jQuery.ajax() call. * * :param String action: The action: "search", "create", "update" or "destroy". * :param obj: The data to be sent, either annotation object or query string. * * :returns: $.ajax() options. * :rtype: Object */ HttpStorage.prototype._apiRequestOptions = function (action, obj) { var method = this._methodFor(action); var self = this; var opts = { type: method, dataType: "json", error: function () { self._onError.apply(self, arguments); }, headers: this.options.headers }; // If emulateHTTP is enabled, we send a POST and put the real method in an // HTTP request header. if (this.options.emulateHTTP && (method === 'PUT' || method === 'DELETE')) { opts.headers = $.extend(opts.headers, { 'X-HTTP-Method-Override': method }); opts.type = 'POST'; } // Don't JSONify obj if making search request. if (action === "search") { opts = $.extend(opts, {data: obj}); return opts; } var data = obj && JSON.stringify(obj); // If emulateJSON is enabled, we send a form request (the correct // contentType will be set automatically by jQuery), and put the // JSON-encoded payload in the "json" key. if (this.options.emulateJSON) { opts.data = {json: data}; if (this.options.emulateHTTP) { opts.data._method = method; } return opts; } opts = $.extend(opts, { data: data, contentType: "application/json; charset=utf-8" }); return opts; }; /* * Builds the appropriate URL from the options for the action provided. * * :param String action: * :param id: The annotation id as a String or Number. * * :returns String: URL for the request. */ HttpStorage.prototype._urlFor = function (action, id) { if (typeof id === 'undefined' || id === null) { id = ''; } var url = ''; if (typeof this.options.prefix !== 'undefined' && this.options.prefix !== null) { url = this.options.prefix; } url += this.options.urls[action]; // If there's an '{id}' in the URL, then fill in the ID. url = url.replace(/\{id\}/, id); return url; }; /* * Maps an action to an HTTP method. * * :param String action: * :returns String: Method for the request. */ HttpStorage.prototype._methodFor = function (action) { var table = { create: 'POST', update: 'PUT', destroy: 'DELETE', search: 'GET' }; return table[action]; }; /* * jQuery.ajax() callback. Displays an error notification to the user if * the request failed. * * :param jqXHR: The jqXMLHttpRequest object. */ HttpStorage.prototype._onError = function (xhr) { if (typeof this.onError !== 'function') { return; } var message; if (xhr.status === 400) { message = _t("The annotation store did not understand the request! " + "(Error 400)"); } else if (xhr.status === 401) { message = _t("You must be logged in to perform this operation! " + "(Error 401)"); } else if (xhr.status === 403) { message = _t("You don't have permission to perform this operation! " + "(Error 403)"); } else if (xhr.status === 404) { message = _t("Could not connect to the annotation store! " + "(Error 404)"); } else if (xhr.status === 500) { message = _t("Internal error in annotation store! " + "(Error 500)"); } else { message = _t("Unknown error while speaking to annotation store!"); } this.onError(message, xhr); }; /** * attribute:: HttpStorage.options * * Available configuration options for HttpStorage. See below. */ HttpStorage.options = { /** * attribute:: HttpStorage.options.emulateHTTP * * Should the storage emulate HTTP methods like PUT and DELETE for * interaction with legacy web servers? Setting this to `true` will fake * HTTP `PUT` and `DELETE` requests with an HTTP `POST`, and will set the * request header `X-HTTP-Method-Override` with the name of the desired * method. * * **Default**: ``false`` */ emulateHTTP: false, /** * attribute:: HttpStorage.options.emulateJSON * * Should the storage emulate JSON POST/PUT payloads by sending its requests * as application/x-www-form-urlencoded with a single key, "json" * * **Default**: ``false`` */ emulateJSON: false, /** * attribute:: HttpStorage.options.headers * * A set of custom headers that will be sent with every request. See also * the setHeader method. * * **Default**: ``{}`` */ headers: {}, /** * attribute:: HttpStorage.options.onError * * Callback, called if a remote request throws an error. */ onError: function (message) { console.error("API request failed: " + message); }, /** * attribute:: HttpStorage.options.prefix * * This is the API endpoint. If the server supports Cross Origin Resource * Sharing (CORS) a full URL can be used here. * * **Default**: ``'/store'`` */ prefix: '/store', /** * attribute:: HttpStorage.options.urls * * The server URLs for each available action. These URLs can be anything but * must respond to the appropriate HTTP method. The URLs are Level 1 URI * Templates as defined in RFC6570: * * http://tools.ietf.org/html/rfc6570#section-1.2 * * **Default**:: * * { * create: '/annotations', * update: '/annotations/{id}', * destroy: '/annotations/{id}', * search: '/search' * } */ urls: { create: '/annotations', update: '/annotations/{id}', destroy: '/annotations/{id}', search: '/search' } }; /** * class:: StorageAdapter(store, runHook) * * StorageAdapter wraps a concrete implementation of the Storage interface, and * ensures that the appropriate hooks are fired when annotations are created, * updated, deleted, etc. * * :param store: The Store implementation which manages persistence * :param Function runHook: A function which can be used to run lifecycle hooks */ function StorageAdapter(store, runHook) { this.store = store; this.runHook = runHook; } /** * function:: StorageAdapter.prototype.create(obj) * * Creates and returns a new annotation object. * * Runs the 'beforeAnnotationCreated' hook to allow the new annotation to be * initialized or its creation prevented. * * Runs the 'annotationCreated' hook when the new annotation has been created * by the store. * * **Examples**: * * :: * * registry.on('beforeAnnotationCreated', function (annotation) { * annotation.myProperty = 'This is a custom property'; * }); * registry.create({}); // Resolves to {myProperty: "This is a…"} * * * :param Object annotation: An object from which to create an annotation. * :returns Promise: Resolves to annotation object when stored. */ StorageAdapter.prototype.create = function (obj) { if (typeof obj === 'undefined' || obj === null) { obj = {}; } return this._cycle( obj, 'create', 'beforeAnnotationCreated', 'annotationCreated' ); }; /** * function:: StorageAdapter.prototype.update(obj) * * Updates an annotation. * * Runs the 'beforeAnnotationUpdated' hook to allow an annotation to be * modified before being passed to the store, or for an update to be prevented. * * Runs the 'annotationUpdated' hook when the annotation has been updated by * the store. * * **Examples**: * * :: * * annotation = {tags: 'apples oranges pears'}; * registry.on('beforeAnnotationUpdated', function (annotation) { * // validate or modify a property. * annotation.tags = annotation.tags.split(' ') * }); * registry.update(annotation) * // => Resolves to {tags: ["apples", "oranges", "pears"]} * * :param Object annotation: An annotation object to update. * :returns Promise: Resolves to annotation object when stored. */ StorageAdapter.prototype.update = function (obj) { if (typeof obj.id === 'undefined' || obj.id === null) { throw new TypeError("annotation must have an id for update()"); } return this._cycle( obj, 'update', 'beforeAnnotationUpdated', 'annotationUpdated' ); }; /** * function:: StorageAdapter.prototype.delete(obj) * * Deletes the annotation. * * Runs the 'beforeAnnotationDeleted' hook to allow an annotation to be * modified before being passed to the store, or for the a deletion to be * prevented. * * Runs the 'annotationDeleted' hook when the annotation has been deleted by * the store. * * :param Object annotation: An annotation object to delete. * :returns Promise: Resolves to annotation object when deleted. */ StorageAdapter.prototype['delete'] = function (obj) { if (typeof obj.id === 'undefined' || obj.id === null) { throw new TypeError("annotation must have an id for delete()"); } return this._cycle( obj, 'delete', 'beforeAnnotationDeleted', 'annotationDeleted' ); }; /** * function:: StorageAdapter.prototype.query(query) * * Queries the store * * :param Object query: * A query. This may be interpreted differently by different stores. * * :returns Promise: Resolves to the store return value. */ StorageAdapter.prototype.query = function (query) { return Promise.resolve(this.store.query(query)); }; /** * function:: StorageAdapter.prototype.load(query) * * Load and draw annotations from a given query. * * Runs the 'load' hook to allow modules to respond to annotations being loaded. * * :param Object query: * A query. This may be interpreted differently by different stores. * * :returns Promise: Resolves when loading is complete. */ StorageAdapter.prototype.load = function (query) { var self = this; return this.query(query) .then(function (data) { self.runHook('annotationsLoaded', [data.results]); }); }; // Cycle a store event, keeping track of the annotation object and updating it // as necessary. StorageAdapter.prototype._cycle = function ( obj, storeFunc, beforeEvent, afterEvent ) { var self = this; return this.runHook(beforeEvent, [obj]) .then(function () { var safeCopy = $.extend(true, {}, obj); delete safeCopy._local; // We use Promise.resolve() to coerce the result of the store // function, which can be either a value or a promise, to a promise. var result = self.store[storeFunc](safeCopy); return Promise.resolve(result); }) .then(function (ret) { // Empty obj without changing identity for (var k in obj) { if (obj.hasOwnProperty(k)) { if (k !== '_local') { delete obj[k]; } } } // Update with store return value $.extend(obj, ret); self.runHook(afterEvent, [obj]); return obj; }); }; exports.StorageAdapter = StorageAdapter; },{"./util":39}],28:[function(require,module,exports){ // Main module: default UI exports.main = require('./ui/main').main; // Export submodules for browser environments exports.adder = require('./ui/adder'); exports.editor = require('./ui/editor'); exports.filter = require('./ui/filter'); exports.highlighter = require('./ui/highlighter'); exports.markdown = require('./ui/markdown'); exports.tags = require('./ui/tags'); exports.textselector = require('./ui/textselector'); exports.viewer = require('./ui/viewer'); exports.widget = require('./ui/widget'); },{"./ui/adder":29,"./ui/editor":30,"./ui/filter":31,"./ui/highlighter":32,"./ui/main":33,"./ui/markdown":34,"./ui/tags":35,"./ui/textselector":36,"./ui/viewer":37,"./ui/widget":38}],29:[function(require,module,exports){ "use strict"; var Widget = require('./widget').Widget, util = require('../util'); var $ = util.$; var _t = util.gettext; var NS = 'annotator-adder'; // Adder shows and hides an annotation adder button that can be clicked on to // create an annotation. var Adder = Widget.extend({ constructor: function (options) { Widget.call(this, options); this.ignoreMouseup = false; this.annotation = null; this.onCreate = this.options.onCreate; var self = this; this.element .on("click." + NS, 'button', function (e) { self._onClick(e); }) .on("mousedown." + NS, 'button', function (e) { self._onMousedown(e); }); this.document = this.element[0].ownerDocument; $(this.document.body).on("mouseup." + NS, function (e) { self._onMouseup(e); }); }, destroy: function () { this.element.off("." + NS); $(this.document.body).off("." + NS); Widget.prototype.destroy.call(this); }, // Public: Load an annotation and show the adder. // // annotation - An annotation Object to load. // position - An Object specifying the position in which to show the editor // (optional). // // If the user clicks on the adder with an annotation loaded, the onCreate // handler will be called. In this way, the adder can serve as an // intermediary step between making a selection and creating an annotation. // // Returns nothing. load: function (annotation, position) { this.annotation = annotation; this.show(position); }, // Public: Show the adder. // // position - An Object specifying the position in which to show the editor // (optional). // // Examples // // adder.show() // adder.hide() // adder.show({top: '100px', left: '80px'}) // // Returns nothing. show: function (position) { if (typeof position !== 'undefined' && position !== null) { this.element.css({ top: position.top, left: position.left }); } Widget.prototype.show.call(this); }, // Event callback: called when the mouse button is depressed on the adder. // // event - A mousedown Event object // // Returns nothing. _onMousedown: function (event) { // Do nothing for right-clicks, middle-clicks, etc. if (event.which > 1) { return; } event.preventDefault(); // Prevent the selection code from firing when the mouse button is // released this.ignoreMouseup = true; }, // Event callback: called when the mouse button is released // // event - A mouseup Event object // // Returns nothing. _onMouseup: function (event) { // Do nothing for right-clicks, middle-clicks, etc. if (event.which > 1) { return; } // Prevent the selection code from firing when the ignoreMouseup flag is // set if (this.ignoreMouseup) { event.stopImmediatePropagation(); } }, // Event callback: called when the adder is clicked. The click event is used // as well as the mousedown so that we get the :active state on the adder // when clicked. // // event - A mousedown Event object // // Returns nothing. _onClick: function (event) { // Do nothing for right-clicks, middle-clicks, etc. if (event.which > 1) { return; } event.preventDefault(); // Hide the adder this.hide(); this.ignoreMouseup = false; // Create a new annotation if (this.annotation !== null && typeof this.onCreate === 'function') { this.onCreate(this.annotation, event); } } }); Adder.template = [ '
', ' ', '
' ].join('\n'); // Configuration options Adder.options = { // Callback, called when the user clicks the adder when an // annotation is loaded. onCreate: null }; exports.Adder = Adder; },{"../util":39,"./widget":38}],30:[function(require,module,exports){ "use strict"; var Widget = require('./widget').Widget, util = require('../util'); var $ = util.$; var _t = util.gettext; var Promise = util.Promise; var NS = "annotator-editor"; // id returns an identifier unique within this session var id = (function () { var counter; counter = -1; return function () { return counter += 1; }; }()); // preventEventDefault prevents an event's default, but handles the condition // that the event is null or doesn't have a preventDefault function. function preventEventDefault(event) { if (typeof event !== 'undefined' && event !== null && typeof event.preventDefault === 'function') { event.preventDefault(); } } // dragTracker is a function which allows a callback to track changes made to // the position of a draggable "handle" element. // // handle - A DOM element to make draggable // callback - Callback function // // Callback arguments: // // delta - An Object with two properties, "x" and "y", denoting the amount the // mouse has moved since the last (tracked) call. // // Callback returns: Boolean indicating whether to track the last movement. If // the movement is not tracked, then the amount the mouse has moved will be // accumulated and passed to the next mousemove event. // var dragTracker = exports.dragTracker = function dragTracker(handle, callback) { var lastPos = null, throttled = false; // Event handler for mousemove function mouseMove(e) { if (throttled || lastPos === null) { return; } var delta = { y: e.pageY - lastPos.top, x: e.pageX - lastPos.left }; var trackLastMove = true; // The callback function can return false to indicate that the tracker // shouldn't keep updating the last position. This can be used to // implement "walls" beyond which (for example) resizing has no effect. if (typeof callback === 'function') { trackLastMove = callback(delta); } if (trackLastMove !== false) { lastPos = { top: e.pageY, left: e.pageX }; } // Throttle repeated mousemove events throttled = true; setTimeout(function () { throttled = false; }, 1000 / 60); } // Event handler for mouseup function mouseUp() { lastPos = null; $(handle.ownerDocument) .off('mouseup', mouseUp) .off('mousemove', mouseMove); } // Event handler for mousedown -- starts drag tracking function mouseDown(e) { if (e.target !== handle) { return; } lastPos = { top: e.pageY, left: e.pageX }; $(handle.ownerDocument) .on('mouseup', mouseUp) .on('mousemove', mouseMove); e.preventDefault(); } // Public: turn off drag tracking for this dragTracker object. function destroy() { $(handle).off('mousedown', mouseDown); } $(handle).on('mousedown', mouseDown); return {destroy: destroy}; }; // resizer is a component that uses a dragTracker under the hood to track the // dragging of a handle element, using that motion to resize another element. // // element - DOM Element to resize // handle - DOM Element to use as a resize handle // options - Object of options. // // Available options: // // invertedX - If this option is defined as a function, and that function // returns a truthy value, the horizontal sense of the drag will be // inverted. Useful if the drag handle is at the left of the // element, and so dragging left means "grow the element" // invertedY - If this option is defined as a function, and that function // returns a truthy value, the vertical sense of the drag will be // inverted. Useful if the drag handle is at the bottom of the // element, and so dragging down means "grow the element" var resizer = exports.resizer = function resizer(element, handle, options) { var $el = $(element); if (typeof options === 'undefined' || options === null) { options = {}; } // Translate the delta supplied by dragTracker into a delta that takes // account of the invertedX and invertedY callbacks if defined. function translate(delta) { var directionX = 1, directionY = -1; if (typeof options.invertedX === 'function' && options.invertedX()) { directionX = -1; } if (typeof options.invertedY === 'function' && options.invertedY()) { directionY = 1; } return { x: delta.x * directionX, y: delta.y * directionY }; } // Callback for dragTracker function resize(delta) { var height = $el.height(), width = $el.width(), translated = translate(delta); if (Math.abs(translated.x) > 0) { $el.width(width + translated.x); } if (Math.abs(translated.y) > 0) { $el.height(height + translated.y); } // Did the element dimensions actually change? If not, then we've // reached the minimum size, and we shouldn't track var didChange = ($el.height() !== height || $el.width() !== width); return didChange; } // We return the dragTracker object in order to expose its methods. return dragTracker(handle, resize); }; // mover is a component that uses a dragTracker under the hood to track the // dragging of a handle element, using that motion to move another element. // // element - DOM Element to move // handle - DOM Element to use as a move handle // var mover = exports.mover = function mover(element, handle) { function move(delta) { $(element).css({ top: parseInt($(element).css('top'), 10) + delta.y, left: parseInt($(element).css('left'), 10) + delta.x }); } // We return the dragTracker object in order to expose its methods. return dragTracker(handle, move); }; // Public: Creates an element for editing annotations. var Editor = exports.Editor = Widget.extend({ // Public: Creates an instance of the Editor object. // // options - An Object literal containing options. // // Examples // // # Creates a new editor, adds a custom field and // # loads an annotation for editing. // editor = new Annotator.Editor // editor.addField({ // label: 'My custom input field', // type: 'textarea' // load: someLoadCallback // save: someSaveCallback // }) // editor.load(annotation) // // Returns a new Editor instance. constructor: function (options) { Widget.call(this, options); this.fields = []; this.annotation = {}; if (this.options.defaultFields) { this.addField({ type: 'textarea', label: _t('Comments') + '\u2026', load: function (field, annotation) { $(field).find('textarea').val(annotation.text || ''); }, submit: function (field, annotation) { annotation.text = $(field).find('textarea').val(); } }); } var self = this; this.element .on("submit." + NS, 'form', function (e) { self._onFormSubmit(e); }) .on("click." + NS, '.annotator-save', function (e) { self._onSaveClick(e); }) .on("click." + NS, '.annotator-cancel', function (e) { self._onCancelClick(e); }) .on("mouseover." + NS, '.annotator-cancel', function (e) { self._onCancelMouseover(e); }) .on("keydown." + NS, 'textarea', function (e) { self._onTextareaKeydown(e); }); }, destroy: function () { this.element.off("." + NS); Widget.prototype.destroy.call(this); }, // Public: Show the editor. // // position - An Object specifying the position in which to show the editor // (optional). // // Examples // // editor.show() // editor.hide() // editor.show({top: '100px', left: '80px'}) // // Returns nothing. show: function (position) { if (typeof position !== 'undefined' && position !== null) { this.element.css({ top: position.top, left: position.left }); } this.element .find('.annotator-save') .addClass(this.classes.focus); Widget.prototype.show.call(this); // give main textarea focus this.element.find(":input:first").focus(); this._setupDraggables(); }, // Public: Load an annotation into the editor and display it. // // annotation - An annotation Object to display for editing. // position - An Object specifying the position in which to show the editor // (optional). // // Returns a Promise that is resolved when the editor is submitted, or // rejected if editing is cancelled. load: function (annotation, position) { this.annotation = annotation; for (var i = 0, len = this.fields.length; i < len; i++) { var field = this.fields[i]; field.load(field.element, this.annotation); } var self = this; return new Promise(function (resolve, reject) { self.dfd = {resolve: resolve, reject: reject}; self.show(position); }); }, // Public: Submits the editor and saves any changes made to the annotation. // // Returns nothing. submit: function () { for (var i = 0, len = this.fields.length; i < len; i++) { var field = this.fields[i]; field.submit(field.element, this.annotation); } if (typeof this.dfd !== 'undefined' && this.dfd !== null) { this.dfd.resolve(); } this.hide(); }, // Public: Cancels the editing process, discarding any edits made to the // annotation. // // Returns itself. cancel: function () { if (typeof this.dfd !== 'undefined' && this.dfd !== null) { this.dfd.reject('editing cancelled'); } this.hide(); }, // Public: Adds an addional form field to the editor. Callbacks can be // provided to update the view and anotations on load and submission. // // options - An options Object. Options are as follows: // id - A unique id for the form element will also be set as // the "for" attrubute of a label if there is one. // (default: "annotator-field-{number}") // type - Input type String. One of "input", "textarea", // "checkbox", "select" (default: "input") // label - Label to display either in a label Element or as // placeholder text depending on the type. (default: "") // load - Callback Function called when the editor is loaded // with a new annotation. Receives the field
  • element // and the annotation to be loaded. // submit - Callback Function called when the editor is submitted. // Receives the field
  • element and the annotation to // be updated. // // Examples // // # Add a new input element. // editor.addField({ // label: "Tags", // // # This is called when the editor is loaded use it to update your // # input. // load: (field, annotation) -> // # Do something with the annotation. // value = getTagString(annotation.tags) // $(field).find('input').val(value) // // # This is called when the editor is submitted use it to retrieve data // # from your input and save it to the annotation. // submit: (field, annotation) -> // value = $(field).find('input').val() // annotation.tags = getTagsFromString(value) // }) // // # Add a new checkbox element. // editor.addField({ // type: 'checkbox', // id: 'annotator-field-my-checkbox', // label: 'Allow anyone to see this annotation', // load: (field, annotation) -> // # Check what state of input should be. // if checked // $(field).find('input').attr('checked', 'checked') // else // $(field).find('input').removeAttr('checked') // submit: (field, annotation) -> // checked = $(field).find('input').is(':checked') // # Do something. // }) // // Returns the created
  • Element. addField: function (options) { var field = $.extend({ id: 'annotator-field-' + id(), type: 'input', label: '', load: function () {}, submit: function () {} }, options); var input = null, element = $('
  • '); field.element = element[0]; if (field.type === 'textarea') { input = $('