addreses #526, highlight/unhighlight bugs (Sean, let me know if you're still having problems; a site and a list of procedures to follow to reproduce is probably the most useful thing, although some bugs can be very difficult to troubleshoot)

closes #531, error when closing tabs
This commit is contained in:
Simon Kornblith 2007-02-13 20:15:58 +00:00
parent 00ff46d068
commit f756d16db0
2 changed files with 84 additions and 30 deletions

View File

@ -306,6 +306,7 @@ var Zotero_Browser = new function() {
// clear data object // clear data object
var tab = _getTabObject(browser); var tab = _getTabObject(browser);
if(!tab) return;
// save annotations // save annotations
if(tab.page && tab.page.annotations) tab.page.annotations.save(); if(tab.page && tab.page.annotations) tab.page.annotations.save();

View File

@ -20,6 +20,8 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
const TEXT_TYPE = Components.interfaces.nsIDOMNode.TEXT_NODE;
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// //
// Zotero.Annotate // Zotero.Annotate
@ -36,8 +38,7 @@ Zotero.Annotate = new function() {
this.getPathForPoint = getPathForPoint; this.getPathForPoint = getPathForPoint;
this.getPointForPath = getPointForPath; this.getPointForPath = getPointForPath;
this.getPixelOffset = getPixelOffset; this.getPixelOffset = getPixelOffset;
this.normalizeRange = normalizeRange;
var textType = Components.interfaces.nsIDOMNode.TEXT_NODE;
/* /*
* gets a path object, comprising an XPath, text node index, and offset, for * gets a path object, comprising an XPath, text node index, and offset, for
@ -48,14 +49,14 @@ Zotero.Annotate = new function() {
var path = {parent:"", textNode:null, offset:(offset ? offset : null)}; var path = {parent:"", textNode:null, offset:(offset ? offset : null)};
var lastWasTextNode = node.nodeType == textType; var lastWasTextNode = node.nodeType == TEXT_TYPE;
if(node.parentNode.getAttribute && node.parentNode.getAttribute("zotero")) { if(node.parentNode.getAttribute && node.parentNode.getAttribute("zotero")) {
// if the selected point is inside a highlight node, add offsets of // if the selected point is inside a highlight node, add offsets of
// preceding text nodes in this zotero node // preceding text nodes in this zotero node
var sibling = node.previousSibling; var sibling = node.previousSibling;
while(sibling) { while(sibling) {
if(sibling.nodeType == textType) path.offset += sibling.nodeValue.length; if(sibling.nodeType == TEXT_TYPE) path.offset += sibling.nodeValue.length;
sibling = sibling.previousSibling; sibling = sibling.previousSibling;
} }
@ -65,13 +66,16 @@ Zotero.Annotate = new function() {
// if selected point is a zotero node, move it to the last character // if selected point is a zotero node, move it to the last character
// of the previous node // of the previous node
node = node.previousSibling; node = node.previousSibling;
if(node.nodeType == textType) { if(node.nodeType == TEXT_TYPE) {
offset = node.nodeValue.length; path.offset = node.nodeValue.length;
lastWasTextNode = true;
} else { } else {
offset = 0; path.offset = 0;
} }
} }
lastWasTextNode = lastWasTextNode || node.nodeType == TEXT_TYPE;
if(lastWasTextNode) { if(lastWasTextNode) {
path.textNode = 1; path.textNode = 1;
var sibling = node.previousSibling; var sibling = node.previousSibling;
@ -81,7 +85,7 @@ Zotero.Annotate = new function() {
var isZotero = undefined; var isZotero = undefined;
if(sibling.getAttribute) isZotero = sibling.getAttribute("zotero"); if(sibling.getAttribute) isZotero = sibling.getAttribute("zotero");
if(sibling.nodeType == textType || if(sibling.nodeType == TEXT_TYPE ||
(isZotero == "highlight")) { (isZotero == "highlight")) {
// is a text node // is a text node
if(first == true) { if(first == true) {
@ -89,7 +93,9 @@ Zotero.Annotate = new function() {
if(sibling.getAttribute) { if(sibling.getAttribute) {
// get offset of all child nodes // get offset of all child nodes
for each(var child in sibling.childNodes) { for each(var child in sibling.childNodes) {
if(child.nodeType == textType) path.offset += child.nodeValue.length; if(child && child.nodeType == TEXT_TYPE) {
path.offset += child.nodeValue.length;
}
} }
} else { } else {
path.offset += sibling.nodeValue.length; path.offset += sibling.nodeValue.length;
@ -108,6 +114,10 @@ Zotero.Annotate = new function() {
} }
node = node.parentNode; node = node.parentNode;
} else if(path.offset != 0) {
for(; path.offset > 0 && node.nextSibling; path.offset--) {
node = node.nextSibling;
}
} }
var doc = node.ownerDocument; var doc = node.ownerDocument;
@ -121,10 +131,14 @@ Zotero.Annotate = new function() {
} }
// don't add highlight nodes // don't add highlight nodes
if(node.tagName) {
var tag = node.tagName.toLowerCase(); var tag = node.tagName.toLowerCase();
if(tag == "span") { if(tag == "span") {
tag += "[not(@zotero)]"; tag += "[not(@zotero)]";
} }
} else {
var tag = node.nodeType;
}
path.parent = "/"+tag+"["+number+"]"+path.parent; path.parent = "/"+tag+"["+number+"]"+path.parent;
@ -167,7 +181,7 @@ Zotero.Annotate = new function() {
var isZotero = undefined; var isZotero = undefined;
if(point.node.getAttribute) isZotero = point.node.getAttribute("zotero"); if(point.node.getAttribute) isZotero = point.node.getAttribute("zotero");
if(point.node.nodeType == textType || if(point.node.nodeType == TEXT_TYPE ||
isZotero == "highlight") { isZotero == "highlight") {
if(!lastWasTextNode) { if(!lastWasTextNode) {
number++; number++;
@ -198,7 +212,7 @@ Zotero.Annotate = new function() {
var parentNode = point.node; var parentNode = point.node;
point.node = point.node.firstChild; point.node = point.node.firstChild;
while(point.node) { while(point.node) {
if(point.node.nodeType == textType) { if(point.node.nodeType == TEXT_TYPE) {
// break if end condition reached // break if end condition reached
if(point.node.nodeValue.length >= point.offset) return point; if(point.node.nodeValue.length >= point.offset) return point;
// otherwise, continue subtracting offsets // otherwise, continue subtracting offsets
@ -218,7 +232,7 @@ Zotero.Annotate = new function() {
point.node = point.node.nextSibling; point.node = point.node.nextSibling;
// if next node does not exist or is not a text node, this // if next node does not exist or is not a text node, this
// point is invalid // point is invalid
if(!point.node || (point.node.nodeType != textType && if(!point.node || (point.node.nodeType != TEXT_TYPE &&
(!point.node.getAttribute || !point.node.getAttribute("zotero")))) { (!point.node.getAttribute || !point.node.getAttribute("zotero")))) {
Zotero.debug("Annotate: could not find point.offset "+point.offset+" for text node "+textNode+" of "+parent); Zotero.debug("Annotate: could not find point.offset "+point.offset+" for text node "+textNode+" of "+parent);
return false; return false;
@ -242,6 +256,30 @@ Zotero.Annotate = new function() {
return [x, y]; return [x, y];
} }
/*
* Sometimes, Firefox stupidly decides to use node offsets to reference
* nodes. This is a terrible idea, because Firefox then can't figure out
* when the node boundary matches another. This fixes it.
*/
function normalizeRange(range) {
for each(var type in ["start", "end"]) {
var container = range[type+"Container"];
var offset = range[type+"Offset"];
if(container.nodeType != TEXT_TYPE && offset) {
for(; offset > 0 && container.nextSibling; offset--) {
container = container.nextSibling;
}
if(type == "start") {
range.setStart(container, offset);
} else {
range.setEnd(container, offset);
}
}
}
}
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -332,6 +370,7 @@ Zotero.Annotations.prototype.createHighlight = function(selectedRange) {
return this.highlights[endIn]; return this.highlights[endIn];
} }
Zotero.Annotate.normalizeRange(selectedRange);
var highlight = new Zotero.Highlight(this); var highlight = new Zotero.Highlight(this);
highlight.initWithRange(selectedRange); highlight.initWithRange(selectedRange);
this.highlights.push(highlight); this.highlights.push(highlight);
@ -344,6 +383,8 @@ Zotero.Annotations.prototype.unhighlight = function(selectedRange) {
var compareHighlight = this.highlights[i]; var compareHighlight = this.highlights[i];
var compareRange = compareHighlight.range; var compareRange = compareHighlight.range;
Zotero.Annotate.normalizeRange(compareRange);
var startToStart = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.START_TO_START, selectedRange); var startToStart = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.START_TO_START, selectedRange);
var endToEnd = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.END_TO_END, selectedRange); var endToEnd = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.END_TO_END, selectedRange);
@ -366,11 +407,14 @@ Zotero.Annotations.prototype.unhighlight = function(selectedRange) {
selectEndPoint.offset == compareEndPoint.offset) { selectEndPoint.offset == compareEndPoint.offset) {
endToEnd = 0; endToEnd = 0;
} else { } else {
// this will unhighlight the entire end // this will unhighlight the entire end
compareHighlight.unhighlight(selectedRange.startContainer, selectedRange.startOffset, 2); compareHighlight.unhighlight(selectedRange.startContainer, selectedRange.startOffset, 2);
// need to use point references because they disregard highlights
var newRange = this.document.createRange(); var newRange = this.document.createRange();
// need to use point references because they disregard highlights
var startPoint = Zotero.Annotate.getPointForPath(selectEndPoint.parent, selectEndPoint.textNode, selectEndPoint.offset, var startPoint = Zotero.Annotate.getPointForPath(selectEndPoint.parent, selectEndPoint.textNode, selectEndPoint.offset,
this.document, this.nsResolver); this.document, this.nsResolver);
var endPoint = Zotero.Annotate.getPointForPath(compareEndPoint.parent, compareEndPoint.textNode, compareEndPoint.offset, var endPoint = Zotero.Annotate.getPointForPath(compareEndPoint.parent, compareEndPoint.textNode, compareEndPoint.offset,
@ -456,9 +500,13 @@ Zotero.Annotations.prototype.load = function() {
// load highlights // load highlights
var rows = Zotero.DB.query("SELECT * FROM highlights WHERE itemID = ?", [this.itemID]); var rows = Zotero.DB.query("SELECT * FROM highlights WHERE itemID = ?", [this.itemID]);
for each(var row in rows) { for each(var row in rows) {
try {
var highlight = new Zotero.Highlight(this); var highlight = new Zotero.Highlight(this);
highlight.initWithDBRow(row); highlight.initWithDBRow(row);
this.highlights.push(highlight); this.highlights.push(highlight);
} catch(e) {
Zotero.debug("Annotate: could not load highlight");
}
} }
} }
@ -737,7 +785,6 @@ Zotero.Highlight = function(annotationsObj) {
} }
Zotero.Highlight.prototype.initWithDBRow = function(row) { Zotero.Highlight.prototype.initWithDBRow = function(row) {
Zotero.debug(row.startParent);
var start = Zotero.Annotate.getPointForPath(row.startParent, row.startTextNode, var start = Zotero.Annotate.getPointForPath(row.startParent, row.startTextNode,
row.startOffset, this.document, this.nsResolver); row.startOffset, this.document, this.nsResolver);
var end = Zotero.Annotate.getPointForPath(row.endParent, row.endTextNode, var end = Zotero.Annotate.getPointForPath(row.endParent, row.endTextNode,
@ -796,7 +843,7 @@ Zotero.Highlight.prototype.unhighlight = function(container, offset, mode) {
this.range.setEnd(container, offset); this.range.setEnd(container, offset);
} }
for(var i in this.spans) { for(var i=0; i<this.spans.length; i++) {
var span = this.spans[i]; var span = this.spans[i];
var parentNode = span.parentNode; var parentNode = span.parentNode;
@ -842,24 +889,28 @@ Zotero.Highlight.prototype.unhighlight = function(container, offset, mode) {
this.range.setEnd(textNode, 0); this.range.setEnd(textNode, 0);
} }
} else if(mode == 0 || !this.range.isPointInRange(span, 1)) { } else if(mode == 0 || !this.range.isPointInRange(span, 1) && parentNode) {
Zotero.debug("point is in range"); Zotero.debug("point is in range");
// attach child nodes before // attach child nodes before
while(span.hasChildNodes()) { while(span.hasChildNodes()) {
Zotero.debug("moving "+span.firstChild.textContent); Zotero.debug("moving "+span.firstChild.textContent);
span.parentNode.insertBefore(span.removeChild(span.firstChild), span); parentNode.insertBefore(span.removeChild(span.firstChild), span);
} }
// remove span from DOM // remove span from DOM
span.parentNode.removeChild(span); parentNode.removeChild(span);
}
parentNode.normalize(); // remove span from list
this.spans.splice(i, 1);
i--;
}
} }
} }
Zotero.Highlight.prototype._highlight = function() { Zotero.Highlight.prototype._highlight = function() {
Zotero.Annotate.normalizeRange(this.range);
var startNode = this.range.startContainer; var startNode = this.range.startContainer;
var endNode = this.range.endContainer; var endNode = this.range.endContainer;
@ -869,7 +920,7 @@ Zotero.Highlight.prototype._highlight = function() {
if(!onlyOneNode) { if(!onlyOneNode) {
// highlight nodes after start node in the DOM hierarchy not at ancestor level // highlight nodes after start node in the DOM hierarchy not at ancestor level
while(!startNode.parentNode.isSameNode(ancestor)) { while(startNode.parentNode && !startNode.parentNode.isSameNode(ancestor)) {
if(startNode.nextSibling) { if(startNode.nextSibling) {
this._highlightSpaceBetween(startNode.nextSibling, startNode.parentNode.lastChild); this._highlightSpaceBetween(startNode.nextSibling, startNode.parentNode.lastChild);
} }
@ -877,7 +928,7 @@ Zotero.Highlight.prototype._highlight = function() {
startNode = startNode.parentNode; startNode = startNode.parentNode;
} }
// highlight nodes after end node in the DOM hierarchy not at ancestor level // highlight nodes after end node in the DOM hierarchy not at ancestor level
while(!endNode.parentNode.isSameNode(ancestor)) { while(endNode.parentNode && !endNode.parentNode.isSameNode(ancestor)) {
if(endNode.previousSibling) { if(endNode.previousSibling) {
this._highlightSpaceBetween(endNode.parentNode.firstChild, endNode.previousSibling); this._highlightSpaceBetween(endNode.parentNode.firstChild, endNode.previousSibling);
} }
@ -892,8 +943,10 @@ Zotero.Highlight.prototype._highlight = function() {
// split the end off the existing node // split the end off the existing node
if(this.range.endContainer.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE && this.range.endOffset != 0) { if(this.range.endContainer.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE && this.range.endOffset != 0) {
Zotero.debug("using end range");
if(this.range.endOffset != this.range.endContainer.nodeValue) { if(this.range.endOffset != this.range.endContainer.nodeValue) {
var textNode = this.range.endContainer.splitText(this.range.endOffset); var textNode = this.range.endContainer.splitText(this.range.endOffset);
Zotero.debug("split along "+textNode.textContent);
} }
if(!onlyOneNode) { if(!onlyOneNode) {
this._highlightTextNode(this.range.endContainer); this._highlightTextNode(this.range.endContainer);
@ -927,7 +980,7 @@ Zotero.Highlight.prototype._highlightTextNode = function(textNode) {
nextSibling.getAttribute("zotero") == "highlight") { nextSibling.getAttribute("zotero") == "highlight") {
// next node is highlighted // next node is highlighted
parent.removeChild(textNode); parent.removeChild(textNode);
nextSibling.firstChild.nodeValue = textNode.nodeValue + nextSibling.firstChild.nodeValue; nextSibling.insertBefore(textNode, nextSibling.firstChild);
return nextSibling; return nextSibling;
} }
@ -936,7 +989,7 @@ Zotero.Highlight.prototype._highlightTextNode = function(textNode) {
previousSibling.getAttribute("zotero") == "highlight") { previousSibling.getAttribute("zotero") == "highlight") {
// previous node is highlighted // previous node is highlighted
parent.removeChild(textNode); parent.removeChild(textNode);
previousSibling.firstChild.nodeValue += textNode.nodeValue; previousSibling.appendChild(textNode);
return previousSibling; return previousSibling;
} }