closes #440, In-page highlighting of snapshots

closes #441, In-page annotations of snapshots

testers: please check pages containing both annotations and highlights to make sure everything works correctly
This commit is contained in:
Simon Kornblith 2007-01-03 02:21:26 +00:00
parent 09920275ac
commit 70d06e02f2
18 changed files with 1667 additions and 597 deletions

View File

@ -5,6 +5,5 @@ locale zotero de-DE chrome/locale/de-DE/zotero/
skin zotero default chrome/skin/default/zotero/
overlay chrome://browser/content/browser.xul chrome://zotero/content/overlay.xul
overlay chrome://browser/content/browser.xul chrome://zotero/content/ingester/browser.xul
style chrome://browser/content/browser.xul chrome://zotero/skin/zotero.css
style chrome://global/content/customizeToolbar.xul chrome://zotero/skin/zotero.css

View File

@ -0,0 +1,766 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Based on code from Greasemonkey and PiggyBank
***** END LICENSE BLOCK *****
*/
//
// Zotero Ingester Browser Functions
//
//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Browser
//
//////////////////////////////////////////////////////////////////////////////
// Class to interface with the browser when ingesting data
var Zotero_Browser = new function() {
this.init = init;
this.scrapeThisPage = scrapeThisPage;
this.annotateThisPage = annotateThisPage;
this.toggleAnnotateMode = toggleAnnotateMode;
this.toggleHighlightMode = toggleHighlightMode;
this.chromeLoad = chromeLoad;
this.chromeUnload = chromeUnload;
this.contentLoad = contentLoad;
this.contentHide = contentHide;
this.tabSelect = tabSelect;
this.tabClose = tabClose;
this.resize = resize;
this.tabbrowser = null;
this.appcontent = null;
this.statusImage = null;
var _scrapePopupShowing = false;
var _annotateNextLoad = false;
var _browserData = new Object();
var _blacklist = [
"googlesyndication.com",
"doubleclick.net",
"questionmarket.com",
"atdmt.com"
];
//////////////////////////////////////////////////////////////////////////////
//
// Public Zotero_Browser methods
//
//////////////////////////////////////////////////////////////////////////////
/*
* Initialize some variables and prepare event listeners for when chrome is done
* loading
*/
function init() {
Zotero_Browser.browserData = new Object();
Zotero_Browser._scrapePopupShowing = false;
Zotero.Ingester.ProxyMonitor.init();
Zotero.Ingester.MIMEHandler.init();
Zotero.Translate.init();
window.addEventListener("load",
function(e) { Zotero_Browser.chromeLoad(e) }, false);
window.addEventListener("unload",
function(e) { Zotero_Browser.chromeUnload(e) }, false);
}
/*
* Scrapes a page (called when the capture icon is clicked); takes a collection
* ID as the argument
*/
function scrapeThisPage(saveLocation) {
_getTabObject(this.tabbrowser.selectedBrowser).translate(saveLocation);
}
/*
* flags a page for annotation
*/
function annotateThisPage(id) {
var tab = _getTabObject(this.tabbrowser.selectedBrowser);
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();
}
var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0];
var addElement = document.getElementById('zotero-annotate-tb-highlight');
if(addElement.getAttribute("tool-active")) {
body.style.cursor = "auto";
addElement.removeAttribute("tool-active");
Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener("mouseup", _addHighlight, true);
} else {
body.style.cursor = "text";
addElement.setAttribute("tool-active", "true");
Zotero_Browser.tabbrowser.selectedBrowser.addEventListener("mouseup", _addHighlight, true);
}
}
/*
* called to hide the collection selection popup
*/
function hidePopup(collectionID) {
_scrapePopupShowing = false;
}
/*
* called to show the collection selection popup
*/
function showPopup(collectionID, parentElement) {
if(_scrapePopupShowing && parentElement.hasChildNodes()) {
return false; // Don't dynamically reload popups that are already showing
}
_scrapePopupShowing = true;
parentElement.removeAllItems();
if(collectionID == null) { // show library
var newItem = document.createElement("menuitem");
newItem.setAttribute("label", Zotero.getString("pane.collections.library"));
newItem.setAttribute("class", "menuitem-iconic zotero-scrape-popup-library");
newItem.setAttribute("oncommand", 'Zotero_Browser.scrapeThisPage()');
parentElement.appendChild(newItem);
}
var childrenList = Zotero.getCollections(collectionID);
for(var i = 0; i < childrenList.length; i++) {
if(childrenList[i].hasChildCollections()) {
var newItem = document.createElement("menu");
var subMenu = document.createElement("menupopup");
subMenu.setAttribute("onpopupshowing", 'Zotero_Browser.showPopup("'+childrenList[i].getID()+'", this)');
newItem.setAttribute("class", "menu-iconic zotero-scrape-popup-collection");
newItem.appendChild(subMenu);
} else {
var newItem = document.createElement("menuitem");
newItem.setAttribute("class", "menuitem-iconic zotero-scrape-popup-collection");
}
newItem.setAttribute("label", childrenList[i].getName());
newItem.setAttribute("oncommand", 'Zotero_Browser.scrapeThisPage("'+childrenList[i].getID()+'")');
parentElement.appendChild(newItem);
}
return true;
}
/*
* When chrome loads, register our event handlers with the appropriate interfaces
*/
function chromeLoad() {
this.tabbrowser = document.getElementById("content");
this.appcontent = document.getElementById("appcontent");
this.statusImage = document.getElementById("zotero-status-image");
// this gives us onLocationChange, for updating when tabs are switched/created
this.tabbrowser.addEventListener("TabClose",
function(e) { Zotero_Browser.tabClose(e) }, false);
this.tabbrowser.addEventListener("TabSelect",
function(e) { Zotero_Browser.tabSelect(e) }, false);
// this is for pageshow, for updating the status of the book icon
this.appcontent.addEventListener("pageshow",
function(e) { Zotero_Browser.contentLoad(e) }, true);
// this is for turning off the book icon when a user navigates away from a page
this.appcontent.addEventListener("pagehide",
function(e) { Zotero_Browser.contentHide(e) }, true);
this.tabbrowser.addEventListener("resize",
function(e) { Zotero_Browser.resize(e) }, false);
}
/*
* When chrome unloads, delete our document objects
*/
function chromeUnload() {
delete Zotero_Browser.browserData;
}
/*
* An event handler called when a new document is loaded. Creates a new document
* object, and updates the status of the capture icon
*/
function contentLoad(event) {
var isHTML = event.originalTarget instanceof HTMLDocument;
if(isHTML) {
var doc = event.originalTarget;
var rootDoc = doc;
// get the appropriate root document to check which browser we're on
while(rootDoc.defaultView.frameElement) {
rootDoc = rootDoc.defaultView.frameElement.ownerDocument;
}
// ignore blacklisted domains
if(doc.domain) {
for each(var blacklistedURL in _blacklist) {
if(doc.domain.substr(doc.domain.length-blacklistedURL.length) == blacklistedURL) {
Zotero.debug("Ignoring blacklisted URL "+doc.location);
return;
}
}
}
}
// Figure out what browser this contentDocument is associated with
var browser;
for(var i=0; i<this.tabbrowser.browsers.length; i++) {
if(rootDoc == this.tabbrowser.browsers[i].contentDocument) {
browser = this.tabbrowser.browsers[i];
break;
}
}
if(!browser) return;
// get data object
var tab = _getTabObject(browser);
if(isHTML) {
if(tab.annotateNextLoad) {
// enable annotation
tab.page.annotations = new Zotero.Annotations(browser, tab.annotateID);
}
// detect translators
tab.detectTranslators(rootDoc, doc);
// update status
if(this.tabbrowser.selectedBrowser == browser) {
_updateStatus();
}
}
// clear annotateNextLoad
if(tab.annotateNextLoad) {
tab.annotateNextLoad = tab.annotateID = undefined;
}
}
/*
* called to unregister Zotero icon, etc.
*/
function contentHide(event) {
if(event.originalTarget instanceof HTMLDocument && !event.originalTarget.defaultView.frameElement) {
var doc = event.originalTarget;
// Figure out what browser this contentDocument is associated with
var browser;
for(var i=0; i<this.tabbrowser.browsers.length; i++) {
if(doc == this.tabbrowser.browsers[i].contentDocument) {
browser = this.tabbrowser.browsers[i];
break;
}
}
// clear data object
var tab = _getTabObject(browser);
// save annotations
if(tab.page.annotations) tab.page.annotations.save();
tab.clear();
// update status
if(this.tabbrowser.selectedBrowser == browser) {
_updateStatus();
}
}
}
/*
* called when a tab is closed
*/
function tabClose(event) {
// To execute if document object does not exist
_deleteTabObject(event.target.linkedBrowser);
_deselectTools();
}
/*
* called when a tab is switched
*/
function tabSelect(event) {
_updateStatus();
}
/*
* called when the window is resized
*/
function resize(event) {
var tab = _getTabObject(this.tabbrowser.selectedBrowser);
if(!tab.page.annotations) return;
tab.page.annotations.refresh();
}
//////////////////////////////////////////////////////////////////////////////
//
// Private Zotero_Browser methods
//
//////////////////////////////////////////////////////////////////////////////
/*
* Gets a data object given a browser window object
*
* NOTE: Browser objects are associated with document objects via keys generated
* from the time the browser object is opened. I'm not sure if this is the
* appropriate mechanism for handling this, but it's what PiggyBank used and it
* appears to work.
*
* Currently, the data object contains only one property: "translators," which
* is an array of translators that should work with the given page as returned
* from Zotero.Translate.getTranslator()
*/
function _getTabObject(browser) {
if(!browser) return false;
try {
var key = browser.getAttribute("zotero-key");
if(_browserData[key]) {
return _browserData[key];
}
} finally {
if(!key) {
var key = (new Date()).getTime();
browser.setAttribute("zotero-key", key);
return (_browserData[key] = new Zotero_Browser.Tab(browser));
}
}
return false;
}
/*
* Deletes the document object associated with a given browser window object
*/
function _deleteTabObject(browser) {
if(!browser) return false;
try {
var key = browser.getAttribute("zotero-key");
if(_browserData[key]) {
delete _browserData[key];
return true;
}
} finally {}
return false;
}
/*
* Updates the status of the capture icon to reflect the scrapability or lack
* thereof of the current page
*/
function _updateStatus() {
var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
var captureIcon = tab.getCaptureIcon();
if(captureIcon) {
Zotero_Browser.statusImage.src = captureIcon;
Zotero_Browser.statusImage.hidden = false;
} else {
Zotero_Browser.statusImage.hidden = true;
}
// set annotation bar status
if(tab.page.annotations) {
document.getElementById('zotero-annotate-tb').hidden = false;
_deselectTools();
} 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) {
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(selection.isCollapsed) return;
tab.page.annotations.createHighlight(selection.getRangeAt(0));
selection.removeAllRanges();
// stop propagation
e.stopPropagation();
e.preventDefault();
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Browser.Tab
//
//////////////////////////////////////////////////////////////////////////////
Zotero_Browser.Tab = function(browser) {
this.browser = browser;
this.page = new Object();
}
/*
* clears page-specific information
*/
Zotero_Browser.Tab.prototype.clear = function() {
delete this.page;
this.page = new Object();
}
/*
* detects translators for this browser object
*/
Zotero_Browser.Tab.prototype.detectTranslators = function(rootDoc, doc) {
// if there's already a scrapable page in the browser window, and it's
// still there, ensure it is actually part of the page, then return
if(this.page.translators && this.page.translators.length && this.page.document.location) {
if(this._searchFrames(rootDoc, this.page.document)) {
return;
} else {
this.page.document = null;
}
}
// get translators
this.page.translate = new Zotero.Translate("web");
this.page.translate.setDocument(doc);
this.page.translators = this.page.translate.getTranslators();
// add document
if(this.page.translators && this.page.translators.length) {
this.page.document = doc;
}
}
/*
* searches for a document in all of the frames of a given document
*/
Zotero_Browser.Tab.prototype._searchFrames = function(rootDoc, searchDoc) {
var frames = rootDoc.getElementsByTagName("frame");
for each(var frame in frames) {
if(frame.contentDocument &&
(frame.contentDocument == searchDoc ||
this._searchFrames(frame.contentDocument, searchDoc))) {
return true;
}
}
return false;
}
/*
* translate a page, saving in saveLocation
*/
Zotero_Browser.Tab.prototype.translate = function(saveLocation) {
if(this.page.translators && this.page.translators.length) {
Zotero_Browser.Progress.show();
if(saveLocation) {
saveLocation = Zotero.Collections.get(saveLocation);
} else { // save to currently selected collection, if a collection is selected
try {
saveLocation = ZoteroPane.getSelectedCollection();
} catch(e) {}
}
var me = this;
// use first translator available
this.page.translate.setTranslator(this.page.translators[0]);
this.page.translate.setHandler("select", me._selectItems);
this.page.translate.setHandler("itemDone", function(obj, item) { me._itemDone(obj, item, saveLocation) });
this.page.translate.setHandler("done", function(obj, item) { me._finishScraping(obj, item, saveLocation) });
this.page.translate.translate();
}
}
/*
* Callback to be executed when an item has been finished
*/
Zotero_Browser.Tab.prototype._itemDone = function(obj, item, collection) {
var title = item.getField("title");
var icon = item.getImageSrc();
Zotero_Browser.Progress.addLines([title], [icon]);
// add item to collection, if one was specified
if(collection) {
Zotero.Notifier.disable();
collection.addItem(item.getID());
Zotero.Notifier.enable();
}
}
/*
* called when a user is supposed to select items
*/
Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList) {
// this is kinda ugly, mozillazine made me do it! honest!
var io = { dataIn:itemList, dataOut:null }
var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
"_blank","chrome,modal,centerscreen,resizable=yes", io);
if(!io.dataOut) { // user selected no items, so kill the progress indicatior
Zotero_Browser.Progress.kill();
}
return io.dataOut;
}
/*
* Callback to be executed when scraping is complete
*/
Zotero_Browser.Tab.prototype._finishScraping = function(obj, returnValue, collection) {
if(!returnValue) {
Zotero_Browser.Progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
Zotero_Browser.Progress.addDescription(Zotero.getString("ingester.scrapeErrorDescription"));
}
if(collection) {
// notify about modified items
Zotero.Notifier.trigger("modify", "collection", collection.getID());
}
Zotero_Browser.Progress.fade();
}
/*
* returns the URL of the image representing the translator to be called on the
* current page, or false if the page cannot be scraped
*/
Zotero_Browser.Tab.prototype.getCaptureIcon = function() {
if(this.page.translators && this.page.translators.length) {
var itemType = this.page.translators[0].itemType;
if(itemType == "multiple") {
// Use folder icon for multiple types, for now
return "chrome://zotero/skin/treesource-collection.png";
} else {
return Zotero.ItemTypes.getImageSrc(itemType);
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Browser.Progress
//
//////////////////////////////////////////////////////////////////////////////
// Handles the display of a div showing progress in scraping
Zotero_Browser.Progress = new function() {
var _windowLoaded = false;
var _windowLoading = false;
// keep track of all of these things in case they're called before we're
// done loading the progress window
var _loadDescription = null;
var _loadLines = new Array();
var _loadIcons = new Array();
var _loadHeadline = Zotero.getString("ingester.scraping");
this.show = show;
this.changeHeadline = changeHeadline;
this.addLines = addLines;
this.addDescription = addDescription;
this.fade = fade;
this.kill = kill;
function show() {
if(_windowLoading || _windowLoaded) { // already loading or loaded
return false;
}
_progressWindow = window.openDialog("chrome://zotero/chrome/ingester/progress.xul",
"", "chrome,dialog=no,titlebar=no,popup=yes");
_progressWindow.addEventListener("load", _onWindowLoaded, false);
_windowLoading = true;
return true;
}
function changeHeadline(headline) {
if(_windowLoaded) {
_progressWindow.document.getElementById("zotero-progress-text-headline").value = headline;
} else {
_loadHeadline = headline;
}
}
function addLines(label, icon) {
if(_windowLoaded) {
for(i in label) {
var newLabel = _progressWindow.document.createElement("label");
newLabel.setAttribute("class", "zotero-progress-item-label");
newLabel.setAttribute("crop", "end");
newLabel.setAttribute("value", label[i]);
var newImage = _progressWindow.document.createElement("image");
newImage.setAttribute("class", "zotero-progress-item-icon");
newImage.setAttribute("src", icon[i]);
var newHB = _progressWindow.document.createElement("hbox");
newHB.setAttribute("class", "zotero-progress-item-hbox");
newHB.setAttribute("valign", "center");
newHB.appendChild(newImage);
newHB.appendChild(newLabel);
_progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
}
_move();
} else {
_loadLines = _loadLines.concat(label);
_loadIcons = _loadIcons.concat(icon);
}
}
function addDescription(text) {
if(_windowLoaded) {
var newHB = _progressWindow.document.createElement("hbox");
newHB.setAttribute("class", "zotero-progress-item-hbox");
var newDescription = _progressWindow.document.createElement("description");
newDescription.setAttribute("class", "zotero-progress-description");
var newText = _progressWindow.document.createTextNode(text);
newDescription.appendChild(newText);
newHB.appendChild(newDescription);
_progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
_move();
} else {
_loadDescription = text;
}
}
function fade() {
if(_windowLoaded || _windowLoading) {
setTimeout(_timeout, 2500);
}
}
function kill() {
_windowLoaded = false;
_windowLoading = false;
try {
_progressWindow.close();
} catch(ex) {}
}
function _onWindowLoaded() {
_windowLoading = false;
_windowLoaded = true;
_move();
// do things we delayed because the window was loading
changeHeadline(_loadHeadline);
addLines(_loadLines, _loadIcons);
if(_loadDescription) {
addDescription(_loadDescription);
}
// reset parameters
_loadDescription = null;
_loadLines = new Array();
_loadIcons = new Array();
_loadHeadline = Zotero.getString("ingester.scraping")
}
function _move() {
_progressWindow.sizeToContent();
_progressWindow.moveTo(
window.screenX + window.innerWidth - _progressWindow.outerWidth - 30,
window.screenY + window.innerHeight - _progressWindow.outerHeight - 10
);
}
function _timeout() {
kill(); // could check to see if we're really supposed to fade yet
// (in case multiple scrapers are operating at once)
}
}
Zotero_Browser.init();

View File

@ -1,548 +0,0 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Based on code from Greasemonkey and PiggyBank
***** END LICENSE BLOCK *****
*/
//
// Zotero Ingester Browser Functions
//
//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Ingester_Interface
//
//////////////////////////////////////////////////////////////////////////////
// Class to interface with the browser when ingesting data
var Zotero_Ingester_Interface = function() {}
Zotero_Ingester_Interface.blacklist = [
"googlesyndication.com",
"doubleclick.net",
"questionmarket.com",
"atdmt.com"
];
//////////////////////////////////////////////////////////////////////////////
//
// Public Zotero_Ingester_Interface methods
//
//////////////////////////////////////////////////////////////////////////////
/*
* Initialize some variables and prepare event listeners for when chrome is done
* loading
*/
Zotero_Ingester_Interface.init = function() {
Zotero_Ingester_Interface.browserData = new Object();
Zotero_Ingester_Interface._scrapePopupShowing = false;
Zotero.Ingester.ProxyMonitor.init();
Zotero.Ingester.MIMEHandler.init();
Zotero.Translate.init();
window.addEventListener("load", Zotero_Ingester_Interface.chromeLoad, false);
window.addEventListener("unload", Zotero_Ingester_Interface.chromeUnload, false);
}
/*
* When chrome loads, register our event handlers with the appropriate interfaces
*/
Zotero_Ingester_Interface.chromeLoad = function() {
Zotero_Ingester_Interface.tabBrowser = document.getElementById("content");
Zotero_Ingester_Interface.appContent = document.getElementById("appcontent");
Zotero_Ingester_Interface.statusImage = document.getElementById("zotero-status-image");
// this gives us onLocationChange, for updating when tabs are switched/created
Zotero_Ingester_Interface.tabBrowser.addEventListener("TabClose",
Zotero_Ingester_Interface.tabClose, false);
Zotero_Ingester_Interface.tabBrowser.addEventListener("TabSelect",
Zotero_Ingester_Interface.tabSelect, false);
// this is for pageshow, for updating the status of the book icon
Zotero_Ingester_Interface.appContent.addEventListener("pageshow",
Zotero_Ingester_Interface.contentLoad, true);
// this is for turning off the book icon when a user navigates away from a page
Zotero_Ingester_Interface.appContent.addEventListener("pagehide",
Zotero_Ingester_Interface.contentHide, true);
}
/*
* When chrome unloads, delete our document objects and remove our listeners
*/
Zotero_Ingester_Interface.chromeUnload = function() {
delete Zotero_Ingester_Interface.browserData;
}
/*
* Scrapes a page (called when the capture icon is clicked); takes a collection
* ID as the argument
*/
Zotero_Ingester_Interface.scrapeThisPage = function(saveLocation) {
var browser = Zotero_Ingester_Interface.tabBrowser.selectedBrowser;
var data = Zotero_Ingester_Interface._getData(browser);
if(data.translators && data.translators.length) {
Zotero_Ingester_Interface.Progress.show();
if(saveLocation) {
saveLocation = Zotero.Collections.get(saveLocation);
} else { // save to currently selected collection, if a collection is selected
try {
saveLocation = ZoteroPane.getSelectedCollection();
} catch(e) {}
}
var translate = new Zotero.Translate("web");
translate.setDocument(data.document);
// use first translator available
translate.setTranslator(data.translators[0]);
translate.setHandler("select", Zotero_Ingester_Interface._selectItems);
translate.setHandler("itemDone", function(obj, item) { Zotero_Ingester_Interface._itemDone(obj, item, saveLocation) });
translate.setHandler("done", function(obj, item) { Zotero_Ingester_Interface._finishScraping(obj, item, saveLocation) });
translate.translate();
}
}
Zotero_Ingester_Interface.searchFrames = function(rootDoc, searchDoc) {
var frames = rootDoc.getElementsByTagName("frame");
for each(var frame in frames) {
if(frame.contentDocument &&
(frame.contentDocument == searchDoc ||
Zotero_Ingester_Interface.searchFrames(frame.contentDocument, searchDoc))) {
return true;
}
}
return false;
}
/*
* An event handler called when a new document is loaded. Creates a new document
* object, and updates the status of the capture icon
*/
Zotero_Ingester_Interface.contentLoad = function(event) {
if(event.originalTarget instanceof HTMLDocument) {
var doc = event.originalTarget;
var rootDoc = doc;
try {
if (!doc.domain) {
return;
}
}
catch (e) {
return;
}
// get the appropriate root document to check which browser we're on
while(rootDoc.defaultView.frameElement) {
rootDoc = rootDoc.defaultView.frameElement.ownerDocument;
}
// Figure out what browser this contentDocument is associated with
var browser;
for(var i=0; i<Zotero_Ingester_Interface.tabBrowser.browsers.length; i++) {
if(rootDoc == Zotero_Ingester_Interface.tabBrowser.browsers[i].contentDocument) {
browser = Zotero_Ingester_Interface.tabBrowser.browsers[i];
break;
}
}
if(!browser) {
return;
}
// get data object
var data = Zotero_Ingester_Interface._getData(browser);
// if there's already a scrapable page in the browser window, and it's
// still there, ensure it is actually part of the page, then return
if(data.translators && data.translators.length && data.document.location) {
if(Zotero_Ingester_Interface.searchFrames(rootDoc, data.document)) {
return;
} else {
data.document = null;
}
}
for each(var blacklistedURL in Zotero_Ingester_Interface.blacklist) {
if(doc.domain.substr(doc.domain.length-blacklistedURL.length) == blacklistedURL) {
Zotero.debug("Ignoring blacklisted URL "+doc.location);
return;
}
}
// get translators
var translate = new Zotero.Translate("web");
translate.setDocument(doc);
data.translators = translate.getTranslators();
// update status
if(Zotero_Ingester_Interface.tabBrowser.selectedBrowser == browser) {
Zotero_Ingester_Interface._updateStatus(data);
}
// add document
if(data.translators && data.translators.length) {
data.document = doc;
}
}
}
/*
* called to unregister Zotero icon, etc.
*/
Zotero_Ingester_Interface.contentHide = function(event) {
if(event.originalTarget instanceof HTMLDocument && !event.originalTarget.defaultView.frameElement) {
var doc = event.originalTarget;
// Figure out what browser this contentDocument is associated with
var browser;
for(var i=0; i<Zotero_Ingester_Interface.tabBrowser.browsers.length; i++) {
if(doc == Zotero_Ingester_Interface.tabBrowser.browsers[i].contentDocument) {
browser = Zotero_Ingester_Interface.tabBrowser.browsers[i];
break;
}
}
// get data object
var data = Zotero_Ingester_Interface._getData(browser);
data.translators = false;
data.document = false;
// update status
if(Zotero_Ingester_Interface.tabBrowser.selectedBrowser == browser) {
Zotero_Ingester_Interface._updateStatus(data);
}
}
}
/*
* called when a tab is closed
*/
Zotero_Ingester_Interface.tabClose = function(event) {
// To execute if document object does not exist
Zotero_Ingester_Interface._deleteData(event.target.linkedBrowser);
}
/*
* called when a tab is switched
*/
Zotero_Ingester_Interface.tabSelect = function(event) {
var data = Zotero_Ingester_Interface._getData(Zotero_Ingester_Interface.tabBrowser.selectedBrowser);
Zotero_Ingester_Interface._updateStatus(data);
}
Zotero_Ingester_Interface.hidePopup = function(collectionID) {
Zotero_Ingester_Interface._scrapePopupShowing = false;
}
Zotero_Ingester_Interface.showPopup = function(collectionID, parentElement) {
if(Zotero_Ingester_Interface._scrapePopupShowing && parentElement.hasChildNodes()) {
return false; // Don't dynamically reload popups that are already showing
}
Zotero_Ingester_Interface._scrapePopupShowing = true;
parentElement.removeAllItems();
if(collectionID == null) { // show library
var newItem = document.createElement("menuitem");
newItem.setAttribute("label", Zotero.getString("pane.collections.library"));
newItem.setAttribute("class", "menuitem-iconic zotero-scrape-popup-library");
newItem.setAttribute("oncommand", 'Zotero_Ingester_Interface.scrapeThisPage()');
parentElement.appendChild(newItem);
}
var childrenList = Zotero.getCollections(collectionID);
for(var i = 0; i < childrenList.length; i++) {
if(childrenList[i].hasChildCollections()) {
var newItem = document.createElement("menu");
var subMenu = document.createElement("menupopup");
subMenu.setAttribute("onpopupshowing", 'Zotero_Ingester_Interface.showPopup("'+childrenList[i].getID()+'", this)');
newItem.setAttribute("class", "menu-iconic zotero-scrape-popup-collection");
newItem.appendChild(subMenu);
} else {
var newItem = document.createElement("menuitem");
newItem.setAttribute("class", "menuitem-iconic zotero-scrape-popup-collection");
}
newItem.setAttribute("label", childrenList[i].getName());
newItem.setAttribute("oncommand", 'Zotero_Ingester_Interface.scrapeThisPage("'+childrenList[i].getID()+'")');
parentElement.appendChild(newItem);
}
return true;
}
//////////////////////////////////////////////////////////////////////////////
//
// Private Zotero_Ingester_Interface methods
//
//////////////////////////////////////////////////////////////////////////////
/*
* Gets a data object given a browser window object
*
* NOTE: Browser objects are associated with document objects via keys generated
* from the time the browser object is opened. I'm not sure if this is the
* appropriate mechanism for handling this, but it's what PiggyBank used and it
* appears to work.
*
* Currently, the data object contains only one property: "translators," which
* is an array of translators that should work with the given page as returned
* from Zotero.Translate.getTranslator()
*/
Zotero_Ingester_Interface._getData = function(browser) {
if(!browser) return false;
try {
var key = browser.getAttribute("zotero-key");
if(Zotero_Ingester_Interface.browserData[key]) {
return Zotero_Ingester_Interface.browserData[key];
}
} finally {
if(!key) {
var key = (new Date()).getTime();
browser.setAttribute("zotero-key", key);
Zotero_Ingester_Interface.browserData[key] = new Array();
return Zotero_Ingester_Interface.browserData[key];
}
}
return false;
}
/*
* Deletes the document object associated with a given browser window object
*/
Zotero_Ingester_Interface._deleteData = function(browser) {
if(!browser) return false;
try {
var key = browser.getAttribute("zotero-key");
if(Zotero_Ingester_Interface.browserData[key]) {
delete Zotero_Ingester_Interface.browserData[key];
return true;
}
} finally {}
return false;
}
/*
* Updates the status of the capture icon to reflect the scrapability or lack
* thereof of the current page
*/
Zotero_Ingester_Interface._updateStatus = function(data) {
if(data.translators && data.translators.length) {
var itemType = data.translators[0].itemType;
if(itemType == "multiple") {
// Use folder icon for multiple types, for now
Zotero_Ingester_Interface.statusImage.src = "chrome://zotero/skin/treesource-collection.png";
} else {
Zotero_Ingester_Interface.statusImage.src = Zotero.ItemTypes.getImageSrc(itemType);
}
Zotero_Ingester_Interface.statusImage.hidden = false;
} else {
Zotero_Ingester_Interface.statusImage.hidden = true;
}
}
/*
* Callback to be executed when an item has been finished
*/
Zotero_Ingester_Interface._itemDone = function(obj, item, collection) {
var title = item.getField("title");
var icon = item.getImageSrc();
Zotero_Ingester_Interface.Progress.addLines([title], [icon]);
// add item to collection, if one was specified
if(collection) {
Zotero.Notifier.disable();
collection.addItem(item.getID());
Zotero.Notifier.enable();
}
}
/*
* called when a user is supposed to select items
*/
Zotero_Ingester_Interface._selectItems = function(obj, itemList) {
// this is kinda ugly, mozillazine made me do it! honest!
var io = { dataIn:itemList, dataOut:null }
var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
"_blank","chrome,modal,centerscreen,resizable=yes", io);
if(!io.dataOut) { // user selected no items, so kill the progress indicatior
Zotero_Ingester_Interface.Progress.kill();
}
return io.dataOut;
}
/*
* Callback to be executed when scraping is complete
*/
Zotero_Ingester_Interface._finishScraping = function(obj, returnValue, collection) {
if(!returnValue) {
Zotero_Ingester_Interface.Progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
Zotero_Ingester_Interface.Progress.addDescription(Zotero.getString("ingester.scrapeErrorDescription"));
}
if(collection) {
// notify about modified items
Zotero.Notifier.trigger("modify", "collection", collection.getID());
}
Zotero_Ingester_Interface.Progress.fade();
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero.Ingester.Progress
//
//////////////////////////////////////////////////////////////////////////////
// Handles the display of a div showing progress in scraping
Zotero_Ingester_Interface.Progress = new function() {
var _windowLoaded = false;
var _windowLoading = false;
// keep track of all of these things in case they're called before we're
// done loading the progress window
var _loadDescription = null;
var _loadLines = new Array();
var _loadIcons = new Array();
var _loadHeadline = Zotero.getString("ingester.scraping");
this.show = show;
this.changeHeadline = changeHeadline;
this.addLines = addLines;
this.addDescription = addDescription;
this.fade = fade;
this.kill = kill;
function show() {
if(_windowLoading || _windowLoaded) { // already loading or loaded
return false;
}
_progressWindow = window.openDialog("chrome://zotero/chrome/ingester/progress.xul",
"", "chrome,dialog=no,titlebar=no,popup=yes");
_progressWindow.addEventListener("load", _onWindowLoaded, false);
_windowLoading = true;
return true;
}
function changeHeadline(headline) {
if(_windowLoaded) {
_progressWindow.document.getElementById("zotero-progress-text-headline").value = headline;
} else {
_loadHeadline = headline;
}
}
function addLines(label, icon) {
if(_windowLoaded) {
for(i in label) {
var newLabel = _progressWindow.document.createElement("label");
newLabel.setAttribute("class", "zotero-progress-item-label");
newLabel.setAttribute("crop", "end");
newLabel.setAttribute("value", label[i]);
var newImage = _progressWindow.document.createElement("image");
newImage.setAttribute("class", "zotero-progress-item-icon");
newImage.setAttribute("src", icon[i]);
var newHB = _progressWindow.document.createElement("hbox");
newHB.setAttribute("class", "zotero-progress-item-hbox");
newHB.setAttribute("valign", "center");
newHB.appendChild(newImage);
newHB.appendChild(newLabel);
_progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
}
_move();
} else {
_loadLines = _loadLines.concat(label);
_loadIcons = _loadIcons.concat(icon);
}
}
function addDescription(text) {
if(_windowLoaded) {
var newHB = _progressWindow.document.createElement("hbox");
newHB.setAttribute("class", "zotero-progress-item-hbox");
var newDescription = _progressWindow.document.createElement("description");
newDescription.setAttribute("class", "zotero-progress-description");
var newText = _progressWindow.document.createTextNode(text);
newDescription.appendChild(newText);
newHB.appendChild(newDescription);
_progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
_move();
} else {
_loadDescription = text;
}
}
function fade() {
if(_windowLoaded || _windowLoading) {
setTimeout(_timeout, 2500);
}
}
function kill() {
_windowLoaded = false;
_windowLoading = false;
try {
_progressWindow.close();
} catch(ex) {}
}
function _onWindowLoaded() {
_windowLoading = false;
_windowLoaded = true;
_move();
// do things we delayed because the window was loading
changeHeadline(_loadHeadline);
addLines(_loadLines, _loadIcons);
if(_loadDescription) {
addDescription(_loadDescription);
}
// reset parameters
_loadDescription = null;
_loadLines = new Array();
_loadIcons = new Array();
_loadHeadline = Zotero.getString("ingester.scraping")
}
function _move() {
_progressWindow.sizeToContent();
_progressWindow.moveTo(
window.screenX + window.innerWidth - _progressWindow.outerWidth - 30,
window.screenY + window.innerHeight - _progressWindow.outerHeight - 10
);
}
function _timeout() {
kill(); // could check to see if we're really supposed to fade yet
// (in case multiple scrapers are operating at once)
}
}

View File

@ -1,40 +0,0 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***** END LICENSE BLOCK *****
-->
<overlay id="zotero-ingester-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="../include.js"/>
<script src="browser.js"/>
<script type="application/x-javascript">
Zotero_Ingester_Interface.init();
</script>
<hbox id="urlbar-icons">
<popupset>
<popup id="zotero-scrape-popup" onpopupshowing="Zotero_Ingester_Interface.showPopup(null, this)" onpopuphidden="Zotero_Ingester_Interface.hidePopup(null, this)">
</popup>
</popupset>
<image src="chrome://zotero/skin/treeitem-book.png" id="zotero-status-image" onclick="Zotero_Ingester_Interface.scrapeThisPage()" position="1" hidden="true" context="zotero-scrape-popup" />
</hbox>
</overlay>

View File

@ -42,7 +42,6 @@ var Zotero_Ingester_Interface_SelectItems = function() {}
*/
Zotero_Ingester_Interface_SelectItems.init = function() {
this.io = window.arguments[0];
this.Zotero_Ingester_Interface = window.arguments[1];
var listbox = document.getElementById("zotero-selectitems-links");
for(i in this.io.dataIn) { // we could use a tree for this if we wanted to

View File

@ -1285,6 +1285,8 @@ var ZoteroPane = new function()
if (internal || Zotero.MIME.fileHasInternalHandler(file))
{
// enable annotation
Zotero_Browser.annotateThisPage(attachment.getID());
window.loadURI(fileURL);
}
else {

View File

@ -26,13 +26,14 @@
<overlay id="zotero"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<?xul-overlay href="itemPane.xul" ?>
<!-- Include the global XPCOM object -->
<script src="include.js"/>
<script src="overlay.js"/>
<script src="fileInterface.js"/>
<script src="reportInterface.js"/>
<script src="browser.js"/>
<commandset id="mainCommandSet">
<command id="cmd_zotero_search" oncommand="ZoteroPane.search();"/>
@ -268,8 +269,24 @@
</groupbox>
</vbox>
</hbox>
<!-- 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();"/>
</toolbar>
</vbox>
<!-- Scrape Code -->
<hbox id="urlbar-icons">
<popupset>
<popup id="zotero-scrape-popup" onpopupshowing="Zotero_Browser.showPopup(null, this)" onpopuphidden="Zotero_Browser.hidePopup(null, this)">
</popup>
</popupset>
<image src="chrome://zotero/skin/treeitem-book.png" id="zotero-status-image" onclick="Zotero_Browser.scrapeThisPage()" position="1" hidden="true" context="zotero-scrape-popup" />
</hbox>
<statusbar id="status-bar">
<statusbarpanel id="zotero-status-bar-icon" hidden="true"
class="statusbarpanel-iconic" onclick="ZoteroPane.toggleDisplay();"/>

View File

@ -0,0 +1,822 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***** END LICENSE BLOCK *****
*/
//////////////////////////////////////////////////////////////////////////////
//
// Zotero.Annotate
//
//////////////////////////////////////////////////////////////////////////////
// general purpose annotation/highlighting methods
Zotero.Annotate = new function() {
this.annotationColor = "#fff580";
this.annotationBarColor = "#c0b860";
this.annotationBorderColor = "#878244";
this.highlightColor = "#fff580";
this.getPathForPoint = getPathForPoint;
this.getPointForPath = getPointForPath;
this.getPixelOffset = getPixelOffset;
var textType = Components.interfaces.nsIDOMNode.TEXT_NODE;
/*
* gets a path object, comprising an XPath, text node index, and offset, for
* a given node.
*/
function getPathForPoint(node, offset) {
Zotero.debug("have node of offset "+offset);
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
var sibling = node.previousSibling;
while(sibling) {
if(child.nodeType == textType) path.offset += child.nodeValue.length;
sibling = sibling.previousSibling;
}
// use parent node for future purposes
node = node.parentNode;
}
if(lastWasTextNode) {
path.textNode = 1;
var sibling = node.previousSibling;
var first = true;
while(sibling) {
var isZotero = undefined;
if(sibling.getAttribute) isZotero = sibling.getAttribute("zotero");
if(sibling.nodeType == textType ||
(isZotero == "highlight")) {
// is a text node
if(first == true) {
// is still part of the first text node
if(sibling.getAttribute) {
// get offset of all child nodes
for each(var child in sibling.childNodes) {
if(child.nodeType == textType) path.offset += child.nodeValue.length;
}
} else {
path.offset += sibling.nodeValue.length;
}
} else if(!lastWasTextNode) {
// is part of another text node
path.textNode++;
lastWasTextNode = true;
}
} else if(!isZotero) { // skip over annotation marker nodes
// is not a text node
lastWasTextNode = first = false;
}
sibling = sibling.previousSibling;
}
node = node.parentNode;
}
var doc = node.ownerDocument;
while(node && node != doc) {
var number = 1;
var sibling = node.previousSibling;
while(sibling) {
if(sibling.tagName == node.tagName) number++;
sibling = sibling.previousSibling;
}
// don't add highlight nodes
var tag = node.tagName.toLowerCase();
if(tag == "span") {
tag += "[not(@zotero)]";
}
path.parent = "/"+tag+"["+number+"]"+path.parent;
node = node.parentNode;
}
Zotero.debug("Annotate: got path "+path.parent+", "+path.textNode+", "+path.offset);
return path;
}
function getPointForPath(parent, textNode, offset, document, nsResolver) {
var point = {offset:0};
// try to evaluate parent
try {
point.node = document.evaluate(parent, document, nsResolver,
Components.interfaces.nsIDOMXPathResult.ANY_TYPE, null).iterateNext();
} catch(e) {
Zotero.debug("Annotate: could not find XPath "+parent+" in getPointForPath");
return false;
}
// don't do further processing if this path does not refer to a text node
if(!textNode) return point;
// parent node must have children if we have a text node index
if(!point.node.firstChild) {
Zotero.debug("Annotate: node "+parent+" has no children in getPointForPath");
return false;
}
point.node = point.node.firstChild;
point.offset = offset;
var lastWasTextNode = false;
var number = 0;
// find text node
while(true) {
var isZotero = undefined;
if(point.node.getAttribute) isZotero = point.node.getAttribute("zotero");
if(point.node.nodeType == textType ||
isZotero == "highlight") {
if(!lastWasTextNode) {
number++;
// if we found the node we're looking for, break
if(number == textNode) break;
lastWasTextNode = true;
}
} else if(!isZotero) {
lastWasTextNode = false;
}
point.node = point.node.nextSibling;
// if there's no node, this point is invalid
if(!point.node) {
Zotero.debug("Annotate: reached end of node list while searching for text node "+textNode+" of "+parent);
return false;
}
}
// find point.offset
while(true) {
// get length of enclosed text node
if(point.node.getAttribute) {
// this is a highlighted node; loop through and subtract all
// offsets, breaking if we reach the end
var parentNode = point.node;
point.node = point.node.firstChild;
while(point.node) {
if(point.node.nodeType == textType) {
// break if end condition reached
if(point.node.nodeValue.length >= point.offset) return point;
// otherwise, continue subtracting offsets
point.offset -= point.node.nodeValue.length;
}
point.node = point.node.nextSibling;
}
// restore parent node
point.node = parentNode;
} else {
// this is not a highlighted node; use simple node length
if(point.node.nodeValue.length >= point.offset) return point;
point.offset -= point.node.nodeValue.length;
}
// get next node
point.node = point.node.nextSibling;
// if next node does not exist or is not a text node, this
// point is invalid
if(!point.node || (point.node.nodeType != textType &&
(!point.node.getAttribute || !point.node.getAttribute("zotero")))) {
Zotero.debug("Annotate: could not find point.offset "+point.offset+" for text node "+textNode+" of "+parent);
return false;
}
}
}
/*
* gets the pixel offset of an item from the top left of a page. the
* optional "offset" argument specifies a text offset.
*/
function getPixelOffset(node, offset) {
var x = 0;
var y = 0;
do {
x += node.offsetLeft;
y += node.offsetTop;
node = node.offsetParent;
} while(node);
return [x, y];
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero.Annotations
//
//////////////////////////////////////////////////////////////////////////////
// a set of annotations to correspond to a given page
Zotero.Annotations = function(browser, itemID) {
this.browser = browser;
this.document = browser.contentDocument;
this.window = browser.contentWindow;
this.nsResolver = this.document.createNSResolver(this.document.documentElement);
this.itemID = itemID;
this.annotations = new Array();
this.highlights = new Array();
this.zIndex = 100;
this.load();
}
Zotero.Annotations.prototype.createAnnotation = function() {
var annotation = new Zotero.Annotation(this);
this.annotations.push(annotation);
return annotation;
}
Zotero.Annotations.prototype.createHighlight = function(selectedRange) {
var deleteHighlights = new Array();
var startIn = false, endIn = false;
// 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);
if(startToStart != 1 && endToEnd != -1) {
// if the selected range is inside this one
return compareHighlight;
} else if(startToStart != -1 && endToEnd != 1) {
// if this range is inside selected range, delete
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) {
// if the end of the selected range is between the start and
// end of this range
var endIn = i;
} else {
var startToEnd = compareRange.compareBoundaryPoints(Components.interfaces.nsIDOMRange.START_TO_END, selectedRange);
if(startToEnd != -1 && startToStart != 1) {
// if the start of the selected range is between the
// start and end of this range
var startIn = i;
}
}
}
}
if(startIn !== false && endIn !== false) {
selectedRange.setStart(this.highlights[startIn].range.endContainer,
this.highlights[startIn].range.endOffset);
selectedRange.setEnd(this.highlights[endIn].range.endContainer,
this.highlights[endIn].range.endOffset);
this.highlights[startIn].initWithRange(selectedRange);
// delete end range
this.highlights[endIn] = undefined;
delete this.highlights[endIn];
return startIn;
} else if(startIn !== false) {
selectedRange.setStart(this.highlights[startIn].range.startContainer,
this.highlights[startIn].range.startOffset);
this.highlights[startIn].initWithRange(selectedRange);
return this.highlights[startIn];
} else if(endIn != false) {
selectedRange.setEnd(this.highlights[endIn].range.endContainer,
this.highlights[endIn].range.endOffset);
this.highlights[endIn].initWithRange(selectedRange);
return this.highlights[endIn];
}
var highlight = new Zotero.Highlight(this);
highlight.initWithRange(selectedRange);
this.highlights.push(highlight);
return highlight;
}
Zotero.Annotations.prototype.refresh = function() {
for each(var annotation in this.annotations) {
annotation.display();
}
}
Zotero.Annotations.prototype.save = function() {
Zotero.DB.beginTransaction();
try {
Zotero.DB.query("DELETE FROM highlights WHERE itemID = ?", [this.itemID]);
// save highlights
for each(var highlight in this.highlights) {
if(highlight) highlight.save();
}
// save annotations
for each(var annotation in this.annotations) {
annotation.save();
}
Zotero.DB.commitTransaction();
} catch(e) {
Zotero.DB.rollbackTransaction();
}
}
Zotero.Annotations.prototype.load = function() {
// load annotations
var rows = Zotero.DB.query("SELECT * FROM annotations WHERE itemID = ?", [this.itemID]);
for each(var row in rows) {
var annotation = this.createAnnotation();
annotation.initWithDBRow(row);
}
// load highlights
var rows = Zotero.DB.query("SELECT * FROM highlights WHERE itemID = ?", [this.itemID]);
for each(var row in rows) {
var highlight = new Zotero.Highlight(this);
highlight.initWithDBRow(row);
this.highlights.push(highlight);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero.Annotation
//
//////////////////////////////////////////////////////////////////////////////
// an annotation (usually generated using Zotero.Annotations.createAnnotation())
Zotero.Annotation = function(annotationsObj) {
this.annotationsObj = annotationsObj;
this.window = annotationsObj.browser.contentWindow;
this.document = annotationsObj.browser.contentDocument;
this.nsResolver = annotationsObj.nsResolver;
}
Zotero.Annotation.prototype.initWithEvent = function(e) {
var maxOffset = false;
try {
var range = this.window.getSelection().getRangeAt(0);
this.node = range.startContainer;
var offset = range.startOffset;
if(this.node.nodeValue) maxOffset = this.node.nodeValue.length;
} catch(err) {
this.node = e.target;
var offset = 0;
}
var clickX = this.window.pageXOffset + e.clientX;
var clickY = this.window.pageYOffset + e.clientY;
var isTextNode = (this.node.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE);
if(offset == 0 || !isTextNode) {
// tag by this.offset from parent this.node, rather than text
if(isTextNode) this.node = this.node.parentNode;
offset = 0;
}
if(offset) this._generateMarker(offset);
var pixelOffset = Zotero.Annotate.getPixelOffset(this.node);
this.x = clickX - pixelOffset[0];
this.y = clickY - pixelOffset[1];
this.editable = true;
Zotero.debug("Annotate: added new annotation");
this.displayWithAbsoluteCoordinates(clickX, clickY);
}
Zotero.Annotation.prototype.initWithDBRow = function(row) {
var point = Zotero.Annotate.getPointForPath(row.parent, row.textNode,
row.offset, this.document, this.nsResolver);
if(!point) {
Zotero.debug("Annotate: could not load annotation "+row.annotationID+" from DB");
return;
}
this.node = point.node;
if(point.offset) this._generateMarker(point.offset);
this.x = row.x;
this.y = row.y;
this.annotationID = row.annotationID;
this.editable = true;
this.display();
this.textarea.value = row.text;
}
Zotero.Annotation.prototype.save = function() {
var text = this.textarea.value;
if(this.annotationID) {
// already in the DB; all we need to do is update the text
var query = "UPDATE annotations SET text = ? WHERE annotationID = ?";
var parameters = [
text,
this.annotationID
];
} else {
// fetch marker location
if(this.node.getAttribute && this.node.getAttribute("zotero") == "annotation-marker") {
var node = this.node.previousSibling;
if(node.nodeType != Components.interfaces.nsIDOMNode.TEXT_NODE) {
// someone added a highlight around this annotation
node = node.lastChild;
}
var offset = node.nodeValue.length;
} else {
var node = this.node;
var offset = 0;
}
// fetch path to node
var path = Zotero.Annotate.getPathForPoint(node, offset);
var query = "INSERT INTO annotations VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)";
var parameters = [
this.annotationsObj.itemID, // itemID
path.parent, // parent
path.textNode, // textNode
path.offset, // offset
this.x, // x
this.y, // y
text // text
];
}
Zotero.DB.query(query, parameters);
}
Zotero.Annotation.prototype.display = function() {
if(!this.node) throw "Annotation not initialized!";
var x = 0, y = 0;
// first fetch the coordinates
var pixelOffset = Zotero.Annotate.getPixelOffset(this.node);
var x = pixelOffset[0] + this.x;
var y = pixelOffset[1] + this.y;
// then display
this.displayWithAbsoluteCoordinates(x, y);
}
Zotero.Annotation.prototype.displayWithAbsoluteCoordinates = function(absX, absY) {
if(!this.node) throw "Annotation not initialized!";
var startScroll = this.window.scrollMaxX;
if(!this.div) {
this.div = this.document.createElement("div");
this.div.setAttribute("zotero", "annotation");
this.document.getElementsByTagName("body")[0].appendChild(this.div);
this.div.style.backgroundColor = Zotero.Annotate.annotationColor;
this.div.style.padding = "0";
this.div.style.display = "block";
this.div.style.position = "absolute";
this.div.style.border = "1px solid";
this.div.style.borderColor = Zotero.Annotate.annotationBorderColor;
this.div.style.MozOpacity = 0.9;
this.div.style.zIndex = this.annotationsObj.zIndex;
var me = this;
this.div.addEventListener("click", function() { me._click() }, false);
this._addChildElements();
}
this.div.style.display = "block";
this.div.style.left = absX+"px";
this.div.style.top = absY+"px";
// move to the left if we're making things scroll
if(absX + this.div.scrollWidth > this.window.innerWidth) {
this.div.style.left = (absX-this.div.scrollWidth)+"px";
}
}
Zotero.Annotation.prototype._generateMarker = function(offset) {
// first, we create a new span at the correct offset in the node
var range = this.document.createRange();
range.setStart(this.node, offset);
range.setEnd(this.node, offset);
// next, we insert a span
this.node = this.document.createElement("span");
this.node.setAttribute("zotero", "annotation-marker");
range.insertNode(this.node);
}
Zotero.Annotation.prototype._addChildElements = function() {
var me = this;
if(this.editable) {
var div = this.document.createElement("div");
div.style.display = "block";
div.style.textAlign = "left";
div.style.backgroundColor = Zotero.Annotate.annotationBarColor;
div.style.paddingRight = "0";
div.style.paddingLeft = div.style.paddingTop = div.style.paddingBottom = "1px";
div.style.borderBottom = "1px solid";
div.style.borderColor = Zotero.Annotate.annotationBorderColor;
var img = this.document.createElement("img");
img.src = "chrome://zotero/skin/annotation-close.png";
img.addEventListener("click", function() { me._delete() }, false);
div.appendChild(img);
this.textarea = this.document.createElement("textarea");
this.textarea.setAttribute("zotero", "annotation");
this.textarea.setAttribute("cols", "30");
this.textarea.setAttribute("rows", "5");
this.textarea.setAttribute("wrap", "soft");
this.textarea.style.fontFamily = "Arial, Lucida Grande, FreeSans, sans";
this.textarea.style.fontSize = "12px";
this.textarea.style.backgroundColor = Zotero.Annotate.annotationColor;
this.textarea.style.border = "none";
this.textarea.style.margin = "3px";
this.div.appendChild(div);
this.div.appendChild(this.textarea);
var me = this;
}
}
Zotero.Annotation.prototype._click = function() {
this.annotationsObj.zIndex++
this.div.style.zIndex = this.annotationsObj.zIndex;
}
Zotero.Annotation.prototype._delete = function() {
if(this.annotationID) {
Zotero.DB.query("DELETE FROM annotations WHERE annotationID = ?", [this.annotationID]);
}
// hide div
this.div.parentNode.removeChild(this.div);
// delete from list
for(var i in this.annotationsObj.annotations) {
if(this.annotationsObj.annotations[i] == this) {
this.annotationsObj.annotations.splice(i, 1);
}
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Zotero.Highlight
//
//////////////////////////////////////////////////////////////////////////////
// a highlight (usually generated using Zotero.Annotations.createHighlight())
Zotero.Highlight = function(annotationsObj) {
this.annotationsObj = annotationsObj;
this.window = annotationsObj.browser.contentWindow;
this.document = annotationsObj.browser.contentDocument;
this.nsResolver = annotationsObj.nsResolver;
this.spans = new Array();
}
Zotero.Highlight.prototype.initWithDBRow = function(row) {
Zotero.debug(row.startParent);
var start = Zotero.Annotate.getPointForPath(row.startParent, row.startTextNode,
row.startOffset, this.document, this.nsResolver);
var end = Zotero.Annotate.getPointForPath(row.endParent, row.endTextNode,
row.endOffset, this.document, this.nsResolver);
if(!start || !end) {
Zotero.debug("Highlight: could not initialize from DB row");
return false;
}
this.range = this.document.createRange();
this.range.setStart(start.node, start.offset);
this.range.setEnd(end.node, end.offset);
this._highlight();
}
Zotero.Highlight.prototype.initWithRange = function(range) {
this.range = range;
this._highlight();
}
Zotero.Highlight.prototype.save = function(index) {
var textType = Components.interfaces.nsIDOMNode.TEXT_NODE;
var start = Zotero.Annotate.getPathForPoint(this.range.startContainer, this.range.startOffset);
var end = Zotero.Annotate.getPathForPoint(this.range.endContainer, this.range.endOffset);
var query = "INSERT INTO highlights VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)";
var parameters = [
this.annotationsObj.itemID, // itemID
start.parent, // startParent
start.textNode, // startTextNode
start.offset, // startOffset
end.parent, // endParent
end.textNode, // endTextNode
end.offset // endOffset
];
Zotero.DB.query(query, parameters);
}
Zotero.Highlight.prototype.remove = function() {
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);
}
this.spans = new Array();
}
Zotero.Highlight.prototype._highlight = function() {
var startNode = this.range.startContainer;
var endNode = this.range.endContainer;
var ancestor = this.range.commonAncestorContainer;
var onlyOneNode = startNode.isSameNode(endNode);
if(!onlyOneNode) {
// highlight nodes after start node in the DOM hierarchy not at ancestor level
while(!startNode.parentNode.isSameNode(ancestor)) {
if(startNode.nextSibling) {
this._highlightSpaceBetween(startNode.nextSibling, startNode.parentNode.lastChild);
}
startNode = startNode.parentNode;
}
// highlight nodes after end node in the DOM hierarchy not at ancestor level
while(!endNode.parentNode.isSameNode(ancestor)) {
if(endNode.previousSibling) {
this._highlightSpaceBetween(endNode.parentNode.firstChild, endNode.previousSibling);
}
endNode = endNode.parentNode;
}
// highlight nodes between start node and end node at ancestor level
if(!startNode.isSameNode(endNode.previousSibling)) {
this._highlightSpaceBetween(startNode.nextSibling, endNode.previousSibling);
}
}
// split the end off the existing node
if(this.range.endContainer.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE && this.range.endOffset != 0) {
if(this.range.endOffset != this.range.endContainer.nodeValue) {
var textNode = this.range.endContainer.splitText(this.range.endOffset);
}
if(!onlyOneNode) {
this._highlightTextNode(this.range.endContainer);
}
if(textNode) this.range.setEnd(textNode, 0);
}
// split the start off of the first node
if(this.range.startContainer.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE) {
if(this.range.startOffset == 0) {
var highlightNode = this.range.startContainer;
} else {
var highlightNode = this.range.startContainer.splitText(this.range.startOffset);
}
var span = this._highlightTextNode(highlightNode);
this.range.setStart(span.firstChild, 0);
} else {
this._highlightSpaceBetween(this.range.startContainer, this.range.startContainer);
}
}
Zotero.Highlight.prototype._highlightTextNode = function(textNode) {
var parent = textNode.parentNode;
if(parent.getAttribute("zotero") == "highlight") {
// already highlighted
return parent;
}
var nextSibling = textNode.nextSibling;
if(nextSibling && nextSibling.getAttribute &&
nextSibling.getAttribute("zotero") == "highlight") {
// next node is highlighted
parent.removeChild(textNode);
nextSibling.firstChild.nodeValue = textNode.nodeValue + nextSibling.firstChild.nodeValue;
return nextSibling;
}
var previousSibling = textNode.previousSibling;
if(previousSibling && previousSibling.getAttribute &&
previousSibling.getAttribute("zotero") == "highlight") {
// previous node is highlighted
parent.removeChild(textNode);
previousSibling.firstChild.nodeValue += textNode.nodeValue;
return previousSibling;
}
var span = this.document.createElement("span");
span.setAttribute("zotero", "highlight");
span.style.display = "inline";
span.style.backgroundColor = Zotero.Annotate.highlightColor;
parent.removeChild(textNode);
span.appendChild(textNode);
parent.insertBefore(span, (nextSibling ? nextSibling : null));
this.spans.push(span);
return span;
}
Zotero.Highlight.prototype._highlightSpaceBetween = function(start, end) {
var meaningfulRe = /[^\s\r\n]/;
var node = start;
var text;
while(node) {
// process nodes
if(node.nodeType == Components.interfaces.nsIDOMNode.TEXT_NODE) {
var textArray = [node];
} else {
var texts = this.document.evaluate('.//text()', node, this.nsResolver,
Components.interfaces.nsIDOMXPathResult.ANY_TYPE, null);
var textArray = new Array()
while(text = texts.iterateNext()) textArray.push(text);
}
// do this in the middle, after we're finished with node but before we
// add any spans
if(node.isSameNode(end)) {
node = false;
} else {
node = node.nextSibling;
}
for each(var textNode in textArray) {
this._highlightTextNode(textNode);
}
}
}

View File

@ -697,7 +697,7 @@ Zotero.Ingester.MIMEHandler.StreamListener = function(request, contentType) {
var windowWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
getService(Components.interfaces.nsIWindowWatcher);
this._frontWindow = windowWatcher.activeWindow;
this._frontWindow.Zotero_Ingester_Interface.Progress.show();
this._frontWindow.Zotero_Browser.Progress.show();
Zotero.debug("EndNote prepared to grab content type "+contentType);
}
@ -749,15 +749,15 @@ Zotero.Ingester.MIMEHandler.StreamListener.prototype.onStopRequest = function(ch
try {
saveLocation = frontWindow.ZoteroPane.getSelectedCollection();
} catch(e) {}
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Ingester_Interface._itemDone(obj, item, saveLocation) });
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Ingester_Interface._finishScraping(obj, item, saveLocation) });
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Browser._itemDone(obj, item, saveLocation) });
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Browser._finishScraping(obj, item, saveLocation) });
// attempt to retrieve translators
var translators = translation.getTranslators();
if(!translators.length) {
// we lied. we can't really translate this file. call
// nsIExternalHelperAppService with the data
this._frontWindow.Zotero_Ingester_Interface.Progress.kill();
this._frontWindow.Zotero_Browser.Progress.kill();
var streamListener;
if(streamListener = externalHelperAppService.doContent(this._contentType, this._request, this._frontWindow)) {

View File

@ -86,4 +86,7 @@
<!ENTITY zotero.citation.page "Page">
<!ENTITY zotero.citation.paragraph "Paragraph">
<!ENTITY zotero.citation.line "Line">
<!ENTITY zotero.citation.line "Line">
<!ENTITY zotero.annotate.toolbar.add.label "Add Annotation">
<!ENTITY zotero.annotate.toolbar.highlight.label "Highlight Text">

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

View File

@ -225,4 +225,24 @@
min-height: 4px;
max-height: 4px;
background: #f5f5f5 !important;
}
#zotero-annotate-tb-add
{
list-style-image: url('chrome://zotero/skin/annotate-add.png');
}
#zotero-annotate-tb-highlight
{
list-style-image: url('chrome://zotero/skin/annotate-highlight.png');
}
#zotero-annotate-tb-add[tool-active=true]
{
list-style-image: url('chrome://zotero/skin/annotate-add-selected.png');
}
#zotero-annotate-tb-highlight[tool-active=true]
{
list-style-image: url('chrome://zotero/skin/annotate-highlight-selected.png');
}

View File

@ -94,6 +94,10 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/progressWindow.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/annotate.js");
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://global/content/nsTransferable.js");

View File

@ -1,4 +1,4 @@
-- 13
-- 14
-- This file creates tables containing user-specific data -- any changes
-- to existing tables made here must be mirrored in transition steps in
@ -222,3 +222,29 @@ CREATE TABLE IF NOT EXISTS csl (
title TEXT,
csl TEXT
);
CREATE TABLE IF NOT EXISTS annotations (
annotationID INTEGER PRIMARY KEY,
itemID INT,
parent TEXT,
textNode INT,
offset INT,
x INT,
y INT,
text TEXT,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
);
CREATE INDEX IF NOT EXISTS annotations_itemID ON annotations(itemID);
CREATE TABLE IF NOT EXISTS highlights (
highlightID INTEGER PRIMARY KEY,
itemID INTEGER,
startParent TEXT,
startTextNode INT,
startOffset INT,
endParent TEXT,
endTextNode INT,
endOffset INT,
FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
);
CREATE INDEX IF NOT EXISTS highlights_itemID ON highlights(itemID);