Commit fe803b56 authored by Sean Hammond's avatar Sean Hammond

Merge pull request #2549 from hypothesis/2484-fix-page-notes

Fix comments (AKA "page notes") with search_normalized
parents ab7ceae1 f1c3d2fd
...@@ -197,11 +197,19 @@ module.exports = class Guest extends Annotator ...@@ -197,11 +197,19 @@ module.exports = class Guest extends Annotator
sync = (anchors) -> sync = (anchors) ->
# Store the results of anchoring. # Store the results of anchoring.
annotation.$orphan = anchors.length > 0
# An annotation is considered to be an orphan if it has at least one
# target with selectors, and all targets with selectors failed to anchor
# (i.e. we didn't find it in the page and thus it has no range).
hasAnchorableTargets = false
hasAnchoredTargets = false
for anchor in anchors for anchor in anchors
if anchor.target.selector?
hasAnchorableTargets = true
if anchor.range? if anchor.range?
annotation.$orphan = false hasAnchoredTargets = true
break break
annotation.$orphan = hasAnchorableTargets and not hasAnchoredTargets
# Add the anchors for this annotation to instance storage. # Add the anchors for this annotation to instance storage.
self.anchors = self.anchors.concat(anchors) self.anchors = self.anchors.concat(anchors)
...@@ -269,6 +277,7 @@ module.exports = class Guest extends Annotator ...@@ -269,6 +277,7 @@ module.exports = class Guest extends Annotator
cache: self.anchoringCache cache: self.anchoringCache
ignoreSelector: '[class^="annotator-"]' ignoreSelector: '[class^="annotator-"]'
} }
# Returns an array of selectors for the passed range.
return self.anchoring.describe(root, range, options) return self.anchoring.describe(root, range, options)
setDocumentInfo = (info) -> setDocumentInfo = (info) ->
...@@ -276,6 +285,8 @@ module.exports = class Guest extends Annotator ...@@ -276,6 +285,8 @@ module.exports = class Guest extends Annotator
annotation.uri = info.uri annotation.uri = info.uri
setTargets = ([info, selectors]) -> setTargets = ([info, selectors]) ->
# `selectors` is an array of arrays: each item is an array of selectors
# identifying a distinct target.
source = info.uri source = info.uri
annotation.target = ({source, selector} for selector in selectors) annotation.target = ({source, selector} for selector in selectors)
...@@ -293,6 +304,22 @@ module.exports = class Guest extends Annotator ...@@ -293,6 +304,22 @@ module.exports = class Guest extends Annotator
createHighlight: -> createHighlight: ->
return this.createAnnotation({$highlight: true}) return this.createAnnotation({$highlight: true})
# Create a blank comment (AKA "page note")
createComment: () ->
annotation = {}
self = this
prepare = (info) ->
annotation.document = info.metadata
annotation.uri = info.uri
annotation.target = [{source: info.uri}]
this.getDocumentInfo()
.then(prepare)
.then(-> self.publish('beforeAnnotationCreated', [annotation]))
annotation
showAnnotations: (annotations) -> showAnnotations: (annotations) ->
tags = (a.$$tag for a in annotations) tags = (a.$$tag for a in annotations)
@crossframe?.call('showAnnotations', tags) @crossframe?.call('showAnnotations', tags)
......
...@@ -57,7 +57,7 @@ module.exports = class Toolbar extends Annotator.Plugin ...@@ -57,7 +57,7 @@ module.exports = class Toolbar extends Annotator.Plugin
"click": (event) => "click": (event) =>
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@annotator.createAnnotation() @annotator.createComment()
@annotator.show() @annotator.show()
] ]
@buttons = $(makeButton(item) for item in items) @buttons = $(makeButton(item) for item in items)
......
...@@ -19,6 +19,10 @@ Guest = proxyquire('../guest', { ...@@ -19,6 +19,10 @@ Guest = proxyquire('../guest', {
'scroll-into-view': scrollIntoView, 'scroll-into-view': scrollIntoView,
}) })
# A little helper which returns a promise that resolves after a timeout
timeoutPromise = (millis = 0) ->
new Promise((resolve) -> setTimeout(resolve, millis))
describe 'Guest', -> describe 'Guest', ->
sandbox = sinon.sandbox.create() sandbox = sinon.sandbox.create()
CrossFrame = null CrossFrame = null
...@@ -215,18 +219,20 @@ describe 'Guest', -> ...@@ -215,18 +219,20 @@ describe 'Guest', ->
assert.isTrue(event.isPropagationStopped()) assert.isTrue(event.isPropagationStopped())
describe 'createAnnotation()', -> describe 'createAnnotation()', ->
it 'adds metadata to the annotation object', (done) -> it 'adds metadata to the annotation object', ->
guest = createGuest() guest = createGuest()
sinon.stub(guest, 'getDocumentInfo').returns(Promise.resolve({ sinon.stub(guest, 'getDocumentInfo').returns(Promise.resolve({
metadata: {title: 'hello'} metadata: {title: 'hello'}
uri: 'http://example.com/' uri: 'http://example.com/'
})) }))
annotation = {} annotation = {}
guest.createAnnotation(annotation) guest.createAnnotation(annotation)
setTimeout ->
timeoutPromise()
.then ->
assert.equal(annotation.uri, 'http://example.com/') assert.equal(annotation.uri, 'http://example.com/')
assert.deepEqual(annotation.document, {title: 'hello'}) assert.deepEqual(annotation.document, {title: 'hello'})
done()
it 'treats an argument as the annotation object', -> it 'treats an argument as the annotation object', ->
guest = createGuest() guest = createGuest()
...@@ -234,6 +240,46 @@ describe 'Guest', -> ...@@ -234,6 +240,46 @@ describe 'Guest', ->
annotation = guest.createAnnotation(annotation) annotation = guest.createAnnotation(annotation)
assert.equal(annotation.foo, 'bar') assert.equal(annotation.foo, 'bar')
it 'triggers a beforeAnnotationCreated event', (done) ->
guest = createGuest()
guest.subscribe('beforeAnnotationCreated', -> done())
guest.createAnnotation()
describe 'createComment()', ->
it 'adds metadata to the annotation object', ->
guest = createGuest()
sinon.stub(guest, 'getDocumentInfo').returns(Promise.resolve({
metadata: {title: 'hello'}
uri: 'http://example.com/'
}))
annotation = guest.createComment()
timeoutPromise()
.then ->
assert.equal(annotation.uri, 'http://example.com/')
assert.deepEqual(annotation.document, {title: 'hello'})
it 'adds a single target with a source property', ->
guest = createGuest()
sinon.stub(guest, 'getDocumentInfo').returns(Promise.resolve({
metadata: {title: 'hello'}
uri: 'http://example.com/'
}))
annotation = guest.createComment()
timeoutPromise()
.then ->
assert.deepEqual(annotation.target, [{source: 'http://example.com/'}])
it 'triggers a beforeAnnotationCreated event', (done) ->
guest = createGuest()
guest.subscribe('beforeAnnotationCreated', -> done())
guest.createComment()
describe 'anchor()', -> describe 'anchor()', ->
el = null el = null
range = null range = null
...@@ -249,28 +295,60 @@ describe 'Guest', -> ...@@ -249,28 +295,60 @@ describe 'Guest', ->
afterEach -> afterEach ->
document.body.removeChild(el) document.body.removeChild(el)
it "doesn't declare annotations without targets as orphans", (done) -> it "doesn't mark an annotation lacking targets as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = target: [] annotation = target: []
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
assert.isFalse(annotation.$orphan) assert.isFalse(annotation.$orphan)
.then(done, done)
it "doesn't declare annotations with a working target orphans", (done) -> it "doesn't mark an annotation with a selectorless target as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = target: [{selector: "test"}] annotation = {target: [{source: 'wibble'}]}
guest.anchor(annotation).then ->
assert.isFalse(annotation.$orphan)
it "doesn't mark an annotation with only selectorless targets as an orphan", ->
guest = createGuest()
annotation = {target: [{source: 'foo'}, {source: 'bar'}]}
guest.anchor(annotation).then ->
assert.isFalse(annotation.$orphan)
it "doesn't mark an annotation in which the target anchors as an orphan", ->
guest = createGuest()
annotation = {target: [{selector: []}]}
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range)) sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
assert.isFalse(annotation.$orphan) assert.isFalse(annotation.$orphan)
.then(done, done)
it "declares annotations with broken targets as orphans", (done) -> it "doesn't mark an annotation in which at least one target anchors as an orphan", ->
guest = createGuest()
annotation = {target: [{selector: []}, {selector: []}]}
sandbox.stub(anchoring, 'anchor')
.onFirstCall().returns(Promise.reject())
.onSecondCall().returns(Promise.resolve(range))
guest.anchor(annotation).then ->
assert.isFalse(annotation.$orphan)
it "marks an annotation in which the target fails to anchor as an orphan", ->
guest = createGuest()
annotation = target: [{selector: []}]
sandbox.stub(anchoring, 'anchor').returns(Promise.reject())
guest.anchor(annotation).then ->
assert.isTrue(annotation.$orphan)
it "marks an annotation in which all (suitable) targets fail to anchor as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = target: [{selector: 'broken selector'}] annotation = target: [{selector: []}, {selector: []}]
sandbox.stub(anchoring, 'anchor').returns(Promise.reject()) sandbox.stub(anchoring, 'anchor').returns(Promise.reject())
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
assert.isTrue(annotation.$orphan) assert.isTrue(annotation.$orphan)
.then(done, done)
it 'updates the cross frame and bucket bar plugins', (done) -> it 'updates the cross frame and bucket bar plugins', (done) ->
guest = createGuest() guest = createGuest()
......
...@@ -83,14 +83,6 @@ AnnotationController = [ ...@@ -83,14 +83,6 @@ AnnotationController = [
this.tagsAutoComplete = (query) -> this.tagsAutoComplete = (query) ->
$q.when(tags.filter(query)) $q.when(tags.filter(query))
###*
# @ngdoc method
# @name annotation.AnnotationController#isComment.
# @returns {boolean} True if the annotation is a comment.
###
this.isComment = ->
not (model.target?.length or model.references?.length)
###* ###*
# @ngdoc method # @ngdoc method
# @name annotation.AnnotationController#isHighlight. # @name annotation.AnnotationController#isHighlight.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment