diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js index 6ee4e0b01a..786cf4dfb0 100644 --- a/chrome/content/zotero/browser.js +++ b/chrome/content/zotero/browser.js @@ -40,8 +40,7 @@ var Zotero_Browser = new function() { this.init = init; this.scrapeThisPage = scrapeThisPage; this.annotateThisPage = annotateThisPage; - this.toggleAnnotateMode = toggleAnnotateMode; - this.toggleHighlightMode = toggleHighlightMode; + this.toggleMode = toggleMode; this.chromeLoad = chromeLoad; this.chromeUnload = chromeUnload; this.contentLoad = contentLoad; @@ -66,6 +65,24 @@ var Zotero_Browser = new function() { "questionmarket.com", "atdmt.com" ]; + + var tools = { + 'zotero-annotate-tb-add':{ + cursor:"pointer", + event:"click", + callback:function(e) { _add("annotation", e) } + }, + 'zotero-annotate-tb-highlight':{ + cursor:"text", + event:"mouseup", + callback:function(e) { _add("highlight", e) } + }, + 'zotero-annotate-tb-unhighlight':{ + cursor:"text", + event:"mouseup", + callback:function(e) { _add("unhighlight", e) } + } + }; ////////////////////////////////////////////////////////////////////////////// // @@ -107,48 +124,31 @@ var Zotero_Browser = new function() { tab.annotateNextLoad = true; tab.annotateID = id; } - - /* - * toggles the "add annotation" button - */ - function toggleAnnotateMode() { - if(document.getElementById('zotero-annotate-tb-highlight').getAttribute("tool-active")) { - toggleHighlightMode(); - } - - var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0]; - var addElement = document.getElementById('zotero-annotate-tb-add'); - - if(addElement.getAttribute("tool-active")) { - body.style.cursor = "auto"; - addElement.removeAttribute("tool-active"); - Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener("click", _addAnnotation, true); - } else { - body.style.cursor = "pointer"; - addElement.setAttribute("tool-active", "true"); - Zotero_Browser.tabbrowser.selectedBrowser.addEventListener("click", _addAnnotation, true); - } - } - /* - * toggles the "higlight" button - */ - function toggleHighlightMode() { - if(document.getElementById('zotero-annotate-tb-add').getAttribute("tool-active")) { - toggleAnnotateMode(); + function toggleMode(toggleTool, ignoreOtherTools) { + // make sure other tools are turned off + if(!ignoreOtherTools) { + for(var tool in tools) { + if(tool != toggleTool && document.getElementById(tool).getAttribute("tool-active")) { + toggleMode(tool, true); + } + } } + if(!toggleTool) return; + var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0]; - var addElement = document.getElementById('zotero-annotate-tb-highlight'); + var addElement = document.getElementById(toggleTool); if(addElement.getAttribute("tool-active")) { + // turn off body.style.cursor = "auto"; addElement.removeAttribute("tool-active"); - Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener("mouseup", _addHighlight, true); + Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener(tools[toggleTool].event, tools[toggleTool].callback, true); } else { - body.style.cursor = "text"; + body.style.cursor = tools[toggleTool].cursor; addElement.setAttribute("tool-active", "true"); - Zotero_Browser.tabbrowser.selectedBrowser.addEventListener("mouseup", _addHighlight, true); + Zotero_Browser.tabbrowser.selectedBrowser.addEventListener(tools[toggleTool].event, tools[toggleTool].callback, true); } } @@ -326,7 +326,7 @@ var Zotero_Browser = new function() { function tabClose(event) { // To execute if document object does not exist _deleteTabObject(event.target.linkedBrowser); - _deselectTools(); + toggleMode(null); } /* @@ -442,59 +442,43 @@ var Zotero_Browser = new function() { // set annotation bar status if(tab.page.annotations) { document.getElementById('zotero-annotate-tb').hidden = false; - _deselectTools(); + toggleMode(); } else { document.getElementById('zotero-annotate-tb').hidden = true; } } - /* - * Deselects annotation tools - */ - function _deselectTools() { - if(document.getElementById('zotero-annotate-tb-add').getAttribute("tool-active")) { - toggleAnnotateMode(); - } - if(document.getElementById('zotero-annotate-tb-highlight').getAttribute("tool-active")) { - toggleHighlightMode(); - } - } - /* * adds an annotation */ - function _addAnnotation(e) { + function _add(type, e) { var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); - // ignore click if it's on an existing annotation - if(e.target.getAttribute("zotero-annotation")) return; - - var annotation = tab.page.annotations.createAnnotation(); - annotation.initWithEvent(e); - - // stop propagation - e.stopPropagation(); - e.preventDefault(); - - // disable add mode, now that we've used it - toggleAnnotateMode(); - } - - /* - * adds a highlight - */ - function _addHighlight(e) { - var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); - - try { - var selection = Zotero_Browser.tabbrowser.selectedBrowser.contentWindow.getSelection(); - } catch(err) { - return; + if(type == "annotation") { + // ignore click if it's on an existing annotation + if(e.target.getAttribute("zotero-annotation")) return; + + var annotation = tab.page.annotations.createAnnotation(); + annotation.initWithEvent(e); + + // disable add mode, now that we've used it + toggleMode(); + } else { + try { + var selection = Zotero_Browser.tabbrowser.selectedBrowser.contentWindow.getSelection(); + } catch(err) { + return; + } + if(selection.isCollapsed) return; + + if(type == "highlight") { + tab.page.annotations.createHighlight(selection.getRangeAt(0)); + } else if(type == "unhighlight") { + tab.page.annotations.unhighlight(selection.getRangeAt(0)); + } + + selection.removeAllRanges(); } - if(selection.isCollapsed) return; - - tab.page.annotations.createHighlight(selection.getRangeAt(0)); - selection.removeAllRanges(); // stop propagation e.stopPropagation(); diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul index 548613c180..a02abecb9f 100644 --- a/chrome/content/zotero/overlay.xul +++ b/chrome/content/zotero/overlay.xul @@ -296,8 +296,9 @@ diff --git a/chrome/content/zotero/xpcom/annotate.js b/chrome/content/zotero/xpcom/annotate.js index 3ef86be084..e4dc78a5b8 100644 --- a/chrome/content/zotero/xpcom/annotate.js +++ b/chrome/content/zotero/xpcom/annotate.js @@ -49,18 +49,27 @@ Zotero.Annotate = new function() { var path = {parent:"", textNode:null, offset:(offset ? offset : null)}; var lastWasTextNode = node.nodeType == textType; - - // if the selected point is inside a highlight node - if(node.parentNode.getAttribute && node.parentNode.getAttribute("zotero")) { - // add offsets of preceding text nodes in this zotero node + + if(node.parentNode.getAttribute && node.parentNode.getAttribute("zotero")) { + // if the selected point is inside a highlight node, add offsets of + // preceding text nodes in this zotero node var sibling = node.previousSibling; while(sibling) { - if(child.nodeType == textType) path.offset += child.nodeValue.length; + if(sibling.nodeType == textType) path.offset += sibling.nodeValue.length; sibling = sibling.previousSibling; } // use parent node for future purposes node = node.parentNode; + } else if(node.getAttribute && node.getAttribute("zotero")) { + // if selected point is a zotero node, move it to the last character + // of the previous node + node = node.previousSibling; + if(node.nodeType == textType) { + offset = node.nodeValue.length; + } else { + offset = 0; + } } if(lastWasTextNode) { @@ -329,6 +338,87 @@ Zotero.Annotations.prototype.createHighlight = function(selectedRange) { return highlight; } +Zotero.Annotations.prototype.unhighlight = function(selectedRange) { + // first, see if part of this range is already covered + for(var i in this.highlights) { + var compareHighlight = this.highlights[i]; + var compareRange = compareHighlight.range; + + var startToStart = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.START_TO_START, selectedRange); + var endToEnd = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.END_TO_END, selectedRange); + + var done = false; + + if(startToStart == -1 && endToEnd == 1) { + Zotero.debug("checkpoint 1"); + // there's a bug in Mozilla's handling of ranges + var selectStartPoint = Zotero.Annotate.getPathForPoint(selectedRange.startContainer, selectedRange.startOffset); + var compareStartPoint = Zotero.Annotate.getPathForPoint(compareRange.startContainer, compareRange.startOffset); + if(selectStartPoint.parent == compareStartPoint.parent && + selectStartPoint.textNode == compareStartPoint.textNode && + selectStartPoint.offset == compareStartPoint.offset) { + startToStart = 0; + } else { + var selectEndPoint = Zotero.Annotate.getPathForPoint(selectedRange.endContainer, selectedRange.endOffset); + var compareEndPoint = Zotero.Annotate.getPathForPoint(compareRange.endContainer, compareRange.endOffset); + if(selectEndPoint.parent == compareEndPoint.parent && + selectEndPoint.textNode == compareEndPoint.textNode && + selectEndPoint.offset == compareEndPoint.offset) { + endToEnd = 0; + } else { + // this will unhighlight the entire end + compareHighlight.unhighlight(selectedRange.startContainer, selectedRange.startOffset, 2); + + // need to use point references because they disregard highlights + var newRange = this.document.createRange(); + var startPoint = Zotero.Annotate.getPointForPath(selectEndPoint.parent, selectEndPoint.textNode, selectEndPoint.offset, + this.document, this.nsResolver); + var endPoint = Zotero.Annotate.getPointForPath(compareEndPoint.parent, compareEndPoint.textNode, compareEndPoint.offset, + this.document, this.nsResolver); + newRange.setStart(startPoint.node, startPoint.offset); + newRange.setEnd(endPoint.node, endPoint.offset); + + // create new node + var highlight = new Zotero.Highlight(this); + highlight.initWithRange(newRange); + this.highlights.push(highlight); + + done = true; + } + } + } + + if(!done) { + if(startToStart != -1 && endToEnd != 1) { + Zotero.debug("checkpoint 2"); + // if this range is inside selected range, delete + compareHighlight.unhighlight(null, null, 0); + + this.highlights[i] = undefined; + delete this.highlights[i]; + } else { + var endToStart = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.END_TO_START, selectedRange); + if(endToStart != 1 && endToEnd != -1) { + Zotero.debug("checkpoint 3"); + // if the end of the selected range is between the start and + // end of this range + //compareRange.setStart(selectedRange.endContainer, selectedRange.endOffset); + compareHighlight.unhighlight(selectedRange.endContainer, selectedRange.endOffset, 1); + } else { + var startToEnd = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.START_TO_END, selectedRange); + if(startToEnd != -1 && startToStart != 1) { + Zotero.debug("checkpoint 4"); + // if the start of the selected range is between the + // start and end of this range + //compareRange.setEnd(selectedRange.startContainer, selectedRange.startOffset); + compareHighlight.unhighlight(selectedRange.startContainer, selectedRange.startOffset, 2); + } + } + } + } + } +} + Zotero.Annotations.prototype.refresh = function() { for each(var annotation in this.annotations) { annotation.display(); @@ -660,40 +750,84 @@ Zotero.Highlight.prototype.save = function(index) { Zotero.DB.query(query, parameters); } -Zotero.Highlight.prototype.remove = function() { +/** + * Un-highlights a range. + * + * mode can be: + * 0: unhighlight all + * 1: unhighlight from start to point + * 2: unhighlight from point to end + **/ +Zotero.Highlight.prototype.unhighlight = function(container, offset, mode) { var textType = Components.interfaces.nsIDOMNode.TEXT_NODE; - for each(var span in this.spans) { - var parentNode = span.parentNode; - - // deal with split text nodes - if(span.childNodes.length == 1 && span.previousSibling && span.nextSibling && - textType == span.previousSibling.nodeType == span.firstChild.nodeType == span.nextSibling.nodeType) { - span.previousSibling.nodeValue += span.firstChild.nodeValue + span.nextSibling.nodeValue; - span.removeChild(span.firstChild); - parentNode.removeChild(span.nextSibling); - } else if(span.previousSibling && - textType == span.firstChild.nodeType == span.previousSibling.nodeType) { - span.previousSibling.nodeValue += span.firstChild.nodeValue; - span.removeChild(span.firstChild); - } else if(span.nextSibling && - textType == span.lastChild.nodeType == span.nextSibling.nodeType) { - span.nextSibling.nodeValue = span.lastChild.nodeValue + span.nextSibling.nodeValue; - span.removeChild(span.lastChild); - } - - // attach child nodes before - while(span.firstChild) { - var child = span.firstChild; - span.removeChild(child); - parentNode.insertBefore(child, span); - } - - // remove span from tree - parentNode.removeChild(span); + if(mode == 1) { + this.range.setStart(container, offset); + } else if(mode == 2) { + this.range.setEnd(container, offset); } - this.spans = new Array(); + for(var i in this.spans) { + var span = this.spans[i]; + var parentNode = span.parentNode; + + if(mode != 0 && span.isSameNode(container.parentNode) && offset != 0) { + if(mode == 1) { + // split text node + var textNode = container.splitText(offset); + this.range.setStart(textNode, 0); + + if(span.nextSibling && span.nextSibling.nodeType == span.lastChild == textType) { + // attach last node to next text node if possible + span.nextSibling.nodeValue = span.lastChild.nodeValue + span.nextSibling.nodeValue; + span.removeChild(span.lastChild); + } + + // loop through, removing nodes + var node = span.firstChild; + + while(span.firstChild && !span.firstChild.isSameNode(textNode)) { + parentNode.insertBefore(span.removeChild(span.firstChild), span); + } + } else if(mode == 2) { + // split text node + var textNode = container.splitText(offset); + + if(span.previousSibling && span.previousSibling.nodeType == span.firstChild == textType) { + // attach last node to next text node if possible + span.previousSibling.nodeValue += span.firstChild.nodeValue; + span.removeChild(span.firstChild); + } + + // loop through, removing nodes + var node = textNode; + var child = node; + + while(node) { + child = node; + node = node.nextSibling; + + span.removeChild(child); + parentNode.insertBefore(child, span.nextSibling); + } + + this.range.setEnd(textNode, 0); + } + } else if(mode == 0 || !this.range.isPointInRange(span, 1)) { + Zotero.debug("point is in range"); + + // attach child nodes before + while(span.hasChildNodes()) { + Zotero.debug("moving "+span.firstChild.textContent); + span.parentNode.insertBefore(span.removeChild(span.firstChild), span); + } + + // remove span from DOM + span.parentNode.removeChild(span); + } + + parentNode.normalize(); + } } Zotero.Highlight.prototype._highlight = function() { diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index a30f57dbfe..92b92e2814 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -94,4 +94,5 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css index f68d8747e8..523ad7bf6e 100644 --- a/chrome/skin/default/zotero/overlay.css +++ b/chrome/skin/default/zotero/overlay.css @@ -257,6 +257,11 @@ list-style-image: url('chrome://zotero/skin/annotate-highlight.png'); } +#zotero-annotate-tb-unhighlight +{ + list-style-image: url('chrome://zotero/skin/annotate-unhighlight.png'); +} + #zotero-annotate-tb-add[tool-active=true] { list-style-image: url('chrome://zotero/skin/annotate-add-selected.png'); @@ -265,4 +270,9 @@ #zotero-annotate-tb-highlight[tool-active=true] { list-style-image: url('chrome://zotero/skin/annotate-highlight-selected.png'); +} + +#zotero-annotate-tb-unhighlight[tool-active=true] +{ + list-style-image: url('chrome://zotero/skin/annotate-unhighlight-selected.png'); } \ No newline at end of file