fileInterface.js (26210B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 Components.utils.import("resource://gre/modules/osfile.jsm") 27 28 /****Zotero_File_Exporter**** 29 ** 30 * A class to handle exporting of items, collections, or the entire library 31 **/ 32 33 /** 34 * Constructs a new Zotero_File_Exporter with defaults 35 **/ 36 var Zotero_File_Exporter = function() { 37 this.name = Zotero.getString("fileInterface.exportedItems"); 38 this.collection = false; 39 this.items = false; 40 } 41 42 /** 43 * Performs the actual export operation 44 * 45 * @return {Promise} 46 **/ 47 Zotero_File_Exporter.prototype.save = Zotero.Promise.coroutine(function* () { 48 var translation = new Zotero.Translate.Export(); 49 var translators = yield translation.getTranslators(); 50 51 // present options dialog 52 var io = {translators:translators} 53 window.openDialog("chrome://zotero/content/exportOptions.xul", 54 "_blank", "chrome,modal,centerscreen,resizable=no", io); 55 if(!io.selectedTranslator) { 56 return false; 57 } 58 59 const nsIFilePicker = Components.interfaces.nsIFilePicker; 60 var fp = Components.classes["@mozilla.org/filepicker;1"] 61 .createInstance(nsIFilePicker); 62 fp.init(window, Zotero.getString("fileInterface.export"), nsIFilePicker.modeSave); 63 64 // set file name and extension 65 if(io.displayOptions.exportFileData) { 66 // if the result will be a folder, don't append any extension or use 67 // filters 68 fp.defaultString = this.name; 69 fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll); 70 } else { 71 // if the result will be a file, append an extension and use filters 72 fp.defaultString = this.name+(io.selectedTranslator.target ? "."+io.selectedTranslator.target : ""); 73 fp.defaultExtension = io.selectedTranslator.target; 74 fp.appendFilter(io.selectedTranslator.label, "*."+(io.selectedTranslator.target ? io.selectedTranslator.target : "*")); 75 } 76 77 var rv = fp.show(); 78 if (rv != nsIFilePicker.returnOK && rv != nsIFilePicker.returnReplace) { 79 return; 80 } 81 82 if(this.collection) { 83 translation.setCollection(this.collection); 84 } else if(this.items) { 85 translation.setItems(this.items); 86 } else if(this.libraryID === undefined) { 87 throw new Error('No export configured'); 88 } else { 89 translation.setLibraryID(this.libraryID); 90 } 91 92 translation.setLocation(fp.file); 93 translation.setTranslator(io.selectedTranslator); 94 translation.setDisplayOptions(io.displayOptions); 95 translation.setHandler("itemDone", function () { 96 Zotero.updateZoteroPaneProgressMeter(translation.getProgress()); 97 }); 98 translation.setHandler("done", this._exportDone); 99 Zotero_File_Interface.Progress.show( 100 Zotero.getString("fileInterface.itemsExported") 101 ); 102 translation.translate() 103 }); 104 105 /* 106 * Closes the items exported indicator 107 */ 108 Zotero_File_Exporter.prototype._exportDone = function(obj, worked) { 109 Zotero_File_Interface.Progress.close(); 110 111 if(!worked) { 112 Zotero.alert( 113 null, 114 Zotero.getString('general.error'), 115 Zotero.getString("fileInterface.exportError") 116 ); 117 } 118 } 119 120 /****Zotero_File_Interface**** 121 ** 122 * A singleton to interface with ZoteroPane to provide export/bibliography 123 * capabilities 124 **/ 125 var Zotero_File_Interface = new function() { 126 var _unlock; 127 128 this.exportCollection = exportCollection; 129 this.exportItemsToClipboard = exportItemsToClipboard; 130 this.exportItems = exportItems; 131 this.bibliographyFromItems = bibliographyFromItems; 132 133 /** 134 * Creates Zotero.Translate instance and shows file picker for file export 135 * 136 * @return {Promise} 137 */ 138 this.exportFile = Zotero.Promise.method(function () { 139 var exporter = new Zotero_File_Exporter(); 140 exporter.libraryID = ZoteroPane_Local.getSelectedLibraryID(); 141 if (exporter.libraryID === false) { 142 throw new Error('No library selected'); 143 } 144 exporter.name = Zotero.Libraries.getName(exporter.libraryID); 145 return exporter.save(); 146 }); 147 148 /* 149 * exports a collection or saved search 150 */ 151 function exportCollection() { 152 var exporter = new Zotero_File_Exporter(); 153 154 var collection = ZoteroPane_Local.getSelectedCollection(); 155 if(collection) { 156 exporter.name = collection.getName(); 157 exporter.collection = collection; 158 } else { 159 // find sorted items 160 exporter.items = ZoteroPane_Local.getSortedItems(); 161 if(!exporter.items) throw ("No items to save"); 162 163 // find name 164 var search = ZoteroPane_Local.getSelectedSavedSearch(); 165 if(search) { 166 exporter.name = search.name; 167 } 168 } 169 exporter.save(); 170 } 171 172 173 /* 174 * exports items 175 */ 176 function exportItems() { 177 var exporter = new Zotero_File_Exporter(); 178 179 exporter.items = ZoteroPane_Local.getSelectedItems(); 180 if(!exporter.items || !exporter.items.length) throw("no items currently selected"); 181 182 exporter.save(); 183 } 184 185 /* 186 * exports items to clipboard 187 */ 188 function exportItemsToClipboard(items, translatorID) { 189 var translation = new Zotero.Translate.Export(); 190 translation.setItems(items); 191 translation.setTranslator(translatorID); 192 translation.setHandler("done", _copyToClipboard); 193 translation.translate(); 194 } 195 196 /* 197 * handler when done exporting items to clipboard 198 */ 199 function _copyToClipboard(obj, worked) { 200 if(!worked) { 201 Zotero.alert( 202 null, Zotero.getString('general.error'), Zotero.getString("fileInterface.exportError") 203 ); 204 } else { 205 Components.classes["@mozilla.org/widget/clipboardhelper;1"] 206 .getService(Components.interfaces.nsIClipboardHelper) 207 .copyString(obj.string.replace(/\r\n/g, "\n")); 208 } 209 } 210 211 212 this.getMendeleyDirectory = function () { 213 Components.classes["@mozilla.org/net/osfileconstantsservice;1"] 214 .getService(Components.interfaces.nsIOSFileConstantsService) 215 .init(); 216 var path = OS.Constants.Path.homeDir; 217 if (Zotero.isMac) { 218 path = OS.Path.join(path, 'Library', 'Application Support', 'Mendeley Desktop'); 219 } 220 else if (Zotero.isWin) { 221 path = OS.Path.join(path, 'AppData', 'Local', 'Mendeley Ltd', 'Mendeley Desktop'); 222 } 223 else if (Zotero.isLinux) { 224 path = OS.Path.join(path, '.local', 'share', 'data', 'Mendeley Ltd.', 'Mendeley Desktop'); 225 } 226 else { 227 throw new Error("Invalid platform"); 228 } 229 return path; 230 }; 231 232 233 this.findMendeleyDatabases = async function () { 234 var dbs = []; 235 try { 236 var dir = this.getMendeleyDirectory(); 237 if (!await OS.File.exists(dir)) { 238 Zotero.debug(`${dir} does not exist`); 239 return dbs; 240 } 241 await Zotero.File.iterateDirectory(dir, function* (iterator) { 242 while (true) { 243 let entry = yield iterator.next(); 244 if (entry.isDir) continue; 245 // online.sqlite, counterintuitively, is the default database before you sign in 246 if (entry.name == 'online.sqlite' || entry.name.endsWith('@www.mendeley.com.sqlite')) { 247 dbs.push({ 248 name: entry.name, 249 path: entry.path, 250 lastModified: null, 251 size: null 252 }); 253 } 254 } 255 }); 256 for (let i = 0; i < dbs.length; i++) { 257 let dbPath = OS.Path.join(dir, dbs[i].name); 258 let info = await OS.File.stat(dbPath); 259 dbs[i].size = info.size; 260 dbs[i].lastModified = info.lastModificationDate; 261 } 262 dbs.sort((a, b) => { 263 return b.lastModified - a.lastModified; 264 }); 265 } 266 catch (e) { 267 Zotero.logError(e); 268 } 269 return dbs; 270 }; 271 272 273 this.showImportWizard = function () { 274 var libraryID = Zotero.Libraries.userLibraryID; 275 try { 276 let zp = Zotero.getActiveZoteroPane(); 277 libraryID = zp.getSelectedLibraryID(); 278 } 279 catch (e) { 280 Zotero.logError(e); 281 } 282 var args = { 283 libraryID 284 }; 285 args.wrappedJSObject = args; 286 287 Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xul", 288 "importFile", "chrome,dialog=yes,centerscreen,width=600,height=400", args); 289 }; 290 291 292 /** 293 * Creates Zotero.Translate instance and shows file picker for file import 294 * 295 * @param {Object} options 296 * @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker 297 * @param {Boolean} [options.addToLibraryRoot=false] 298 * @param {Boolean} [options.createNewCollection=true] - Put items in a new collection 299 * @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful 300 * for displaying progress in a different way. This also causes an error to be throw 301 * instead of shown in the main window. 302 */ 303 this.importFile = Zotero.Promise.coroutine(function* (options = {}) { 304 if (!options) { 305 options = {}; 306 } 307 if (typeof options == 'string' || options instanceof Components.interfaces.nsIFile) { 308 Zotero.debug("WARNING: importFile() now takes a single options object -- update your code"); 309 options = { 310 file: options, 311 createNewCollection: arguments[1] 312 }; 313 } 314 315 var file = options.file ? Zotero.File.pathToFile(options.file) : null; 316 var createNewCollection = options.createNewCollection; 317 var addToLibraryRoot = options.addToLibraryRoot; 318 var onBeforeImport = options.onBeforeImport; 319 320 if (createNewCollection === undefined && !addToLibraryRoot) { 321 createNewCollection = true; 322 } 323 else if (!createNewCollection) { 324 try { 325 if (!ZoteroPane.collectionsView.editable) { 326 ZoteroPane.collectionsView.selectLibrary(null); 327 } 328 } catch(e) {} 329 } 330 331 var defaultNewCollectionPrefix = Zotero.getString("fileInterface.imported"); 332 333 var translation; 334 // Check if the file is an SQLite database 335 var sample = yield Zotero.File.getSample(file.path); 336 if (file.path == Zotero.DataDirectory.getDatabase()) { 337 // Blacklist the current Zotero database, which would cause a hang 338 } 339 else if (Zotero.MIME.sniffForMIMEType(sample) == 'application/x-sqlite3') { 340 // Mendeley import doesn't use the real translation architecture, but we create a 341 // translation object with the same interface 342 translation = yield _getMendeleyTranslation(); 343 translation.createNewCollection = createNewCollection; 344 defaultNewCollectionPrefix = Zotero.getString( 345 'fileInterface.appImportCollection', 'Mendeley' 346 ); 347 } 348 else if (file.path.endsWith('@www.mendeley.com.sqlite') 349 || file.path.endsWith('online.sqlite')) { 350 // Keep in sync with importWizard.js 351 throw new Error('Encrypted Mendeley database'); 352 } 353 354 if (!translation) { 355 translation = new Zotero.Translate.Import(); 356 } 357 translation.setLocation(file); 358 return _finishImport({ 359 translation, 360 createNewCollection, 361 addToLibraryRoot, 362 defaultNewCollectionPrefix, 363 onBeforeImport 364 }); 365 }); 366 367 368 /** 369 * Imports from clipboard 370 */ 371 this.importFromClipboard = Zotero.Promise.coroutine(function* () { 372 var str = Zotero.Utilities.Internal.getClipboard("text/unicode"); 373 if(!str) { 374 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 375 .getService(Components.interfaces.nsIPromptService); 376 ps.alert( 377 null, 378 Zotero.getString('general.error'), 379 Zotero.getString('fileInterface.importClipboardNoDataError') 380 ); 381 } 382 383 var translation = new Zotero.Translate.Import(); 384 translation.setString(str); 385 386 try { 387 if (!ZoteroPane.collectionsView.editable) { 388 yield ZoteroPane.collectionsView.selectLibrary(); 389 } 390 } catch(e) {} 391 392 yield _finishImport({ 393 translation, 394 createNewCollection: false 395 }); 396 397 // Select imported items 398 try { 399 if (translation.newItems) { 400 ZoteroPane.itemsView.selectItems(translation.newItems.map(item => item.id)); 401 } 402 } 403 catch (e) { 404 Zotero.logError(e, 2); 405 } 406 }); 407 408 409 var _finishImport = Zotero.Promise.coroutine(function* (options) { 410 var t = performance.now(); 411 412 var translation = options.translation; 413 var addToLibraryRoot = options.addToLibraryRoot; 414 var createNewCollection = options.createNewCollection; 415 var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix; 416 var onBeforeImport = options.onBeforeImport; 417 418 if (addToLibraryRoot && createNewCollection) { 419 throw new Error("Can't add to library root and create new collection"); 420 } 421 422 var showProgressWindow = !onBeforeImport; 423 424 let translators = yield translation.getTranslators(); 425 426 // Unrecognized file 427 if (!translators.length) { 428 if (onBeforeImport) { 429 yield onBeforeImport(false); 430 } 431 432 let ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 433 .getService(Components.interfaces.nsIPromptService); 434 let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK 435 + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING; 436 let index = ps.confirmEx( 437 null, 438 Zotero.getString('general.error'), 439 Zotero.getString("fileInterface.unsupportedFormat"), 440 buttonFlags, 441 null, 442 Zotero.getString("fileInterface.viewSupportedFormats"), 443 null, null, {} 444 ); 445 if (index == 1) { 446 Zotero.launchURL("https://www.zotero.org/support/kb/importing"); 447 } 448 return false; 449 } 450 451 var libraryID = Zotero.Libraries.userLibraryID; 452 var importCollection = null; 453 try { 454 let zp = Zotero.getActiveZoteroPane(); 455 libraryID = zp.getSelectedLibraryID(); 456 if (addToLibraryRoot) { 457 yield zp.collectionsView.selectLibrary(libraryID); 458 } 459 else if (!createNewCollection) { 460 importCollection = zp.getSelectedCollection(); 461 } 462 } 463 catch (e) { 464 Zotero.logError(e); 465 } 466 467 if(createNewCollection) { 468 // Create a new collection to take imported items 469 let collectionName; 470 if(translation.location instanceof Components.interfaces.nsIFile) { 471 let leafName = translation.location.leafName; 472 collectionName = (translation.location.isDirectory() || leafName.indexOf(".") === -1 ? leafName 473 : leafName.substr(0, leafName.lastIndexOf("."))); 474 let allCollections = Zotero.Collections.getByLibrary(libraryID); 475 for(var i=0; i<allCollections.length; i++) { 476 if(allCollections[i].name == collectionName) { 477 collectionName += " "+(new Date()).toLocaleString(); 478 break; 479 } 480 } 481 } 482 else { 483 collectionName = defaultNewCollectionPrefix + " " + (new Date()).toLocaleString(); 484 } 485 importCollection = new Zotero.Collection; 486 importCollection.libraryID = libraryID; 487 importCollection.name = collectionName; 488 yield importCollection.saveTx(); 489 } 490 491 translation.setTranslator(translators[0]); 492 493 // Show progress popup 494 var progressWin; 495 var progress; 496 if (showProgressWindow) { 497 progressWin = new Zotero.ProgressWindow({ 498 closeOnClick: false 499 }); 500 progressWin.changeHeadline(Zotero.getString('fileInterface.importing')); 501 let icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png'; 502 progress = new progressWin.ItemProgress( 503 icon, translation.path ? OS.Path.basename(translation.path) : translators[0].label 504 ); 505 progressWin.show(); 506 507 translation.setHandler("itemDone", function () { 508 progress.setProgress(translation.getProgress()); 509 }); 510 511 yield Zotero.Promise.delay(0); 512 } 513 else { 514 yield onBeforeImport(translation); 515 } 516 517 let failed = false; 518 try { 519 yield translation.translate({ 520 libraryID, 521 collections: importCollection ? [importCollection.id] : null 522 }); 523 } catch(e) { 524 if (!showProgressWindow) { 525 throw e; 526 } 527 528 progressWin.close(); 529 Zotero.logError(e); 530 Zotero.alert( 531 null, 532 Zotero.getString('general.error'), 533 Zotero.getString("fileInterface.importError") 534 ); 535 return false; 536 } 537 538 var numItems = translation.newItems.length; 539 540 // Show popup on completion 541 if (showProgressWindow) { 542 progressWin.changeHeadline(Zotero.getString('fileInterface.importComplete')); 543 let icon; 544 if (numItems == 1) { 545 icon = translation.newItems[0].getImageSrc(); 546 } 547 else { 548 icon = 'chrome://zotero/skin/treesource-unfiled' + (Zotero.hiDPI ? "@2x" : "") + '.png'; 549 } 550 let text = Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems); 551 progress.setIcon(icon); 552 progress.setText(text); 553 // For synchronous translators, which don't update progress 554 progress.setProgress(100); 555 progressWin.startCloseTimer(5000); 556 } 557 558 Zotero.debug(`Imported ${numItems} item(s) in ${performance.now() - t} ms`); 559 560 return true; 561 }); 562 563 564 var _getMendeleyTranslation = async function () { 565 if (true) { 566 Components.utils.import("chrome://zotero/content/import/mendeley/mendeleyImport.js"); 567 } 568 // TEMP: Load uncached from ~/zotero-client for development 569 else { 570 Components.utils.import("resource://gre/modules/FileUtils.jsm"); 571 let file = FileUtils.getDir("Home", []); 572 file = OS.Path.join( 573 file.path, 574 'zotero-client', 'chrome', 'content', 'zotero', 'import', 'mendeley', 'mendeleyImport.js' 575 ); 576 let fileURI = OS.Path.toFileURI(file); 577 let xmlhttp = await Zotero.HTTP.request( 578 'GET', 579 fileURI, 580 { 581 dontCache: true, 582 responseType: 'text' 583 } 584 ); 585 eval(xmlhttp.response); 586 } 587 return new Zotero_Import_Mendeley(); 588 } 589 590 591 /** 592 * Creates a bibliography from a collection or saved search 593 */ 594 this.bibliographyFromCollection = function () { 595 var items = ZoteroPane.getSortedItems(); 596 597 // Find collection name 598 var name = false; 599 var collection = ZoteroPane.getSelectedCollection(); 600 if (collection) { 601 name = collection.name; 602 } 603 else { 604 let search = ZoteroPane.getSelectedSavedSearch(); 605 if (search) { 606 name = search.name; 607 } 608 } 609 610 _doBibliographyOptions(name, items); 611 } 612 613 /* 614 * Creates a bibliography from a items 615 */ 616 function bibliographyFromItems() { 617 var items = ZoteroPane_Local.getSelectedItems(); 618 if(!items || !items.length) throw("no items currently selected"); 619 620 _doBibliographyOptions(Zotero.getString("fileInterface.untitledBibliography"), items); 621 } 622 623 624 /** 625 * Copies HTML and text citations or bibliography entries for passed items in given style 626 * 627 * Does not check that items are actual references (and not notes or attachments) 628 * 629 * @param {Zotero.Item[]} items 630 * @param {String} style - Style id string (e.g., 'http://www.zotero.org/styles/apa') 631 * @param {String} locale - Locale (e.g., 'en-US') 632 * @param {Boolean} [asHTML=false] - Use HTML source for plain-text data 633 * @param {Boolean} [asCitations=false] - Copy citation cluster instead of bibliography 634 */ 635 this.copyItemsToClipboard = function (items, style, locale, asHTML, asCitations) { 636 // copy to clipboard 637 var transferable = Components.classes["@mozilla.org/widget/transferable;1"]. 638 createInstance(Components.interfaces.nsITransferable); 639 var clipboardService = Components.classes["@mozilla.org/widget/clipboard;1"]. 640 getService(Components.interfaces.nsIClipboard); 641 style = Zotero.Styles.get(style); 642 var cslEngine = style.getCiteProc(locale); 643 644 if (asCitations) { 645 cslEngine.updateItems(items.map(item => item.id)); 646 var citation = { 647 citationItems: items.map(item => ({ id: item.id })), 648 properties: {} 649 }; 650 var output = cslEngine.previewCitationCluster(citation, [], [], "html"); 651 } 652 else { 653 var output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html"); 654 } 655 656 // add HTML 657 var str = Components.classes["@mozilla.org/supports-string;1"]. 658 createInstance(Components.interfaces.nsISupportsString); 659 str.data = output; 660 transferable.addDataFlavor("text/html"); 661 transferable.setTransferData("text/html", str, output.length * 2); 662 663 // If not "Copy as HTML", add plaintext; otherwise use HTML from above and just mark as text 664 if(!asHTML) { 665 if (asCitations) { 666 output = cslEngine.previewCitationCluster(citation, [], [], "text"); 667 } 668 else { 669 // Generate engine again to work around citeproc-js problem: 670 // https://github.com/zotero/zotero/commit/4a475ff3 671 cslEngine = style.getCiteProc(locale); 672 output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text"); 673 } 674 } 675 676 var str = Components.classes["@mozilla.org/supports-string;1"]. 677 createInstance(Components.interfaces.nsISupportsString); 678 str.data = output; 679 transferable.addDataFlavor("text/unicode"); 680 transferable.setTransferData("text/unicode", str, output.length * 2); 681 682 clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard); 683 } 684 685 686 /* 687 * Shows bibliography options and creates a bibliography 688 */ 689 function _doBibliographyOptions(name, items) { 690 // make sure at least one item is not a standalone note or attachment 691 var haveRegularItem = false; 692 for (let item of items) { 693 if (item.isRegularItem()) { 694 haveRegularItem = true; 695 break; 696 } 697 } 698 if (!haveRegularItem) { 699 Zotero.alert( 700 null, 701 Zotero.getString('general.error'), 702 Zotero.getString("fileInterface.noReferencesError") 703 ); 704 return; 705 } 706 707 var io = new Object(); 708 var newDialog = window.openDialog("chrome://zotero/content/bibliography.xul", 709 "_blank","chrome,modal,centerscreen", io); 710 711 if(!io.method) return; 712 713 // determine output format 714 var format = "html"; 715 if(io.method == "save-as-rtf") { 716 format = "rtf"; 717 } 718 719 // determine locale preference 720 var locale = io.locale; 721 722 // generate bibliography 723 try { 724 if(io.method == 'copy-to-clipboard') { 725 Zotero_File_Interface.copyItemsToClipboard(items, io.style, locale, false, io.mode === "citations"); 726 } 727 else { 728 var style = Zotero.Styles.get(io.style); 729 var cslEngine = style.getCiteProc(locale); 730 var bibliography = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, 731 items, format, io.mode === "citations"); 732 } 733 } catch(e) { 734 Zotero.alert( 735 null, 736 Zotero.getString('general.error'), 737 Zotero.getString("fileInterface.bibliographyGenerationError") 738 ); 739 throw(e); 740 } 741 742 if(io.method == "print") { 743 // printable bibliography, using a hidden browser 744 var browser = Zotero.Browser.createHiddenBrowser(window); 745 746 var listener = function() { 747 if(browser.contentDocument.location.href == "about:blank") return; 748 browser.removeEventListener("pageshow", listener, false); 749 750 // this is kinda nasty, but we have to temporarily modify the user's 751 // settings to eliminate the header and footer. the other way to do 752 // this would be to attempt to print with an embedded browser, but 753 // it's not even clear how to attempt to create one 754 var prefService = Components.classes["@mozilla.org/preferences-service;1"]. 755 getService(Components.interfaces.nsIPrefBranch); 756 var prefsToClear = ["print.print_headerleft", "print.print_headercenter", 757 "print.print_headerright", "print.print_footerleft", 758 "print.print_footercenter", "print.print_footerright"]; 759 var oldPrefs = []; 760 for(var i in prefsToClear) { 761 oldPrefs[i] = prefService.getCharPref(prefsToClear[i]); 762 prefService.setCharPref(prefsToClear[i], ""); 763 } 764 765 // print 766 browser.contentWindow.print(); 767 768 // set the prefs back 769 for(var i in prefsToClear) { 770 prefService.setCharPref(prefsToClear[i], oldPrefs[i]); 771 } 772 773 // TODO can't delete hidden browser object here or else print will fail... 774 } 775 776 browser.addEventListener("pageshow", listener, false); 777 browser.loadURIWithFlags("data:text/html;charset=utf-8,"+encodeURI(bibliography), 778 Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, "utf-8", null); 779 } else if(io.method == "save-as-html") { 780 var fStream = _saveBibliography(name, "HTML"); 781 782 if(fStream !== false) { 783 var html = ""; 784 html +='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'; 785 html +='<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'; 786 html +='<head>\n'; 787 html +='<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>\n'; 788 html +='<title>'+Zotero.getString("fileInterface.bibliographyHTMLTitle")+'</title>\n'; 789 html +='</head>\n'; 790 html +='<body>\n'; 791 html += bibliography; 792 html +='</body>\n'; 793 html +='</html>\n'; 794 795 // create UTF-8 output stream 796 var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]. 797 createInstance(Components.interfaces.nsIConverterOutputStream); 798 os.init(fStream, "UTF-8", 0, "?".charCodeAt(0)); 799 800 os.writeString(html); 801 802 os.close(); 803 fStream.close(); 804 } 805 } else if(io.method == "save-as-rtf") { 806 var fStream = _saveBibliography(name, "RTF"); 807 if(fStream !== false) { 808 fStream.write(bibliography, bibliography.length); 809 fStream.close(); 810 } 811 } 812 } 813 814 815 function _saveBibliography(name, format) { 816 // savable bibliography, using a file stream 817 const nsIFilePicker = Components.interfaces.nsIFilePicker; 818 var fp = Components.classes["@mozilla.org/filepicker;1"] 819 .createInstance(nsIFilePicker); 820 fp.init(window, "Save Bibliography", nsIFilePicker.modeSave); 821 822 if(format == "RTF") { 823 var extension = "rtf"; 824 fp.appendFilter("RTF", "*.rtf"); 825 } else { 826 var extension = "html"; 827 fp.appendFilters(nsIFilePicker.filterHTML); 828 } 829 830 fp.defaultString = name+"."+extension; 831 832 var rv = fp.show(); 833 if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { 834 // open file 835 var fStream = Components.classes["@mozilla.org/network/file-output-stream;1"]. 836 createInstance(Components.interfaces.nsIFileOutputStream); 837 fStream.init(fp.file, 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate 838 return fStream; 839 } else { 840 return false; 841 } 842 } 843 } 844 845 // Handles the display of a progress indicator 846 Zotero_File_Interface.Progress = new function() { 847 this.show = show; 848 this.close = close; 849 850 function show(headline) { 851 Zotero.showZoteroPaneProgressMeter(headline); 852 } 853 854 function close() { 855 Zotero.hideZoteroPaneOverlays(); 856 } 857 }