Created
July 17, 2014 13:45
-
-
Save simong/0f7e3597b000831080ec to your computer and use it in GitHub Desktop.
Margin viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Original at https://github.com/habeanf/annotator/blob/master/src/plugin/marginviewer.coffee | |
| // Generated by CoffeeScript 1.7.1 | |
| (function() { | |
| var clone, | |
| __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; }; | |
| clone = function(obj) { | |
| var key, newInstance; | |
| if ((obj == null) || typeof obj !== 'object') { | |
| return obj; | |
| } | |
| newInstance = new obj.constructor(); | |
| for (key in obj) { | |
| newInstance[key] = clone(obj[key]); | |
| } | |
| return newInstance; | |
| }; | |
| Annotator.Plugin.MarginViewerObjectStore = (function() { | |
| function MarginViewerObjectStore(data, paramFuncObject, idfield, marginobjfield, indexfield) { | |
| var index, obj, _i, _ref; | |
| if (data == null) { | |
| data = []; | |
| } | |
| this.idfield = idfield != null ? idfield : "id"; | |
| this.marginobjfield = marginobjfield != null ? marginobjfield : "_marginObject"; | |
| this.indexfield = indexfield != null ? indexfield : "_marginindex"; | |
| this.funcObject = clone(paramFuncObject); | |
| this.funcObject.sortDataComparison = (function(_this) { | |
| return function(x, y) { | |
| return paramFuncObject.sortComparison(x[0], y[0]); | |
| }; | |
| })(this); | |
| this.funcObject.mapFunc = (function(_this) { | |
| return function(x) { | |
| return [paramFuncObject.sortDataMap(x), x]; | |
| }; | |
| })(this); | |
| this.data = data.map(this.funcObject.mapFunc); | |
| this.data.sort(this.funcObject.sortDataComparison); | |
| this.deletions = 0; | |
| this.insertions = 0; | |
| if (this.data.length > 0) { | |
| for (index = _i = 0, _ref = this.data.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; index = 0 <= _ref ? ++_i : --_i) { | |
| obj = this.data[index][1]; | |
| obj[this.indexfield] = index; | |
| } | |
| } | |
| } | |
| MarginViewerObjectStore.prototype.getMarginObjects = function() { | |
| return this.data.map(function(x) { | |
| return x[1]; | |
| }); | |
| }; | |
| MarginViewerObjectStore.prototype.updateObjectLocation = function(obj) { | |
| var objIndex; | |
| objIndex = this.getObjectLocation(obj); | |
| this.data[objIndex] = [this.funcObject.sortDataMap(obj), obj]; | |
| return obj[this.indexfield] = objIndex; | |
| }; | |
| MarginViewerObjectStore.prototype.objectEquals = function(obj1, obj2) { | |
| if (("id" in obj1) && ("id" in obj2)) { | |
| return obj1.id === obj2.id; | |
| } | |
| if (("id" in obj1) || ("id" in obj2)) { | |
| return false; | |
| } | |
| if ((this.indexfield in obj1) && (this.indexfield in obj2)) { | |
| return obj1[this.indexfield] === obj2[this.indexfield]; | |
| } | |
| return false; | |
| }; | |
| MarginViewerObjectStore.prototype.getObjectLocation = function(obj) { | |
| var currentObject, index, maximumIndex, minimumIndex, supposedLocation, _i; | |
| supposedLocation = obj[this.indexfield]; | |
| if (supposedLocation < this.data.length && this.objectEquals(this.data[supposedLocation][1], obj)) { | |
| return supposedLocation; | |
| } | |
| minimumIndex = Math.max(0, supposedLocation - this.deletions); | |
| maximumIndex = Math.min(this.data.length - 1, supposedLocation + this.insertions); | |
| for (index = _i = minimumIndex; minimumIndex <= maximumIndex ? _i <= maximumIndex : _i >= maximumIndex; index = minimumIndex <= maximumIndex ? ++_i : --_i) { | |
| currentObject = this.data[index][1]; | |
| if (this.objectEquals(currentObject, obj)) { | |
| currentObject[this.indexField] = index; | |
| return index; | |
| } | |
| } | |
| return -1; | |
| }; | |
| MarginViewerObjectStore.prototype.getNewLocationsForObject = function(top, bottom, obj) { | |
| var currentIndex, currentNewBottom, currentNewTop, currentObject, currentObjectBottom, currentObjectGoalBottom, currentObjectGoalLocation, currentObjectSize, currentObjectTop, locationChanges, objectIndex, objectNewTop; | |
| objectIndex = this.getObjectLocation(obj.annotation); | |
| currentIndex = objectIndex - 1; | |
| currentNewTop = top; | |
| currentNewBottom = bottom; | |
| locationChanges = []; | |
| while (currentIndex >= 0) { | |
| currentObject = this.data[currentIndex][1][this.marginobjfield]; | |
| currentObjectSize = $(currentObject).outerHeight(true); | |
| currentObjectTop = $(currentObject).offset().top; | |
| currentObjectBottom = currentObjectTop + currentObjectSize; | |
| currentObjectGoalLocation = this.funcObject.sortDataMap(currentObject.annotation); | |
| currentObjectGoalBottom = currentObjectGoalLocation.top + currentObjectSize; | |
| if (currentObjectBottom > currentNewTop) { | |
| objectNewTop = currentNewTop - currentObjectSize; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewTop = objectNewTop; | |
| } else { | |
| if (currentObjectGoalLocation.top > currentObjectTop) { | |
| if (currentObjectGoalBottom < currentNewTop) { | |
| objectNewTop = currentObjectGoalLocation.top; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewTop = currentObjectGoalLocation.top; | |
| } else { | |
| objectNewTop = currentNewTop - currentObjectSize; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewTop = objectNewTop; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| currentIndex -= 1; | |
| } | |
| currentIndex = objectIndex + 1; | |
| while (currentIndex < this.data.length) { | |
| currentObject = this.data[currentIndex][1][this.marginobjfield]; | |
| currentObjectSize = $(currentObject).outerHeight(true); | |
| currentObjectTop = $(currentObject).offset().top; | |
| currentObjectBottom = currentObjectTop + currentObjectSize; | |
| currentObjectGoalLocation = this.funcObject.sortDataMap(currentObject.annotation); | |
| currentObjectGoalBottom = currentObjectGoalLocation.top + currentObjectSize; | |
| if (currentObjectTop < currentNewBottom) { | |
| objectNewTop = currentNewBottom; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewBottom = objectNewTop + currentObjectSize; | |
| } else { | |
| if (currentObjectGoalLocation.top < currentObjectTop) { | |
| if (currentObjectGoalLocation.top > currentNewBottom) { | |
| objectNewTop = currentObjectGoalLocation.top; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewBottom = objectNewTop + currentObjectSize; | |
| } else { | |
| objectNewTop = currentNewBottom; | |
| locationChanges.push([objectNewTop, currentObject]); | |
| currentNewBottom = objectNewTop + currentObjectSize; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| currentIndex += 1; | |
| } | |
| return locationChanges; | |
| }; | |
| MarginViewerObjectStore.prototype.findIndexForNewObject = function(location) { | |
| var currentIndex, endIndex, startIndex; | |
| startIndex = 0; | |
| endIndex = this.data.length; | |
| while (startIndex < endIndex) { | |
| currentIndex = Math.floor((startIndex + endIndex) / 2); | |
| if (this.funcObject.sortComparison(location, this.data[currentIndex][0]) > 0) { | |
| startIndex = currentIndex + 1; | |
| } else { | |
| endIndex = currentIndex; | |
| } | |
| } | |
| return startIndex; | |
| }; | |
| MarginViewerObjectStore.prototype.addNewObject = function(obj, topval, leftval) { | |
| var location, newObjectLocation; | |
| location = { | |
| top: topval, | |
| left: leftval | |
| }; | |
| newObjectLocation = this.findIndexForNewObject(location); | |
| this.data.splice(newObjectLocation, 0, this.funcObject.mapFunc(obj)); | |
| obj[this.indexfield] = newObjectLocation; | |
| return this.insertions += 1; | |
| }; | |
| MarginViewerObjectStore.prototype.deleteObject = function(object) { | |
| var objectLocation; | |
| objectLocation = this.getObjectLocation(object); | |
| this.data.splice(objectLocation, 1); | |
| return this.deletions += 1; | |
| }; | |
| MarginViewerObjectStore.prototype.getObject = function(object) { | |
| var objectLocation; | |
| objectLocation = this.getObjectLocation(object); | |
| return this.data[objectLocation]; | |
| }; | |
| MarginViewerObjectStore.prototype.getNextObject = function(object) { | |
| var currentLocation; | |
| currentLocation = this.getObjectLocation(object); | |
| if (currentLocation + 1 < this.data.length) { | |
| return this.data[currentLocation + 1]; | |
| } else { | |
| return null; | |
| } | |
| }; | |
| return MarginViewerObjectStore; | |
| })(); | |
| Annotator.Plugin.MarginViewer = (function(_super) { | |
| __extends(MarginViewer, _super); | |
| function MarginViewer(element, options) { | |
| MarginViewer.__super__.constructor.apply(this, arguments); | |
| } | |
| MarginViewer.prototype.events = { | |
| 'annotationsLoaded': 'onAnnotationsLoaded', | |
| 'annotationCreated': 'onAnnotationCreated', | |
| 'annotationDeleted': 'onAnnotationDeleted', | |
| 'annotationUpdated': 'onAnnotationUpdated', | |
| '.annotator-hl click': 'onAnnotationSelected' | |
| }; | |
| MarginViewer.prototype.pluginInit = function() { | |
| var RTL_MULT, sign; | |
| if (!Annotator.supported()) { | |
| return; | |
| } | |
| this.annotator.viewer = { | |
| on: function() {}, | |
| hide: (function(_this) { | |
| return function(annotations) { | |
| return _this.hideHighlightedMargin(annotations); | |
| }; | |
| })(this), | |
| load: (function(_this) { | |
| return function(annotations) { | |
| return _this.highlightMargin(annotations); | |
| }; | |
| })(this), | |
| isShown: function() {}, | |
| addField: function() {}, | |
| element: { | |
| position: function() {}, | |
| css: function() {} | |
| } | |
| }; | |
| this.highlightedObjects = []; | |
| this.currentSelectedAnnotation = null; | |
| RTL_MULT = -1; | |
| sign = function(x) { | |
| if (x === 0) { | |
| return 0; | |
| } else { | |
| return x / Math.abs(x); | |
| } | |
| }; | |
| this.funcObject = { | |
| sortDataMap: function(annotation) { | |
| var dbg; | |
| dbg = { | |
| top: $(annotation.highlights[0]).offset().top, | |
| left: $(annotation.highlights[0]).offset().left | |
| }; | |
| return dbg; | |
| }, | |
| sortComparison: function(left, right) { | |
| return sign(sign(left.top - right.top) * 2 + sign(left.left - right.left) * RTL_MULT); | |
| }, | |
| idFunction: function(annotation) { | |
| return annotation.id; | |
| }, | |
| sizeFunction: function(element) { | |
| return element.outerHeight(true); | |
| } | |
| }; | |
| return this.marginData = new Annotator.Plugin.MarginViewerObjectStore([], this.funcObject); | |
| }; | |
| MarginViewer.prototype.onAnnotationsLoaded = function(annotations) { | |
| var annotation, annotationStart, currentLocation, marginObject, newLocation, _i, _len, _ref, _results; | |
| this.marginData = new Annotator.Plugin.MarginViewerObjectStore(annotations, this.funcObject); | |
| if (annotations.length > 0) { | |
| currentLocation = 0; | |
| _ref = this.marginData.getMarginObjects(); | |
| _results = []; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| annotation = _ref[_i]; | |
| annotationStart = annotation.highlights[0]; | |
| newLocation = $(annotationStart).offset().top; | |
| if (currentLocation > newLocation) { | |
| newLocation = currentLocation; | |
| } | |
| marginObject = this.createMarginObject(annotation, newLocation); | |
| this.marginData.updateObjectLocation(annotation); | |
| _results.push(currentLocation = $(marginObject).offset().top + $(marginObject).outerHeight(true)); | |
| } | |
| return _results; | |
| } | |
| }; | |
| MarginViewer.prototype.onAnnotationCreated = function(annotation) { | |
| var hide, marginObject, newObjectBottom, newObjectTop; | |
| marginObject = this.createMarginObject(annotation, hide = true); | |
| newObjectTop = $(annotation.highlights[0]).offset().top; | |
| newObjectBottom = newObjectTop + $(marginObject).outerHeight(true); | |
| this.marginData.addNewObject(annotation, newObjectTop, $(annotation.highlights[0]).offset().left); | |
| $(marginObject).fadeIn('fast'); | |
| return this.onMarginSelected(marginObject); | |
| }; | |
| MarginViewer.prototype.onAnnotationSelected = function(event) { | |
| var annotations, i, selectIndex, _i, _ref; | |
| event.stopPropagation(); | |
| annotations = $(event.target).parents('.annotator-hl').andSelf().map(function() { | |
| return $(this).data("annotation"); | |
| }); | |
| selectIndex = 0; | |
| if (annotations.length > 1) { | |
| for (i = _i = 0, _ref = annotations.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { | |
| if ($(annotations[i]._marginObject).hasClass("annotator-marginviewer-selected")) { | |
| selectIndex = (i + 1) % annotations.length; | |
| break; | |
| } | |
| } | |
| } | |
| return this.onMarginSelected(annotations[selectIndex]._marginObject); | |
| }; | |
| MarginViewer.prototype.onAnnotationDeleted = function(annotation) { | |
| var nextObject; | |
| nextObject = this.marginData.getNextObject(annotation); | |
| this.marginData.deleteObject(annotation); | |
| if (nextObject !== null) { | |
| return this.onMarginSelected(nextObject[1]._marginObject); | |
| } | |
| }; | |
| MarginViewer.prototype.zeroPad = function(num, count) { | |
| var numZeroPad; | |
| numZeroPad = String(num); | |
| while (numZeroPad.length < count) { | |
| numZeroPad = "0" + numZeroPad; | |
| } | |
| return numZeroPad; | |
| }; | |
| MarginViewer.prototype.formattedDateTime = function(dateobj) { | |
| var dateString, meridiemString, timeString; | |
| meridiemString = dateobj.getHours() < 12 ? "AM" : "PM"; | |
| timeString = dateobj.getHours() + ":" + this.zeroPad(dateobj.getMinutes(), 2) + meridiemString; | |
| dateString = this.zeroPad(dateobj.getDate(), 2) + "." + this.zeroPad(dateobj.getMonth(), 2) + "." + dateobj.getFullYear(); | |
| return timeString + " " + dateString; | |
| }; | |
| MarginViewer.prototype.renderMarginObject = function(annotation) { | |
| var datetime, delel, user; | |
| datetime = annotation.created ? this.formattedDateTime(new Date(annotation.created)) : ''; | |
| user = this.annotator.plugins.AnnotateItPermissions.user.userId; | |
| delel = annotation.user === user || annotation.permissions["delete"].indexOf(user) >= 0 ? '<span class="annotator-marginviewer-delete" style="float: left; direction: ltr;">X</span>' : ''; | |
| return '<div class="annotator-marginviewer-element"><div class="annotator-marginviewer-header"><span class="annotator-marginviewer-user">' + annotation.user + '</span>' + delel + '<span class="annotator-marginviewer-date" style="float: left; direction: ltr;">' + datetime + '</span></div><div class="annotator-marginviewer-text">' + annotation.text + '</div></div>'; | |
| }; | |
| MarginViewer.prototype.createMarginObject = function(annotation, location, hide) { | |
| var marginObject, marginObjects; | |
| if (location == null) { | |
| location = null; | |
| } | |
| if (hide == null) { | |
| hide = false; | |
| } | |
| marginObjects = $(this.renderMarginObject(annotation)).appendTo('.secondary').click((function(_this) { | |
| return function(event) { | |
| return _this.onMarginSelected(event.target); | |
| }; | |
| })(this)).mouseenter((function(_this) { | |
| return function(event) { | |
| return _this.onMarginMouseIn(event.target); | |
| }; | |
| })(this)).mouseleave((function(_this) { | |
| return function(event) { | |
| return _this.onMarginMouseOut(event.target); | |
| }; | |
| })(this)); | |
| marginObjects.children(".annotator-marginviewer-header").children(".annotator-marginviewer-delete").click((function(_this) { | |
| return function(event) { | |
| return _this.onMarginDeleted(event.target); | |
| }; | |
| })(this)); | |
| if (location !== null) { | |
| marginObjects.offset({ | |
| top: location | |
| }); | |
| } | |
| if (hide) { | |
| marginObjects.hide(); | |
| } | |
| marginObject = marginObjects[0]; | |
| annotation._marginObject = marginObject; | |
| marginObject.annotation = annotation; | |
| return marginObject; | |
| }; | |
| MarginViewer.prototype.onAnnotationUpdated = function(annotation) {}; | |
| MarginViewer.prototype.onMarginMouseIn = function(obj) { | |
| return $($(obj).closest(".annotator-marginviewer-element")[0].annotation.highlights).addClass("annotator-hl-uber-temp").removeClass("annotator-hl"); | |
| }; | |
| MarginViewer.prototype.onMarginMouseOut = function(obj) { | |
| return $($(obj).closest(".annotator-marginviewer-element")[0].annotation.highlights).addClass("annotator-hl").removeClass("annotator-hl-uber-temp"); | |
| }; | |
| MarginViewer.prototype.onMarginSelected = function(obj) { | |
| var annotation, currentMarginObject, currentObjectNewTop, horizontalSlide, marginObject, newBottom, newLocationsByObject, newTop; | |
| marginObject = $(obj).closest(".annotator-marginviewer-element")[0]; | |
| annotation = marginObject.annotation; | |
| horizontalSlide = []; | |
| newTop = $(annotation.highlights[0]).offset().top; | |
| newBottom = $(marginObject).outerHeight(true) + newTop; | |
| newLocationsByObject = this.marginData.getNewLocationsForObject(newTop, newBottom, marginObject); | |
| if (this.currentSelectedAnnotation !== null) { | |
| if (annotation.id === this.currentSelectedAnnotation.id) { | |
| return; | |
| } else { | |
| currentMarginObject = this.currentSelectedAnnotation._marginObject; | |
| if (currentMarginObject !== null) { | |
| $(this.currentSelectedAnnotation.highlights).removeClass("annotator-hl-uber").removeClass("annotator-hl-uber-temp").addClass("annotator-hl"); | |
| currentObjectNewTop = $(currentMarginObject).offset().top; | |
| newLocationsByObject = $.grep(newLocationsByObject, function(value) { | |
| if (value[1].annotation.id === currentMarginObject.annotation.id) { | |
| currentObjectNewTop = value[0]; | |
| return false; | |
| } else { | |
| return true; | |
| } | |
| }); | |
| horizontalSlide.push([currentObjectNewTop, "+=20px", currentMarginObject]); | |
| $(currentMarginObject).removeClass("annotator-marginviewer-selected"); | |
| } | |
| } | |
| } | |
| $(marginObject).addClass("annotator-marginviewer-selected"); | |
| horizontalSlide.push([newTop, "-=20px", marginObject]); | |
| this.moveObjectsToNewLocation(newLocationsByObject, horizontalSlide); | |
| $(annotation.highlights).addClass("annotator-hl-uber").removeClass("annotator-hl"); | |
| return this.currentSelectedAnnotation = annotation; | |
| }; | |
| MarginViewer.prototype.onMarginDeleted = function(obj) { | |
| var annotation, marginObject; | |
| marginObject = $(obj).closest(".annotator-marginviewer-element")[0]; | |
| annotation = marginObject.annotation; | |
| $(marginObject).remove(); | |
| annotation._marginObject = null; | |
| return this.annotator.deleteAnnotation(annotation); | |
| }; | |
| MarginViewer.prototype.moveObjectsToNewLocation = function(newLocations, horizontalSlideObjects) { | |
| var currentObject, horizontalSlide, newLocationStructure, newMarginRight, newTop, _i, _j, _len, _len1, _results; | |
| if (horizontalSlideObjects == null) { | |
| horizontalSlideObjects = []; | |
| } | |
| for (_i = 0, _len = newLocations.length; _i < _len; _i++) { | |
| newLocationStructure = newLocations[_i]; | |
| newTop = newLocationStructure[0]; | |
| currentObject = newLocationStructure[1]; | |
| $(currentObject).animate({ | |
| top: "+=" + (newTop - $(currentObject).offset().top) | |
| }, 'fast', 'swing'); | |
| this.marginData.updateObjectLocation(currentObject.annotation); | |
| } | |
| _results = []; | |
| for (_j = 0, _len1 = horizontalSlideObjects.length; _j < _len1; _j++) { | |
| horizontalSlide = horizontalSlideObjects[_j]; | |
| newTop = horizontalSlide[0]; | |
| newMarginRight = horizontalSlide[1]; | |
| currentObject = horizontalSlide[2]; | |
| $(currentObject).animate({ | |
| top: "+=" + (newTop - $(currentObject).offset().top), | |
| 'margin-right': newMarginRight | |
| }, 'fast', 'swing'); | |
| _results.push(this.marginData.updateObjectLocation(currentObject.annotation)); | |
| } | |
| return _results; | |
| }; | |
| MarginViewer.prototype.highlightMargin = function(annotations) { | |
| var existingHighlight, found, marginObjects, newHighlight, oldObjects, _i, _j, _len, _len1, _ref; | |
| if (this.highlightedObjects.length > 0) { | |
| oldObjects = []; | |
| _ref = this.highlightedObjects; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| existingHighlight = _ref[_i]; | |
| found = false; | |
| for (_j = 0, _len1 = annotations.length; _j < _len1; _j++) { | |
| newHighlight = annotations[_j]; | |
| if (newHighlight.id === existingHighlight.id) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| oldObjects.push(existingHighlight); | |
| } | |
| } | |
| this.hideHighlightedMargin(oldObjects); | |
| } | |
| this.highlightedObjects = annotations; | |
| marginObjects = jQuery.map(annotations, function(val, i) { | |
| return val._marginObject; | |
| }); | |
| return $(marginObjects).addClass("annotator-marginviewer-highlighted"); | |
| }; | |
| MarginViewer.prototype.hideHighlightedMargin = function(annotations) { | |
| var marginObjects; | |
| this.highlightedObjects = []; | |
| marginObjects = jQuery.map(annotations, function(val, i) { | |
| return val._marginObject; | |
| }); | |
| return $(marginObjects).removeClass("annotator-marginviewer-highlighted"); | |
| }; | |
| return MarginViewer; | |
| })(Annotator.Plugin); | |
| }).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment