Commit 7faa6cec authored by csillag's avatar csillag

Update Annotator to e2bea2d6 (typed-packaging branch)

parent 889c7533
# Abstract anchor class.
class Anchor
constructor: (@annotator, @annotation, @target
@startPage, @endPage,
@quote, @diffHTML, @diffCaseOnly) ->
unless @annotator? then throw "annotator is required!"
unless @annotation? then throw "annotation is required!"
unless @target? then throw "target is required!"
unless @startPage? then "startPage is required!"
unless @endPage? then throw "endPage is required!"
unless @quote? then throw "quote is required!"
@highlight = {}
# Return highlights for the given page
_createHighlight: (page) ->
throw "Function not implemented"
# Create the missing highlights for this anchor
realize: () =>
return if @fullyRealized # If we have everything, go home
# Collect the pages that are already rendered
renderedPages = [@startPage .. @endPage].filter (index) =>
@annotator.domMapper.isPageMapped index
# Collect the pages that are already rendered, but not yet anchored
pagesTodo = renderedPages.filter (index) => not @highlight[index]?
return unless pagesTodo.length # Return if nothing to do
# Create the new highlights
created = for page in pagesTodo
@highlight[page] = @_createHighlight page
# Check if everything is rendered now
@fullyRealized = renderedPages.length is @endPage - @startPage + 1
# Announce the creation of the highlights
@annotator.publish 'highlightsCreated', created
# Remove the highlights for the given set of pages
virtualize: (pageIndex) =>
highlight = @highlight[pageIndex]
return unless highlight? # No highlight for this page
highlight.removeFromDocument()
delete @highlight[pageIndex]
# Mark this anchor as not fully rendered
@fullyRealized = false
# Announce the removal of the highlight
@annotator.publish 'highlightRemoved', highlight
# Virtualize and remove an anchor from all involved pages
remove: ->
# Go over all the pages
for index in [@startPage .. @endPage]
@virtualize index
anchors = @annotator.anchors[index]
# Remove the anchor from the list
i = anchors.indexOf this
anchors[i..i] = []
# Kill the list if it's empty
delete @annotator.anchors[index] unless anchors.length
# This is called when the underlying Annotator has been udpated
annotationUpdated: ->
# Notify the highlights
for index in [@startPage .. @endPage]
@highlight[index]?.annotationUpdated()
...@@ -41,16 +41,6 @@ util = ...@@ -41,16 +41,6 @@ util =
# Store a reference to the current Annotator object. # Store a reference to the current Annotator object.
_Annotator = this.Annotator _Annotator = this.Annotator
# Fake two-phase / pagination support, used for HTML documents
class DummyDocumentAccess
@applicable: -> true
getPageIndex: -> 0
getPageCount: -> 1
getPageIndexForPos: -> 0
isPageMapped: -> true
scan: ->
class Annotator extends Delegator class Annotator extends Delegator
# Events to be bound on Annotator#element. # Events to be bound on Annotator#element.
events: events:
...@@ -104,75 +94,20 @@ class Annotator extends Delegator ...@@ -104,75 +94,20 @@ class Annotator extends Delegator
constructor: (element, options) -> constructor: (element, options) ->
super super
@plugins = {} @plugins = {}
@selectorCreators = []
@anchoringStrategies = []
# Return early if the annotator is not supported. # Return early if the annotator is not supported.
return this unless Annotator.supported() return this unless Annotator.supported()
this._setupDocumentEvents() unless @options.readOnly this._setupDocumentEvents() unless @options.readOnly
this._setupAnchorEvents()
this._setupWrapper() this._setupWrapper()
this._setupDocumentAccessStrategies()
this._setupViewer()._setupEditor() this._setupViewer()._setupEditor()
this._setupDynamicStyle() this._setupDynamicStyle()
# Perform initial DOM scan, unless told not to. # Perform initial DOM scan, unless told not to.
this._scan() unless @options.noScan #this._scan() unless @options.noScan
# Create adder # Create adder
this.adder = $(this.html.adder).appendTo(@wrapper).hide() this.adder = $(this.html.adder).appendTo(@wrapper).hide()
# Initializes the available document access strategies
_setupDocumentAccessStrategies: ->
@documentAccessStrategies = [
# Default dummy strategy for simple HTML documents.
# The generic fallback.
name: "Dummy"
mapper: DummyDocumentAccess
]
this
# Initializes the components used for analyzing the document
_chooseAccessPolicy: ->
if @domMapper? then return
# Go over the available strategies
for s in @documentAccessStrategies
# Can we use this strategy for this document?
if s.mapper.applicable()
@documentAccessStrategy = s
@domMapper = new s.mapper()
@anchors = {}
addEventListener "docPageMapped", (evt) =>
@_realizePage evt.pageIndex
addEventListener "docPageUnmapped", (evt) =>
@_virtualizePage evt.pageIndex
s.init?()
return this
# Remove the current document access policy
_removeCurrentAccessPolicy: ->
return unless @domMapper?
list = @documentAccessStrategies
index = list.indexOf @documentAccessStrategy
list.splice(index, 1) unless index is -1
@domMapper.destroy?()
delete @domMapper
# Perform a scan of the DOM. Required for finding anchors.
_scan: ->
# Ensure that we have a document access strategy
this._chooseAccessPolicy()
try
@pendingScan = @domMapper.scan()
catch
@_removeCurrentAccessPolicy()
@_scan()
return
# Wraps the children of @element in a @wrapper div. NOTE: This method will also # Wraps the children of @element in a @wrapper div. NOTE: This method will also
# remove any script elements inside @element to prevent them re-executing. # remove any script elements inside @element to prevent them re-executing.
# #
...@@ -243,14 +178,6 @@ class Annotator extends Delegator ...@@ -243,14 +178,6 @@ class Annotator extends Delegator
}) })
this this
# Sets up handlers to anchor-related events
_setupAnchorEvents: ->
# When annotations are updated
@on 'annotationUpdated', (annotation) =>
# Notify the anchors
for anchor in annotation.anchors or []
anchor.annotationUpdated()
# Sets up any dynamically calculated CSS for the Annotator. # Sets up any dynamically calculated CSS for the Annotator.
# #
# Returns itself for chaining. # Returns itself for chaining.
...@@ -336,37 +263,6 @@ class Annotator extends Delegator ...@@ -336,37 +263,6 @@ class Annotator extends Delegator
this.publish('beforeAnnotationCreated', [annotation]) this.publish('beforeAnnotationCreated', [annotation])
annotation annotation
# Do some normalization to get a "canonical" form of a string.
# Used to even out some browser differences.
normalizeString: (string) -> string.replace /\s{2,}/g, " "
# Find the given type of selector from an array of selectors, if it exists.
# If it does not exist, null is returned.
findSelector: (selectors, type) ->
for selector in selectors
if selector.type is type then return selector
null
# Try to find the right anchoring point for a given target
#
# Returns an Anchor object if succeeded, null otherwise
createAnchor: (annotation, target) ->
unless target?
throw new Error "Trying to find anchor for null target!"
error = null
anchor = null
for s in @anchoringStrategies
try
a = s.code.call this, annotation, target
if a
return result: a
catch error
console.log "Strategy '" + s.name + "' has thrown an error.",
error.stack ? error
return error: "No strategies worked."
# Public: Initialises an annotation either from an object representation or # Public: Initialises an annotation either from an object representation or
# an annotation created with Annotator#createAnnotation(). It finds the # an annotation created with Annotator#createAnnotation(). It finds the
# selected range and higlights the selection in the DOM, extracts the # selected range and higlights the selection in the DOM, extracts the
...@@ -397,7 +293,7 @@ class Annotator extends Delegator ...@@ -397,7 +293,7 @@ class Annotator extends Delegator
for t in annotation.target ? [] for t in annotation.target ? []
try try
# Create an anchor for this target # Create an anchor for this target
result = this.createAnchor annotation, t result = this.anchoring.createAnchor annotation, t
anchor = result.result anchor = result.result
if result.error? instanceof Range.RangeError if result.error? instanceof Range.RangeError
this.publish 'rangeNormalizeFail', [annotation, result.error.range, result.error] this.publish 'rangeNormalizeFail', [annotation, result.error.range, result.error]
...@@ -408,14 +304,6 @@ class Annotator extends Delegator ...@@ -408,14 +304,6 @@ class Annotator extends Delegator
# Store this anchor for the annotation # Store this anchor for the annotation
annotation.anchors.push anchor annotation.anchors.push anchor
# Store the anchor for all involved pages
for pageIndex in [anchor.startPage .. anchor.endPage]
@anchors[pageIndex] ?= []
@anchors[pageIndex].push anchor
# Realizing the anchor
anchor.realize()
catch exception catch exception
console.log "Error in setupAnnotation for", annotation.id, console.log "Error in setupAnnotation for", annotation.id,
":", exception.stack ? exception ":", exception.stack ? exception
...@@ -624,19 +512,6 @@ class Annotator extends Delegator ...@@ -624,19 +512,6 @@ class Annotator extends Delegator
this.startViewerHideTimer() this.startViewerHideTimer()
@mouseIsDown = true @mouseIsDown = true
# This is called to create a target from a raw selection,
# using selectors created by the registered selector creators
_getTargetFromSelection: (selection) =>
selectors = []
for c in @selectorCreators
description = c.describe selection
for selector in description
selectors.push selector
# Create the target
source: @getHref()
selector: selectors
# This method is to be called by the mechanisms responsible for # This method is to be called by the mechanisms responsible for
# triggering annotation (and highlight) creation. # triggering annotation (and highlight) creation.
# #
...@@ -673,6 +548,12 @@ class Annotator extends Delegator ...@@ -673,6 +548,12 @@ class Annotator extends Delegator
true true
# This is called to create a target from a raw selection,
# using selectors created by the registered selector creators
_getTargetFromSelection: (selection) ->
source: @getHref()
selector: @anchoring.getSelectorsFromSelection(selection)
onFailedSelection: (event) -> onFailedSelection: (event) ->
@adder.hide() @adder.hide()
@selectedTargets = [] @selectedTargets = []
...@@ -796,36 +677,6 @@ class Annotator extends Delegator ...@@ -796,36 +677,6 @@ class Annotator extends Delegator
# Delete highlight elements. # Delete highlight elements.
this.deleteAnnotation annotation this.deleteAnnotation annotation
# Collect all the highlights (optionally for a given set of annotations)
getHighlights: (annotations) ->
results = []
if annotations?
# Collect only the given set of annotations
for annotation in annotations
for anchor in annotation.anchors
for page, hl of anchor.highlight
results.push hl
else
# Collect from everywhere
for page, anchors of @anchors
$.merge results, (anchor.highlight[page] for anchor in anchors when anchor.highlight[page]?)
results
# Realize anchors on a given pages
_realizePage: (index) ->
# If the page is not mapped, give up
return unless @domMapper.isPageMapped index
# Go over all anchors related to this page
for anchor in @anchors[index] ? []
anchor.realize()
# Virtualize anchors on a given page
_virtualizePage: (index) ->
# Go over all anchors related to this page
for anchor in @anchors[index] ? []
anchor.virtualize index
onAnchorMouseover: (event) -> onAnchorMouseover: (event) ->
# Cancel any pending hiding of the viewer. # Cancel any pending hiding of the viewer.
this.clearViewerHideTimer() this.clearViewerHideTimer()
...@@ -896,7 +747,6 @@ Annotator.Util = Util ...@@ -896,7 +747,6 @@ Annotator.Util = Util
Annotator._instances = [] Annotator._instances = []
Annotator.Highlight = Highlight Annotator.Highlight = Highlight
Annotator.Anchor = Anchor
# Bind gettext helper so plugins can use localisation. # Bind gettext helper so plugins can use localisation.
Annotator._t = _t Annotator._t = _t
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
class Highlight class Highlight
constructor: (@anchor, @pageIndex) -> constructor: (@anchor, @pageIndex) ->
@annotator = @anchor.annotator
@annotation = @anchor.annotation @annotation = @anchor.annotation
@anchoring = @anchor.anchoring
@annotator = @anchoring.annotator
# Mark/unmark this hl as temporary (while creating an annotation) # Mark/unmark this hl as temporary (while creating an annotation)
setTemporary: (value) -> setTemporary: (value) ->
...@@ -80,3 +81,4 @@ class Highlight ...@@ -80,3 +81,4 @@ class Highlight
# Scroll down to the highlight, with a comfortable margin. # Scroll down to the highlight, with a comfortable margin.
paddedScrollDownTo: -> @paddedScrollTo "down" paddedScrollDownTo: -> @paddedScrollTo "down"
...@@ -6,9 +6,11 @@ class Annotator.Plugin.DomTextMapper extends Annotator.Plugin ...@@ -6,9 +6,11 @@ class Annotator.Plugin.DomTextMapper extends Annotator.Plugin
console.log "Not registering DOM-Text-Mapper." console.log "Not registering DOM-Text-Mapper."
return return
@annotator.documentAccessStrategies.unshift @anchoring = @annotator.anchoring
@anchoring.documentAccessStrategies.unshift
# Document access strategy for simple HTML documents, # Document access strategy for simple HTML documents,
# with enhanced text extraction and mapping features. # with enhanced text extraction and mapping features.
name: "DOM-Text-Mapper" name: "DOM-Text-Mapper"
mapper: window.DomTextMapper mapper: window.DomTextMapper
init: => @annotator.domMapper.setRootNode @annotator.wrapper[0] init: => @anchoring.document.setRootNode @annotator.wrapper[0]
# Fake two-phase / pagination support, used for HTML documents
class DummyDocumentAccess
@applicable: -> true
getPageIndex: -> 0
getPageCount: -> 1
getPageIndexForPos: -> 0
isPageMapped: -> true
scan: ->
# Abstract anchor class.
class Anchor
constructor: (@anchoring, @annotation, @target
@startPage, @endPage,
@quote, @diffHTML, @diffCaseOnly) ->
unless @anchoring? then throw "anchoring manager is required!"
unless @annotation? then throw "annotation is required!"
unless @target? then throw "target is required!"
unless @startPage? then "startPage is required!"
unless @endPage? then throw "endPage is required!"
unless @quote? then throw "quote is required!"
@highlight = {}
_getSegment: (page) ->
throw "Function not implemented"
# Create the missing highlights for this anchor
realize: () =>
return if @fullyRealized # If we have everything, go home
# Collect the pages that are already rendered
renderedPages = [@startPage .. @endPage].filter (index) =>
@anchoring.document.isPageMapped index
# Collect the pages that are already rendered, but not yet anchored
pagesTodo = renderedPages.filter (index) => not @highlight[index]?
return unless pagesTodo.length # Return if nothing to do
# Create the new highlights
created = for page in pagesTodo
# TODO: add a layer of abstraction here
# Don't call TextHighlight directly; instead, make a system
# For registering highlight creators, or publish an event, or
# whatever
@highlight[page] = @Annotator.TextHighlight.createFrom @_getSegment(page), this, page
# Check if everything is rendered now
@fullyRealized = renderedPages.length is @endPage - @startPage + 1
# Announce the creation of the highlights
@anchoring.annotator.publish 'highlightsCreated', created
# Remove the highlights for the given set of pages
virtualize: (pageIndex) =>
highlight = @highlight[pageIndex]
return unless highlight? # No highlight for this page
highlight.removeFromDocument()
delete @highlight[pageIndex]
# Mark this anchor as not fully rendered
@fullyRealized = false
# Announce the removal of the highlight
@anchoring.annotator.publish 'highlightRemoved', highlight
# Virtualize and remove an anchor from all involved pages
remove: ->
# Go over all the pages
for index in [@startPage .. @endPage]
@virtualize index
anchors = @anchoring.anchors[index]
# Remove the anchor from the list
i = anchors.indexOf this
anchors[i..i] = []
# Kill the list if it's empty
delete @anchoring.anchors[index] unless anchors.length
# This is called when the underlying Annotator has been udpated
annotationUpdated: ->
# Notify the highlights
for index in [@startPage .. @endPage]
@highlight[index]?.annotationUpdated()
Annotator.Anchor = Anchor
# This plugin contains the enhanced anchoring framework.
class Annotator.Plugin.EnhancedAnchoring extends Annotator.Plugin
constructor: ->
# Initializes the available document access strategies
_setupDocumentAccessStrategies: ->
@documentAccessStrategies = [
# Default dummy strategy for simple HTML documents.
# The generic fallback.
name: "Dummy"
mapper: DummyDocumentAccess
]
this
# Sets up handlers to anchor-related events
_setupAnchorEvents: ->
# When annotations are updated
@annotator.on 'annotationUpdated', (annotation) =>
# Notify the anchors
for anchor in annotation.anchors or []
anchor.annotationUpdated()
# Initializes the components used for analyzing the document
_chooseAccessPolicy: ->
if @document? then return
# Go over the available strategies
for s in @documentAccessStrategies
# Can we use this strategy for this document?
if s.mapper.applicable()
@documentAccessStrategy = s
@document = new s.mapper()
@anchors = {}
addEventListener "docPageMapped", (evt) =>
@_realizePage evt.pageIndex
addEventListener "docPageUnmapped", (evt) =>
@_virtualizePage evt.pageIndex
s.init?()
return this
# Remove the current document access policy
_removeCurrentAccessPolicy: ->
return unless @document?
list = @documentAccessStrategies
index = list.indexOf @documentAccessStrategy
list.splice(index, 1) unless index is -1
@document.destroy?()
delete @document
# Perform a scan of the DOM. Required for finding anchors.
_scan: ->
# Ensure that we have a document access strategy
this._chooseAccessPolicy()
try
@pendingScan = @document.scan()
catch
@_removeCurrentAccessPolicy()
@_scan()
return
# Plugin initialization
pluginInit: ->
@$ = Annotator.$
@selectorCreators = []
@strategies = []
@_setupDocumentAccessStrategies()
this._setupAnchorEvents()
@annotator.anchoring = this
# PUBLIC Try to find the right anchoring point for a given target
#
# Returns an Anchor object if succeeded, null otherwise
createAnchor: (annotation, target) ->
unless target?
throw new Error "Trying to find anchor for null target!"
error = null
anchor = null
for s in @strategies
try
a = s.code.call this, annotation, target
if a
# Store the anchor for all involved pages
for pageIndex in [a.startPage .. a.endPage]
@anchors[pageIndex] ?= []
@anchors[pageIndex].push a
# Realizing the anchor
a.realize()
return result: a
catch error
console.log "Strategy '" + s.name + "' has thrown an error.",
error.stack ? error
return error: "No strategies worked."
# Do some normalization to get a "canonical" form of a string.
# Used to even out some browser differences.
normalizeString: (string) -> string.replace /\s{2,}/g, " "
# Find the given type of selector from an array of selectors, if it exists.
# If it does not exist, null is returned.
findSelector: (selectors, type) ->
for selector in selectors
if selector.type is type then return selector
null
# Realize anchors on a given pages
_realizePage: (index) ->
# If the page is not mapped, give up
return unless @document.isPageMapped index
# Go over all anchors related to this page
for anchor in @anchors[index] ? []
anchor.realize()
# Virtualize anchors on a given page
_virtualizePage: (index) ->
# Go over all anchors related to this page
for anchor in @anchors[index] ? []
anchor.virtualize index
# Collect all the highlights (optionally for a given set of annotations)
getHighlights: (annotations) ->
results = []
if annotations?
# Collect only the given set of annotations
for annotation in annotations
for anchor in annotation.anchors
for page, hl of anchor.highlight
results.push hl
else
# Collect from everywhere
for page, anchors of @anchors
@$.merge results, (anchor.highlight[page] for anchor in anchors when anchor.highlight[page]?)
results
# PUBLIC entry point 1:
# This is called to create a target from a raw selection,
# using selectors created by the registered selector creators
getSelectorsFromSelection: (selection) =>
selectors = []
for c in @selectorCreators
description = c.describe selection
for selector in description
selectors.push selector
selectors
...@@ -3,24 +3,26 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -3,24 +3,26 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
pluginInit: -> pluginInit: ->
# Do we have the basic text anchors plugin loaded? # Do we have the basic text anchors plugin loaded?
unless @annotator.plugins.TextAnchors unless @annotator.plugins.TextPosition
console.warn "The FuzzyTextAnchors Annotator plugin requires the TextAnchors plugin. Skipping." console.warn "The FuzzyTextAnchors Annotator plugin requires the TextPosition plugin. Skipping."
return return
@Annotator = Annotator @Annotator = Annotator
@anchoring = @annotator.anchoring
# Initialize the text matcher library # Initialize the text matcher library
@textFinder = new DomTextMatcher => @annotator.domMapper.getCorpus() @textFinder = new DomTextMatcher => @anchoring.document.getCorpus()
# Register our fuzzy strategies # Register our fuzzy strategies
@annotator.anchoringStrategies.push @anchoring.strategies.push
# Two-phased fuzzy text matching strategy. (Using context and quote.) # Two-phased fuzzy text matching strategy. (Using context and quote.)
# This can handle document structure changes, # This can handle document structure changes,
# and also content changes. # and also content changes.
name: "two-phase fuzzy" name: "two-phase fuzzy"
code: this.twoPhaseFuzzyMatching code: this.twoPhaseFuzzyMatching
@annotator.anchoringStrategies.push @anchoring.strategies.push
# Naive fuzzy text matching strategy. (Using only the quote.) # Naive fuzzy text matching strategy. (Using only the quote.)
# This can handle document structure changes, # This can handle document structure changes,
# and also content changes. # and also content changes.
...@@ -28,11 +30,14 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -28,11 +30,14 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
code: this.fuzzyMatching code: this.fuzzyMatching
twoPhaseFuzzyMatching: (annotation, target) => twoPhaseFuzzyMatching: (annotation, target) =>
document = @anchoring.document
# This won't work without DTM # This won't work without DTM
return unless @annotator.domMapper.getInfoForNode? return unless document.getInfoForNode?
# Fetch the quote and the context # Fetch the quote and the context
quoteSelector = @annotator.findSelector target.selector, "TextQuoteSelector" quoteSelector = @anchoring.findSelector target.selector, "TextQuoteSelector"
prefix = quoteSelector?.prefix prefix = quoteSelector?.prefix
suffix = quoteSelector?.suffix suffix = quoteSelector?.suffix
quote = quoteSelector?.exact quote = quoteSelector?.exact
...@@ -41,12 +46,12 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -41,12 +46,12 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
unless (prefix? and suffix?) then return null unless (prefix? and suffix?) then return null
# Fetch the expected start and end positions # Fetch the expected start and end positions
posSelector = @annotator.findSelector target.selector, "TextPositionSelector" posSelector = @anchoring.findSelector target.selector, "TextPositionSelector"
expectedStart = posSelector?.start expectedStart = posSelector?.start
expectedEnd = posSelector?.end expectedEnd = posSelector?.end
options = options =
contextMatchDistance: @annotator.domMapper.getCorpus().length * 2 contextMatchDistance: document.getCorpus().length * 2
contextMatchThreshold: 0.5 contextMatchThreshold: 0.5
patternMatchThreshold: 0.5 patternMatchThreshold: 0.5
flexContext: true flexContext: true
...@@ -65,20 +70,23 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -65,20 +70,23 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
# OK, we have everything # OK, we have everything
# Create a TextPositionAnchor from this data # Create a TextPositionAnchor from this data
new @Annotator.TextPositionAnchor @annotator, annotation, target, new @Annotator.TextPositionAnchor @anchoring, annotation, target,
match.start, match.end, match.start, match.end,
(@annotator.domMapper.getPageIndexForPos match.start), (document.getPageIndexForPos match.start),
(@annotator.domMapper.getPageIndexForPos match.end), (document.getPageIndexForPos match.end),
match.found, match.found,
unless match.exact then match.comparison.diffHTML, unless match.exact then match.comparison.diffHTML,
unless match.exact then match.exactExceptCase unless match.exact then match.exactExceptCase
fuzzyMatching: (annotation, target) => fuzzyMatching: (annotation, target) =>
document = @anchoring.document
# This won't work without DTM # This won't work without DTM
return unless @annotator.domMapper.getInfoForNode? return unless document.getInfoForNode?
# Fetch the quote # Fetch the quote
quoteSelector = @annotator.findSelector target.selector, "TextQuoteSelector" quoteSelector = @anchoring.findSelector target.selector, "TextQuoteSelector"
quote = quoteSelector?.exact quote = quoteSelector?.exact
# No quote, no joy # No quote, no joy
...@@ -89,11 +97,11 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -89,11 +97,11 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
return unless quote.length >= 32 return unless quote.length >= 32
# Get a starting position for the search # Get a starting position for the search
posSelector = @annotator.findSelector target.selector, "TextPositionSelector" posSelector = @anchoring.findSelector target.selector, "TextPositionSelector"
expectedStart = posSelector?.start expectedStart = posSelector?.start
# Get full document length # Get full document length
len = @annotator.domMapper.getCorpus().length len = document.getCorpus().length
# If we don't have the position saved, start at the middle of the doc # If we don't have the position saved, start at the middle of the doc
expectedStart ?= Math.floor(len / 2) expectedStart ?= Math.floor(len / 2)
...@@ -116,10 +124,11 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin ...@@ -116,10 +124,11 @@ class Annotator.Plugin.FuzzyTextAnchors extends Annotator.Plugin
# OK, we have everything # OK, we have everything
# Create a TextPosutionAnchor from this data # Create a TextPosutionAnchor from this data
new @Annotator.TextPositionAnchor @annotator, annotation, target, new @Annotator.TextPositionAnchor @anchoring, annotation, target,
match.start, match.end, match.start, match.end,
(@annotator.domMapper.getPageIndexForPos match.start), (document.getPageIndexForPos match.start),
(@annotator.domMapper.getPageIndexForPos match.end), (document.getPageIndexForPos match.end),
match.found, match.found,
unless match.exact then match.comparison.diffHTML, unless match.exact then match.comparison.diffHTML,
unless match.exact then match.exactExceptCase unless match.exact then match.exactExceptCase
...@@ -222,7 +222,9 @@ class Annotator.Plugin.PDF extends Annotator.Plugin ...@@ -222,7 +222,9 @@ class Annotator.Plugin.PDF extends Annotator.Plugin
console.warn "The PDF Annotator plugin requires the DomTextMapper plugin. Skipping." console.warn "The PDF Annotator plugin requires the DomTextMapper plugin. Skipping."
return return
@annotator.documentAccessStrategies.unshift @anchoring = @annotator.anchoring
@anchoring.documentAccessStrategies.unshift
# Strategy to handle PDF documents rendered by PDF.js # Strategy to handle PDF documents rendered by PDF.js
name: "PDF.js" name: "PDF.js"
mapper: PDFTextMapper mapper: PDFTextMapper
...@@ -247,7 +249,7 @@ class Annotator.Plugin.PDF extends Annotator.Plugin ...@@ -247,7 +249,7 @@ class Annotator.Plugin.PDF extends Annotator.Plugin
# Get a PDF fingerPrint-based URI # Get a PDF fingerPrint-based URI
_getFingerPrintURI: -> _getFingerPrintURI: ->
fingerprint = @annotator.domMapper.getDocumentFingerprint() fingerprint = @anchoring.document.getDocumentFingerprint()
# This is an experimental URN, # This is an experimental URN,
# as per http://tools.ietf.org/html/rfc3406#section-3.0 # as per http://tools.ietf.org/html/rfc3406#section-3.0
...@@ -266,7 +268,7 @@ class Annotator.Plugin.PDF extends Annotator.Plugin ...@@ -266,7 +268,7 @@ class Annotator.Plugin.PDF extends Annotator.Plugin
# Try to extract the title; first from metadata, then HTML header # Try to extract the title; first from metadata, then HTML header
_getTitle: -> _getTitle: ->
title = @annotator.domMapper.getDocumentInfo().Title?.trim() title = @anchoring.document.getDocumentInfo().Title?.trim()
if title? and title isnt "" if title? and title isnt ""
title title
else else
...@@ -291,8 +293,8 @@ class Annotator.Plugin.PDF extends Annotator.Plugin ...@@ -291,8 +293,8 @@ class Annotator.Plugin.PDF extends Annotator.Plugin
# Public: Get metadata (when the doc is loaded). Returns a promise. # Public: Get metadata (when the doc is loaded). Returns a promise.
getMetaData: => getMetaData: =>
new Promise (resolve, reject) => new Promise (resolve, reject) =>
if @annotator.domMapper.waitForInit? if @anchoring.document.waitForInit?
@annotator.domMapper.waitForInit().then => @anchoring.document.waitForInit().then =>
try try
resolve @_metadata() resolve @_metadata()
catch error catch error
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
# required for annotating text. # required for annotating text.
class TextHighlight extends Annotator.Highlight class TextHighlight extends Annotator.Highlight
@createFrom: (segment, anchor, page) ->
return null if segment.type isnt "magic range"
new TextHighlight anchor, page, segment.data
# XXX: This is a temporay workaround until the Highlighter extension # XXX: This is a temporay workaround until the Highlighter extension
# PR will be merged which will restore separation properly # PR will be merged which will restore separation properly
@highlightClass = 'annotator-hl' @highlightClass = 'annotator-hl'
...@@ -110,7 +116,7 @@ class TextHighlight extends Annotator.Highlight ...@@ -110,7 +116,7 @@ class TextHighlight extends Annotator.Highlight
removeFromDocument: -> removeFromDocument: ->
for hl in @_highlights for hl in @_highlights
# Is this highlight actually the part of the document? # Is this highlight actually the part of the document?
if hl.parentNode? and @annotator.domMapper.isPageMapped @pageIndex if hl.parentNode? and @anchoring.document.isPageMapped @pageIndex
# We should restore original state # We should restore original state
child = hl.childNodes[0] child = hl.childNodes[0]
@$(hl).replaceWith hl.childNodes @$(hl).replaceWith hl.childNodes
......
...@@ -4,11 +4,11 @@ class TextPositionAnchor extends Annotator.Anchor ...@@ -4,11 +4,11 @@ class TextPositionAnchor extends Annotator.Anchor
@Annotator = Annotator @Annotator = Annotator
constructor: (annotator, annotation, target, constructor: (anchoring, annotation, target,
@start, @end, startPage, endPage, @start, @end, startPage, endPage,
quote, diffHTML, diffCaseOnly) -> quote, diffHTML, diffCaseOnly) ->
super annotator, annotation, target, super anchoring, annotation, target,
startPage, endPage, startPage, endPage,
quote, diffHTML, diffCaseOnly quote, diffHTML, diffCaseOnly
...@@ -20,10 +20,10 @@ class TextPositionAnchor extends Annotator.Anchor ...@@ -20,10 +20,10 @@ class TextPositionAnchor extends Annotator.Anchor
@Annotator = TextPositionAnchor.Annotator @Annotator = TextPositionAnchor.Annotator
# This is how we create a highlight out of this kind of anchor # This is how we create a highlight out of this kind of anchor
_createHighlight: (page) -> _getSegment: (page) ->
# First we create the range from the stored stard and end offsets # First we create the range from the stored stard and end offsets
mappings = @annotator.domMapper.getMappingsForCharRange @start, @end, [page] mappings = @anchoring.document.getMappingsForCharRange @start, @end, [page]
# Get the wanted range out of the response of DTM # Get the wanted range out of the response of DTM
realRange = mappings.sections[page].realRange realRange = mappings.sections[page].realRange
...@@ -32,11 +32,10 @@ class TextPositionAnchor extends Annotator.Anchor ...@@ -32,11 +32,10 @@ class TextPositionAnchor extends Annotator.Anchor
browserRange = new @Annotator.Range.BrowserRange realRange browserRange = new @Annotator.Range.BrowserRange realRange
# Get a NormalizedRange # Get a NormalizedRange
normedRange = browserRange.normalize @annotator.wrapper[0] normedRange = browserRange.normalize @anchoring.annotator.wrapper[0]
# Create the highligh
new @Annotator.TextHighlight this, page, normedRange
type: "magic range"
data: normedRange
# Annotator plugin for text position-based anchoring # Annotator plugin for text position-based anchoring
class Annotator.Plugin.TextPosition extends Annotator.Plugin class Annotator.Plugin.TextPosition extends Annotator.Plugin
...@@ -45,12 +44,14 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin ...@@ -45,12 +44,14 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin
@Annotator = Annotator @Annotator = Annotator
@anchoring = @annotator.anchoring
# Register the creator for text quote selectors # Register the creator for text quote selectors
@annotator.selectorCreators.push @anchoring.selectorCreators.push
name: "TextPositionSelector" name: "TextPositionSelector"
describe: @_getTextPositionSelector describe: @_getTextPositionSelector
@annotator.anchoringStrategies.push @anchoring.strategies.push
# Position-based strategy. (The quote is verified.) # Position-based strategy. (The quote is verified.)
# This can handle document structure changes, # This can handle document structure changes,
# but not the content changes. # but not the content changes.
...@@ -65,11 +66,13 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin ...@@ -65,11 +66,13 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin
# We only care about "text range" selections. # We only care about "text range" selections.
return [] unless selection.type is "text range" return [] unless selection.type is "text range"
document = @anchoring.document
# We need dom-text-mapper - style functionality # We need dom-text-mapper - style functionality
return [] unless @annotator.domMapper.getStartPosForNode? return [] unless document.getStartPosForNode?
startOffset = @annotator.domMapper.getStartPosForNode selection.range.start startOffset = document.getStartPosForNode selection.range.start
endOffset = @annotator.domMapper.getEndPosForNode selection.range.end endOffset = document.getEndPosForNode selection.range.end
if startOffset? and endOffset? if startOffset? and endOffset?
[ [
...@@ -95,7 +98,7 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin ...@@ -95,7 +98,7 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin
createFromPositionSelector: (annotation, target) => createFromPositionSelector: (annotation, target) =>
# We need the TextPositionSelector # We need the TextPositionSelector
selector = @annotator.findSelector target.selector, "TextPositionSelector" selector = @anchoring.findSelector target.selector, "TextPositionSelector"
return unless selector? return unless selector?
unless selector.start? unless selector.start?
...@@ -106,13 +109,15 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin ...@@ -106,13 +109,15 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin
console.log "Warning: 'end' field is missing from TextPositionSelector. Skipping." console.log "Warning: 'end' field is missing from TextPositionSelector. Skipping."
return null return null
corpus = @annotator.domMapper.getCorpus?() document = @anchoring.document
corpus = document.getCorpus?()
# This won't work without d-t-m # This won't work without d-t-m
return null unless corpus return null unless corpus
content = corpus[selector.start ... selector.end].trim() content = corpus[selector.start ... selector.end].trim()
currentQuote = @annotator.normalizeString content currentQuote = @anchoring.normalizeString content
savedQuote = @annotator.getQuoteForTarget? target savedQuote = @anchoring.getQuoteForTarget? target
if savedQuote? and currentQuote isnt savedQuote if savedQuote? and currentQuote isnt savedQuote
# We have a saved quote, let's compare it to current content # We have a saved quote, let's compare it to current content
#console.log "Could not apply position selector" + #console.log "Could not apply position selector" +
...@@ -123,8 +128,9 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin ...@@ -123,8 +128,9 @@ class Annotator.Plugin.TextPosition extends Annotator.Plugin
return null return null
# Create a TextPositionAnchor from this data # Create a TextPositionAnchor from this data
new TextPositionAnchor @annotator, annotation, target, new TextPositionAnchor @anchoring, annotation, target,
selector.start, selector.end, selector.start, selector.end,
(@annotator.domMapper.getPageIndexForPos selector.start), (document.getPageIndexForPos selector.start),
(@annotator.domMapper.getPageIndexForPos selector.end), (document.getPageIndexForPos selector.end),
currentQuote currentQuote
...@@ -7,16 +7,18 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin ...@@ -7,16 +7,18 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin
# Plugin initialization # Plugin initialization
pluginInit: -> pluginInit: ->
@anchoring = @annotator.anchoring
# Register the creator for text quote selectors # Register the creator for text quote selectors
@annotator.selectorCreators.push @anchoring.selectorCreators.push
name: "TextQuoteSelector" name: "TextQuoteSelector"
describe: @_getTextQuoteSelector describe: @_getTextQuoteSelector
# Register function to get quote from this selector # Register function to get quote from this selector
@annotator.getQuoteForTarget = (target) => @anchoring.getQuoteForTarget = (target) =>
selector = @annotator.findSelector target.selector, "TextQuoteSelector" selector = @anchoring.findSelector target.selector, "TextQuoteSelector"
if selector? if selector?
@annotator.normalizeString selector.exact @anchoring.normalizeString selector.exact
else else
null null
...@@ -24,6 +26,8 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin ...@@ -24,6 +26,8 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin
_getTextQuoteSelector: (selection) => _getTextQuoteSelector: (selection) =>
return [] unless selection.type is "text range" return [] unless selection.type is "text range"
document = @anchoring.document
unless selection.range? unless selection.range?
throw new Error "Called getTextQuoteSelector() with null range!" throw new Error "Called getTextQuoteSelector() with null range!"
...@@ -34,15 +38,15 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin ...@@ -34,15 +38,15 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin
unless rangeEnd? unless rangeEnd?
throw new Error "Called getTextQuoteSelector() on a range with no valid end." throw new Error "Called getTextQuoteSelector() on a range with no valid end."
if @annotator.domMapper.getStartPosForNode? if document.getStartPosForNode?
# Calculate the quote and context using DTM # Calculate the quote and context using DTM
startOffset = @annotator.domMapper.getStartPosForNode rangeStart startOffset = document.getStartPosForNode rangeStart
endOffset = @annotator.domMapper.getEndPosForNode rangeEnd endOffset = document.getEndPosForNode rangeEnd
if startOffset? and endOffset? if startOffset? and endOffset?
quote = @annotator.domMapper.getCorpus()[startOffset .. endOffset-1].trim() quote = document.getCorpus()[startOffset .. endOffset-1].trim()
[prefix, suffix] = @annotator.domMapper.getContextForCharRange startOffset, endOffset [prefix, suffix] = document.getContextForCharRange startOffset, endOffset
[ [
type: "TextQuoteSelector" type: "TextQuoteSelector"
...@@ -61,3 +65,4 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin ...@@ -61,3 +65,4 @@ class Annotator.Plugin.TextQuote extends Annotator.Plugin
type: "TextQuoteSelector" type: "TextQuoteSelector"
exact: selection.range.text().trim() exact: selection.range.text().trim()
] ]
...@@ -30,11 +30,9 @@ class TextRangeAnchor extends Annotator.Anchor ...@@ -30,11 +30,9 @@ class TextRangeAnchor extends Annotator.Anchor
@Annotator = TextRangeAnchor.Annotator @Annotator = TextRangeAnchor.Annotator
# This is how we create a highlight out of this kind of anchor # This is how we create a highlight out of this kind of anchor
_createHighlight: -> _getSegment: ->
type: "magic range"
# Create the highligh data: @range
new @Annotator.TextHighlight this, 0, @range
# Annotator plugin for creating, and anchoring based on text range # Annotator plugin for creating, and anchoring based on text range
# selectors # selectors
...@@ -44,13 +42,15 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin ...@@ -44,13 +42,15 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin
@Annotator = Annotator @Annotator = Annotator
@anchoring = @annotator.anchoring
# Register the creator for range selectors # Register the creator for range selectors
@annotator.selectorCreators.push @anchoring.selectorCreators.push
name: "RangeSelector" name: "RangeSelector"
describe: @_getRangeSelector describe: @_getRangeSelector
# Register our anchoring strategies # Register our anchoring strategies
@annotator.anchoringStrategies.push @anchoring.strategies.push
# Simple strategy based on DOM Range # Simple strategy based on DOM Range
name: "range" name: "range"
code: @createFromRangeSelector code: @createFromRangeSelector
...@@ -74,7 +74,10 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin ...@@ -74,7 +74,10 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin
# Create and anchor using the saved Range selector. # Create and anchor using the saved Range selector.
# The quote is verified. # The quote is verified.
createFromRangeSelector: (annotation, target) => createFromRangeSelector: (annotation, target) =>
selector = @annotator.findSelector target.selector, "RangeSelector"
document = @anchoring.document
selector = @anchoring.findSelector target.selector, "RangeSelector"
unless selector? then return null unless selector? then return null
# Try to apply the saved XPath # Try to apply the saved XPath
...@@ -85,24 +88,24 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin ...@@ -85,24 +88,24 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin
return null return null
# Get the text of this range # Get the text of this range
if @annotator.domMapper.getInfoForNode? if document.getInfoForNode?
# Determine the current content of the given range using DTM # Determine the current content of the given range using DTM
startInfo = @annotator.domMapper.getInfoForNode normedRange.start startInfo = document.getInfoForNode normedRange.start
return null unless startInfo # Don't fret if page is not mapped return null unless startInfo # Don't fret if page is not mapped
startOffset = startInfo.start startOffset = startInfo.start
endInfo = @annotator.domMapper.getInfoForNode normedRange.end endInfo = document.getInfoForNode normedRange.end
return null unless endInfo # Don't fret if page is not mapped return null unless endInfo # Don't fret if page is not mapped
endOffset = endInfo.end endOffset = endInfo.end
rawQuote = @annotator.domMapper.getCorpus()[startOffset .. endOffset-1].trim() rawQuote = document.getCorpus()[startOffset .. endOffset-1].trim()
else else
# Determine the current content of the given range directly # Determine the current content of the given range directly
rawQuote = normedRange.text().trim() rawQuote = normedRange.text().trim()
currentQuote = @annotator.normalizeString rawQuote currentQuote = @anchoring.normalizeString rawQuote
# Look up the saved quote # Look up the saved quote
savedQuote = @annotator.getQuoteForTarget? target savedQuote = @anchoring.getQuoteForTarget? target
if savedQuote? and currentQuote isnt savedQuote if savedQuote? and currentQuote isnt savedQuote
#console.log "Could not apply XPath selector to current document, " + #console.log "Could not apply XPath selector to current document, " +
# "because the quote has changed. (Saved quote is '#{savedQuote}'." + # "because the quote has changed. (Saved quote is '#{savedQuote}'." +
...@@ -113,12 +116,13 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin ...@@ -113,12 +116,13 @@ class Annotator.Plugin.TextRange extends Annotator.Plugin
# Create a TextPositionAnchor from the start and end offsets # Create a TextPositionAnchor from the start and end offsets
# of this range # of this range
# (to be used with dom-text-mapper) # (to be used with dom-text-mapper)
new @Annotator.TextPositionAnchor @annotator, annotation, target, new @Annotator.TextPositionAnchor @anchoring, annotation, target,
startInfo.start, endInfo.end, startInfo.start, endInfo.end,
(startInfo.pageIndex ? 0), (endInfo.pageIndex ? 0), (startInfo.pageIndex ? 0), (endInfo.pageIndex ? 0),
currentQuote currentQuote
else else
# Create a TextRangeAnchor from this range # Create a TextRangeAnchor from this range
# (to be used whithout dom-text-mapper) # (to be used whithout dom-text-mapper)
new TextRangeAnchor @annotator, annotation, target, new TextRangeAnchor @anchoring, annotation, target,
normedRange, currentQuote normedRange, currentQuote
# This plugin implements the UI code for creating text annotations # This plugin implements the UI code for creating text annotations
class Annotator.Plugin.TextAnchors extends Annotator.Plugin class Annotator.Plugin.TextSelection extends Annotator.Plugin
# Plugin initialization
pluginInit: -> pluginInit: ->
# We need text highlights # We need text highlights
unless @annotator.plugins.TextHighlights unless @annotator.plugins.TextHighlights
throw new Error "The TextAnchors Annotator plugin requires the TextHighlights plugin." throw new Error "The TextSelection Annotator plugin requires the TextHighlights plugin."
@Annotator = Annotator @Annotator = Annotator
@$ = Annotator.$ @$ = Annotator.$
...@@ -114,3 +113,4 @@ class Annotator.Plugin.TextAnchors extends Annotator.Plugin ...@@ -114,3 +113,4 @@ class Annotator.Plugin.TextAnchors extends Annotator.Plugin
@annotator.onFailedSelection event @annotator.onFailedSelection event
# Strategies used for creating anchors from saved data # Strategies used for creating anchors from saved data
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