zotero/chrome/content/zotero/browser.js

639 lines
18 KiB
JavaScript

/*
***** 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.annotatePage = annotatePage;
this.toggleMode = toggleMode;
this.chromeLoad = chromeLoad;
this.chromeUnload = chromeUnload;
this.contentLoad = contentLoad;
this.contentHide = contentHide;
this.tabClose = tabClose;
this.resize = resize;
this.updateStatus = updateStatus;
this.finishScraping = finishScraping;
this.itemDone = itemDone;
this.tabbrowser = null;
this.appcontent = null;
this.statusImage = null;
var _scrapePopupShowing = false;
var _browserData = new Object();
var _blacklist = [
"googlesyndication.com",
"doubleclick.net",
"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) }
}
};
//////////////////////////////////////////////////////////////////////////////
//
// Public Zotero_Browser methods
//
//////////////////////////////////////////////////////////////////////////////
/*
* Initialize some variables and prepare event listeners for when chrome is done
* loading
*/
function init() {
if (!Zotero || !Zotero.initialized) {
return;
}
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 annotatePage(id, browser) {
if (browser) {
var tab = _getTabObject(browser);
}
else {
var tab = _getTabObject(this.tabbrowser.selectedBrowser);
}
tab.annotateNextLoad = true;
tab.annotateID = id;
}
/*
* toggles a tool on/off
*/
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);
}
}
}
// make sure annotation action is toggled
var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
if(tab.page.annotations.clearAction) tab.page.annotations.clearAction();
if(!toggleTool) return;
var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0];
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(tools[toggleTool].event, tools[toggleTool].callback, true);
} else {
body.style.cursor = tools[toggleTool].cursor;
addElement.setAttribute("tool-active", "true");
Zotero_Browser.tabbrowser.selectedBrowser.addEventListener(tools[toggleTool].event, tools[toggleTool].callback, 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.updateStatus() }, 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(this, browser, tab.annotateID);
}
// detect translators
tab.detectTranslators(rootDoc, doc);
}
// 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);
if(!tab) return;
// save annotations
if(tab.page && 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);
toggleMode(null);
}
/*
* called when the window is resized
*/
function resize(event) {
var tab = _getTabObject(this.tabbrowser.selectedBrowser);
if(!tab.page.annotations) return;
tab.page.annotations.refresh();
}
/*
* 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;
toggleMode();
} else {
document.getElementById('zotero-annotate-tb').hidden = true;
}
}
/*
* Callback to be executed when scraping is complete
*/
function finishScraping(obj, returnValue, collection) {
if(!returnValue) {
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
// Include link to Known Translator Issues page
var url = "http://www.zotero.org/documentation/known_translator_issues";
var linkText = '<a href="' + url + '" tooltiptext="' + url + '">'
+ Zotero.getString('ingester.scrapeErrorDescription.linkText') + '</a>';
Zotero_Browser.progress.addDescription(Zotero.getString("ingester.scrapeErrorDescription", linkText));
}
Zotero_Browser.progress.fade();
}
/*
* Callback to be executed when an item has been finished
*/
function itemDone(obj, item, collection) {
var title = item.getField("title");
var icon = item.getImageSrc();
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scraping"));
Zotero_Browser.progress.addLines([title], [icon]);
// add item to collection, if one was specified
if(collection) {
collection.addItem(item.getID());
}
}
//////////////////////////////////////////////////////////////////////////////
//
// 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;
}
/*
* adds an annotation
*/
function _add(type, e) {
var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
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();
}
// 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
var me = this;
var translate = new Zotero.Translate("web");
translate.setDocument(doc);
translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
var translators = translate.getTranslators();
}
/*
* 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;
if(!this.page.hasBeenTranslated) {
// 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) { Zotero_Browser.itemDone(obj, item, saveLocation) });
this.page.translate.setHandler("done", function(obj, item) { Zotero_Browser.finishScraping(obj, item, saveLocation) });
this.page.hasBeenTranslated = true;
}
this.page.translate.translate();
}
}
/*
* 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;
}
/**********CALLBACKS**********/
/*
* 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;
}
/*
* called when translators are available
*/
Zotero_Browser.Tab.prototype._translatorsAvailable = function(translate, translators) {
if(translators && translators.length) {
this.page.translate = translate;
this.page.translators = translators;
this.page.document = translate.document;
}
Zotero_Browser.updateStatus();
}
// Handles the display of a div showing progress in scraping
Zotero_Browser.progress = new Zotero.ProgressWindow();
Zotero_Browser.init();