Commit d87b94bf authored by csillag's avatar csillag

Added dom-text-* libraries. Annotations are now saved with full selectors.

parent cbadb7cc
......@@ -146,6 +146,11 @@ class Annotator.Host extends Annotator
scrollTop: (y) =>
$('html, body').stop().animate {scrollTop: y}, 600
scanDocument: (reason = "something happened") =>
console.log "Analyzing host frame, because " + reason + "..."
scanTime = @domMatcher.prepareSearch null, true
console.log "Traversal+scan took " + scanTime + " ms."
remote:
publish: {}
addPlugin: {}
......@@ -161,6 +166,7 @@ class Annotator.Host extends Annotator
if not @ignoreMouseup
setTimeout =>
@consumer.back() unless @selectedRanges?.length
@domMatcher.setRootNode @wrapper[0]
this
_setupDocumentEvents: ->
......
......@@ -53,6 +53,13 @@ class Hypothesis extends Annotator
id: a.id
references: a.thread?.split '/'
# After annotations were loaded (or none was found), scan the document
for event in ['annotationsLoaded', 'foundNoAnnotations']
this.subscribe event, =>
$rootScope.$apply =>
@provider.scanDocument "annotations were loaded (if any)"
# Update the thread when an annotation changes
this.subscribe 'annotationUpdated', (annotation) =>
$rootScope.$apply ->
......@@ -210,6 +217,7 @@ class Hypothesis extends Annotator
getHref: {}
getMaxBottom: {}
scrollTop: {}
scanDocument: {}
_setupWrapper: ->
@wrapper = @element.find('#wrapper')
......
/*
** Annotator 1.2.5-dev-95dc8ee
** Annotator 1.2.5-dev-02598fb
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-02-28 16:15:11Z
** Built at: 2013-03-02 22:47:44Z
*/
(function() {
......@@ -379,6 +379,9 @@
while (nr.commonAncestor.nodeType !== 1) {
nr.commonAncestor = nr.commonAncestor.parentNode;
}
if (window.DomTextMapper != null) {
window.DomTextMapper.changed(nr.commonAncestor, "range normalization");
}
return new Range.NormalizedRange(nr);
};
......@@ -654,6 +657,8 @@
this.getHref = __bind(this.getHref, this); Annotator.__super__.constructor.apply(this, arguments);
this.plugins = {};
if (!Annotator.supported()) return this;
this.domMapper = new DomTextMapper();
this.domMatcher = new DomTextMatcher(this.domMapper);
if (!this.options.readOnly) this._setupDocumentEvents();
this._setupWrapper()._setupViewer()._setupEditor();
this._setupDynamicStyle();
......@@ -779,25 +784,29 @@
};
Annotator.prototype.getContextQuoteSelectorFromRange = function(range) {
var quote, r, selector;
r = range.normalize(this.wrapper[0]);
quote = $.trim(r.text());
var endOffset, prefix, quote, selector, startOffset, suffix, _ref2;
startOffset = (this.domMapper.getMappingsForNode(range.start)).start;
endOffset = (this.domMapper.getMappingsForNode(range.end)).end;
quote = this.domMapper.getContentForRange(startOffset, endOffset);
_ref2 = this.domMapper.getContextForRange(startOffset, endOffset), prefix = _ref2[0], suffix = _ref2[1];
return selector = {
source: this.getHref(),
type: "context+quote",
exact: quote,
prefix: "TODO prefix",
suffix: "TODO suffix"
prefix: prefix,
suffix: suffix
};
};
Annotator.prototype.getPositionSelectorFromRange = function(range) {
var selector;
var endOffset, selector, startOffset;
startOffset = (this.domMapper.getMappingsForNode(range.start)).start;
endOffset = (this.domMapper.getMappingsForNode(range.end)).end;
return selector = {
source: this.getHref(),
type: "position",
start: "100",
end: "200"
start: startOffset,
end: endOffset
};
};
......@@ -928,11 +937,13 @@
};
Annotator.prototype.deleteAnnotation = function(annotation) {
var h, _k, _len3, _ref2;
var child, h, _k, _len3, _ref2;
_ref2 = annotation.highlights;
for (_k = 0, _len3 = _ref2.length; _k < _len3; _k++) {
h = _ref2[_k];
child = h.childNodes[0];
$(h).replaceWith(h.childNodes);
window.DomTextMapper.changed(child.parentNode, "removed hilite (annotation deleted)");
}
this.publish('annotationDeleted', [annotation]);
return annotation;
......@@ -959,7 +970,11 @@
}
};
clone = annotations.slice();
if (annotations.length) loader(annotations);
if (annotations.length) {
loader(annotations);
} else {
this.publish('foundNoAnnotations');
}
return this;
};
......@@ -972,7 +987,7 @@
};
Annotator.prototype.highlightRange = function(normedRange, cssClass) {
var hl, node, white, _k, _len3, _ref2, _results;
var hl, node, r, white, _k, _len3, _ref2, _results;
if (cssClass == null) cssClass = 'annotator-hl';
white = /^\s*$/;
hl = $("<span class='" + cssClass + "'></span>");
......@@ -980,9 +995,10 @@
_results = [];
for (_k = 0, _len3 = _ref2.length; _k < _len3; _k++) {
node = _ref2[_k];
if (!white.test(node.nodeValue)) {
_results.push($(node).wrapAll(hl).parent().show()[0]);
}
if (!(!white.test(node.nodeValue))) continue;
r = $(node).wrapAll(hl).parent().show()[0];
window.DomTextMapper.changed(node, "created hilite");
_results.push(r);
}
return _results;
};
......@@ -1114,13 +1130,15 @@
return _this.publish('annotationCreated', [annotation]);
};
cancel = function() {
var h, _k, _len3, _ref2, _results;
var child, h, _k, _len3, _ref2, _results;
cleanup();
_ref2 = annotation.highlights;
_results = [];
for (_k = 0, _len3 = _ref2.length; _k < _len3; _k++) {
h = _ref2[_k];
_results.push($(h).replaceWith(h.childNodes));
child = h.childNodes[0];
$(h).replaceWith(h.childNodes);
_results.push(window.DomTextMapper.changed(child.parentNode, "removed hilite, edit cancelled"));
}
return _results;
};
......
This diff is collapsed.
class window.DomTextMatcher
# ===== Public methods =======
# Switch the library into "serializable-only" mode.
# If set to true, all public API calls will be restricted to return
# strictly serializable data structures.
# (References to DOM objects will be omitted.)
restrictToSerializable: (value = true) -> @mapper.restrictToSerializable value
# Consider only the sub-tree beginning with the given node.
#
# This will be the root node to use for all operations.
setRootNode: (rootNode) -> @mapper.setRootNode rootNode
# Consider only the sub-tree beginning with the node whose ID was given.
#
# This will be the root node to use for all operations.
setRootId: (rootId) -> @mapper.setRootId rootId
# Use this iframe for operations.
#
# Call this when mapping content in an iframe.
setRootIframe: (iframeId) -> @mapper.setRootIframe iframeId
# Work with the whole DOM tree
#
# (This is the default; you only need to call this, if you have configured
# a different root earlier, and now you want to restore the default setting.)
setRealRoot: -> @mapper.setRealRoot()
# Notify the library that the document has changed.
# This means that subsequent calls can not safely re-use previously cached
# data structures, so some calculations will be necessary again.
#
# The usage of this feature is not mandatorry; if not receiving change notifications,
# the library will just assume that the document can change anythime, and therefore
# will not assume any stability.
documentChanged: -> @mapper.documentChanged()
# The available paths which can be searched
#
# An map is returned, where the keys are the paths, and the values are objects with the following fields:
# path: the valid path value
# node: reference to the DOM node
# content: the text content of the node, as rendered by the browser
# length: the length of the next content
getAllPaths: ->
t0 = @timestamp()
paths = @mapper.getAllPaths()
t1 = @timestamp()
return time: t1 - t0, paths: paths
# Return the default path
getDefaultPath: -> @mapper.getDefaultPath()
# Prepare for searching the specified path
#
# Returns the time (in ms) it took the scan the specified path
prepareSearch: (path) ->
t0 = @timestamp()
@mapper.scan path
t1 = @timestamp()
t1 - t0
# Search for text using exact string matching
#
# Parameters:
# pattern: what to search for
#
# distinct: forbid overlapping matches? (defaults to true)
#
# caseSensitive: should the search be case sensitive? (defaults to false)
#
# path: the sub-tree inside the DOM you want to search.
# Must be an XPath expression, relative to the configured root node.
# You can check for valid input values using the getAllPaths method above.
# It's not necessary to submit path, if the search was prepared beforehand,
# with the prepareSearch() method
#
# For the details about the returned data structure, see the documentation of the search() method.
searchExact: (pattern, distinct = true, caseSensitive = false, path = null) ->
if not @pm then @pm = new window.DTM_ExactMatcher
@pm.setDistinct(distinct)
@pm.setCaseSensitive(caseSensitive)
@search @pm, pattern, null, path
# Search for text using regular expressions
#
# Parameters:
# pattern: what to search for
#
# caseSensitive: should the search be case sensitive? (defaults to false)
#
# path: the sub-tree inside the DOM you want to search.
# Must be an XPath expression, relative to the configured root node.
# You can check for valid input values using the getAllPaths method above.
# It's not necessary to submit path, if the search was prepared beforehand,
# with the prepareSearch() method
#
# For the details about the returned data structure, see the documentation of the search() method.
searchRegex: (pattern, caseSensitive = false, path = null) ->
if not @rm then @rm = new window.DTM_RegexMatcher
@rm.setCaseSensitive(caseSensitive)
@search @rm, pattern, null, path
# Search for text using fuzzy text matching
#
# Parameters:
# pattern: what to search for
#
# pos: where to start searching
#
# caseSensitive: should the search be case sensitive? (defaults to false)
#
# matchDistance and
# matchThreshold:
# fine-tuning parameters for the d-m-p library.
# See http://code.google.com/p/google-diff-match-patch/wiki/API for details.
#
# path: the sub-tree inside the DOM you want to search.
# Must be an XPath expression, relative to the configured root node.
# You can check for valid input values using the getAllPaths method above.
# It's not necessary to submit path, if the search was prepared beforehand,
# with the prepareSearch() method
#
# For the details about the returned data structure, see the documentation of the search() method.
searchFuzzy: (pattern, pos, caseSensitive = false, matchDistance = 1000, matchThreshold = 0.5, path = null) ->
if not @dmp? then @dmp = new window.DTM_DMPMatcher
@dmp.setMatchDistance matchDistance
@dmp.setMatchThreshold matchThreshold
@dmp.setCaseSensitive caseSensitive
@search @dmp, pattern, pos, path
# ===== Private methods (never call from outside the module) =======
constructor: (domTextMapper) ->
@mapper = domTextMapper
# Search for text with a custom matcher object
#
# Parameters:
# matcher: the object to use for doing the plain-text part of the search
# path: the sub-tree inside the DOM you want to search.
# Must be an XPath expression, relative to the configured root node.
# You can check for valid input values using the getAllPaths method above.
# pattern: what to search for
# pos: where do we expect to find it
#
# A list of matches is returned.
#
# , each element with "start", "end", "found" and "nodes" fields.
# start and end specify where the pattern was found; "found" is the matching slice.
# Nodes is the list of matching nodes, with details about the matches.
#
# If no match is found, null is returned. #
search: (matcher, pattern, pos, path = null) ->
# Prepare and check the pattern
unless pattern? then throw new Error "Can't search for null pattern!"
pattern = pattern.trim()
unless pattern? then throw new Error "Can't search an for empty pattern!"
# Do some preparation, if required
t0 = @timestamp()#
if path? then @prepareSearch path
t1 = @timestamp()
# Check preparations
unless @mapper.corpus? then throw new Error "Not prepared to search! (call PrepareSearch, or pass me a path)"
# Do the text search
textMatches = matcher.search @mapper.corpus, pattern, pos
t2 = @timestamp()
# Collect the mappings
# Should work like a comprehension, but it does not. WIll fix later.
# matches = ($.extend {}, match, @mapper.getMappingsFor match.start, match.end) for match in textMatches
matches = []
for match in textMatches
do (match) =>
matches.push $.extend {}, match, @analyzeMatch(pattern, match), @mapper.getMappingsForRange(match.start, match.end)
t3 = @timestamp()
return {
matches: matches
time:
phase0_domMapping: t1 - t0
phase1_textMatching: t2 - t1
phase2_matchMapping: t3 - t2
total: t3 - t0
}
timestamp: -> new Date().getTime()
analyzeMatch: (pattern, match) ->
found = @mapper.corpus.substr match.start, match.end-match.start
return {
found: found
exact: found is pattern
}
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