1828 lines
54 KiB
JavaScript
1828 lines
54 KiB
JavaScript
/*
|
|
** Annotator 1.2.6-dev-dc18206
|
|
** https://github.com/okfn/annotator/
|
|
**
|
|
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
|
|
** Dual licensed under the MIT and GPLv3 licenses.
|
|
** https://github.com/okfn/annotator/blob/master/LICENSE
|
|
**
|
|
** Built at: 2013-05-16 18:01:57Z
|
|
*/
|
|
|
|
|
|
(function() {
|
|
var $, Annotator, Delegator, LinkParser, Range, findChild, fn, functions, g, getNodeName, getNodePosition, gettext, simpleXPathJQuery, simpleXPathPure, util, _Annotator, _gettext, _i, _j, _len, _len1, _ref, _ref1, _t,
|
|
__slice = [].slice,
|
|
__hasProp = {}.hasOwnProperty,
|
|
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
|
|
simpleXPathJQuery = function(relativeRoot) {
|
|
var jq;
|
|
|
|
jq = this.map(function() {
|
|
var elem, idx, path, tagName;
|
|
|
|
path = '';
|
|
elem = this;
|
|
while (elem && elem.nodeType === 1 && elem !== relativeRoot) {
|
|
tagName = elem.tagName.replace(":", "\\:");
|
|
idx = $(elem.parentNode).children(tagName).index(elem) + 1;
|
|
idx = "[" + idx + "]";
|
|
path = "/" + elem.tagName.toLowerCase() + idx + path;
|
|
elem = elem.parentNode;
|
|
}
|
|
return path;
|
|
});
|
|
return jq.get();
|
|
};
|
|
|
|
simpleXPathPure = function(relativeRoot) {
|
|
var getPathSegment, getPathTo, jq, rootNode;
|
|
|
|
getPathSegment = function(node) {
|
|
var name, pos;
|
|
|
|
name = getNodeName(node);
|
|
pos = getNodePosition(node);
|
|
return "" + name + "[" + pos + "]";
|
|
};
|
|
rootNode = relativeRoot;
|
|
getPathTo = function(node) {
|
|
var xpath;
|
|
|
|
xpath = '';
|
|
while (node !== rootNode) {
|
|
if (node == null) {
|
|
throw new Error("Called getPathTo on a node which was not a descendant of @rootNode. " + rootNode);
|
|
}
|
|
xpath = (getPathSegment(node)) + '/' + xpath;
|
|
node = node.parentNode;
|
|
}
|
|
xpath = '/' + xpath;
|
|
xpath = xpath.replace(/\/$/, '');
|
|
return xpath;
|
|
};
|
|
jq = this.map(function() {
|
|
var path;
|
|
|
|
path = getPathTo(this);
|
|
return path;
|
|
});
|
|
return jq.get();
|
|
};
|
|
|
|
findChild = function(node, type, index) {
|
|
var child, children, found, name, _i, _len;
|
|
|
|
if (!node.hasChildNodes()) {
|
|
throw new Error("XPath error: node has no children!");
|
|
}
|
|
children = node.childNodes;
|
|
found = 0;
|
|
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
|
child = children[_i];
|
|
name = getNodeName(child);
|
|
if (name === type) {
|
|
found += 1;
|
|
if (found === index) {
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
throw new Error("XPath error: wanted child not found.");
|
|
};
|
|
|
|
getNodeName = function(node) {
|
|
var nodeName;
|
|
|
|
nodeName = node.nodeName.toLowerCase();
|
|
switch (nodeName) {
|
|
case "#text":
|
|
return "text()";
|
|
case "#comment":
|
|
return "comment()";
|
|
case "#cdata-section":
|
|
return "cdata-section()";
|
|
default:
|
|
return nodeName;
|
|
}
|
|
};
|
|
|
|
getNodePosition = function(node) {
|
|
var pos, tmp;
|
|
|
|
pos = 0;
|
|
tmp = node;
|
|
while (tmp) {
|
|
if (tmp.nodeName === node.nodeName) {
|
|
pos++;
|
|
}
|
|
tmp = tmp.previousSibling;
|
|
}
|
|
return pos;
|
|
};
|
|
|
|
gettext = null;
|
|
|
|
if (typeof Gettext !== "undefined" && Gettext !== null) {
|
|
_gettext = new Gettext({
|
|
domain: "annotator"
|
|
});
|
|
gettext = function(msgid) {
|
|
return _gettext.gettext(msgid);
|
|
};
|
|
} else {
|
|
gettext = function(msgid) {
|
|
return msgid;
|
|
};
|
|
}
|
|
|
|
_t = function(msgid) {
|
|
return gettext(msgid);
|
|
};
|
|
|
|
if (!(typeof jQuery !== "undefined" && jQuery !== null ? (_ref = jQuery.fn) != null ? _ref.jquery : void 0 : void 0)) {
|
|
console.error(_t("Annotator requires jQuery: have you included lib/vendor/jquery.js?"));
|
|
}
|
|
|
|
if (!(JSON && JSON.parse && JSON.stringify)) {
|
|
console.error(_t("Annotator requires a JSON implementation: have you included lib/vendor/json2.js?"));
|
|
}
|
|
|
|
$ = jQuery.sub();
|
|
|
|
$.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);
|
|
};
|
|
|
|
$.plugin = function(name, object) {
|
|
return jQuery.fn[name] = function(options) {
|
|
var args;
|
|
|
|
args = Array.prototype.slice.call(arguments, 1);
|
|
return this.each(function() {
|
|
var instance;
|
|
|
|
instance = $.data(this, name);
|
|
if (instance) {
|
|
return options && instance[options].apply(instance, args);
|
|
} else {
|
|
instance = new object(this, options);
|
|
return $.data(this, name, instance);
|
|
}
|
|
});
|
|
};
|
|
};
|
|
|
|
$.fn.textNodes = function() {
|
|
var getTextNodes;
|
|
|
|
getTextNodes = function(node) {
|
|
var nodes;
|
|
|
|
if (node && node.nodeType !== 3) {
|
|
nodes = [];
|
|
if (node.nodeType !== 8) {
|
|
node = node.lastChild;
|
|
while (node) {
|
|
nodes.push(getTextNodes(node));
|
|
node = node.previousSibling;
|
|
}
|
|
}
|
|
return nodes.reverse();
|
|
} else {
|
|
return node;
|
|
}
|
|
};
|
|
return this.map(function() {
|
|
return $.flatten(getTextNodes(this));
|
|
});
|
|
};
|
|
|
|
$.fn.xpath = function(relativeRoot) {
|
|
var exception, result;
|
|
|
|
try {
|
|
result = simpleXPathJQuery.call(this, relativeRoot);
|
|
} catch (_error) {
|
|
exception = _error;
|
|
console.log("jQuery-based XPath construction failed! Falling back to manual.");
|
|
result = simpleXPathPure.call(this, relativeRoot);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
$.xpath = function(xp, root) {
|
|
var idx, name, node, step, steps, _i, _len, _ref1;
|
|
|
|
steps = xp.substring(1).split("/");
|
|
node = root;
|
|
for (_i = 0, _len = steps.length; _i < _len; _i++) {
|
|
step = steps[_i];
|
|
_ref1 = step.split("["), name = _ref1[0], idx = _ref1[1];
|
|
idx = idx != null ? parseInt((idx != null ? idx.split("]") : void 0)[0]) : 1;
|
|
node = findChild(node, name.toLowerCase(), idx);
|
|
}
|
|
return node;
|
|
};
|
|
|
|
$.escape = function(html) {
|
|
return html.replace(/&(?!\w+;)/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
};
|
|
|
|
$.fn.escape = function(html) {
|
|
if (arguments.length) {
|
|
return this.html($.escape(html));
|
|
}
|
|
return this.html();
|
|
};
|
|
|
|
$.fn.reverse = []._reverse || [].reverse;
|
|
|
|
functions = ["log", "debug", "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "time", "timeEnd", "profile", "profileEnd", "count", "clear", "table", "error", "notifyFirebug", "firebug", "userObjects"];
|
|
|
|
if (typeof console !== "undefined" && console !== null) {
|
|
if (console.group == null) {
|
|
console.group = function(name) {
|
|
return console.log("GROUP: ", name);
|
|
};
|
|
}
|
|
if (console.groupCollapsed == null) {
|
|
console.groupCollapsed = console.group;
|
|
}
|
|
for (_i = 0, _len = functions.length; _i < _len; _i++) {
|
|
fn = functions[_i];
|
|
if (console[fn] == null) {
|
|
console[fn] = function() {
|
|
return console.log(_t("Not implemented:") + (" console." + name));
|
|
};
|
|
}
|
|
}
|
|
} else {
|
|
this.console = {};
|
|
for (_j = 0, _len1 = functions.length; _j < _len1; _j++) {
|
|
fn = functions[_j];
|
|
this.console[fn] = function() {};
|
|
}
|
|
this.console['error'] = function() {
|
|
var args;
|
|
|
|
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
return alert("ERROR: " + (args.join(', ')));
|
|
};
|
|
this.console['warn'] = function() {
|
|
var args;
|
|
|
|
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
return alert("WARNING: " + (args.join(', ')));
|
|
};
|
|
}
|
|
|
|
Delegator = (function() {
|
|
Delegator.prototype.events = {};
|
|
|
|
Delegator.prototype.options = {};
|
|
|
|
Delegator.prototype.element = null;
|
|
|
|
function Delegator(element, options) {
|
|
this.options = $.extend(true, {}, this.options, options);
|
|
this.element = $(element);
|
|
this.on = this.subscribe;
|
|
this.addEvents();
|
|
}
|
|
|
|
Delegator.prototype.addEvents = function() {
|
|
var event, functionName, sel, selector, _k, _ref1, _ref2, _results;
|
|
|
|
_ref1 = this.events;
|
|
_results = [];
|
|
for (sel in _ref1) {
|
|
functionName = _ref1[sel];
|
|
_ref2 = sel.split(' '), selector = 2 <= _ref2.length ? __slice.call(_ref2, 0, _k = _ref2.length - 1) : (_k = 0, []), event = _ref2[_k++];
|
|
_results.push(this.addEvent(selector.join(' '), event, functionName));
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
Delegator.prototype.addEvent = function(bindTo, event, functionName) {
|
|
var closure, isBlankSelector,
|
|
_this = this;
|
|
|
|
closure = function() {
|
|
return _this[functionName].apply(_this, arguments);
|
|
};
|
|
isBlankSelector = typeof bindTo === 'string' && bindTo.replace(/\s+/g, '') === '';
|
|
if (isBlankSelector) {
|
|
bindTo = this.element;
|
|
}
|
|
if (typeof bindTo === 'string') {
|
|
this.element.delegate(bindTo, event, closure);
|
|
} else {
|
|
if (this.isCustomEvent(event)) {
|
|
this.subscribe(event, closure);
|
|
} else {
|
|
$(bindTo).bind(event, closure);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Delegator.prototype.isCustomEvent = function(event) {
|
|
event = event.split('.')[0];
|
|
return $.inArray(event, Delegator.natives) === -1;
|
|
};
|
|
|
|
Delegator.prototype.publish = function() {
|
|
this.element.triggerHandler.apply(this.element, arguments);
|
|
return this;
|
|
};
|
|
|
|
Delegator.prototype.subscribe = function(event, callback) {
|
|
var closure;
|
|
|
|
closure = function() {
|
|
return callback.apply(this, [].slice.call(arguments, 1));
|
|
};
|
|
closure.guid = callback.guid = ($.guid += 1);
|
|
this.element.bind(event, closure);
|
|
return this;
|
|
};
|
|
|
|
Delegator.prototype.unsubscribe = function() {
|
|
this.element.unbind.apply(this.element, arguments);
|
|
return this;
|
|
};
|
|
|
|
return Delegator;
|
|
|
|
})();
|
|
|
|
Delegator.natives = (function() {
|
|
var key, specials, val;
|
|
|
|
specials = (function() {
|
|
var _ref1, _results;
|
|
|
|
_ref1 = jQuery.event.special;
|
|
_results = [];
|
|
for (key in _ref1) {
|
|
if (!__hasProp.call(_ref1, key)) continue;
|
|
val = _ref1[key];
|
|
_results.push(key);
|
|
}
|
|
return _results;
|
|
})();
|
|
return "blur focus focusin focusout load resize scroll unload click dblclick\nmousedown mouseup mousemove mouseover mouseout mouseenter mouseleave\nchange select submit keydown keypress keyup error".split(/[^a-z]+/).concat(specials);
|
|
})();
|
|
|
|
Range = {};
|
|
|
|
Range.sniff = function(r) {
|
|
if (r.commonAncestorContainer != null) {
|
|
return new Range.BrowserRange(r);
|
|
} else if (typeof r.start === "string") {
|
|
return new Range.SerializedRange(r);
|
|
} else if (r.start && typeof r.start === "object") {
|
|
return new Range.NormalizedRange(r);
|
|
} else {
|
|
console.error(_t("Could not sniff range type"));
|
|
return false;
|
|
}
|
|
};
|
|
|
|
Range.nodeFromXPath = function(xpath, root) {
|
|
var customResolver, evaluateXPath, namespace, node, segment;
|
|
|
|
if (root == null) {
|
|
root = document;
|
|
}
|
|
evaluateXPath = function(xp, nsResolver) {
|
|
var exception;
|
|
|
|
if (nsResolver == null) {
|
|
nsResolver = null;
|
|
}
|
|
try {
|
|
return document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
} catch (_error) {
|
|
exception = _error;
|
|
console.log("XPath evaluation failed.");
|
|
console.log("Trying fallback...");
|
|
return $.xpath(xp, root);
|
|
}
|
|
};
|
|
if (!$.isXMLDoc(document.documentElement)) {
|
|
return evaluateXPath(xpath);
|
|
} else {
|
|
customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement);
|
|
node = evaluateXPath(xpath, customResolver);
|
|
if (!node) {
|
|
xpath = ((function() {
|
|
var _k, _len2, _ref1, _results;
|
|
|
|
_ref1 = xpath.split('/');
|
|
_results = [];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
segment = _ref1[_k];
|
|
if (segment && segment.indexOf(':') === -1) {
|
|
_results.push(segment.replace(/^([a-z]+)/, 'xhtml:$1'));
|
|
} else {
|
|
_results.push(segment);
|
|
}
|
|
}
|
|
return _results;
|
|
})()).join('/');
|
|
namespace = document.lookupNamespaceURI(null);
|
|
customResolver = function(ns) {
|
|
if (ns === 'xhtml') {
|
|
return namespace;
|
|
} else {
|
|
return document.documentElement.getAttribute('xmlns:' + ns);
|
|
}
|
|
};
|
|
node = evaluateXPath(xpath, customResolver);
|
|
}
|
|
return node;
|
|
}
|
|
};
|
|
|
|
Range.RangeError = (function(_super) {
|
|
__extends(RangeError, _super);
|
|
|
|
function RangeError(type, message, parent) {
|
|
this.type = type;
|
|
this.message = message;
|
|
this.parent = parent != null ? parent : null;
|
|
RangeError.__super__.constructor.call(this, this.message);
|
|
}
|
|
|
|
return RangeError;
|
|
|
|
})(Error);
|
|
|
|
Range.BrowserRange = (function() {
|
|
function BrowserRange(obj) {
|
|
this.commonAncestorContainer = obj.commonAncestorContainer;
|
|
this.startContainer = obj.startContainer;
|
|
this.startOffset = obj.startOffset;
|
|
this.endContainer = obj.endContainer;
|
|
this.endOffset = obj.endOffset;
|
|
}
|
|
|
|
BrowserRange.prototype.normalize = function(root) {
|
|
var it, node, nr, offset, p, r, _k, _len2, _ref1;
|
|
|
|
if (this.tainted) {
|
|
console.error(_t("You may only call normalize() once on a BrowserRange!"));
|
|
return false;
|
|
} else {
|
|
this.tainted = true;
|
|
}
|
|
r = {};
|
|
nr = {};
|
|
_ref1 = ['start', 'end'];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
p = _ref1[_k];
|
|
node = this[p + 'Container'];
|
|
offset = this[p + 'Offset'];
|
|
if (node.nodeType === 1) {
|
|
it = node.childNodes[offset];
|
|
node = it || node.childNodes[offset - 1];
|
|
if (node.nodeType === 1 && !node.firstChild) {
|
|
it = null;
|
|
node = node.previousSibling;
|
|
}
|
|
while (node.nodeType !== 3) {
|
|
node = node.firstChild;
|
|
}
|
|
offset = it ? 0 : node.nodeValue.length;
|
|
}
|
|
r[p] = node;
|
|
r[p + 'Offset'] = offset;
|
|
}
|
|
nr.start = r.startOffset > 0 ? r.start.splitText(r.startOffset) : r.start;
|
|
if (r.start === r.end) {
|
|
if ((r.endOffset - r.startOffset) < nr.start.nodeValue.length) {
|
|
nr.start.splitText(r.endOffset - r.startOffset);
|
|
}
|
|
nr.end = nr.start;
|
|
} else {
|
|
if (r.endOffset < r.end.nodeValue.length) {
|
|
r.end.splitText(r.endOffset);
|
|
}
|
|
nr.end = r.end;
|
|
}
|
|
nr.commonAncestor = this.commonAncestorContainer;
|
|
while (nr.commonAncestor.nodeType !== 1) {
|
|
nr.commonAncestor = nr.commonAncestor.parentNode;
|
|
}
|
|
return new Range.NormalizedRange(nr);
|
|
};
|
|
|
|
BrowserRange.prototype.serialize = function(root, ignoreSelector) {
|
|
return this.normalize(root).serialize(root, ignoreSelector);
|
|
};
|
|
|
|
return BrowserRange;
|
|
|
|
})();
|
|
|
|
Range.NormalizedRange = (function() {
|
|
function NormalizedRange(obj) {
|
|
this.commonAncestor = obj.commonAncestor;
|
|
this.start = obj.start;
|
|
this.end = obj.end;
|
|
}
|
|
|
|
NormalizedRange.prototype.normalize = function(root) {
|
|
return this;
|
|
};
|
|
|
|
NormalizedRange.prototype.limit = function(bounds) {
|
|
var nodes, parent, startParents, _k, _len2, _ref1;
|
|
|
|
nodes = $.grep(this.textNodes(), function(node) {
|
|
return node.parentNode === bounds || $.contains(bounds, node.parentNode);
|
|
});
|
|
if (!nodes.length) {
|
|
return null;
|
|
}
|
|
this.start = nodes[0];
|
|
this.end = nodes[nodes.length - 1];
|
|
startParents = $(this.start).parents();
|
|
_ref1 = $(this.end).parents();
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
parent = _ref1[_k];
|
|
if (startParents.index(parent) !== -1) {
|
|
this.commonAncestor = parent;
|
|
break;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
NormalizedRange.prototype.serialize = function(root, ignoreSelector) {
|
|
var end, serialization, start;
|
|
|
|
serialization = function(node, isEnd) {
|
|
var n, nodes, offset, origParent, textNodes, xpath, _k, _len2;
|
|
|
|
if (ignoreSelector) {
|
|
origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0);
|
|
} else {
|
|
origParent = $(node).parent();
|
|
}
|
|
xpath = origParent.xpath(root)[0];
|
|
textNodes = origParent.textNodes();
|
|
nodes = textNodes.slice(0, textNodes.index(node));
|
|
offset = 0;
|
|
for (_k = 0, _len2 = nodes.length; _k < _len2; _k++) {
|
|
n = nodes[_k];
|
|
offset += n.nodeValue.length;
|
|
}
|
|
if (isEnd) {
|
|
return [xpath, offset + node.nodeValue.length];
|
|
} else {
|
|
return [xpath, offset];
|
|
}
|
|
};
|
|
start = serialization(this.start);
|
|
end = serialization(this.end, true);
|
|
return new Range.SerializedRange({
|
|
start: start[0],
|
|
end: end[0],
|
|
startOffset: start[1],
|
|
endOffset: end[1]
|
|
});
|
|
};
|
|
|
|
NormalizedRange.prototype.text = function() {
|
|
var node;
|
|
|
|
return ((function() {
|
|
var _k, _len2, _ref1, _results;
|
|
|
|
_ref1 = this.textNodes();
|
|
_results = [];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
node = _ref1[_k];
|
|
_results.push(node.nodeValue);
|
|
}
|
|
return _results;
|
|
}).call(this)).join('');
|
|
};
|
|
|
|
NormalizedRange.prototype.textNodes = function() {
|
|
var end, start, textNodes, _ref1;
|
|
|
|
textNodes = $(this.commonAncestor).textNodes();
|
|
_ref1 = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref1[0], end = _ref1[1];
|
|
return $.makeArray(textNodes.slice(start, +end + 1 || 9e9));
|
|
};
|
|
|
|
NormalizedRange.prototype.toRange = function() {
|
|
var range;
|
|
|
|
range = document.createRange();
|
|
range.setStartBefore(this.start);
|
|
range.setEndAfter(this.end);
|
|
return range;
|
|
};
|
|
|
|
return NormalizedRange;
|
|
|
|
})();
|
|
|
|
Range.SerializedRange = (function() {
|
|
function SerializedRange(obj) {
|
|
this.start = obj.start;
|
|
this.startOffset = obj.startOffset;
|
|
this.end = obj.end;
|
|
this.endOffset = obj.endOffset;
|
|
}
|
|
|
|
SerializedRange.prototype.normalize = function(root) {
|
|
var contains, e, length, node, p, range, tn, _k, _l, _len2, _len3, _ref1, _ref2;
|
|
|
|
range = {};
|
|
_ref1 = ['start', 'end'];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
p = _ref1[_k];
|
|
try {
|
|
node = Range.nodeFromXPath(this[p], root);
|
|
} catch (_error) {
|
|
e = _error;
|
|
throw new Range.RangeError(p, ("Error while finding " + p + " node: " + this[p] + ": ") + e, e);
|
|
}
|
|
if (!node) {
|
|
throw new Range.RangeError(p, "Couldn't find " + p + " node: " + this[p]);
|
|
}
|
|
length = 0;
|
|
_ref2 = $(node).textNodes();
|
|
for (_l = 0, _len3 = _ref2.length; _l < _len3; _l++) {
|
|
tn = _ref2[_l];
|
|
if (length + tn.nodeValue.length >= this[p + 'Offset']) {
|
|
range[p + 'Container'] = tn;
|
|
range[p + 'Offset'] = this[p + 'Offset'] - length;
|
|
break;
|
|
} else {
|
|
length += tn.nodeValue.length;
|
|
}
|
|
}
|
|
if (range[p + 'Offset'] == null) {
|
|
throw new Range.RangeError("" + p + "offset", "Couldn't find offset " + this[p + 'Offset'] + " in element " + this[p]);
|
|
}
|
|
}
|
|
contains = document.compareDocumentPosition == null ? function(a, b) {
|
|
return a.contains(b);
|
|
} : function(a, b) {
|
|
return a.compareDocumentPosition(b) & 16;
|
|
};
|
|
$(range.startContainer).parents().each(function() {
|
|
if (contains(this, range.endContainer)) {
|
|
range.commonAncestorContainer = this;
|
|
return false;
|
|
}
|
|
});
|
|
return new Range.BrowserRange(range).normalize(root);
|
|
};
|
|
|
|
SerializedRange.prototype.serialize = function(root, ignoreSelector) {
|
|
return this.normalize(root).serialize(root, ignoreSelector);
|
|
};
|
|
|
|
SerializedRange.prototype.toObject = function() {
|
|
return {
|
|
start: this.start,
|
|
startOffset: this.startOffset,
|
|
end: this.end,
|
|
endOffset: this.endOffset
|
|
};
|
|
};
|
|
|
|
return SerializedRange;
|
|
|
|
})();
|
|
|
|
util = {
|
|
uuid: (function() {
|
|
var counter;
|
|
|
|
counter = 0;
|
|
return function() {
|
|
return counter++;
|
|
};
|
|
})(),
|
|
getGlobal: function() {
|
|
return (function() {
|
|
return this;
|
|
})();
|
|
},
|
|
maxZIndex: function($elements) {
|
|
var all, el;
|
|
|
|
all = (function() {
|
|
var _k, _len2, _results;
|
|
|
|
_results = [];
|
|
for (_k = 0, _len2 = $elements.length; _k < _len2; _k++) {
|
|
el = $elements[_k];
|
|
if ($(el).css('position') === 'static') {
|
|
_results.push(-1);
|
|
} else {
|
|
_results.push(parseInt($(el).css('z-index'), 10) || -1);
|
|
}
|
|
}
|
|
return _results;
|
|
})();
|
|
return Math.max.apply(Math, all);
|
|
},
|
|
mousePosition: function(e, offsetEl) {
|
|
var offset;
|
|
|
|
offset = $(offsetEl).position();
|
|
return {
|
|
top: e.pageY - offset.top,
|
|
left: e.pageX - offset.left
|
|
};
|
|
},
|
|
preventEventDefault: function(event) {
|
|
return event != null ? typeof event.preventDefault === "function" ? event.preventDefault() : void 0 : void 0;
|
|
}
|
|
};
|
|
|
|
_Annotator = this.Annotator;
|
|
|
|
Annotator = (function(_super) {
|
|
__extends(Annotator, _super);
|
|
|
|
Annotator.prototype.events = {
|
|
".annotator-adder button click": "onAdderClick",
|
|
".annotator-adder button mousedown": "onAdderMousedown",
|
|
".annotator-hl mouseover": "onHighlightMouseover",
|
|
".annotator-hl mouseout": "startViewerHideTimer"
|
|
};
|
|
|
|
Annotator.prototype.html = {
|
|
adder: '<div class="annotator-adder"><button>' + _t('Annotate') + '</button></div>',
|
|
wrapper: '<div class="annotator-wrapper"></div>'
|
|
};
|
|
|
|
Annotator.prototype.options = {
|
|
readOnly: false
|
|
};
|
|
|
|
Annotator.prototype.plugins = {};
|
|
|
|
Annotator.prototype.editor = null;
|
|
|
|
Annotator.prototype.viewer = null;
|
|
|
|
Annotator.prototype.selectedRanges = null;
|
|
|
|
Annotator.prototype.mouseIsDown = false;
|
|
|
|
Annotator.prototype.ignoreMouseup = false;
|
|
|
|
Annotator.prototype.viewerHideTimer = null;
|
|
|
|
function Annotator(element, options) {
|
|
this.onDeleteAnnotation = __bind(this.onDeleteAnnotation, this);
|
|
this.onEditAnnotation = __bind(this.onEditAnnotation, this);
|
|
this.onAdderClick = __bind(this.onAdderClick, this);
|
|
this.onAdderMousedown = __bind(this.onAdderMousedown, this);
|
|
this.onHighlightMouseover = __bind(this.onHighlightMouseover, this);
|
|
this.checkForEndSelection = __bind(this.checkForEndSelection, this);
|
|
this.checkForStartSelection = __bind(this.checkForStartSelection, this);
|
|
this.clearViewerHideTimer = __bind(this.clearViewerHideTimer, this);
|
|
this.startViewerHideTimer = __bind(this.startViewerHideTimer, this);
|
|
this.showViewer = __bind(this.showViewer, this);
|
|
this.onEditorSubmit = __bind(this.onEditorSubmit, this);
|
|
this.onEditorHide = __bind(this.onEditorHide, this);
|
|
this.showEditor = __bind(this.showEditor, this); Annotator.__super__.constructor.apply(this, arguments);
|
|
this.plugins = {};
|
|
if (!Annotator.supported()) {
|
|
return this;
|
|
}
|
|
if (!this.options.readOnly) {
|
|
this._setupDocumentEvents();
|
|
}
|
|
this._setupWrapper()._setupViewer()._setupEditor();
|
|
this._setupDynamicStyle();
|
|
this.adder = $(this.html.adder).appendTo(this.wrapper).hide();
|
|
}
|
|
|
|
Annotator.prototype._setupWrapper = function() {
|
|
this.wrapper = $(this.html.wrapper);
|
|
this.element.find('script').remove();
|
|
this.element.wrapInner(this.wrapper);
|
|
this.wrapper = this.element.find('.annotator-wrapper');
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype._setupViewer = function() {
|
|
var _this = this;
|
|
|
|
this.viewer = new Annotator.Viewer({
|
|
readOnly: this.options.readOnly
|
|
});
|
|
this.viewer.hide().on("edit", this.onEditAnnotation).on("delete", this.onDeleteAnnotation).addField({
|
|
load: function(field, annotation) {
|
|
if (annotation.text) {
|
|
$(field).escape(annotation.text);
|
|
} else {
|
|
$(field).html("<i>" + (_t('No Comment')) + "</i>");
|
|
}
|
|
return _this.publish('annotationViewerTextField', [field, annotation]);
|
|
}
|
|
}).element.appendTo(this.wrapper).bind({
|
|
"mouseover": this.clearViewerHideTimer,
|
|
"mouseout": this.startViewerHideTimer
|
|
});
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype._setupEditor = function() {
|
|
this.editor = new Annotator.Editor();
|
|
this.editor.hide().on('hide', this.onEditorHide).on('save', this.onEditorSubmit).addField({
|
|
type: 'textarea',
|
|
label: _t('Comments') + '\u2026',
|
|
load: function(field, annotation) {
|
|
return $(field).find('textarea').val(annotation.text || '');
|
|
},
|
|
submit: function(field, annotation) {
|
|
return annotation.text = $(field).find('textarea').val();
|
|
}
|
|
});
|
|
this.editor.element.appendTo(this.wrapper);
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype._setupDocumentEvents = function() {
|
|
$(document).bind({
|
|
"mouseup": this.checkForEndSelection,
|
|
"mousedown": this.checkForStartSelection
|
|
});
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype._setupDynamicStyle = function() {
|
|
var max, sel, style, x;
|
|
|
|
style = $('#annotator-dynamic-style');
|
|
if (!style.length) {
|
|
style = $('<style id="annotator-dynamic-style"></style>').appendTo(document.head);
|
|
}
|
|
sel = '*' + ((function() {
|
|
var _k, _len2, _ref1, _results;
|
|
|
|
_ref1 = ['adder', 'outer', 'notice', 'filter'];
|
|
_results = [];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
x = _ref1[_k];
|
|
_results.push(":not(.annotator-" + x + ")");
|
|
}
|
|
return _results;
|
|
})()).join('');
|
|
max = util.maxZIndex($(document.body).find(sel));
|
|
max = Math.max(max, 1000);
|
|
style.text([".annotator-adder, .annotator-outer, .annotator-notice {", " z-index: " + (max + 20) + ";", "}", ".annotator-filter {", " z-index: " + (max + 10) + ";", "}"].join("\n"));
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype.getSelectedRanges = function() {
|
|
var browserRange, i, normedRange, r, ranges, rangesToIgnore, selection, _k, _len2;
|
|
|
|
selection = util.getGlobal().getSelection();
|
|
ranges = [];
|
|
rangesToIgnore = [];
|
|
if (!selection.isCollapsed) {
|
|
ranges = (function() {
|
|
var _k, _ref1, _results;
|
|
|
|
_results = [];
|
|
for (i = _k = 0, _ref1 = selection.rangeCount; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
|
|
r = selection.getRangeAt(i);
|
|
browserRange = new Range.BrowserRange(r);
|
|
normedRange = browserRange.normalize().limit(this.wrapper[0]);
|
|
if (normedRange === null) {
|
|
rangesToIgnore.push(r);
|
|
}
|
|
_results.push(normedRange);
|
|
}
|
|
return _results;
|
|
}).call(this);
|
|
selection.removeAllRanges();
|
|
}
|
|
for (_k = 0, _len2 = rangesToIgnore.length; _k < _len2; _k++) {
|
|
r = rangesToIgnore[_k];
|
|
selection.addRange(r);
|
|
}
|
|
return $.grep(ranges, function(range) {
|
|
if (range) {
|
|
selection.addRange(range.toRange());
|
|
}
|
|
return range;
|
|
});
|
|
};
|
|
|
|
Annotator.prototype.createAnnotation = function() {
|
|
var annotation;
|
|
|
|
annotation = {};
|
|
this.publish('beforeAnnotationCreated', [annotation]);
|
|
return annotation;
|
|
};
|
|
|
|
Annotator.prototype.setupAnnotation = function(annotation) {
|
|
var e, normed, normedRanges, r, root, _k, _l, _len2, _len3, _ref1;
|
|
|
|
root = this.wrapper[0];
|
|
annotation.ranges || (annotation.ranges = this.selectedRanges);
|
|
normedRanges = [];
|
|
_ref1 = annotation.ranges;
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
r = _ref1[_k];
|
|
try {
|
|
normedRanges.push(Range.sniff(r).normalize(root));
|
|
} catch (_error) {
|
|
e = _error;
|
|
if (e instanceof Range.RangeError) {
|
|
this.publish('rangeNormalizeFail', [annotation, r, e]);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
annotation.quote = [];
|
|
annotation.ranges = [];
|
|
annotation.highlights = [];
|
|
for (_l = 0, _len3 = normedRanges.length; _l < _len3; _l++) {
|
|
normed = normedRanges[_l];
|
|
annotation.quote.push($.trim(normed.text()));
|
|
annotation.ranges.push(normed.serialize(this.wrapper[0], '.annotator-hl'));
|
|
$.merge(annotation.highlights, this.highlightRange(normed));
|
|
}
|
|
annotation.quote = annotation.quote.join(' / ');
|
|
$(annotation.highlights).data('annotation', annotation);
|
|
return annotation;
|
|
};
|
|
|
|
Annotator.prototype.updateAnnotation = function(annotation) {
|
|
this.publish('beforeAnnotationUpdated', [annotation]);
|
|
this.publish('annotationUpdated', [annotation]);
|
|
return annotation;
|
|
};
|
|
|
|
Annotator.prototype.deleteAnnotation = function(annotation) {
|
|
var child, h, _k, _len2, _ref1;
|
|
|
|
if (annotation.highlights != null) {
|
|
_ref1 = annotation.highlights;
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
h = _ref1[_k];
|
|
if (!(h.parentNode != null)) {
|
|
continue;
|
|
}
|
|
child = h.childNodes[0];
|
|
$(h).replaceWith(h.childNodes);
|
|
}
|
|
}
|
|
this.publish('annotationDeleted', [annotation]);
|
|
return annotation;
|
|
};
|
|
|
|
Annotator.prototype.loadAnnotations = function(annotations) {
|
|
var clone, loader,
|
|
_this = this;
|
|
|
|
if (annotations == null) {
|
|
annotations = [];
|
|
}
|
|
loader = function(annList) {
|
|
var n, now, _k, _len2;
|
|
|
|
if (annList == null) {
|
|
annList = [];
|
|
}
|
|
now = annList.splice(0, 10);
|
|
for (_k = 0, _len2 = now.length; _k < _len2; _k++) {
|
|
n = now[_k];
|
|
_this.setupAnnotation(n);
|
|
}
|
|
if (annList.length > 0) {
|
|
return setTimeout((function() {
|
|
return loader(annList);
|
|
}), 10);
|
|
} else {
|
|
return _this.publish('annotationsLoaded', [clone]);
|
|
}
|
|
};
|
|
clone = annotations.slice();
|
|
if (annotations.length) {
|
|
loader(annotations);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype.dumpAnnotations = function() {
|
|
if (this.plugins['Store']) {
|
|
return this.plugins['Store'].dumpAnnotations();
|
|
} else {
|
|
console.warn(_t("Can't dump annotations without Store plugin."));
|
|
return false;
|
|
}
|
|
};
|
|
|
|
Annotator.prototype.highlightRange = function(normedRange, cssClass) {
|
|
var hl, node, white, _k, _len2, _ref1, _results;
|
|
|
|
if (cssClass == null) {
|
|
cssClass = 'annotator-hl';
|
|
}
|
|
white = /^\s*$/;
|
|
hl = $("<span class='" + cssClass + "'></span>");
|
|
_ref1 = normedRange.textNodes();
|
|
_results = [];
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
node = _ref1[_k];
|
|
if (!white.test(node.nodeValue)) {
|
|
_results.push($(node).wrapAll(hl).parent().show()[0]);
|
|
}
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
Annotator.prototype.highlightRanges = function(normedRanges, cssClass) {
|
|
var highlights, r, _k, _len2;
|
|
|
|
if (cssClass == null) {
|
|
cssClass = 'annotator-hl';
|
|
}
|
|
highlights = [];
|
|
for (_k = 0, _len2 = normedRanges.length; _k < _len2; _k++) {
|
|
r = normedRanges[_k];
|
|
$.merge(highlights, this.highlightRange(r, cssClass));
|
|
}
|
|
return highlights;
|
|
};
|
|
|
|
Annotator.prototype.addPlugin = function(name, options) {
|
|
var klass, _base;
|
|
|
|
if (this.plugins[name]) {
|
|
console.error(_t("You cannot have more than one instance of any plugin."));
|
|
} else {
|
|
klass = Annotator.Plugin[name];
|
|
if (typeof klass === 'function') {
|
|
this.plugins[name] = new klass(this.element[0], options);
|
|
this.plugins[name].annotator = this;
|
|
if (typeof (_base = this.plugins[name]).pluginInit === "function") {
|
|
_base.pluginInit();
|
|
}
|
|
} else {
|
|
console.error(_t("Could not load ") + name + _t(" plugin. Have you included the appropriate <script> tag?"));
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype.showEditor = function(annotation, location) {
|
|
this.editor.element.css(location);
|
|
this.editor.load(annotation);
|
|
this.publish('annotationEditorShown', [this.editor, annotation]);
|
|
return this;
|
|
};
|
|
|
|
Annotator.prototype.onEditorHide = function() {
|
|
this.publish('annotationEditorHidden', [this.editor]);
|
|
return this.ignoreMouseup = false;
|
|
};
|
|
|
|
Annotator.prototype.onEditorSubmit = function(annotation) {
|
|
return this.publish('annotationEditorSubmit', [this.editor, annotation]);
|
|
};
|
|
|
|
Annotator.prototype.showViewer = function(annotations, location) {
|
|
this.viewer.element.css(location);
|
|
this.viewer.load(annotations);
|
|
return this.publish('annotationViewerShown', [this.viewer, annotations]);
|
|
};
|
|
|
|
Annotator.prototype.startViewerHideTimer = function() {
|
|
if (!this.viewerHideTimer) {
|
|
return this.viewerHideTimer = setTimeout(this.viewer.hide, 250);
|
|
}
|
|
};
|
|
|
|
Annotator.prototype.clearViewerHideTimer = function() {
|
|
clearTimeout(this.viewerHideTimer);
|
|
return this.viewerHideTimer = false;
|
|
};
|
|
|
|
Annotator.prototype.checkForStartSelection = function(event) {
|
|
if (!(event && this.isAnnotator(event.target))) {
|
|
this.startViewerHideTimer();
|
|
return this.mouseIsDown = true;
|
|
}
|
|
};
|
|
|
|
Annotator.prototype.checkForEndSelection = function(event) {
|
|
var container, range, _k, _len2, _ref1;
|
|
|
|
this.mouseIsDown = false;
|
|
if (this.ignoreMouseup) {
|
|
return;
|
|
}
|
|
this.selectedRanges = this.getSelectedRanges();
|
|
_ref1 = this.selectedRanges;
|
|
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
|
range = _ref1[_k];
|
|
container = range.commonAncestor;
|
|
if ($(container).hasClass('annotator-hl')) {
|
|
container = $(container).parents('[class^=annotator-hl]')[0];
|
|
}
|
|
if (this.isAnnotator(container)) {
|
|
return;
|
|
}
|
|
}
|
|
if (event && this.selectedRanges.length) {
|
|
return this.adder.css(util.mousePosition(event, this.wrapper[0])).show();
|
|
} else {
|
|
return this.adder.hide();
|
|
}
|
|
};
|
|
|
|
Annotator.prototype.isAnnotator = function(element) {
|
|
return !!$(element).parents().andSelf().filter('[class^=annotator-]').not(this.wrapper).length;
|
|
};
|
|
|
|
Annotator.prototype.onHighlightMouseover = function(event) {
|
|
var annotations;
|
|
|
|
this.clearViewerHideTimer();
|
|
if (this.mouseIsDown || this.viewer.isShown()) {
|
|
return false;
|
|
}
|
|
annotations = $(event.target).parents('.annotator-hl').andSelf().map(function() {
|
|
return $(this).data("annotation");
|
|
});
|
|
return this.showViewer($.makeArray(annotations), util.mousePosition(event, this.wrapper[0]));
|
|
};
|
|
|
|
Annotator.prototype.onAdderMousedown = function(event) {
|
|
if (event != null) {
|
|
event.preventDefault();
|
|
}
|
|
return this.ignoreMouseup = true;
|
|
};
|
|
|
|
Annotator.prototype.onAdderClick = function(event) {
|
|
var annotation, cancel, cleanup, position, save,
|
|
_this = this;
|
|
|
|
if (event != null) {
|
|
event.preventDefault();
|
|
}
|
|
position = this.adder.position();
|
|
this.adder.hide();
|
|
annotation = this.setupAnnotation(this.createAnnotation());
|
|
$(annotation.highlights).addClass('annotator-hl-temporary');
|
|
save = function() {
|
|
cleanup();
|
|
$(annotation.highlights).removeClass('annotator-hl-temporary');
|
|
return _this.publish('annotationCreated', [annotation]);
|
|
};
|
|
cancel = function() {
|
|
cleanup();
|
|
return _this.deleteAnnotation(annotation);
|
|
};
|
|
cleanup = function() {
|
|
_this.unsubscribe('annotationEditorHidden', cancel);
|
|
return _this.unsubscribe('annotationEditorSubmit', save);
|
|
};
|
|
this.subscribe('annotationEditorHidden', cancel);
|
|
this.subscribe('annotationEditorSubmit', save);
|
|
return this.showEditor(annotation, position);
|
|
};
|
|
|
|
Annotator.prototype.onEditAnnotation = function(annotation) {
|
|
var cleanup, offset, update,
|
|
_this = this;
|
|
|
|
offset = this.viewer.element.position();
|
|
update = function() {
|
|
cleanup();
|
|
return _this.updateAnnotation(annotation);
|
|
};
|
|
cleanup = function() {
|
|
_this.unsubscribe('annotationEditorHidden', cleanup);
|
|
return _this.unsubscribe('annotationEditorSubmit', update);
|
|
};
|
|
this.subscribe('annotationEditorHidden', cleanup);
|
|
this.subscribe('annotationEditorSubmit', update);
|
|
this.viewer.hide();
|
|
return this.showEditor(annotation, offset);
|
|
};
|
|
|
|
Annotator.prototype.onDeleteAnnotation = function(annotation) {
|
|
this.viewer.hide();
|
|
return this.deleteAnnotation(annotation);
|
|
};
|
|
|
|
return Annotator;
|
|
|
|
})(Delegator);
|
|
|
|
Annotator.Plugin = (function(_super) {
|
|
__extends(Plugin, _super);
|
|
|
|
function Plugin(element, options) {
|
|
Plugin.__super__.constructor.apply(this, arguments);
|
|
}
|
|
|
|
Plugin.prototype.pluginInit = function() {};
|
|
|
|
return Plugin;
|
|
|
|
})(Delegator);
|
|
|
|
g = util.getGlobal();
|
|
|
|
if (((_ref1 = g.document) != null ? _ref1.evaluate : void 0) == null) {
|
|
$.getScript('//assets.annotateit.org/vendor/xpath.min.js');
|
|
}
|
|
|
|
if (g.getSelection == null) {
|
|
$.getScript('//assets.annotateit.org/vendor/ierange.min.js');
|
|
}
|
|
|
|
if (g.JSON == null) {
|
|
$.getScript('//assets.annotateit.org/vendor/json2.min.js');
|
|
}
|
|
|
|
Annotator.$ = $;
|
|
|
|
Annotator.Delegator = Delegator;
|
|
|
|
Annotator.Range = Range;
|
|
|
|
Annotator._t = _t;
|
|
|
|
Annotator.supported = function() {
|
|
return (function() {
|
|
return !!this.getSelection;
|
|
})();
|
|
};
|
|
|
|
Annotator.noConflict = function() {
|
|
util.getGlobal().Annotator = _Annotator;
|
|
return this;
|
|
};
|
|
|
|
$.plugin('annotator', Annotator);
|
|
|
|
this.Annotator = Annotator;
|
|
|
|
Annotator.Widget = (function(_super) {
|
|
__extends(Widget, _super);
|
|
|
|
Widget.prototype.classes = {
|
|
hide: 'annotator-hide',
|
|
invert: {
|
|
x: 'annotator-invert-x',
|
|
y: 'annotator-invert-y'
|
|
}
|
|
};
|
|
|
|
function Widget(element, options) {
|
|
Widget.__super__.constructor.apply(this, arguments);
|
|
this.classes = $.extend({}, Annotator.Widget.prototype.classes, this.classes);
|
|
}
|
|
|
|
Widget.prototype.checkOrientation = function() {
|
|
var current, offset, viewport, widget, window;
|
|
|
|
this.resetOrientation();
|
|
window = $(util.getGlobal());
|
|
widget = this.element.children(":first");
|
|
offset = widget.offset();
|
|
viewport = {
|
|
top: window.scrollTop(),
|
|
right: window.width() + window.scrollLeft()
|
|
};
|
|
current = {
|
|
top: offset.top,
|
|
right: offset.left + widget.width()
|
|
};
|
|
if ((current.top - viewport.top) < 0) {
|
|
this.invertY();
|
|
}
|
|
if ((current.right - viewport.right) > 0) {
|
|
this.invertX();
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Widget.prototype.resetOrientation = function() {
|
|
this.element.removeClass(this.classes.invert.x).removeClass(this.classes.invert.y);
|
|
return this;
|
|
};
|
|
|
|
Widget.prototype.invertX = function() {
|
|
this.element.addClass(this.classes.invert.x);
|
|
return this;
|
|
};
|
|
|
|
Widget.prototype.invertY = function() {
|
|
this.element.addClass(this.classes.invert.y);
|
|
return this;
|
|
};
|
|
|
|
Widget.prototype.isInvertedY = function() {
|
|
return this.element.hasClass(this.classes.invert.y);
|
|
};
|
|
|
|
Widget.prototype.isInvertedX = function() {
|
|
return this.element.hasClass(this.classes.invert.x);
|
|
};
|
|
|
|
return Widget;
|
|
|
|
})(Delegator);
|
|
|
|
Annotator.Editor = (function(_super) {
|
|
__extends(Editor, _super);
|
|
|
|
Editor.prototype.events = {
|
|
"form submit": "submit",
|
|
".annotator-save click": "submit",
|
|
".annotator-cancel click": "hide",
|
|
".annotator-cancel mouseover": "onCancelButtonMouseover",
|
|
"textarea keydown": "processKeypress"
|
|
};
|
|
|
|
Editor.prototype.classes = {
|
|
hide: 'annotator-hide',
|
|
focus: 'annotator-focus'
|
|
};
|
|
|
|
Editor.prototype.html = "<div class=\"annotator-outer annotator-editor\">\n <form class=\"annotator-widget\">\n <ul class=\"annotator-listing\"></ul>\n <div class=\"annotator-controls\">\n <a href=\"#cancel\" class=\"annotator-cancel\">" + _t('Cancel') + "</a>\n<a href=\"#save\" class=\"annotator-save annotator-focus\">" + _t('Save') + "</a>\n </div>\n </form>\n</div>";
|
|
|
|
Editor.prototype.options = {};
|
|
|
|
function Editor(options) {
|
|
this.onCancelButtonMouseover = __bind(this.onCancelButtonMouseover, this);
|
|
this.processKeypress = __bind(this.processKeypress, this);
|
|
this.submit = __bind(this.submit, this);
|
|
this.load = __bind(this.load, this);
|
|
this.hide = __bind(this.hide, this);
|
|
this.show = __bind(this.show, this); Editor.__super__.constructor.call(this, $(this.html)[0], options);
|
|
this.fields = [];
|
|
this.annotation = {};
|
|
}
|
|
|
|
Editor.prototype.show = function(event) {
|
|
util.preventEventDefault(event);
|
|
this.element.removeClass(this.classes.hide);
|
|
this.element.find('.annotator-save').addClass(this.classes.focus);
|
|
this.checkOrientation();
|
|
this.element.find(":input:first").focus();
|
|
this.setupDraggables();
|
|
return this.publish('show');
|
|
};
|
|
|
|
Editor.prototype.hide = function(event) {
|
|
util.preventEventDefault(event);
|
|
this.element.addClass(this.classes.hide);
|
|
return this.publish('hide');
|
|
};
|
|
|
|
Editor.prototype.load = function(annotation) {
|
|
var field, _k, _len2, _ref2;
|
|
|
|
this.annotation = annotation;
|
|
this.publish('load', [this.annotation]);
|
|
_ref2 = this.fields;
|
|
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
|
field = _ref2[_k];
|
|
field.load(field.element, this.annotation);
|
|
}
|
|
return this.show();
|
|
};
|
|
|
|
Editor.prototype.submit = function(event) {
|
|
var field, _k, _len2, _ref2;
|
|
|
|
util.preventEventDefault(event);
|
|
_ref2 = this.fields;
|
|
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
|
field = _ref2[_k];
|
|
field.submit(field.element, this.annotation);
|
|
}
|
|
this.publish('save', [this.annotation]);
|
|
return this.hide();
|
|
};
|
|
|
|
Editor.prototype.addField = function(options) {
|
|
var element, field, input;
|
|
|
|
field = $.extend({
|
|
id: 'annotator-field-' + util.uuid(),
|
|
type: 'input',
|
|
label: '',
|
|
load: function() {},
|
|
submit: function() {}
|
|
}, options);
|
|
input = null;
|
|
element = $('<li class="annotator-item" />');
|
|
field.element = element[0];
|
|
switch (field.type) {
|
|
case 'textarea':
|
|
input = $('<textarea />');
|
|
break;
|
|
case 'input':
|
|
case 'checkbox':
|
|
input = $('<input />');
|
|
}
|
|
element.append(input);
|
|
input.attr({
|
|
id: field.id,
|
|
placeholder: field.label
|
|
});
|
|
if (field.type === 'checkbox') {
|
|
input[0].type = 'checkbox';
|
|
element.addClass('annotator-checkbox');
|
|
element.append($('<label />', {
|
|
"for": field.id,
|
|
html: field.label
|
|
}));
|
|
}
|
|
this.element.find('ul:first').append(element);
|
|
this.fields.push(field);
|
|
return field.element;
|
|
};
|
|
|
|
Editor.prototype.checkOrientation = function() {
|
|
var controls, list;
|
|
|
|
Editor.__super__.checkOrientation.apply(this, arguments);
|
|
list = this.element.find('ul');
|
|
controls = this.element.find('.annotator-controls');
|
|
if (this.element.hasClass(this.classes.invert.y)) {
|
|
controls.insertBefore(list);
|
|
} else if (controls.is(':first-child')) {
|
|
controls.insertAfter(list);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Editor.prototype.processKeypress = function(event) {
|
|
if (event.keyCode === 27) {
|
|
return this.hide();
|
|
} else if (event.keyCode === 13 && !event.shiftKey) {
|
|
return this.submit();
|
|
}
|
|
};
|
|
|
|
Editor.prototype.onCancelButtonMouseover = function() {
|
|
return this.element.find('.' + this.classes.focus).removeClass(this.classes.focus);
|
|
};
|
|
|
|
Editor.prototype.setupDraggables = function() {
|
|
var classes, controls, cornerItem, editor, mousedown, onMousedown, onMousemove, onMouseup, resize, textarea, throttle,
|
|
_this = this;
|
|
|
|
this.element.find('.annotator-resize').remove();
|
|
if (this.element.hasClass(this.classes.invert.y)) {
|
|
cornerItem = this.element.find('.annotator-item:last');
|
|
} else {
|
|
cornerItem = this.element.find('.annotator-item:first');
|
|
}
|
|
if (cornerItem) {
|
|
$('<span class="annotator-resize"></span>').appendTo(cornerItem);
|
|
}
|
|
mousedown = null;
|
|
classes = this.classes;
|
|
editor = this.element;
|
|
textarea = null;
|
|
resize = editor.find('.annotator-resize');
|
|
controls = editor.find('.annotator-controls');
|
|
throttle = false;
|
|
onMousedown = function(event) {
|
|
if (event.target === this) {
|
|
mousedown = {
|
|
element: this,
|
|
top: event.pageY,
|
|
left: event.pageX
|
|
};
|
|
textarea = editor.find('textarea:first');
|
|
$(window).bind({
|
|
'mouseup.annotator-editor-resize': onMouseup,
|
|
'mousemove.annotator-editor-resize': onMousemove
|
|
});
|
|
return event.preventDefault();
|
|
}
|
|
};
|
|
onMouseup = function() {
|
|
mousedown = null;
|
|
return $(window).unbind('.annotator-editor-resize');
|
|
};
|
|
onMousemove = function(event) {
|
|
var diff, directionX, directionY, height, width;
|
|
|
|
if (mousedown && throttle === false) {
|
|
diff = {
|
|
top: event.pageY - mousedown.top,
|
|
left: event.pageX - mousedown.left
|
|
};
|
|
if (mousedown.element === resize[0]) {
|
|
height = textarea.outerHeight();
|
|
width = textarea.outerWidth();
|
|
directionX = editor.hasClass(classes.invert.x) ? -1 : 1;
|
|
directionY = editor.hasClass(classes.invert.y) ? 1 : -1;
|
|
textarea.height(height + (diff.top * directionY));
|
|
textarea.width(width + (diff.left * directionX));
|
|
if (textarea.outerHeight() !== height) {
|
|
mousedown.top = event.pageY;
|
|
}
|
|
if (textarea.outerWidth() !== width) {
|
|
mousedown.left = event.pageX;
|
|
}
|
|
} else if (mousedown.element === controls[0]) {
|
|
editor.css({
|
|
top: parseInt(editor.css('top'), 10) + diff.top,
|
|
left: parseInt(editor.css('left'), 10) + diff.left
|
|
});
|
|
mousedown.top = event.pageY;
|
|
mousedown.left = event.pageX;
|
|
}
|
|
throttle = true;
|
|
return setTimeout(function() {
|
|
return throttle = false;
|
|
}, 1000 / 60);
|
|
}
|
|
};
|
|
resize.bind('mousedown', onMousedown);
|
|
return controls.bind('mousedown', onMousedown);
|
|
};
|
|
|
|
return Editor;
|
|
|
|
})(Annotator.Widget);
|
|
|
|
Annotator.Viewer = (function(_super) {
|
|
__extends(Viewer, _super);
|
|
|
|
Viewer.prototype.events = {
|
|
".annotator-edit click": "onEditClick",
|
|
".annotator-delete click": "onDeleteClick"
|
|
};
|
|
|
|
Viewer.prototype.classes = {
|
|
hide: 'annotator-hide',
|
|
showControls: 'annotator-visible'
|
|
};
|
|
|
|
Viewer.prototype.html = {
|
|
element: "<div class=\"annotator-outer annotator-viewer\">\n <ul class=\"annotator-widget annotator-listing\"></ul>\n</div>",
|
|
item: "<li class=\"annotator-annotation annotator-item\">\n <span class=\"annotator-controls\">\n <a href=\"#\" title=\"View as webpage\" class=\"annotator-link\">View as webpage</a>\n <button title=\"Edit\" class=\"annotator-edit\">Edit</button>\n <button title=\"Delete\" class=\"annotator-delete\">Delete</button>\n </span>\n</li>"
|
|
};
|
|
|
|
Viewer.prototype.options = {
|
|
readOnly: false
|
|
};
|
|
|
|
function Viewer(options) {
|
|
this.onDeleteClick = __bind(this.onDeleteClick, this);
|
|
this.onEditClick = __bind(this.onEditClick, this);
|
|
this.load = __bind(this.load, this);
|
|
this.hide = __bind(this.hide, this);
|
|
this.show = __bind(this.show, this); Viewer.__super__.constructor.call(this, $(this.html.element)[0], options);
|
|
this.item = $(this.html.item)[0];
|
|
this.fields = [];
|
|
this.annotations = [];
|
|
}
|
|
|
|
Viewer.prototype.show = function(event) {
|
|
var controls,
|
|
_this = this;
|
|
|
|
util.preventEventDefault(event);
|
|
controls = this.element.find('.annotator-controls').addClass(this.classes.showControls);
|
|
setTimeout((function() {
|
|
return controls.removeClass(_this.classes.showControls);
|
|
}), 500);
|
|
this.element.removeClass(this.classes.hide);
|
|
return this.checkOrientation().publish('show');
|
|
};
|
|
|
|
Viewer.prototype.isShown = function() {
|
|
return !this.element.hasClass(this.classes.hide);
|
|
};
|
|
|
|
Viewer.prototype.hide = function(event) {
|
|
util.preventEventDefault(event);
|
|
this.element.addClass(this.classes.hide);
|
|
return this.publish('hide');
|
|
};
|
|
|
|
Viewer.prototype.load = function(annotations) {
|
|
var annotation, controller, controls, del, edit, element, field, item, link, links, list, _k, _l, _len2, _len3, _ref2, _ref3;
|
|
|
|
this.annotations = annotations || [];
|
|
list = this.element.find('ul:first').empty();
|
|
_ref2 = this.annotations;
|
|
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
|
annotation = _ref2[_k];
|
|
item = $(this.item).clone().appendTo(list).data('annotation', annotation);
|
|
controls = item.find('.annotator-controls');
|
|
link = controls.find('.annotator-link');
|
|
edit = controls.find('.annotator-edit');
|
|
del = controls.find('.annotator-delete');
|
|
links = new LinkParser(annotation.links || []).get('alternate', {
|
|
'type': 'text/html'
|
|
});
|
|
if (links.length === 0 || (links[0].href == null)) {
|
|
link.remove();
|
|
} else {
|
|
link.attr('href', links[0].href);
|
|
}
|
|
if (this.options.readOnly) {
|
|
edit.remove();
|
|
del.remove();
|
|
} else {
|
|
controller = {
|
|
showEdit: function() {
|
|
return edit.removeAttr('disabled');
|
|
},
|
|
hideEdit: function() {
|
|
return edit.attr('disabled', 'disabled');
|
|
},
|
|
showDelete: function() {
|
|
return del.removeAttr('disabled');
|
|
},
|
|
hideDelete: function() {
|
|
return del.attr('disabled', 'disabled');
|
|
}
|
|
};
|
|
}
|
|
_ref3 = this.fields;
|
|
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
|
|
field = _ref3[_l];
|
|
element = $(field.element).clone().appendTo(item)[0];
|
|
field.load(element, annotation, controller);
|
|
}
|
|
}
|
|
this.publish('load', [this.annotations]);
|
|
return this.show();
|
|
};
|
|
|
|
Viewer.prototype.addField = function(options) {
|
|
var field;
|
|
|
|
field = $.extend({
|
|
load: function() {}
|
|
}, options);
|
|
field.element = $('<div />')[0];
|
|
this.fields.push(field);
|
|
field.element;
|
|
return this;
|
|
};
|
|
|
|
Viewer.prototype.onEditClick = function(event) {
|
|
return this.onButtonClick(event, 'edit');
|
|
};
|
|
|
|
Viewer.prototype.onDeleteClick = function(event) {
|
|
return this.onButtonClick(event, 'delete');
|
|
};
|
|
|
|
Viewer.prototype.onButtonClick = function(event, type) {
|
|
var item;
|
|
|
|
item = $(event.target).parents('.annotator-annotation');
|
|
return this.publish(type, [item.data('annotation')]);
|
|
};
|
|
|
|
return Viewer;
|
|
|
|
})(Annotator.Widget);
|
|
|
|
LinkParser = (function() {
|
|
function LinkParser(data) {
|
|
this.data = data;
|
|
}
|
|
|
|
LinkParser.prototype.get = function(rel, cond) {
|
|
var d, k, keys, match, v, _k, _len2, _ref2, _results;
|
|
|
|
if (cond == null) {
|
|
cond = {};
|
|
}
|
|
cond = $.extend({}, cond, {
|
|
rel: rel
|
|
});
|
|
keys = (function() {
|
|
var _results;
|
|
|
|
_results = [];
|
|
for (k in cond) {
|
|
if (!__hasProp.call(cond, k)) continue;
|
|
v = cond[k];
|
|
_results.push(k);
|
|
}
|
|
return _results;
|
|
})();
|
|
_ref2 = this.data;
|
|
_results = [];
|
|
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
|
d = _ref2[_k];
|
|
match = keys.reduce((function(m, k) {
|
|
return m && (d[k] === cond[k]);
|
|
}), true);
|
|
if (match) {
|
|
_results.push(d);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
return LinkParser;
|
|
|
|
})();
|
|
|
|
Annotator = Annotator || {};
|
|
|
|
Annotator.Notification = (function(_super) {
|
|
__extends(Notification, _super);
|
|
|
|
Notification.prototype.events = {
|
|
"click": "hide"
|
|
};
|
|
|
|
Notification.prototype.options = {
|
|
html: "<div class='annotator-notice'></div>",
|
|
classes: {
|
|
show: "annotator-notice-show",
|
|
info: "annotator-notice-info",
|
|
success: "annotator-notice-success",
|
|
error: "annotator-notice-error"
|
|
}
|
|
};
|
|
|
|
function Notification(options) {
|
|
this.hide = __bind(this.hide, this);
|
|
this.show = __bind(this.show, this); Notification.__super__.constructor.call(this, $(this.options.html).appendTo(document.body)[0], options);
|
|
}
|
|
|
|
Notification.prototype.show = function(message, status) {
|
|
if (status == null) {
|
|
status = Annotator.Notification.INFO;
|
|
}
|
|
$(this.element).addClass(this.options.classes.show).addClass(this.options.classes[status]).escape(message || "");
|
|
setTimeout(this.hide, 5000);
|
|
return this;
|
|
};
|
|
|
|
Notification.prototype.hide = function() {
|
|
$(this.element).removeClass(this.options.classes.show);
|
|
return this;
|
|
};
|
|
|
|
return Notification;
|
|
|
|
})(Delegator);
|
|
|
|
Annotator.Notification.INFO = 'show';
|
|
|
|
Annotator.Notification.SUCCESS = 'success';
|
|
|
|
Annotator.Notification.ERROR = 'error';
|
|
|
|
$(function() {
|
|
var notification;
|
|
|
|
notification = new Annotator.Notification;
|
|
Annotator.showNotification = notification.show;
|
|
return Annotator.hideNotification = notification.hide;
|
|
});
|
|
|
|
}).call(this);
|