View
@@ -8,7 +8,8 @@ require('jquery-scrollintoview')
var g = Annotator.Util.getGlobal();
if (g.wgxpath) g.wgxpath.install();
require('node-iterator-shim').install();
var nodeIteratorShim = require('node-iterator-shim')
nodeIteratorShim();
// Applications
Annotator.Guest = require('./guest')
View
@@ -5,6 +5,9 @@ $ = Annotator.$
# Get the bounding client rectangle of a collection in viewport coordinates.
# Unfortunately, Chrome has issues[1] with Range.getBoundingClient rect or we
# could just use that.
# [1] https://code.google.com/p/chromium/issues/detail?id=324437
getBoundingClientRect = (collection) ->
# Reduce the client rectangles of the highlights to a bounding box
rects = collection.map((n) -> n.getBoundingClientRect())
@@ -16,11 +19,14 @@ getBoundingClientRect = (collection) ->
# Scroll to the next closest anchor off screen in the given direction.
scrollToClosest = (objs, direction) ->
scrollToClosest = (anchors, direction) ->
dir = if direction is "up" then +1 else -1
{next} = objs.reduce (acc, obj) ->
{next} = anchors.reduce (acc, anchor) ->
unless anchor.highlights?.length
return acc
{start, next} = acc
rect = getBoundingClientRect(obj.highlights)
rect = getBoundingClientRect(anchor.highlights)
# Ignore if it's not in the right direction.
if (dir is 1 and rect.top >= 0)
@@ -31,10 +37,10 @@ scrollToClosest = (objs, direction) ->
# Select the closest to carry forward
if not next?
start: rect.top
next: obj
next: anchor
else if start * dir < rect.top * dir
start: rect.top
next: obj
next: anchor
else
acc
, {}
@@ -81,23 +87,25 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
$(element).append @element
pluginInit: ->
_update = => this.update()
events = [
'annotationCreated', 'annotationUpdated', 'annotationDeleted',
'annotationsLoaded'
]
for event in events
@annotator.subscribe event, this._scheduleUpdate
@annotator.subscribe event, _update
$(window).on 'resize scroll', this._scheduleUpdate
$(window).on 'resize scroll', _update
for scrollable in @options.scrollables ? []
$(scrollable).on 'resize scroll', this._scheduleUpdate
$(scrollable).on 'resize scroll', _update
destroy: ->
$(window).off 'resize scroll', this._scheduleUpdate
$(window).off 'resize scroll', _update
for scrollable in @options.scrollables ? []
$(scrollable).off 'resize scroll', this._scheduleUpdate
$(scrollable).off 'resize scroll', _update
_collate: (a, b) ->
for i in [0..a.length-1]
@@ -109,38 +117,32 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
# Update sometime soon
update: ->
this._scheduleUpdate()
_scheduleUpdate: =>
return if @_updatePending?
@_updatePending = raf =>
delete @_updatePending
@_update()
_update: =>
_update: ->
# Keep track of buckets of annotations above and below the viewport
above = []
below = []
# Construct indicator points
points = @annotator.anchored.reduce (points, obj, i) =>
hl = obj.highlights
if hl.length is 0
points = @annotator.anchors.reduce (points, anchor, i) =>
unless anchor.highlights?.length
return points
rect = getBoundingClientRect(hl)
rect = getBoundingClientRect(anchor.highlights)
x = rect.top
h = rect.bottom - rect.top
if x < 0
if obj not in above then above.push obj
if anchor not in above then above.push anchor
else if x + h > window.innerHeight
if obj not in below then below.push obj
if anchor not in below then below.push anchor
else
points.push [x, 1, obj]
points.push [x + h, -1, obj]
points.push [x, 1, anchor]
points.push [x + h, -1, anchor]
points
, []
@@ -166,23 +168,23 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
.sort(this._collate)
.reduce ({buckets, index, carry}, [x, d, a], i, points) =>
if d > 0 # Add annotation
if (j = carry.objs.indexOf a) < 0
carry.objs.unshift a
if (j = carry.anchors.indexOf a) < 0
carry.anchors.unshift a
carry.counts.unshift 1
else
carry.counts[j]++
else # Remove annotation
j = carry.objs.indexOf a # XXX: assert(i >= 0)
j = carry.anchors.indexOf a # XXX: assert(i >= 0)
if --carry.counts[j] is 0
carry.objs.splice j, 1
carry.anchors.splice j, 1
carry.counts.splice j, 1
if (
(index.length is 0 or i is points.length - 1) or # First or last?
carry.objs.length is 0 or # A zero marker?
carry.anchors.length is 0 or # A zero marker?
x - index[index.length-1] > @options.gapSize # A large gap?
) # Mark a new bucket.
buckets.push carry.objs.slice()
buckets.push carry.anchors.slice()
index.push x
else
# Merge the previous bucket, making sure its predecessor contains
@@ -195,15 +197,15 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
else
last = buckets[buckets.length-1]
toMerge = []
last.push a0 for a0 in carry.objs when a0 not in last
last.push a0 for a0 in carry.anchors when a0 not in last
last.push a0 for a0 in toMerge when a0 not in last
{buckets, index, carry}
,
buckets: []
index: []
carry:
objs: []
anchors: []
counts: []
latest: 0
@@ -242,19 +244,19 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
div.addClass('annotator-bucket-indicator')
# Creates highlights corresponding bucket when mouse is hovered
# Focus corresponding highlights bucket when mouse is hovered
# TODO: This should use event delegation on the container.
.on 'mousemove', (event) =>
bucket = @tabs.index(event.currentTarget)
for obj in @annotator.anchored
toggle = obj in @buckets[bucket]
$(obj.highlights).toggleClass('annotator-hl-focused', toggle)
for anchor in @annotator.anchors
toggle = anchor in @buckets[bucket]
$(anchor.highlights).toggleClass('annotator-hl-focused', toggle)
# Gets rid of them after
.on 'mouseout', (event) =>
bucket = @tabs.index(event.currentTarget)
for obj in @buckets[bucket]
$(obj.highlights).removeClass('annotator-hl-focused')
for anchor in @buckets[bucket]
$(anchor.highlights).removeClass('annotator-hl-focused')
# Does one of a few things when a tab is clicked depending on type
.on 'click', (event) =>
@@ -268,7 +270,7 @@ class Annotator.Plugin.BucketBar extends Annotator.Plugin
else if (@isLower bucket)
scrollToClosest(@buckets[bucket], 'down')
else
annotations = (obj.annotation for obj in @buckets[bucket])
annotations = (anchor.annotation for anchor in @buckets[bucket])
annotator.selectAnnotations annotations,
(event.ctrlKey or event.metaKey),
View
@@ -1,16 +1,17 @@
raf = require('raf')
Promise = require('es6-promise').Promise
Promise = global.Promise ? require('es6-promise').Promise
Annotator = require('annotator')
highlighter = require('../highlighter')
class PDF extends Annotator.Plugin
documentPromise: null
documentLoaded: null
observer: null
pdfViewer: null
updatePromise: null
updateTimeout: null
pluginInit: ->
viewer = PDFViewerApplication.pdfViewer.viewer
viewer.classList.add('has-transparent-text-layer')
@pdfViewer = PDFViewerApplication.pdfViewer
@pdfViewer.viewer.classList.add('has-transparent-text-layer')
if PDFViewerApplication.loading
@documentLoaded = new Promise (resolve) ->
@@ -21,10 +22,17 @@ class PDF extends Annotator.Plugin
else
@documentLoaded = Promise.resolve()
document.addEventListener('pagerendered', @onpagerendered)
@observer = new MutationObserver((mutations) => this.update())
@observer.observe(@pdfViewer.viewer, {
attributes: true
attributeFilter: ['data-loaded']
childList: true
subtree: true
})
destroy: ->
document.removeEventListener('pagerendered', @onpagerendered)
@pdfViewer.viewer.classList.remove('has-transparent-text-layer')
@observer.disconnect()
uri: ->
@documentLoaded.then ->
@@ -50,42 +58,51 @@ class PDF extends Annotator.Plugin
return {title, link}
onpagerendered: (event) =>
annotator = {anchored, unanchored} = @annotator
page = PDFViewerApplication.pdfViewer.pages[event.detail.pageNumber - 1]
waitForTextLayer = ->
unless (page.textLayer.renderingDone)
return new Promise(raf).then(waitForTextLayer)
reanchor = ->
unanchored = unanchored.splice(0, unanchored.length)
placeholder = page.el.getElementsByClassName('annotator-placeholder')[0]
if placeholder?
unchanged = []
for info in anchored
attempt = false
for hl in info.highlights
if placeholder.contains(hl)
attempt = true
break
if attempt
highlighter.removeHighlights(info.highlights)
delete info.highlights
unanchored.push(info)
else
unchanged.push(info)
anchored.splice(0, anchored.length, unchanged...)
page.el.removeChild(placeholder)
for obj in unanchored
annotator.setupAnnotation(obj.annotation)
waitForTextLayer().then(reanchor)
update: ->
self = this
{annotator, pdfViewer} = this
_throttle = ->
if self.updateTimeout?
clearTimeout(self.updateTimeout)
self.updateTimeout = setTimeout(_update, 200)
_update = ->
anchors = []
annotations = []
for page in pdfViewer.pages when page.textLayer?.renderingDone
div = page.div ? page.el
placeholder = div.getElementsByClassName('annotator-placeholder')[0]
switch page.renderingState
when RenderingStates.INITIAL
page.textLayer = null
when RenderingStates.FINISHED
if placeholder?
placeholder.parentNode.removeChild(placeholder)
for anchor in annotator.anchors when anchor.annotation not in annotations
unless anchor.range? and anchor.highlights?
delete anchor.range
annotations.push(anchor.annotation)
continue
for hl in anchor.highlights
if not document.body.contains(hl)
delete anchor.range
annotations.push(anchor.annotation)
break
for annotation in annotations
annotator.setupAnnotation(annotation)
anchors.push(annotation.anchors)
self.updateTimeout = null
self.updatePromise = Promise.all(anchors)
@annotator.plugins.BucketBar?.update()
Promise.resolve(@updatePromise).then(_throttle)
Annotator.Plugin.PDF = PDF
View
@@ -1,4 +1,4 @@
Promise = require('es6-promise').Promise
Promise = global.Promise ? require('es6-promise').Promise
Annotator = require('annotator')
Guest = require('../guest')
View
@@ -1,4 +1,4 @@
Promise = require('es6-promise').Promise
Promise = global.Promise ? require('es6-promise').Promise
{module, inject} = require('angular-mock')
assert = chai.assert
View

This file was deleted.

Oops, something went wrong.
View

This file was deleted.

Oops, something went wrong.
View

This file was deleted.

Oops, something went wrong.
View
@@ -135,7 +135,10 @@ $base-font-size: 14px;
}
.annotator-placeholder {
opacity: 0;
position: absolute;
top: 50%;
z-index: -1;
}
View
@@ -9,11 +9,12 @@
"clean-css": "2.2.2",
"coffee-script": "1.7.1",
"coffeeify": "^1.0.0",
"diff-match-patch": "^1.0.0",
"dom-seek": "^1.0.0-beta.1",
"es6-promise": "^2.1.0",
"extend": "^2.0.0",
"hammerjs": "^2.0.4",
"node-iterator-shim": "^1.0.0-beta.1",
"node-iterator-shim": "^1.0.0-beta.3",
"node-uuid": "^1.4.3",
"raf": "^2.0.4",
"strip-bomify": "^0.1.0",
@@ -56,14 +57,12 @@
"annotator-auth": "./h/static/scripts/vendor/annotator.auth.js",
"angular": "./h/static/scripts/vendor/angular.js",
"angular-mock": "./h/static/scripts/vendor/angular-mocks.js",
"diff-match-patch": "./h/static/scripts/vendor/diff_match_patch_uncompressed.js",
"dom-text-matcher": "./h/static/scripts/vendor/dom_text_matcher.js",
"es6-promise": "./node_modules/es6-promise/dist/es6-promise.js",
"hammerjs": "./node_modules/hammerjs/hammer.js",
"jquery": "./h/static/scripts/vendor/jquery.js",
"jquery-scrollintoview": "./h/static/scripts/vendor/jquery.scrollintoview.js",
"jschannel": "./h/static/scripts/vendor/jschannel.js",
"text-match-engines": "./h/static/scripts/vendor/text_match_engines.js"
"node-iterator-shim": "./node_modules/node-iterator-shim/dist/node-iterator-shim.js"
},
"browserify-shim": {
"annotator": {
@@ -85,8 +84,6 @@
]
},
"angular-mock": "global:angular.mock",
"diff-match-patch": "diff_match_patch",
"dom-text-matcher": "DomTextMatcher",
"es6-promise": "ES6Promise",
"hammerjs": "Hammer",
"jquery": "$",
@@ -96,6 +93,6 @@
]
},
"jschannel": "Channel",
"text-match-engines": "TextMatchEngines"
"node-iterator-shim": "nodeIteratorShim"
}
}