preferences_advanced.js (26484B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2006–2013 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/Services.jsm"); 27 28 Zotero_Preferences.Advanced = { 29 _openURLResolvers: null, 30 31 32 init: function () { 33 Zotero_Preferences.Keys.init(); 34 35 // Show Memory Info button if the Error Console menu option is enabled 36 if (Zotero.Prefs.get('devtools.errorconsole.enabled', true)) { 37 document.getElementById('memory-info').hidden = false; 38 } 39 40 this.onDataDirLoad(); 41 this.refreshLocale(); 42 }, 43 44 45 updateTranslators: Zotero.Promise.coroutine(function* () { 46 var updated = yield Zotero.Schema.updateFromRepository(Zotero.Schema.REPO_UPDATE_MANUAL); 47 var button = document.getElementById('updateButton'); 48 if (button) { 49 if (updated===-1) { 50 var label = Zotero.getString('zotero.preferences.update.upToDate'); 51 } 52 else if (updated) { 53 var label = Zotero.getString('zotero.preferences.update.updated'); 54 } 55 else { 56 var label = Zotero.getString('zotero.preferences.update.error'); 57 } 58 button.setAttribute('label', label); 59 60 if (updated && Zotero_Preferences.Cite) { 61 yield Zotero_Preferences.Cite.refreshStylesList(); 62 } 63 } 64 }), 65 66 67 migrateDataDirectory: Zotero.Promise.coroutine(function* () { 68 var currentDir = Zotero.DataDirectory.dir; 69 var defaultDir = Zotero.DataDirectory.defaultDir; 70 if (currentDir == defaultDir) { 71 Zotero.debug("Already using default directory"); 72 return; 73 } 74 75 Components.utils.import("resource://zotero/config.js") 76 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 77 .getService(Components.interfaces.nsIPromptService); 78 79 // If there's a migration marker, point data directory back to the current location and remove 80 // it to trigger the migration again 81 var marker = OS.Path.join(defaultDir, Zotero.DataDirectory.MIGRATION_MARKER); 82 if (yield OS.File.exists(marker)) { 83 Zotero.Prefs.clear('dataDir'); 84 Zotero.Prefs.clear('useDataDir'); 85 yield OS.File.remove(marker); 86 try { 87 yield OS.File.remove(OS.Path.join(defaultDir, '.DS_Store')); 88 } 89 catch (e) {} 90 } 91 92 // ~/Zotero exists and is non-empty 93 if ((yield OS.File.exists(defaultDir)) && !(yield Zotero.File.directoryIsEmpty(defaultDir))) { 94 let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 95 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 96 let index = ps.confirmEx( 97 window, 98 Zotero.getString('general.error'), 99 Zotero.getString('zotero.preferences.advanced.migrateDataDir.directoryExists1', defaultDir) 100 + "\n\n" 101 + Zotero.getString('zotero.preferences.advanced.migrateDataDir.directoryExists2'), 102 buttonFlags, 103 Zotero.getString('general.showDirectory'), 104 null, null, null, {} 105 ); 106 if (index == 0) { 107 yield Zotero.File.reveal( 108 // Windows opens the directory, which might be confusing here, so open parent instead 109 Zotero.isWin ? OS.Path.dirname(defaultDir) : defaultDir 110 ); 111 } 112 return; 113 } 114 115 var additionalText = ''; 116 if (Zotero.isWin) { 117 try { 118 let numItems = yield Zotero.DB.valueQueryAsync( 119 "SELECT COUNT(*) FROM itemAttachments WHERE linkMode IN (?, ?)", 120 [Zotero.Attachments.LINK_MODE_IMPORTED_FILE, Zotero.Attachments.LINK_MODE_IMPORTED_URL] 121 ); 122 if (numItems > 100) { 123 additionalText = '\n\n' + Zotero.getString( 124 'zotero.preferences.advanced.migrateDataDir.manualMigration', 125 [Zotero.appName, defaultDir, ZOTERO_CONFIG.CLIENT_NAME] 126 ); 127 } 128 } 129 catch (e) { 130 Zotero.logError(e); 131 } 132 } 133 134 // Prompt to restart 135 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 136 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 137 var index = ps.confirmEx(window, 138 Zotero.getString('zotero.preferences.advanced.migrateDataDir.title'), 139 Zotero.getString( 140 'zotero.preferences.advanced.migrateDataDir.directoryWillBeMoved', 141 [ZOTERO_CONFIG.CLIENT_NAME, defaultDir] 142 ) + '\n\n' 143 + Zotero.getString( 144 'zotero.preferences.advanced.migrateDataDir.appMustBeRestarted', Zotero.appName 145 ) + additionalText, 146 buttonFlags, 147 Zotero.getString('general.continue'), 148 null, null, null, {} 149 ); 150 151 if (index == 0) { 152 yield Zotero.DataDirectory.markForMigration(currentDir); 153 Zotero.Utilities.Internal.quitZotero(true); 154 } 155 }), 156 157 158 runIntegrityCheck: async function (button) { 159 button.disabled = true; 160 161 try { 162 let ps = Services.prompt; 163 164 var ok = await Zotero.DB.integrityCheck(); 165 if (ok) { 166 ok = await Zotero.Schema.integrityCheck(); 167 if (!ok) { 168 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 169 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 170 var index = ps.confirmEx(window, 171 Zotero.getString('general.failed'), 172 Zotero.getString('db.integrityCheck.failed') + "\n\n" + 173 Zotero.getString('db.integrityCheck.repairAttempt') + " " + 174 Zotero.getString('db.integrityCheck.appRestartNeeded', Zotero.appName), 175 buttonFlags, 176 Zotero.getString('db.integrityCheck.fixAndRestart', Zotero.appName), 177 null, null, null, {} 178 ); 179 180 if (index == 0) { 181 // Safety first 182 await Zotero.DB.backupDatabase(); 183 184 // Fix the errors 185 await Zotero.Schema.integrityCheck(true); 186 187 // And run the check again 188 ok = await Zotero.Schema.integrityCheck(); 189 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING); 190 if (ok) { 191 var str = 'success'; 192 var msg = Zotero.getString('db.integrityCheck.errorsFixed'); 193 } 194 else { 195 var str = 'failed'; 196 var msg = Zotero.getString('db.integrityCheck.errorsNotFixed') 197 + "\n\n" + Zotero.getString('db.integrityCheck.reportInForums'); 198 } 199 200 ps.confirmEx(window, 201 Zotero.getString('general.' + str), 202 msg, 203 buttonFlags, 204 Zotero.getString('general.restartApp', Zotero.appName), 205 null, null, null, {} 206 ); 207 208 var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"] 209 .getService(Components.interfaces.nsIAppStartup); 210 appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit 211 | Components.interfaces.nsIAppStartup.eRestart); 212 } 213 214 return; 215 } 216 217 try { 218 await Zotero.DB.vacuum(); 219 } 220 catch (e) { 221 Zotero.logError(e); 222 ok = false; 223 } 224 } 225 var str = ok ? 'passed' : 'failed'; 226 227 ps.alert(window, 228 Zotero.getString('general.' + str), 229 Zotero.getString('db.integrityCheck.' + str) 230 + (!ok ? "\n\n" + Zotero.getString('db.integrityCheck.dbRepairTool') : '')); 231 } 232 finally { 233 button.disabled = false; 234 } 235 }, 236 237 238 resetTranslatorsAndStyles: function () { 239 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 240 .getService(Components.interfaces.nsIPromptService); 241 242 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 243 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 244 245 var index = ps.confirmEx(null, 246 Zotero.getString('general.warning'), 247 Zotero.getString('zotero.preferences.advanced.resetTranslatorsAndStyles.changesLost'), 248 buttonFlags, 249 Zotero.getString('zotero.preferences.advanced.resetTranslatorsAndStyles'), 250 null, null, null, {}); 251 252 if (index == 0) { 253 Zotero.Schema.resetTranslatorsAndStyles() 254 .then(function () { 255 if (Zotero_Preferences.Export) { 256 Zotero_Preferences.Export.populateQuickCopyList(); 257 } 258 }); 259 } 260 }, 261 262 263 resetTranslators: async function () { 264 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 265 .getService(Components.interfaces.nsIPromptService); 266 267 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 268 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 269 270 var index = ps.confirmEx(null, 271 Zotero.getString('general.warning'), 272 Zotero.getString('zotero.preferences.advanced.resetTranslators.changesLost'), 273 buttonFlags, 274 Zotero.getString('zotero.preferences.advanced.resetTranslators'), 275 null, null, null, {}); 276 277 if (index == 0) { 278 let button = document.getElementById('reset-translators-button'); 279 button.disabled = true; 280 try { 281 await Zotero.Schema.resetTranslators(); 282 if (Zotero_Preferences.Export) { 283 Zotero_Preferences.Export.populateQuickCopyList(); 284 } 285 } 286 finally { 287 button.disabled = false; 288 } 289 } 290 }, 291 292 293 resetStyles: async function () { 294 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 295 .getService(Components.interfaces.nsIPromptService); 296 297 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 298 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 299 300 var index = ps.confirmEx(null, 301 Zotero.getString('general.warning'), 302 Zotero.getString('zotero.preferences.advanced.resetStyles.changesLost'), 303 buttonFlags, 304 Zotero.getString('zotero.preferences.advanced.resetStyles'), 305 null, null, null, {}); 306 307 if (index == 0) { 308 let button = document.getElementById('reset-styles-button'); 309 button.disabled = true; 310 try { 311 await Zotero.Schema.resetStyles() 312 if (Zotero_Preferences.Export) { 313 Zotero_Preferences.Export.populateQuickCopyList(); 314 } 315 } 316 finally { 317 button.disabled = false; 318 } 319 } 320 }, 321 322 323 onDataDirLoad: function () { 324 var useDataDir = Zotero.Prefs.get('useDataDir'); 325 var dataDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'); 326 var currentDir = Zotero.DataDirectory.dir; 327 var defaultDataDir = Zotero.DataDirectory.defaultDir; 328 329 if (Zotero.forceDataDir) { 330 document.getElementById('command-line-data-dir-path').textContent = currentDir; 331 document.getElementById('command-line-data-dir').hidden = false; 332 document.getElementById('data-dir').hidden = true; 333 } 334 335 // Change "Use profile directory" label to home directory location unless using profile dir 336 if (useDataDir || currentDir == defaultDataDir) { 337 document.getElementById('default-data-dir').setAttribute( 338 'label', Zotero.getString('dataDir.default', Zotero.DataDirectory.defaultDir) 339 ); 340 } 341 342 // Don't show custom data dir as in-use if set to the default 343 if (dataDir == defaultDataDir) { 344 useDataDir = false; 345 } 346 347 document.getElementById('data-dir-path').setAttribute('disabled', !useDataDir); 348 document.getElementById('migrate-data-dir').setAttribute( 349 'hidden', !Zotero.DataDirectory.canMigrate() 350 ); 351 352 return useDataDir; 353 }, 354 355 356 onDataDirUpdate: Zotero.Promise.coroutine(function* (event, forceNew) { 357 var radiogroup = document.getElementById('data-dir'); 358 var newUseDataDir = radiogroup.selectedIndex == 1; 359 360 if (!forceNew && newUseDataDir && !this._usingDefaultDataDir()) { 361 return; 362 } 363 364 // This call shows a filepicker if needed, forces a restart if required, and does nothing if 365 // cancel was pressed or value hasn't changed 366 yield Zotero.DataDirectory.choose( 367 true, 368 !newUseDataDir, 369 () => Zotero_Preferences.openURL('https://zotero.org/support/zotero_data') 370 ); 371 radiogroup.selectedIndex = this._usingDefaultDataDir() ? 0 : 1; 372 }), 373 374 375 chooseDataDir: function(event) { 376 document.getElementById('data-dir').selectedIndex = 1; 377 this.onDataDirUpdate(event, true); 378 }, 379 380 381 getDataDirPath: function () { 382 // TEMP: lastDataDir can be removed once old persistent descriptors have been 383 // converted, which they are in getZoteroDirectory() in 5.0 384 var prefValue = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'); 385 386 // Don't show path if the default 387 if (prefValue == Zotero.DataDirectory.defaultDir) { 388 return ''; 389 } 390 391 return prefValue || ''; 392 }, 393 394 395 _usingDefaultDataDir: function () { 396 // Legacy profile directory location 397 if (!Zotero.Prefs.get('useDataDir')) { 398 return true; 399 } 400 401 var dataDir = Zotero.Prefs.get('lastDataDir') || Zotero.Prefs.get('dataDir'); 402 // Default home directory location 403 if (dataDir == Zotero.DataDirectory.defaultDir) { 404 return true; 405 } 406 407 return false; 408 }, 409 410 411 populateOpenURLResolvers: function () { 412 var openURLMenu = document.getElementById('openURLMenu'); 413 414 this._openURLResolvers = Zotero.OpenURL.discoverResolvers(); 415 var i = 0; 416 for (let r of this._openURLResolvers) { 417 openURLMenu.insertItemAt(i, r.name); 418 if (r.url == Zotero.Prefs.get('openURL.resolver') && r.version == Zotero.Prefs.get('openURL.version')) { 419 openURLMenu.selectedIndex = i; 420 } 421 i++; 422 } 423 424 var button = document.getElementById('openURLSearchButton'); 425 switch (this._openURLResolvers.length) { 426 case 0: 427 var num = 'zero'; 428 break; 429 case 1: 430 var num = 'singular'; 431 break; 432 default: 433 var num = 'plural'; 434 } 435 436 button.setAttribute('label', Zotero.getString('zotero.preferences.openurl.resolversFound.' + num, this._openURLResolvers.length)); 437 }, 438 439 440 onOpenURLSelected: function () { 441 var openURLServerField = document.getElementById('openURLServerField'); 442 var openURLVersionMenu = document.getElementById('openURLVersionMenu'); 443 var openURLMenu = document.getElementById('openURLMenu'); 444 445 if(openURLMenu.value == "custom") 446 { 447 openURLServerField.focus(); 448 } 449 else 450 { 451 openURLServerField.value = this._openURLResolvers[openURLMenu.selectedIndex]['url']; 452 openURLVersionMenu.value = this._openURLResolvers[openURLMenu.selectedIndex]['version']; 453 Zotero.Prefs.set("openURL.resolver", this._openURLResolvers[openURLMenu.selectedIndex]['url']); 454 Zotero.Prefs.set("openURL.version", this._openURLResolvers[openURLMenu.selectedIndex]['version']); 455 } 456 }, 457 458 onOpenURLCustomized: function () { 459 document.getElementById('openURLMenu').value = "custom"; 460 }, 461 462 463 _getAutomaticLocaleMenuLabel: function () { 464 return Zotero.getString( 465 'zotero.preferences.locale.automaticWithLocale', 466 Zotero.Locale.availableLocales[Zotero.locale] || Zotero.locale 467 ); 468 }, 469 470 471 refreshLocale: function () { 472 var matchOS = Zotero.Prefs.get('intl.locale.matchOS', true); 473 var autoLocaleName, currentValue; 474 475 // If matching OS, get the name of the current locale 476 if (matchOS) { 477 autoLocaleName = this._getAutomaticLocaleMenuLabel(); 478 currentValue = 'automatic'; 479 } 480 // Otherwise get the name of the locale specified in the pref 481 else { 482 let branch = Services.prefs.getBranch(""); 483 let locale = branch.getComplexValue( 484 'general.useragent.locale', Components.interfaces.nsIPrefLocalizedString 485 ); 486 autoLocaleName = Zotero.getString('zotero.preferences.locale.automatic'); 487 currentValue = locale; 488 } 489 490 // Populate menu 491 var menu = document.getElementById('locale-menu'); 492 var menupopup = menu.firstChild; 493 menupopup.textContent = ''; 494 // Show "Automatic (English)", "Automatic (Français)", etc. 495 menu.appendItem(autoLocaleName, 'automatic'); 496 menu.menupopup.appendChild(document.createElement('menuseparator')); 497 // Add all available locales 498 for (let locale in Zotero.Locale.availableLocales) { 499 menu.appendItem(Zotero.Locale.availableLocales[locale], locale); 500 } 501 menu.value = currentValue; 502 }, 503 504 onLocaleChange: function () { 505 var menu = document.getElementById('locale-menu'); 506 if (menu.value == 'automatic') { 507 // Changed if not already set to automatic (unless we have the automatic locale name, 508 // meaning we just switched away to the same manual locale and back to automatic) 509 var changed = !Zotero.Prefs.get('intl.locale.matchOS', true) 510 && menu.label != this._getAutomaticLocaleMenuLabel(); 511 Zotero.Prefs.set('intl.locale.matchOS', true, true); 512 } 513 else { 514 // Changed if moving to a locale other than the current one 515 var changed = Zotero.locale != menu.value 516 Zotero.Prefs.set('intl.locale.matchOS', false, true); 517 Zotero.Prefs.set('general.useragent.locale', menu.value, true); 518 } 519 520 if (!changed) { 521 return; 522 } 523 524 var ps = Services.prompt; 525 var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING 526 + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING; 527 var index = ps.confirmEx(null, 528 Zotero.getString('general.restartRequired'), 529 Zotero.getString('general.restartRequiredForChange', Zotero.appName), 530 buttonFlags, 531 Zotero.getString('general.restartNow'), 532 Zotero.getString('general.restartLater'), 533 null, null, {}); 534 535 if (index == 0) { 536 Zotero.Utilities.Internal.quitZotero(true); 537 } 538 } 539 }; 540 541 542 Zotero_Preferences.Attachment_Base_Directory = { 543 getPath: function () { 544 var oldPath = Zotero.Prefs.get('baseAttachmentPath'); 545 if (oldPath) { 546 try { 547 return OS.Path.normalize(oldPath); 548 } 549 catch (e) { 550 Zotero.logError(e); 551 return false; 552 } 553 } 554 }, 555 556 557 choosePath: Zotero.Promise.coroutine(function* () { 558 var oldPath = this.getPath(); 559 560 //Prompt user to choose new base path 561 if (oldPath) { 562 var oldPathFile = Zotero.File.pathToFile(oldPath); 563 } 564 var nsIFilePicker = Components.interfaces.nsIFilePicker; 565 var fp = Components.classes["@mozilla.org/filepicker;1"] 566 .createInstance(nsIFilePicker); 567 if (oldPathFile) { 568 fp.displayDirectory = oldPathFile; 569 } 570 fp.init(window, Zotero.getString('attachmentBasePath.selectDir'), nsIFilePicker.modeGetFolder); 571 fp.appendFilters(nsIFilePicker.filterAll); 572 if (fp.show() != nsIFilePicker.returnOK) { 573 return false; 574 } 575 var newPath = OS.Path.normalize(fp.file.path); 576 577 if (oldPath && oldPath == newPath) { 578 Zotero.debug("Base directory hasn't changed"); 579 return false; 580 } 581 582 return this.changePath(newPath); 583 }), 584 585 586 changePath: Zotero.Promise.coroutine(function* (basePath) { 587 // Find all current attachments with relative attachment paths 588 var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=? AND path LIKE ?"; 589 var params = [ 590 Zotero.Attachments.LINK_MODE_LINKED_FILE, 591 Zotero.Attachments.BASE_PATH_PLACEHOLDER + "%" 592 ]; 593 var oldRelativeAttachmentIDs = yield Zotero.DB.columnQueryAsync(sql, params); 594 595 //Find all attachments on the new base path 596 var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=?"; 597 var params = [Zotero.Attachments.LINK_MODE_LINKED_FILE]; 598 var allAttachments = yield Zotero.DB.columnQueryAsync(sql, params); 599 var newAttachmentPaths = {}; 600 var numNewAttachments = 0; 601 var numOldAttachments = 0; 602 for (let i=0; i<allAttachments.length; i++) { 603 let attachmentID = allAttachments[i]; 604 let attachmentPath; 605 let relPath = false 606 607 try { 608 let attachment = yield Zotero.Items.getAsync(attachmentID); 609 // This will return FALSE for relative paths if base directory 610 // isn't currently set 611 attachmentPath = attachment.getFilePath(); 612 // Get existing relative path 613 let storedPath = attachment.attachmentPath; 614 if (storedPath.startsWith(Zotero.Attachments.BASE_PATH_PLACEHOLDER)) { 615 relPath = storedPath.substr(Zotero.Attachments.BASE_PATH_PLACEHOLDER.length); 616 } 617 } 618 catch (e) { 619 // Don't deal with bad attachment paths. Just skip them. 620 Zotero.debug(e, 2); 621 continue; 622 } 623 624 // If a file with the same relative path exists within the new base directory, 625 // don't touch the attachment, since it will continue to work 626 if (relPath) { 627 if (yield OS.File.exists(OS.Path.join(basePath, relPath))) { 628 numNewAttachments++; 629 continue; 630 } 631 } 632 633 // Files within the new base directory need to be updated to use 634 // relative paths (or, if the new base directory is an ancestor or 635 // descendant of the old one, new relative paths) 636 if (attachmentPath && Zotero.File.directoryContains(basePath, attachmentPath)) { 637 newAttachmentPaths[attachmentID] = relPath ? attachmentPath : null; 638 numNewAttachments++; 639 } 640 // Existing relative attachments not within the new base directory 641 // will be converted to absolute paths 642 else if (relPath && this.getPath()) { 643 newAttachmentPaths[attachmentID] = attachmentPath; 644 numOldAttachments++; 645 } 646 } 647 648 //Confirm change of the base path 649 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 650 .getService(Components.interfaces.nsIPromptService); 651 652 var chooseStrPrefix = 'attachmentBasePath.chooseNewPath.'; 653 var clearStrPrefix = 'attachmentBasePath.clearBasePath.'; 654 var title = Zotero.getString(chooseStrPrefix + 'title'); 655 var msg1 = Zotero.getString(chooseStrPrefix + 'message') + "\n\n", msg2 = "", msg3 = ""; 656 switch (numNewAttachments) { 657 case 0: 658 break; 659 660 case 1: 661 msg2 += Zotero.getString(chooseStrPrefix + 'existingAttachments.singular') + " "; 662 break; 663 664 default: 665 msg2 += Zotero.getString(chooseStrPrefix + 'existingAttachments.plural', numNewAttachments) + " "; 666 } 667 668 switch (numOldAttachments) { 669 case 0: 670 break; 671 672 case 1: 673 msg3 += Zotero.getString(clearStrPrefix + 'existingAttachments.singular'); 674 break; 675 676 default: 677 msg3 += Zotero.getString(clearStrPrefix + 'existingAttachments.plural', numOldAttachments); 678 } 679 680 681 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 682 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 683 var index = ps.confirmEx( 684 null, 685 title, 686 (msg1 + msg2 + msg3).trim(), 687 buttonFlags, 688 Zotero.getString(chooseStrPrefix + 'button'), 689 null, 690 null, 691 null, 692 {} 693 ); 694 695 if (index == 1) { 696 return false; 697 } 698 699 // Set new data directory 700 Zotero.debug("Setting new base directory"); 701 Zotero.Prefs.set('baseAttachmentPath', basePath); 702 Zotero.Prefs.set('saveRelativeAttachmentPath', true); 703 // Resave all attachments on base path (so that their paths become relative) 704 // and all other relative attachments (so that their paths become absolute) 705 yield Zotero.Utilities.Internal.forEachChunkAsync( 706 Object.keys(newAttachmentPaths), 707 100, 708 function (chunk) { 709 return Zotero.DB.executeTransaction(function* () { 710 for (let id of chunk) { 711 let attachment = Zotero.Items.get(id); 712 if (newAttachmentPaths[id]) { 713 attachment.attachmentPath = newAttachmentPaths[id]; 714 } 715 else { 716 attachment.attachmentPath = attachment.getFilePath(); 717 } 718 yield attachment.save({ 719 skipDateModifiedUpdate: true 720 }); 721 } 722 }) 723 } 724 ); 725 726 return true; 727 }), 728 729 730 clearPath: Zotero.Promise.coroutine(function* () { 731 // Find all current attachments with relative paths 732 var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=? AND path LIKE ?"; 733 var params = [ 734 Zotero.Attachments.LINK_MODE_LINKED_FILE, 735 Zotero.Attachments.BASE_PATH_PLACEHOLDER + "%" 736 ]; 737 var relativeAttachmentIDs = yield Zotero.DB.columnQueryAsync(sql, params); 738 739 // Prompt for confirmation 740 var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 741 .getService(Components.interfaces.nsIPromptService); 742 743 var strPrefix = 'attachmentBasePath.clearBasePath.'; 744 var title = Zotero.getString(strPrefix + 'title'); 745 var msg = Zotero.getString(strPrefix + 'message'); 746 switch (relativeAttachmentIDs.length) { 747 case 0: 748 break; 749 750 case 1: 751 msg += "\n\n" + Zotero.getString(strPrefix + 'existingAttachments.singular'); 752 break; 753 754 default: 755 msg += "\n\n" + Zotero.getString(strPrefix + 'existingAttachments.plural', 756 relativeAttachmentIDs.length); 757 } 758 759 var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 760 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 761 var index = ps.confirmEx( 762 window, 763 title, 764 msg, 765 buttonFlags, 766 Zotero.getString(strPrefix + 'button'), 767 null, 768 null, 769 null, 770 {} 771 ); 772 773 if (index == 1) { 774 return false; 775 } 776 777 // Disable relative path saving and then resave all relative 778 // attachments so that their absolute paths are stored 779 Zotero.debug('Clearing base directory'); 780 Zotero.Prefs.set('saveRelativeAttachmentPath', false); 781 782 yield Zotero.Utilities.Internal.forEachChunkAsync( 783 relativeAttachmentIDs, 784 100, 785 function (chunk) { 786 return Zotero.DB.executeTransaction(function* () { 787 for (let id of chunk) { 788 let attachment = yield Zotero.Items.getAsync(id); 789 attachment.attachmentPath = attachment.getFilePath(); 790 yield attachment.save({ 791 skipDateModifiedUpdate: true 792 }); 793 } 794 }.bind(this)); 795 }.bind(this) 796 ); 797 798 Zotero.Prefs.set('baseAttachmentPath', ''); 799 }), 800 801 802 updateUI: Zotero.Promise.coroutine(function* () { 803 var filefield = document.getElementById('baseAttachmentPath'); 804 var path = Zotero.Prefs.get('baseAttachmentPath'); 805 Components.utils.import("resource://gre/modules/osfile.jsm"); 806 if (yield OS.File.exists(path)) { 807 filefield.file = Zotero.File.pathToFile(path); 808 filefield.label = path; 809 } 810 else { 811 filefield.label = ''; 812 } 813 document.getElementById('resetBasePath').disabled = !path; 814 }) 815 }; 816 817 818 Zotero_Preferences.Keys = { 819 init: function () { 820 var rows = document.getElementById('zotero-prefpane-advanced-keys-tab').getElementsByTagName('row'); 821 for (var i=0; i<rows.length; i++) { 822 // Display the appropriate modifier keys for the platform 823 let label = rows[i].firstChild.nextSibling; 824 if (label.className == 'modifier') { 825 label.value = Zotero.isMac ? Zotero.getString('general.keys.cmdShift') : Zotero.getString('general.keys.ctrlShift'); 826 } 827 } 828 829 var textboxes = document.getElementById('zotero-keys-rows').getElementsByTagName('textbox'); 830 for (let i=0; i<textboxes.length; i++) { 831 let textbox = textboxes[i]; 832 textbox.value = textbox.value.toUpperCase(); 833 // .value takes care of the initial value, and this takes care of direct pref changes 834 // while the window is open 835 textbox.setAttribute('onsyncfrompreference', 'return Zotero_Preferences.Keys.capitalizePref(this.id)'); 836 textbox.setAttribute('oninput', 'this.value = this.value.toUpperCase()'); 837 } 838 }, 839 840 841 capitalizePref: function (id) { 842 var elem = document.getElementById(id); 843 var pref = document.getElementById(elem.getAttribute('preference')); 844 if (pref.value) { 845 return pref.value.toUpperCase(); 846 } 847 } 848 };