Mercurial > hg > OKFNAnnotator
comparison annotator_files/lib/annotator.js @ 3:6356e78ccf5c
new version contains Annotator JS files to be used with FilesystemSite.
author | casties |
---|---|
date | Thu, 05 Apr 2012 19:37:27 +0200 |
parents | |
children | 6979313586cf |
comparison
equal
deleted
inserted
replaced
2:4c6c8835fc5c | 3:6356e78ccf5c |
---|---|
1 var Annotator, util, _Annotator, | |
2 __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, | |
3 __hasProp = Object.prototype.hasOwnProperty, | |
4 __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; }; | |
5 | |
6 util = { | |
7 uuid: (function() { | |
8 var counter; | |
9 counter = 0; | |
10 return function() { | |
11 return counter++; | |
12 }; | |
13 })(), | |
14 getGlobal: function() { | |
15 return (function() { | |
16 return this; | |
17 })(); | |
18 }, | |
19 mousePosition: function(e, offsetEl) { | |
20 var offset; | |
21 offset = $(offsetEl).offset(); | |
22 return { | |
23 top: e.pageY - offset.top, | |
24 left: e.pageX - offset.left | |
25 }; | |
26 }, | |
27 preventEventDefault: function(event) { | |
28 return event != null ? typeof event.preventDefault === "function" ? event.preventDefault() : void 0 : void 0; | |
29 } | |
30 }; | |
31 | |
32 _Annotator = this.Annotator; | |
33 | |
34 Annotator = (function(_super) { | |
35 | |
36 __extends(Annotator, _super); | |
37 | |
38 Annotator.prototype.events = { | |
39 ".annotator-adder button click": "onAdderClick", | |
40 ".annotator-adder button mousedown": "onAdderMousedown", | |
41 ".annotator-hl mouseover": "onHighlightMouseover", | |
42 ".annotator-hl mouseout": "startViewerHideTimer" | |
43 }; | |
44 | |
45 Annotator.prototype.html = { | |
46 hl: '<span class="annotator-hl"></span>', | |
47 adder: '<div class="annotator-adder"><button>' + _t('Annotate') + '</button></div>', | |
48 wrapper: '<div class="annotator-wrapper"></div>' | |
49 }; | |
50 | |
51 Annotator.prototype.options = { | |
52 readOnly: false | |
53 }; | |
54 | |
55 Annotator.prototype.plugins = {}; | |
56 | |
57 Annotator.prototype.editor = null; | |
58 | |
59 Annotator.prototype.viewer = null; | |
60 | |
61 Annotator.prototype.selectedRanges = null; | |
62 | |
63 Annotator.prototype.mouseIsDown = false; | |
64 | |
65 Annotator.prototype.ignoreMouseup = false; | |
66 | |
67 Annotator.prototype.viewerHideTimer = null; | |
68 | |
69 function Annotator(element, options) { | |
70 this.onDeleteAnnotation = __bind(this.onDeleteAnnotation, this); | |
71 this.onEditAnnotation = __bind(this.onEditAnnotation, this); | |
72 this.onAdderClick = __bind(this.onAdderClick, this); | |
73 this.onAdderMousedown = __bind(this.onAdderMousedown, this); | |
74 this.onHighlightMouseover = __bind(this.onHighlightMouseover, this); | |
75 this.checkForEndSelection = __bind(this.checkForEndSelection, this); | |
76 this.checkForStartSelection = __bind(this.checkForStartSelection, this); | |
77 this.clearViewerHideTimer = __bind(this.clearViewerHideTimer, this); | |
78 this.startViewerHideTimer = __bind(this.startViewerHideTimer, this); | |
79 this.showViewer = __bind(this.showViewer, this); | |
80 this.onEditorSubmit = __bind(this.onEditorSubmit, this); | |
81 this.onEditorHide = __bind(this.onEditorHide, this); | |
82 this.showEditor = __bind(this.showEditor, this); | |
83 var name, src, _ref; | |
84 Annotator.__super__.constructor.apply(this, arguments); | |
85 this.plugins = {}; | |
86 if (!Annotator.supported()) return this; | |
87 if (!this.options.readOnly) this._setupDocumentEvents(); | |
88 this._setupWrapper()._setupViewer()._setupEditor(); | |
89 _ref = this.html; | |
90 for (name in _ref) { | |
91 src = _ref[name]; | |
92 if (name !== 'wrapper') this[name] = $(src).appendTo(this.wrapper).hide(); | |
93 } | |
94 } | |
95 | |
96 Annotator.prototype._setupWrapper = function() { | |
97 this.wrapper = $(this.html.wrapper); | |
98 this.element.find('script').remove(); | |
99 this.element.wrapInner(this.wrapper); | |
100 this.wrapper = this.element.find('.annotator-wrapper'); | |
101 return this; | |
102 }; | |
103 | |
104 Annotator.prototype._setupViewer = function() { | |
105 var _this = this; | |
106 this.viewer = new Annotator.Viewer({ | |
107 readOnly: this.options.readOnly | |
108 }); | |
109 this.viewer.hide().on("edit", this.onEditAnnotation).on("delete", this.onDeleteAnnotation).addField({ | |
110 load: function(field, annotation) { | |
111 if (annotation.text) { | |
112 $(field).escape(annotation.text); | |
113 } else { | |
114 $(field).html("<i>" + (_t('No Comment')) + "</i>"); | |
115 } | |
116 return _this.publish('annotationViewerTextField', [field, annotation]); | |
117 } | |
118 }).element.appendTo(this.wrapper).bind({ | |
119 "mouseover": this.clearViewerHideTimer, | |
120 "mouseout": this.startViewerHideTimer | |
121 }); | |
122 return this; | |
123 }; | |
124 | |
125 Annotator.prototype._setupEditor = function() { | |
126 this.editor = new Annotator.Editor(); | |
127 this.editor.hide().on('hide', this.onEditorHide).on('save', this.onEditorSubmit).addField({ | |
128 type: 'textarea', | |
129 label: _t('Comments') + '\u2026', | |
130 load: function(field, annotation) { | |
131 return $(field).find('textarea').val(annotation.text || ''); | |
132 }, | |
133 submit: function(field, annotation) { | |
134 return annotation.text = $(field).find('textarea').val(); | |
135 } | |
136 }); | |
137 this.editor.element.appendTo(this.wrapper); | |
138 return this; | |
139 }; | |
140 | |
141 Annotator.prototype._setupDocumentEvents = function() { | |
142 $(document).bind({ | |
143 "mouseup": this.checkForEndSelection, | |
144 "mousedown": this.checkForStartSelection | |
145 }); | |
146 return this; | |
147 }; | |
148 | |
149 Annotator.prototype.getSelectedRanges = function() { | |
150 var browserRange, i, normedRange, r, ranges, rangesToIgnore, selection, _i, _len; | |
151 selection = util.getGlobal().getSelection(); | |
152 ranges = []; | |
153 rangesToIgnore = []; | |
154 if (!selection.isCollapsed) { | |
155 ranges = (function() { | |
156 var _ref, _results; | |
157 _results = []; | |
158 for (i = 0, _ref = selection.rangeCount; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) { | |
159 r = selection.getRangeAt(i); | |
160 browserRange = new Range.BrowserRange(r); | |
161 normedRange = browserRange.normalize().limit(this.wrapper[0]); | |
162 if (normedRange === null) rangesToIgnore.push(r); | |
163 _results.push(normedRange); | |
164 } | |
165 return _results; | |
166 }).call(this); | |
167 selection.removeAllRanges(); | |
168 } | |
169 for (_i = 0, _len = rangesToIgnore.length; _i < _len; _i++) { | |
170 r = rangesToIgnore[_i]; | |
171 selection.addRange(r); | |
172 } | |
173 return $.grep(ranges, function(range) { | |
174 if (range) selection.addRange(range.toRange()); | |
175 return range; | |
176 }); | |
177 }; | |
178 | |
179 Annotator.prototype.createAnnotation = function() { | |
180 var annotation; | |
181 annotation = {}; | |
182 this.publish('beforeAnnotationCreated', [annotation]); | |
183 return annotation; | |
184 }; | |
185 | |
186 Annotator.prototype.setupAnnotation = function(annotation, fireEvents) { | |
187 var normed, normedRanges, r, sniffed, _i, _len; | |
188 if (fireEvents == null) fireEvents = true; | |
189 annotation.ranges || (annotation.ranges = this.selectedRanges); | |
190 normedRanges = (function() { | |
191 var _i, _len, _ref, _results; | |
192 _ref = annotation.ranges; | |
193 _results = []; | |
194 for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
195 r = _ref[_i]; | |
196 if (!(r != null)) continue; | |
197 sniffed = Range.sniff(r); | |
198 _results.push(sniffed.normalize(this.wrapper[0])); | |
199 } | |
200 return _results; | |
201 }).call(this); | |
202 normedRanges = $.grep(normedRanges, function(range) { | |
203 return range !== null; | |
204 }); | |
205 annotation.quote = []; | |
206 annotation.ranges = []; | |
207 annotation.highlights = []; | |
208 for (_i = 0, _len = normedRanges.length; _i < _len; _i++) { | |
209 normed = normedRanges[_i]; | |
210 annotation.quote.push($.trim(normed.text())); | |
211 annotation.ranges.push(normed.serialize(this.wrapper[0], '.annotator-hl')); | |
212 $.merge(annotation.highlights, this.highlightRange(normed)); | |
213 } | |
214 annotation.quote = annotation.quote.join(' / '); | |
215 $(annotation.highlights).data('annotation', annotation); | |
216 if (fireEvents) this.publish('annotationCreated', [annotation]); | |
217 return annotation; | |
218 }; | |
219 | |
220 Annotator.prototype.updateAnnotation = function(annotation) { | |
221 this.publish('beforeAnnotationUpdated', [annotation]); | |
222 this.publish('annotationUpdated', [annotation]); | |
223 return annotation; | |
224 }; | |
225 | |
226 Annotator.prototype.deleteAnnotation = function(annotation) { | |
227 var h, _i, _len, _ref; | |
228 _ref = annotation.highlights; | |
229 for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
230 h = _ref[_i]; | |
231 $(h).replaceWith(h.childNodes); | |
232 } | |
233 this.publish('annotationDeleted', [annotation]); | |
234 return annotation; | |
235 }; | |
236 | |
237 Annotator.prototype.loadAnnotations = function(annotations) { | |
238 var clone, loader, | |
239 _this = this; | |
240 if (annotations == null) annotations = []; | |
241 loader = function(annList) { | |
242 var n, now, _i, _len; | |
243 if (annList == null) annList = []; | |
244 now = annList.splice(0, 10); | |
245 for (_i = 0, _len = now.length; _i < _len; _i++) { | |
246 n = now[_i]; | |
247 _this.setupAnnotation(n, false); | |
248 } | |
249 if (annList.length > 0) { | |
250 return setTimeout((function() { | |
251 return loader(annList); | |
252 }), 1); | |
253 } else { | |
254 return _this.publish('annotationsLoaded', [clone]); | |
255 } | |
256 }; | |
257 clone = annotations.slice(); | |
258 if (annotations.length) loader(annotations); | |
259 return this; | |
260 }; | |
261 | |
262 Annotator.prototype.dumpAnnotations = function() { | |
263 if (this.plugins['Store']) { | |
264 return this.plugins['Store'].dumpAnnotations(); | |
265 } else { | |
266 return console.warn(_t("Can't dump annotations without Store plugin.")); | |
267 } | |
268 }; | |
269 | |
270 Annotator.prototype.highlightRange = function(normedRange) { | |
271 var node, white, _i, _len, _ref, _results; | |
272 white = /^\s*$/; | |
273 _ref = normedRange.textNodes(); | |
274 _results = []; | |
275 for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
276 node = _ref[_i]; | |
277 if (!white.test(node.nodeValue)) { | |
278 _results.push($(node).wrapAll(this.hl).parent().show()[0]); | |
279 } | |
280 } | |
281 return _results; | |
282 }; | |
283 | |
284 Annotator.prototype.addPlugin = function(name, options) { | |
285 var klass, _base; | |
286 if (this.plugins[name]) { | |
287 console.error(_t("You cannot have more than one instance of any plugin.")); | |
288 } else { | |
289 klass = Annotator.Plugin[name]; | |
290 if (typeof klass === 'function') { | |
291 this.plugins[name] = new klass(this.element[0], options); | |
292 this.plugins[name].annotator = this; | |
293 if (typeof (_base = this.plugins[name]).pluginInit === "function") { | |
294 _base.pluginInit(); | |
295 } | |
296 } else { | |
297 console.error(_t("Could not load ") + name + _t(" plugin. Have you included the appropriate <script> tag?")); | |
298 } | |
299 } | |
300 return this; | |
301 }; | |
302 | |
303 Annotator.prototype.showEditor = function(annotation, location) { | |
304 this.editor.element.css(location); | |
305 this.editor.load(annotation); | |
306 return this; | |
307 }; | |
308 | |
309 Annotator.prototype.onEditorHide = function() { | |
310 this.publish('annotationEditorHidden', [this.editor]); | |
311 return this.ignoreMouseup = false; | |
312 }; | |
313 | |
314 Annotator.prototype.onEditorSubmit = function(annotation) { | |
315 this.publish('annotationEditorSubmit', [this.editor, annotation]); | |
316 if (annotation.ranges === void 0) { | |
317 return this.setupAnnotation(annotation); | |
318 } else { | |
319 return this.updateAnnotation(annotation); | |
320 } | |
321 }; | |
322 | |
323 Annotator.prototype.showViewer = function(annotations, location) { | |
324 this.viewer.element.css(location); | |
325 this.viewer.load(annotations); | |
326 return this.publish('annotationViewerShown', [this.viewer, annotations]); | |
327 }; | |
328 | |
329 Annotator.prototype.startViewerHideTimer = function() { | |
330 if (!this.viewerHideTimer) { | |
331 return this.viewerHideTimer = setTimeout(this.viewer.hide, 250); | |
332 } | |
333 }; | |
334 | |
335 Annotator.prototype.clearViewerHideTimer = function() { | |
336 clearTimeout(this.viewerHideTimer); | |
337 return this.viewerHideTimer = false; | |
338 }; | |
339 | |
340 Annotator.prototype.checkForStartSelection = function(event) { | |
341 if (!(event && this.isAnnotator(event.target))) { | |
342 this.startViewerHideTimer(); | |
343 return this.mouseIsDown = true; | |
344 } | |
345 }; | |
346 | |
347 Annotator.prototype.checkForEndSelection = function(event) { | |
348 var container, range, _i, _len, _ref; | |
349 this.mouseIsDown = false; | |
350 if (this.ignoreMouseup) return; | |
351 this.selectedRanges = this.getSelectedRanges(); | |
352 _ref = this.selectedRanges; | |
353 for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
354 range = _ref[_i]; | |
355 container = range.commonAncestor; | |
356 if (this.isAnnotator(container)) return; | |
357 } | |
358 if (event && this.selectedRanges.length) { | |
359 return this.adder.css(util.mousePosition(event, this.wrapper[0])).show(); | |
360 } else { | |
361 return this.adder.hide(); | |
362 } | |
363 }; | |
364 | |
365 Annotator.prototype.isAnnotator = function(element) { | |
366 return !!$(element).parents().andSelf().filter('[class^=annotator-]').not(this.wrapper).length; | |
367 }; | |
368 | |
369 Annotator.prototype.onHighlightMouseover = function(event) { | |
370 var annotations; | |
371 this.clearViewerHideTimer(); | |
372 if (this.mouseIsDown || this.viewer.isShown()) return false; | |
373 annotations = $(event.target).parents('.annotator-hl').andSelf().map(function() { | |
374 return $(this).data("annotation"); | |
375 }); | |
376 return this.showViewer($.makeArray(annotations), util.mousePosition(event, this.wrapper[0])); | |
377 }; | |
378 | |
379 Annotator.prototype.onAdderMousedown = function(event) { | |
380 if (event != null) event.preventDefault(); | |
381 return this.ignoreMouseup = true; | |
382 }; | |
383 | |
384 Annotator.prototype.onAdderClick = function(event) { | |
385 var position; | |
386 if (event != null) event.preventDefault(); | |
387 position = this.adder.position(); | |
388 this.adder.hide(); | |
389 return this.showEditor(this.createAnnotation(), position); | |
390 }; | |
391 | |
392 Annotator.prototype.onEditAnnotation = function(annotation) { | |
393 var offset; | |
394 offset = this.viewer.element.position(); | |
395 this.viewer.hide(); | |
396 return this.showEditor(annotation, offset); | |
397 }; | |
398 | |
399 Annotator.prototype.onDeleteAnnotation = function(annotation) { | |
400 this.viewer.hide(); | |
401 return this.deleteAnnotation(annotation); | |
402 }; | |
403 | |
404 return Annotator; | |
405 | |
406 })(Delegator); | |
407 | |
408 Annotator.Plugin = (function(_super) { | |
409 | |
410 __extends(Plugin, _super); | |
411 | |
412 function Plugin(element, options) { | |
413 Plugin.__super__.constructor.apply(this, arguments); | |
414 } | |
415 | |
416 Plugin.prototype.pluginInit = function() {}; | |
417 | |
418 return Plugin; | |
419 | |
420 })(Delegator); | |
421 | |
422 Annotator.$ = $; | |
423 | |
424 Annotator.Delegator = Delegator; | |
425 | |
426 Annotator.Range = Range; | |
427 | |
428 Annotator._t = _t; | |
429 | |
430 Annotator.supported = function() { | |
431 return (function() { | |
432 return !!this.getSelection; | |
433 })(); | |
434 }; | |
435 | |
436 Annotator.noConflict = function() { | |
437 util.getGlobal().Annotator = _Annotator; | |
438 return this; | |
439 }; | |
440 | |
441 $.plugin('annotator', Annotator); | |
442 | |
443 this.Annotator = Annotator; |