closes #477, un-highlight feature. please test this; it's quite likely there are bugs, since the code is nontrivial and Mozilla's DOM range code support is far from perfect.

This commit is contained in:
Simon Kornblith 2007-01-30 03:01:48 +00:00
parent 9197a8b4de
commit 90623de366
5 changed files with 245 additions and 115 deletions

View File

@ -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();

View File

@ -296,8 +296,9 @@
<!-- Annotation Toolbar -->
<toolbar id="zotero-annotate-tb" crop="end" insertbefore="content" hidden="true">
<toolbarbutton id="zotero-annotate-tb-add" tooltiptext="&zotero.annotate.toolbar.add.label;" oncommand="Zotero_Browser.toggleAnnotateMode();"/>
<toolbarbutton id="zotero-annotate-tb-highlight" tooltiptext="&zotero.annotate.toolbar.highlight.label;" oncommand="Zotero_Browser.toggleHighlightMode();"/>
<toolbarbutton id="zotero-annotate-tb-add" tooltiptext="&zotero.annotate.toolbar.add.label;" oncommand="Zotero_Browser.toggleMode(this.id);"/>
<toolbarbutton id="zotero-annotate-tb-highlight" tooltiptext="&zotero.annotate.toolbar.highlight.label;" oncommand="Zotero_Browser.toggleMode(this.id);"/>
<toolbarbutton id="zotero-annotate-tb-unhighlight" tooltiptext="&zotero.annotate.toolbar.unhighlight.label;" oncommand="Zotero_Browser.toggleMode(this.id);"/>
</toolbar>
</vbox>

View File

@ -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() {

View File

@ -94,4 +94,5 @@
<!ENTITY zotero.citation.line "Line">
<!ENTITY zotero.annotate.toolbar.add.label "Add Annotation">
<!ENTITY zotero.annotate.toolbar.highlight.label "Highlight Text">
<!ENTITY zotero.annotate.toolbar.highlight.label "Highlight Text">
<!ENTITY zotero.annotate.toolbar.unhighlight.label "Unhighlight Text">

View File

@ -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');
}