Ticket #745: orphans.patch

File orphans.patch, 171.5 KB (added by fbennett, 6 years ago)
  • chrome/content/zotero/overlay.js

    diff -u -r -N zotero-run/chrome/content/zotero/overlay.js zotero-trunk/chrome/content/zotero/overlay.js
    old new  
    10361036                itemgroup.setSearch(''); 
    10371037                itemgroup.setTags(getTagSelection()); 
    10381038                itemgroup.showDuplicates = false; 
     1039                itemgroup.showOrphans = false; 
    10391040 
    10401041                try { 
    10411042                        Zotero.UnresponsiveScriptIndicator.disable(); 
     
    10831084        } 
    10841085 
    10851086 
     1087        this.showOrphans = function () { 
     1088                if (this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) { 
     1089                        var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1090                        itemGroup.showOrphans = true; 
     1091 
     1092                        try { 
     1093                                Zotero.UnresponsiveScriptIndicator.disable(); 
     1094                                this.itemsView.refresh(); 
     1095                                this.itemsView.sort(); 
     1096                        } 
     1097                        finally { 
     1098                                Zotero.UnresponsiveScriptIndicator.enable(); 
     1099                        } 
     1100                } 
     1101        } 
     1102 
     1103 
    10861104        function itemSelected() 
    10871105        { 
    10881106                if (!Zotero.stateCheck()) { 
     
    20192037                        refreshCommonsBucket: 13, 
    20202038                        sep3: 14, 
    20212039                        showDuplicates: 15, 
    2022                         unshowDuplicates: 16 
     2040                        unshowDuplicates: 16, 
     2041                        showOrphans: 17, 
     2042                        unshowOrphans: 18 
    20232043                }; 
    20242044 
    20252045                var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     
    20982118                // Group 
    20992119                else if (itemGroup.isGroup()) { 
    21002120                        if (itemGroup.editable) { 
    2101                         show = [m.newCollection, m.newSavedSearch]; 
     2121                                show = [m.newCollection, m.newSavedSearch]; 
    21022122                                if (Zotero.Prefs.get('debugShowDuplicates')) { 
    21032123                                        show.push(m.sep3); 
    21042124                                        if (itemGroup.showDuplicates) { 
    21052125                                                show.push(m.unshowDuplicates); 
    21062126                                        } else { 
    21072127                                                show.push(m.showDuplicates); 
    2108                 } 
     2128                                                if (itemGroup.showOrphans) { 
     2129                                                        disable.push(m.showDuplicates); 
     2130                                                } else { 
     2131                                                        enable.push(m.showDuplicates); 
     2132                                                } 
     2133                                        } 
     2134                                        if (itemGroup.showOrphans) { 
     2135                                                show.push(m.unshowOrphans); 
     2136                                        } else { 
     2137                                                show.push(m.showOrphans); 
     2138                                                if (itemGroup.showDuplicates) { 
     2139                                                        disable.push(m.showOrphans); 
     2140                                                } else { 
     2141                                                        enable.push(m.showOrphans); 
     2142                                                } 
     2143                                        } 
    21092144                                } 
    21102145                        } 
    21112146                } 
     
    21192154                                show.push(m.unshowDuplicates); 
    21202155                        } else { 
    21212156                                show.push(m.showDuplicates); 
     2157                                if (itemGroup.showOrphans) { 
     2158                                        disable.push(m.showDuplicates); 
     2159                                } else { 
     2160                                        enable.push(m.showDuplicates); 
     2161                                } 
     2162                        } 
     2163                        if (itemGroup.showOrphans) { 
     2164                                show.push(m.unshowOrphans); 
     2165                        } else { 
     2166                                show.push(m.showOrphans); 
     2167                                if (itemGroup.showDuplicates) { 
     2168                                        disable.push(m.showOrphans); 
     2169                                } else { 
     2170                                        enable.push(m.showOrphans); 
     2171                                } 
    21222172                } 
    21232173                } 
    21242174                } 
  • chrome/content/zotero/overlay.js.orig

    diff -u -r -N zotero-run/chrome/content/zotero/overlay.js.orig zotero-trunk/chrome/content/zotero/overlay.js.orig
    old new  
     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 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 General Public License for more details. 
     19 
     20    You should have received a copy of the GNU General Public License 
     21    along with Zotero.  If not, see <http://www.gnu.org/licenses/>. 
     22 
     23    ***** END LICENSE BLOCK ***** 
     24*/ 
     25 
     26/* 
     27 * This object contains the various functions for the interface 
     28 */ 
     29var ZoteroPane = new function() 
     30{ 
     31        this.collectionsView = false; 
     32        this.itemsView = false; 
     33        this.__defineGetter__('loaded', function () _loaded); 
     34 
     35        //Privileged methods 
     36        this.onLoad = onLoad; 
     37        this.onUnload = onUnload; 
     38        this.toggleDisplay = toggleDisplay; 
     39        this.isShowing = isShowing; 
     40        this.fullScreen = fullScreen; 
     41        this.isFullScreen = isFullScreen; 
     42        this.handleKeyDown = handleKeyDown; 
     43        this.handleKeyUp = handleKeyUp; 
     44        this.setHighlightedRowsCallback = setHighlightedRowsCallback; 
     45        this.handleKeyPress = handleKeyPress; 
     46        this.newItem = newItem; 
     47        this.newCollection = newCollection; 
     48        this.newSearch = newSearch; 
     49        this.openAdvancedSearchWindow = openAdvancedSearchWindow; 
     50        this.toggleTagSelector = toggleTagSelector; 
     51        this.updateTagSelectorSize = updateTagSelectorSize; 
     52        this.getTagSelection = getTagSelection; 
     53        this.clearTagSelection = clearTagSelection; 
     54        this.updateTagFilter = updateTagFilter; 
     55        this.onCollectionSelected = onCollectionSelected; 
     56        this.itemSelected = itemSelected; 
     57        this.reindexItem = reindexItem; 
     58        this.duplicateSubstitute = duplicateSubstitute; 
     59        this.duplicateMarkItem = duplicateMarkItem; 
     60        this.duplicateUnmarkItem = duplicateUnmarkItem; 
     61        this.duplicateUnmarkAll = duplicateUnmarkAll; 
     62        this.duplicateSelectedItem = duplicateSelectedItem; 
     63        this.deleteSelectedCollection = deleteSelectedCollection; 
     64        this.editSelectedCollection = editSelectedCollection; 
     65        this.copySelectedItemsToClipboard = copySelectedItemsToClipboard; 
     66        this.clearQuicksearch = clearQuicksearch; 
     67        this.handleSearchKeypress = handleSearchKeypress; 
     68        this.handleSearchInput = handleSearchInput; 
     69        this.search = search; 
     70        this.selectItem = selectItem; 
     71        this.getSelectedCollection = getSelectedCollection; 
     72        this.getSelectedSavedSearch = getSelectedSavedSearch; 
     73        this.getSelectedItems = getSelectedItems; 
     74        this.getSortedItems = getSortedItems; 
     75        this.getSortField = getSortField; 
     76        this.getSortDirection = getSortDirection; 
     77        this.buildItemContextMenu = buildItemContextMenu; 
     78        this.loadURI = loadURI; 
     79        this.setItemsPaneMessage = setItemsPaneMessage; 
     80        this.clearItemsPaneMessage = clearItemsPaneMessage; 
     81        this.contextPopupShowing = contextPopupShowing; 
     82        this.openNoteWindow = openNoteWindow; 
     83        this.addTextToNote = addTextToNote; 
     84        this.addAttachmentFromDialog = addAttachmentFromDialog; 
     85        this.viewAttachment = viewAttachment; 
     86        this.viewSelectedAttachment = viewSelectedAttachment; 
     87        this.showAttachmentNotFoundDialog = showAttachmentNotFoundDialog; 
     88        this.relinkAttachment = relinkAttachment; 
     89        this.reportErrors = reportErrors; 
     90        this.displayErrorMessage = displayErrorMessage; 
     91 
     92        const DEFAULT_ZPANE_HEIGHT = 300; 
     93        const COLLECTIONS_HEIGHT = 32; // minimum height of the collections pane and toolbar 
     94 
     95        var self = this; 
     96        var _loaded = false; 
     97        var titlebarcolorState, toolbarCollapseState, titleState; 
     98 
     99        // Also needs to be changed in collectionTreeView.js 
     100        var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/; 
     101 
     102        /* 
     103         * Called when the window is open 
     104         */ 
     105        function onLoad() 
     106        { 
     107                if (!Zotero || !Zotero.initialized) { 
     108                        return; 
     109                } 
     110 
     111                if (Zotero.locked) { 
     112                        return; 
     113                } 
     114                _loaded = true; 
     115 
     116                Zotero.setFontSize(document.getElementById('zotero-pane')) 
     117 
     118                if (Zotero.isMac) { 
     119                        //document.getElementById('zotero-tb-actions-zeroconf-update').setAttribute('hidden', false); 
     120                        document.getElementById('zotero-pane-stack').setAttribute('platform', 'mac'); 
     121                } else if(Zotero.isWin) { 
     122                        document.getElementById('zotero-pane-stack').setAttribute('platform', 'win'); 
     123                } 
     124 
     125                if(Zotero.isFx35) document.documentElement.setAttribute("moz-version", "3.5"); 
     126 
     127                //Initialize collections view 
     128                this.collectionsView = new Zotero.CollectionTreeView(); 
     129                var collectionsTree = document.getElementById('zotero-collections-tree'); 
     130                collectionsTree.view = this.collectionsView; 
     131                collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree)); 
     132                collectionsTree.addEventListener("click", ZoteroPane.onTreeClick, true); 
     133 
     134                var itemsTree = document.getElementById('zotero-items-tree'); 
     135                itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree)); 
     136                itemsTree.addEventListener("click", ZoteroPane.onTreeClick, true); 
     137 
     138                this.buildItemTypeMenus(); 
     139 
     140                var menu = document.getElementById("contentAreaContextMenu"); 
     141                menu.addEventListener("popupshowing", ZoteroPane.contextPopupShowing, false); 
     142 
     143                Zotero.Keys.windowInit(document); 
     144 
     145                if (Zotero.restoreFromServer) { 
     146                        Zotero.restoreFromServer = false; 
     147 
     148                        setTimeout(function () { 
     149                                var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     150                                                        .getService(Components.interfaces.nsIPrompt); 
     151                                var buttonFlags = (pr.BUTTON_POS_0) * (pr.BUTTON_TITLE_IS_STRING) 
     152                                                                        + (pr.BUTTON_POS_1) * (pr.BUTTON_TITLE_CANCEL); 
     153                                var index = pr.confirmEx( 
     154                                        "Zotero Restore", 
     155                                        "The local Zotero database has been cleared." 
     156                                                + " " 
     157                                                + "Would you like to restore from the Zotero server now?", 
     158                                        buttonFlags, 
     159                                        "Sync Now", 
     160                                        null, null, null, {} 
     161                                ); 
     162 
     163                                if (index == 0) { 
     164                                        Zotero.Sync.Server.sync({ 
     165                                                onSuccess: function () { 
     166                                                        Zotero.Sync.Runner.setSyncIcon(); 
     167 
     168                                                        pr.alert( 
     169                                                                "Restore Completed", 
     170                                                                "The local Zotero database has been successfully restored." 
     171                                                        ); 
     172                                                }, 
     173 
     174                                                onError: function (msg) { 
     175                                                        pr.alert( 
     176                                                                "Restore Failed", 
     177                                                                "An error occurred while restoring from the server:\n\n" 
     178                                                                        + msg 
     179                                                        ); 
     180 
     181                                                        Zotero.Sync.Runner.error(msg); 
     182                                                } 
     183                                        }); 
     184                                } 
     185                        }, 1000); 
     186                } 
     187                // If the database was initialized or there are no sync credentials and 
     188                // Zotero hasn't been run before in this profile, display the start page 
     189                // -- this way the page won't be displayed when they sync their DB to 
     190                // another profile or if the DB is initialized erroneously (e.g. while 
     191                // switching data directory locations) 
     192                else if (Zotero.Prefs.get('firstRun2')) { 
     193                        if (Zotero.Schema.dbInitialized || !Zotero.Sync.Server.enabled) { 
     194                                setTimeout(function () { 
     195                                        var url = "http://zotero.org/start"; 
     196                                        gBrowser.selectedTab = gBrowser.addTab(url); 
     197                                }, 400); 
     198                        } 
     199                        Zotero.Prefs.set('firstRun2', false); 
     200                        try { 
     201                                Zotero.Prefs.clear('firstRun'); 
     202                        } 
     203                        catch (e) {} 
     204                } 
     205 
     206                // Hide sync debugging menu by default 
     207                if (Zotero.Prefs.get('sync.debugMenu')) { 
     208                        var sep = document.getElementById('zotero-tb-actions-sync-separator'); 
     209                        sep.hidden = false; 
     210                        sep.nextSibling.hidden = false; 
     211                        sep.nextSibling.nextSibling.hidden = false; 
     212                        sep.nextSibling.nextSibling.nextSibling.hidden = false; 
     213                } 
     214 
     215                if(Zotero.isStandalone) { 
     216                        this.toggleDisplay(); 
     217                        this.fullScreen(true); 
     218                } 
     219        } 
     220 
     221 
     222        this.buildItemTypeMenus = function () { 
     223                // 
     224                // Create the New Item (+) menu with each item type 
     225                // 
     226                var addMenu = document.getElementById('zotero-tb-add').firstChild; 
     227                var moreMenu = document.getElementById('zotero-tb-add-more'); 
     228 
     229                // Remove all nodes, in case we're reloading 
     230                var options = addMenu.getElementsByAttribute("class", "zotero-tb-add"); 
     231                while (options.length) { 
     232                        var p = options[0].parentNode; 
     233                        p.removeChild(options[0]); 
     234                } 
     235 
     236                var separator = addMenu.firstChild; 
     237 
     238                // Sort by localized name 
     239                var t = Zotero.ItemTypes.getPrimaryTypes(); 
     240                var itemTypes = []; 
     241                for (var i=0; i<t.length; i++) { 
     242                        itemTypes.push({ 
     243                                id: t[i].id, 
     244                                name: t[i].name, 
     245                                localized: Zotero.ItemTypes.getLocalizedString(t[i].id) 
     246                        }); 
     247                } 
     248                var collation = Zotero.getLocaleCollation(); 
     249                itemTypes.sort(function(a, b) { 
     250                        return collation.compareString(1, a.localized, b.localized); 
     251                }); 
     252 
     253                for (var i = 0; i<itemTypes.length; i++) { 
     254                        var menuitem = document.createElement("menuitem"); 
     255                        menuitem.setAttribute("label", itemTypes[i].localized); 
     256                        menuitem.setAttribute("oncommand","ZoteroPane.newItem("+itemTypes[i]['id']+")"); 
     257                        menuitem.setAttribute("tooltiptext", ""); 
     258                        menuitem.className = "zotero-tb-add"; 
     259                        addMenu.insertBefore(menuitem, separator); 
     260                } 
     261 
     262 
     263                // 
     264                // Create submenu for secondary item types 
     265                // 
     266 
     267                // Sort by localized name 
     268                var t = Zotero.ItemTypes.getSecondaryTypes(); 
     269                var itemTypes = []; 
     270                for (var i=0; i<t.length; i++) { 
     271                        itemTypes.push({ 
     272                                id: t[i].id, 
     273                                name: t[i].name, 
     274                                localized: Zotero.ItemTypes.getLocalizedString(t[i].id) 
     275                        }); 
     276                } 
     277                var collation = Zotero.getLocaleCollation(); 
     278                itemTypes.sort(function(a, b) { 
     279                        return collation.compareString(1, a.localized, b.localized); 
     280                }); 
     281 
     282                for (var i = 0; i<itemTypes.length; i++) { 
     283                        var menuitem = document.createElement("menuitem"); 
     284                        menuitem.setAttribute("label", itemTypes[i].localized); 
     285                        menuitem.setAttribute("oncommand","ZoteroPane.newItem("+itemTypes[i]['id']+")"); 
     286                        menuitem.setAttribute("tooltiptext", ""); 
     287                        menuitem.className = "zotero-tb-add"; 
     288                        moreMenu.appendChild(menuitem); 
     289                } 
     290        } 
     291 
     292 
     293        /* 
     294         * Called when the window closes 
     295         */ 
     296        function onUnload() 
     297        { 
     298                if (!Zotero || !Zotero.initialized || !_loaded) { 
     299                        return; 
     300                } 
     301 
     302                var tagSelector = document.getElementById('zotero-tag-selector'); 
     303                tagSelector.unregister(); 
     304 
     305                this.collectionsView.unregister(); 
     306                if (this.itemsView) 
     307                        this.itemsView.unregister(); 
     308        } 
     309 
     310        /* 
     311         * Hides/displays the Zotero interface 
     312         */ 
     313        function toggleDisplay() 
     314        { 
     315                if (!ZoteroPane.loaded) { 
     316                        if (Zotero.locked) { 
     317                                var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     318                                                        .getService(Components.interfaces.nsIPrompt); 
     319                                var msg = Zotero.getString('general.operationInProgress') + '\n\n' + Zotero.getString('general.operationInProgress.waitUntilFinished'); 
     320                                pr.alert("", msg); 
     321                                return; 
     322                        } 
     323                        ZoteroPane.onLoad(); 
     324                } 
     325 
     326                var zoteroPane = document.getElementById('zotero-pane-stack'); 
     327                var zoteroSplitter = document.getElementById('zotero-splitter') 
     328 
     329                if (zoteroPane.getAttribute('hidden') == 'true') { 
     330                        var isHidden = true; 
     331                } 
     332                else if (zoteroPane.getAttribute('collapsed') == 'true') { 
     333                        var isCollapsed = true; 
     334                } 
     335 
     336                if (isHidden || isCollapsed) { 
     337                        var makeVisible = true; 
     338                } 
     339 
     340                // If Zotero not initialized, try to get the error handler 
     341                // or load the default error page 
     342                if (makeVisible && (!Zotero || !Zotero.initialized)) { 
     343                        if (Zotero) { 
     344                                var errMsg = Zotero.startupError; 
     345                                var errFunc = Zotero.startupErrorHandler; 
     346                        } 
     347 
     348                        if (!errMsg) { 
     349                                // Get the stringbundle manually 
     350                                var src = 'chrome://zotero/locale/zotero.properties'; 
     351                                var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1']. 
     352                                                getService(Components.interfaces.nsILocaleService); 
     353                                var appLocale = localeService.getApplicationLocale(); 
     354                                var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] 
     355                                        .getService(Components.interfaces.nsIStringBundleService); 
     356                                var stringBundle = stringBundleService.createBundle(src, appLocale); 
     357 
     358                                var errMsg = stringBundle.GetStringFromName('startupError'); 
     359                        } 
     360 
     361                        if (errFunc) { 
     362                                errFunc(); 
     363                        } 
     364                        else { 
     365                                // TODO: Add a better error page/window here with reporting 
     366                                // instructions 
     367                                // window.loadURI('chrome://zotero/content/error.xul'); 
     368                                var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     369                                                        .getService(Components.interfaces.nsIPrompt); 
     370                                pr.alert("", errMsg); 
     371                        } 
     372 
     373                        return; 
     374                } 
     375 
     376                zoteroSplitter.setAttribute('hidden', !makeVisible); 
     377 
     378                // Make sure tags splitter isn't missing for people upgrading from <2.0b7 
     379                if (makeVisible) { 
     380                        document.getElementById('zotero-tags-splitter').collapsed = false; 
     381                } 
     382 
     383                // Restore fullscreen mode if necessary 
     384                if (makeVisible && isFullScreen()) { 
     385                        this.fullScreen(true); 
     386                } 
     387 
     388                if (zoteroPane.hasAttribute('savedHeight')) { 
     389                        var savedHeight = zoteroPane.getAttribute('savedHeight'); 
     390                } 
     391                else { 
     392                        var savedHeight = DEFAULT_ZPANE_HEIGHT; 
     393                } 
     394 
     395                /* 
     396                Zotero.debug("zoteroPane.boxObject.height: " + zoteroPane.boxObject.height); 
     397                Zotero.debug("zoteroPane.getAttribute('height'): " + zoteroPane.getAttribute('height')); 
     398                Zotero.debug("zoteroPane.getAttribute('minheight'): " + zoteroPane.getAttribute('minheight')); 
     399                Zotero.debug("savedHeight: " + savedHeight); 
     400                */ 
     401 
     402                if (makeVisible) { 
     403                        this.updateTagSelectorSize(); 
     404 
     405                        var max = document.getElementById('appcontent').boxObject.height 
     406                                                - zoteroSplitter.boxObject.height; 
     407 
     408                        if (isHidden) { 
     409                                zoteroPane.setAttribute('height', Math.min(savedHeight, max)); 
     410                                zoteroPane.setAttribute('hidden', false); 
     411                        } 
     412                        else if (isCollapsed) { 
     413                                zoteroPane.setAttribute('height', Math.min(savedHeight, max)); 
     414                                zoteroPane.setAttribute('collapsed', false); 
     415                        } 
     416 
     417                        // Focus the quicksearch on pane open 
     418                        setTimeout("document.getElementById('zotero-tb-search').inputField.select();", 1); 
     419 
     420                        // Auto-empty trashed items older than a certain number of days 
     421                        var days = Zotero.Prefs.get('trashAutoEmptyDays'); 
     422                        if (days) { 
     423                                var d = new Date(); 
     424                                var deleted = Zotero.Items.emptyTrash(days); 
     425                                var d2 = new Date(); 
     426                                Zotero.debug("Emptied old items from trash in " + (d2 - d) + " ms"); 
     427                        } 
     428 
     429                        var d = new Date(); 
     430                        Zotero.purgeDataObjects(); 
     431                        var d2 = new Date(); 
     432                        Zotero.debug("Purged data tables in " + (d2 - d) + " ms"); 
     433 
     434                        // Auto-sync on pane open 
     435                        if (Zotero.Prefs.get('sync.autoSync') && Zotero.Sync.Server.enabled 
     436                                        && !Zotero.Sync.Server.syncInProgress && !Zotero.Sync.Storage.syncInProgress) { 
     437                                setTimeout(function () { 
     438                                        Zotero.Sync.Runner.sync(true); 
     439                                }, 1000); 
     440                        } 
     441                } 
     442                else { 
     443                        zoteroPane.setAttribute('collapsed', true); 
     444                        zoteroPane.height = 0; 
     445 
     446                        document.getElementById('content').setAttribute('collapsed', false); 
     447 
     448                        // turn off full window mode, if it was on 
     449                        _setFullWindowMode(false); 
     450 
     451                        // Return focus to the browser content pane 
     452                        window.content.window.focus(); 
     453                } 
     454        } 
     455 
     456 
     457        function isShowing() { 
     458                var zoteroPane = document.getElementById('zotero-pane-stack'); 
     459                return zoteroPane.getAttribute('hidden') != 'true' && 
     460                                zoteroPane.getAttribute('collapsed') != 'true'; 
     461        } 
     462 
     463 
     464        function fullScreen(set) 
     465        { 
     466                var zoteroPane = document.getElementById('zotero-pane-stack'); 
     467 
     468                if (set != undefined) { 
     469                        var makeFullScreen = !!set; 
     470                } 
     471                else { 
     472                        var makeFullScreen = zoteroPane.getAttribute('fullscreenmode') != 'true'; 
     473                } 
     474 
     475                // Turn Z-pane flex on to stretch to window in full-screen, but off otherwise so persist works 
     476                zoteroPane.setAttribute('flex', makeFullScreen ? "1" : "0"); 
     477                document.getElementById('content').setAttribute('collapsed', makeFullScreen); 
     478                document.getElementById('zotero-splitter').setAttribute('hidden', makeFullScreen); 
     479 
     480                zoteroPane.setAttribute('fullscreenmode', makeFullScreen); 
     481                _setFullWindowMode(makeFullScreen); 
     482        } 
     483 
     484        /** 
     485         * Hides or shows navigation toolbars 
     486         * @param set {Boolean} Whether navigation toolbars should be hidden or shown 
     487         */ 
     488        function _setFullWindowMode(set) { 
     489                // hide or show navigation toolbars 
     490                var toolbox = getNavToolbox(); 
     491                if(set) { 
     492                        // the below would be a good thing to do if the whole title bar (and not just the center 
     493                        // part) got updated when it happened... 
     494                        /*if(Zotero.isMac) { 
     495                                titlebarcolorState = document.documentElement.getAttribute("activetitlebarcolor"); 
     496                                document.documentElement.removeAttribute("activetitlebarcolor"); 
     497                        }*/ 
     498                        if(document.title != "Zotero") { 
     499                                titleState = document.title; 
     500                                document.title = "Zotero"; 
     501                        } 
     502 
     503                        if(!toolbarCollapseState) { 
     504                                toolbarCollapseState = [node.collapsed for each (node in toolbox.childNodes)]; 
     505                                for(var i=0; i<toolbox.childNodes.length; i++) { 
     506                                        toolbox.childNodes[i].collapsed = true; 
     507                                } 
     508                        } 
     509                } else { 
     510                        /*if(Zotero.isMac) { 
     511                                document.documentElement.setAttribute("activetitlebarcolor", titlebarcolorState); 
     512                        }*/ 
     513                        if(document.title == "Zotero") document.title = titleState; 
     514 
     515                        if(toolbarCollapseState) { 
     516                                for(var i=0; i<toolbox.childNodes.length; i++) { 
     517                                        toolbox.childNodes[i].collapsed = toolbarCollapseState[i]; 
     518                                } 
     519                                toolbarCollapseState = undefined; 
     520                        } 
     521                } 
     522        } 
     523 
     524        function isFullScreen() { 
     525                return document.getElementById('zotero-pane-stack').getAttribute('fullscreenmode') == 'true'; 
     526        } 
     527 
     528 
     529        /* 
     530         * Trigger actions based on keyboard shortcuts 
     531         */ 
     532        function handleKeyDown(event, from) { 
     533                try { 
     534                        // Ignore keystrokes outside of Zotero pane 
     535                        if (!(event.originalTarget.ownerDocument instanceof XULDocument)) { 
     536                                return; 
     537                        } 
     538                } 
     539                catch (e) { 
     540                        Zotero.debug(e); 
     541                } 
     542 
     543                if (Zotero.locked) { 
     544                        event.preventDefault(); 
     545                        return; 
     546                } 
     547 
     548                if (from == 'zotero-pane') { 
     549                        // Highlight collections containing selected items 
     550                        // 
     551                        // We use Control (17) on Windows because Alt triggers the menubar; 
     552                        //      otherwise we use Alt/Option (18) 
     553                        if ((Zotero.isWin && event.keyCode == 17 && !event.altKey) || 
     554                                        (!Zotero.isWin && event.keyCode == 18 && !event.ctrlKey) 
     555                                        && !event.shiftKey && !event.metaKey) { 
     556 
     557                                this.highlightTimer = Components.classes["@mozilla.org/timer;1"]. 
     558                                        createInstance(Components.interfaces.nsITimer); 
     559                                // {} implements nsITimerCallback 
     560                                this.highlightTimer.initWithCallback({ 
     561                                        notify: ZoteroPane.setHighlightedRowsCallback 
     562                                }, 225, Components.interfaces.nsITimer.TYPE_ONE_SHOT); 
     563                        } 
     564                        else if ((Zotero.isWin && event.ctrlKey) || 
     565                                        (!Zotero.isWin && event.altKey)) { 
     566                                if (this.highlightTimer) { 
     567                                        this.highlightTimer.cancel(); 
     568                                        this.highlightTimer = null; 
     569                                } 
     570                                ZoteroPane.collectionsView.setHighlightedRows(); 
     571                        } 
     572 
     573                        return; 
     574                } 
     575 
     576                // Ignore keystrokes if Zotero pane is closed 
     577                var zoteroPane = document.getElementById('zotero-pane-stack'); 
     578                if (zoteroPane.getAttribute('hidden') == 'true' || 
     579                                zoteroPane.getAttribute('collapsed') == 'true') { 
     580                        return; 
     581                } 
     582 
     583                var useShift = Zotero.isMac; 
     584 
     585                var key = String.fromCharCode(event.which); 
     586                if (!key) { 
     587                        Zotero.debug('No key'); 
     588                        return; 
     589                } 
     590 
     591                // Ignore modifiers other than Ctrl-Alt or Cmd-Shift 
     592                if (!((Zotero.isMac ? event.metaKey : event.ctrlKey) && 
     593                                (useShift ? event.shiftKey : event.altKey))) { 
     594                        return; 
     595                } 
     596 
     597                var command = Zotero.Keys.getCommand(key); 
     598                if (!command) { 
     599                        return; 
     600                } 
     601 
     602                Zotero.debug(command); 
     603 
     604                // Errors don't seem to make it out otherwise 
     605                try { 
     606 
     607                switch (command) { 
     608                        case 'openZotero': 
     609                                try { 
     610                                        // Ignore Cmd-Shift-Z keystroke in text areas 
     611                                        if (Zotero.isMac && key == 'Z' && 
     612                                                        event.originalTarget.localName == 'textarea') { 
     613                                                Zotero.debug('Ignoring keystroke in text area'); 
     614                                                return; 
     615                                        } 
     616                                } 
     617                                catch (e) { 
     618                                        Zotero.debug(e); 
     619                                } 
     620                                ZoteroPane.toggleDisplay() 
     621                                break; 
     622                        case 'library': 
     623                                document.getElementById('zotero-collections-tree').focus(); 
     624                                ZoteroPane.collectionsView.selection.select(0); 
     625                                break; 
     626                        case 'quicksearch': 
     627                                document.getElementById('zotero-tb-search').select(); 
     628                                break; 
     629                        case 'newItem': 
     630                                ZoteroPane.newItem(2); // book 
     631                                var menu = document.getElementById('zotero-editpane-item-box').itemTypeMenu; 
     632                                menu.focus(); 
     633                                document.getElementById('zotero-editpane-item-box').itemTypeMenu.menupopup.openPopup(menu, "before_start", 0, 0); 
     634                                break; 
     635                        case 'newNote': 
     636                                // Use key that's not the modifier as the popup toggle 
     637                                ZoteroPane.newNote(useShift ? event.altKey : event.shiftKey); 
     638                                break; 
     639                        case 'toggleTagSelector': 
     640                                ZoteroPane.toggleTagSelector(); 
     641                                break; 
     642                        case 'toggleFullscreen': 
     643                                ZoteroPane.fullScreen(); 
     644                                break; 
     645                        case 'copySelectedItemCitationsToClipboard': 
     646                                ZoteroPane.copySelectedItemsToClipboard(true) 
     647                                break; 
     648                        case 'copySelectedItemsToClipboard': 
     649                                ZoteroPane.copySelectedItemsToClipboard(); 
     650                                break; 
     651                        case 'importFromClipboard': 
     652                                Zotero_File_Interface.importFromClipboard(); 
     653                                break; 
     654                        default: 
     655                                throw ('Command "' + command + '" not found in ZoteroPane.handleKeyDown()'); 
     656                } 
     657 
     658                } 
     659                catch (e) { 
     660                        Zotero.debug(e, 1); 
     661                        Components.utils.reportError(e); 
     662                } 
     663 
     664                event.preventDefault(); 
     665        } 
     666 
     667 
     668        function handleKeyUp(event, from) { 
     669                if (from == 'zotero-pane') { 
     670                        if ((Zotero.isWin && event.keyCode == 17) || 
     671                                        (!Zotero.isWin && event.keyCode == 18)) { 
     672                                if (this.highlightTimer) { 
     673                                        this.highlightTimer.cancel(); 
     674                                        this.highlightTimer = null; 
     675                                } 
     676                                ZoteroPane.collectionsView.setHighlightedRows(); 
     677                        } 
     678                } 
     679        } 
     680 
     681 
     682        /* 
     683         * Highlights collections containing selected items on Ctrl (Win) or 
     684         * Option/Alt (Mac/Linux) press 
     685         */ 
     686        function setHighlightedRowsCallback() { 
     687                var itemIDs = ZoteroPane.getSelectedItems(true); 
     688                if (itemIDs && itemIDs.length) { 
     689                        var collectionIDs = Zotero.Collections.getCollectionsContainingItems(itemIDs, true); 
     690                        if (collectionIDs) { 
     691                                ZoteroPane.collectionsView.setHighlightedRows(collectionIDs); 
     692                        } 
     693                } 
     694        } 
     695 
     696 
     697        function handleKeyPress(event, from) { 
     698                if (from == 'zotero-collections-tree') { 
     699                        if ((event.keyCode == event.DOM_VK_BACK_SPACE && Zotero.isMac) || 
     700                                        event.keyCode == event.DOM_VK_DELETE) { 
     701                                ZoteroPane.deleteSelectedCollection(); 
     702                                event.preventDefault(); 
     703                                return; 
     704                        } 
     705                } 
     706                else if (from == 'zotero-items-tree') { 
     707                        if ((event.keyCode == event.DOM_VK_BACK_SPACE && Zotero.isMac) || 
     708                                        event.keyCode == event.DOM_VK_DELETE) { 
     709                                // If Cmd/Ctrl delete, use forced mode, which does different 
     710                                // things depending on the context 
     711                                var force = event.metaKey || (!Zotero.isMac && event.ctrlKey); 
     712                                ZoteroPane.deleteSelectedItems(force); 
     713                                event.preventDefault(); 
     714                                return; 
     715                        } 
     716                } 
     717        } 
     718 
     719 
     720        /* 
     721         * Create a new item 
     722         * 
     723         * _data_ is an optional object with field:value for itemData 
     724         */ 
     725        function newItem(typeID, data, row) 
     726        { 
     727                if (!Zotero.stateCheck()) { 
     728                        this.displayErrorMessage(true); 
     729                        return false; 
     730                } 
     731 
     732                // Currently selected row 
     733                if (row === undefined) { 
     734                        row = this.collectionsView.selection.currentIndex; 
     735                } 
     736 
     737                if (!this.canEdit(row)) { 
     738                        this.displayCannotEditLibraryMessage(); 
     739                        return; 
     740                } 
     741 
     742                if (row !== undefined) { 
     743                        var itemGroup = this.collectionsView._getItemAtRow(row); 
     744                        var libraryID = itemGroup.ref.libraryID; 
     745                } 
     746                else { 
     747                        var libraryID = null; 
     748                        var itemGroup = null; 
     749                } 
     750 
     751                var item = new Zotero.Item(typeID); 
     752                item.libraryID = libraryID; 
     753                for (var i in data) { 
     754                        item.setField(i, data[i]); 
     755                } 
     756                var itemID = item.save(); 
     757 
     758                if (itemGroup && itemGroup.isCollection()) { 
     759                        itemGroup.ref.addItem(itemID); 
     760                } 
     761 
     762                //set to Info tab 
     763                document.getElementById('zotero-view-item').selectedIndex = 0; 
     764 
     765                this.selectItem(itemID); 
     766 
     767                return Zotero.Items.get(itemID); 
     768        } 
     769 
     770 
     771        function newCollection(parent) 
     772        { 
     773                if (!Zotero.stateCheck()) { 
     774                        this.displayErrorMessage(true); 
     775                        return false; 
     776                } 
     777 
     778                if (!this.canEdit()) { 
     779                        this.displayCannotEditLibraryMessage(); 
     780                        return; 
     781                } 
     782 
     783                var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 
     784                                                                .getService(Components.interfaces.nsIPromptService); 
     785 
     786                var untitled = Zotero.DB.getNextName('collections', 'collectionName', 
     787                        Zotero.getString('pane.collections.untitled')); 
     788 
     789                var newName = { value: untitled }; 
     790                var result = promptService.prompt(window, 
     791                        Zotero.getString('pane.collections.newCollection'), 
     792                        Zotero.getString('pane.collections.name'), newName, "", {}); 
     793 
     794                if (!result) 
     795                { 
     796                        return; 
     797                } 
     798 
     799                if (!newName.value) 
     800                { 
     801                        newName.value = untitled; 
     802                } 
     803 
     804                var collection = new Zotero.Collection; 
     805                collection.libraryID = this.getSelectedLibraryID(); 
     806                collection.name = newName.value; 
     807                collection.parent = parent; 
     808                collection.save(); 
     809        } 
     810 
     811 
     812        this.newGroup = function () { 
     813                if (this.isFullScreen()) { 
     814                        this.toggleDisplay(); 
     815                } 
     816 
     817                window.loadURI(Zotero.Groups.addGroupURL); 
     818        } 
     819 
     820 
     821        function newSearch() 
     822        { 
     823                if (!Zotero.stateCheck()) { 
     824                        this.displayErrorMessage(true); 
     825                        return false; 
     826                } 
     827 
     828                var s = new Zotero.Search(); 
     829                s.libraryID = this.getSelectedLibraryID(); 
     830                s.addCondition('title', 'contains', ''); 
     831 
     832                var untitled = Zotero.getString('pane.collections.untitled'); 
     833                untitled = Zotero.DB.getNextName('savedSearches', 'savedSearchName', 
     834                        Zotero.getString('pane.collections.untitled')); 
     835                var io = {dataIn: {search: s, name: untitled}, dataOut: null}; 
     836                window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io); 
     837        } 
     838 
     839 
     840        this.openLookupWindow = function () { 
     841                if (!Zotero.stateCheck()) { 
     842                        this.displayErrorMessage(true); 
     843                        return false; 
     844                } 
     845 
     846                if (!this.canEdit()) { 
     847                        this.displayCannotEditLibraryMessage(); 
     848                        return; 
     849                } 
     850 
     851                window.openDialog('chrome://zotero/content/lookup.xul', 'zotero-lookup', 'chrome,modal'); 
     852        } 
     853 
     854 
     855        function openAdvancedSearchWindow() { 
     856                var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 
     857                                        .getService(Components.interfaces.nsIWindowMediator); 
     858                var enumerator = wm.getEnumerator('zotero:search'); 
     859                while (enumerator.hasMoreElements()) { 
     860                        var win = enumerator.getNext(); 
     861                } 
     862 
     863                if (win) { 
     864                        win.focus(); 
     865                        return; 
     866                } 
     867 
     868                var s = new Zotero.Search(); 
     869                s.addCondition('title', 'contains', ''); 
     870                var io = {dataIn: {search: s}, dataOut: null}; 
     871                window.openDialog('chrome://zotero/content/advancedSearch.xul', '', 'chrome,dialog=no,centerscreen', io); 
     872        } 
     873 
     874 
     875        function toggleTagSelector(){ 
     876                var tagSelector = document.getElementById('zotero-tag-selector'); 
     877 
     878                var showing = tagSelector.getAttribute('collapsed') == 'true'; 
     879                tagSelector.setAttribute('collapsed', !showing); 
     880                this.updateTagSelectorSize(); 
     881 
     882                // If showing, set scope to items in current view 
     883                // and focus filter textbox 
     884                if (showing) { 
     885                        _setTagScope(); 
     886                        tagSelector.focusTextbox(); 
     887                } 
     888                // If hiding, clear selection 
     889                else { 
     890                        tagSelector.uninit(); 
     891                } 
     892        } 
     893 
     894 
     895        function updateTagSelectorSize() { 
     896                //Zotero.debug('Updating tag selector size'); 
     897                var zoteroPane = document.getElementById('zotero-pane-stack'); 
     898                var splitter = document.getElementById('zotero-tags-splitter'); 
     899                var tagSelector = document.getElementById('zotero-tag-selector'); 
     900 
     901                // Nothing should be bigger than appcontent's height 
     902                var max = document.getElementById('appcontent').boxObject.height 
     903                                        - splitter.boxObject.height; 
     904 
     905                // Shrink tag selector to appcontent's height 
     906                var maxTS = max - COLLECTIONS_HEIGHT; 
     907                if (parseInt(tagSelector.getAttribute("height")) > maxTS) { 
     908                        //Zotero.debug("Limiting tag selector height to appcontent"); 
     909                        tagSelector.setAttribute('height', maxTS); 
     910                } 
     911 
     912                var height = tagSelector.boxObject.height; 
     913 
     914                /* 
     915                Zotero.debug("tagSelector.boxObject.height: " + tagSelector.boxObject.height); 
     916                Zotero.debug("tagSelector.getAttribute('height'): " + tagSelector.getAttribute('height')); 
     917                Zotero.debug("zoteroPane.boxObject.height: " + zoteroPane.boxObject.height); 
     918                Zotero.debug("zoteroPane.getAttribute('height'): " + zoteroPane.getAttribute('height')); 
     919                */ 
     920 
     921                // Don't let the Z-pane jump back down to its previous height 
     922                // (if shrinking or hiding the tag selector let it clear the min-height) 
     923                if (zoteroPane.getAttribute('height') < zoteroPane.boxObject.height) { 
     924                        //Zotero.debug("Setting Zotero pane height attribute to " +  zoteroPane.boxObject.height); 
     925                        zoteroPane.setAttribute('height', zoteroPane.boxObject.height); 
     926                } 
     927 
     928                if (tagSelector.getAttribute('collapsed') == 'true') { 
     929                        // 32px is the default Z pane min-height in overlay.css 
     930                        height = 32; 
     931                } 
     932                else { 
     933                        // tS.boxObject.height doesn't exist at startup, so get from attribute 
     934                        if (!height) { 
     935                                height = parseInt(tagSelector.getAttribute('height')); 
     936                        } 
     937                        // 121px seems to be enough room for the toolbar and collections 
     938                        // tree at minimum height 
     939                        height = height + COLLECTIONS_HEIGHT; 
     940                } 
     941 
     942                //Zotero.debug('Setting Zotero pane minheight to ' + height); 
     943                zoteroPane.setAttribute('minheight', height); 
     944 
     945                if (this.isShowing() && !this.isFullScreen()) { 
     946                        zoteroPane.setAttribute('savedHeight', zoteroPane.boxObject.height); 
     947                } 
     948 
     949                // Fix bug whereby resizing the Z pane downward after resizing 
     950                // the tag selector up and then down sometimes caused the Z pane to 
     951                // stay at a fixed size and get pushed below the bottom 
     952                tagSelector.height++; 
     953                tagSelector.height--; 
     954        } 
     955 
     956 
     957        function getTagSelection(){ 
     958                var tagSelector = document.getElementById('zotero-tag-selector'); 
     959                return tagSelector.selection ? tagSelector.selection : {}; 
     960        } 
     961 
     962 
     963        function clearTagSelection() { 
     964                if (Zotero.hasValues(this.getTagSelection())) { 
     965                        var tagSelector = document.getElementById('zotero-tag-selector'); 
     966                        tagSelector.clearAll(); 
     967                } 
     968        } 
     969 
     970 
     971        /* 
     972         * Sets the tag filter on the items view 
     973         */ 
     974        function updateTagFilter(){ 
     975                this.itemsView.setFilter('tags', getTagSelection()); 
     976        } 
     977 
     978 
     979        /* 
     980         * Set the tags scope to the items in the current view 
     981         * 
     982         * Passed to the items tree to trigger on changes 
     983         */ 
     984        function _setTagScope() { 
     985                var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     986                var tagSelector = document.getElementById('zotero-tag-selector'); 
     987                if (!tagSelector.getAttribute('collapsed') || 
     988                                tagSelector.getAttribute('collapsed') == 'false') { 
     989                        Zotero.debug('Updating tag selector with current tags'); 
     990                        if (itemGroup.editable) { 
     991                                tagSelector.mode = 'edit'; 
     992                        } 
     993                        else { 
     994                                tagSelector.mode = 'view'; 
     995                        } 
     996                        tagSelector.libraryID = itemGroup.ref.libraryID; 
     997                        tagSelector.scope = itemGroup.getChildTags(); 
     998                } 
     999        } 
     1000 
     1001 
     1002        function onCollectionSelected() 
     1003        { 
     1004                if (this.itemsView) 
     1005                { 
     1006                        this.itemsView.unregister(); 
     1007                        if (this.itemsView.wrappedJSObject.listener) { 
     1008                                document.getElementById('zotero-items-tree').removeEventListener( 
     1009                                        'keypress', this.itemsView.wrappedJSObject.listener, false 
     1010                                ); 
     1011                        } 
     1012                        this.itemsView.wrappedJSObject.listener = null; 
     1013                        document.getElementById('zotero-items-tree').view = this.itemsView = null; 
     1014                } 
     1015 
     1016                // Selection may have been set to single for duplicates checking. 
     1017                document.getElementById("zotero-items-tree").setAttribute("seltype", "multiple"); 
     1018                document.getElementById('zotero-tb-search').value = ""; 
     1019 
     1020                if (this.collectionsView.selection.count != 1) { 
     1021                        document.getElementById('zotero-items-tree').view = this.itemsView = null; 
     1022                        return; 
     1023                } 
     1024 
     1025                // this.collectionsView.selection.currentIndex != -1 
     1026 
     1027                var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1028 
     1029                /* 
     1030                if (itemgroup.isSeparator()) { 
     1031                        document.getElementById('zotero-items-tree').view = this.itemsView = null; 
     1032                        return; 
     1033                } 
     1034                */ 
     1035 
     1036                itemgroup.setSearch(''); 
     1037                itemgroup.setTags(getTagSelection()); 
     1038                itemgroup.showDuplicates = false; 
     1039 
     1040                try { 
     1041                        Zotero.UnresponsiveScriptIndicator.disable(); 
     1042                        this.itemsView = new Zotero.ItemTreeView(itemgroup); 
     1043                        this.itemsView.addCallback(_setTagScope); 
     1044                        document.getElementById('zotero-items-tree').view = this.itemsView; 
     1045                        this.itemsView.selection.clearSelection(); 
     1046                } 
     1047                finally { 
     1048                        Zotero.UnresponsiveScriptIndicator.enable(); 
     1049                } 
     1050 
     1051                if (itemgroup.isLibrary()) { 
     1052                        Zotero.Prefs.set('lastViewedFolder', 'L'); 
     1053                } 
     1054                if (itemgroup.isCollection()) { 
     1055                        Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.id); 
     1056                } 
     1057                else if (itemgroup.isSearch()) { 
     1058                        Zotero.Prefs.set('lastViewedFolder', 'S' + itemgroup.ref.id); 
     1059                } 
     1060                else if (itemgroup.isGroup()) { 
     1061                        Zotero.Prefs.set('lastViewedFolder', 'G' + itemgroup.ref.id); 
     1062                } 
     1063        } 
     1064 
     1065        this.showDuplicates = function () { 
     1066                if (this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) { 
     1067                        var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1068                        itemGroup.showDuplicates = true; 
     1069 
     1070                        try { 
     1071                                Zotero.UnresponsiveScriptIndicator.disable(); 
     1072                                // Reset to multiple by onCollectionSelected 
     1073                                document.getElementById("zotero-items-tree").setAttribute("seltype", "single"); 
     1074                                // Toggle forces acquisition of initial duplicates 
     1075                                // match data. 
     1076                                this.itemsView.refresh(true); 
     1077                                this.itemsView.sort(); 
     1078                        } 
     1079                        finally { 
     1080                                Zotero.UnresponsiveScriptIndicator.enable(); 
     1081                        } 
     1082                } 
     1083        } 
     1084 
     1085 
     1086        function itemSelected() 
     1087        { 
     1088                if (!Zotero.stateCheck()) { 
     1089                        this.displayErrorMessage(); 
     1090                        return; 
     1091                } 
     1092 
     1093                // Display restore button if items selected in Trash 
     1094                if (this.itemsView && this.itemsView.selection.count) { 
     1095                        document.getElementById('zotero-item-restore-button').hidden 
     1096                                = !this.itemsView._itemGroup.isTrash() 
     1097                                        || _nonDeletedItemsSelected(this.itemsView); 
     1098                } 
     1099                else { 
     1100                        document.getElementById('zotero-item-restore-button').hidden = true; 
     1101                } 
     1102 
     1103                var tabs = document.getElementById('zotero-view-tabbox'); 
     1104 
     1105                if (this.itemsView && this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1) 
     1106                { 
     1107                        var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex); 
     1108 
     1109                        if(item.ref.isNote()) { 
     1110                                var noteEditor = document.getElementById('zotero-note-editor'); 
     1111                                noteEditor.mode = this.collectionsView.editable ? 'edit' : 'view'; 
     1112 
     1113                                // If loading new or different note, disable undo while we repopulate the text field 
     1114                                // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't 
     1115                                // undo content from another note into the current one. 
     1116                                if (!noteEditor.item || noteEditor.item.id != item.ref.id) { 
     1117                                        noteEditor.disableUndo(); 
     1118                                } 
     1119                                noteEditor.parent = null; 
     1120                                noteEditor.item = item.ref; 
     1121 
     1122                                noteEditor.enableUndo(); 
     1123 
     1124                                var viewButton = document.getElementById('zotero-view-note-button'); 
     1125                                if (this.collectionsView.editable) { 
     1126                                        viewButton.hidden = false; 
     1127                                        viewButton.setAttribute('noteID', item.ref.id); 
     1128                                        if (item.ref.getSource()) { 
     1129                                                viewButton.setAttribute('sourceID', item.ref.getSource()); 
     1130                                        } 
     1131                                        else { 
     1132                                                viewButton.removeAttribute('sourceID'); 
     1133                                        } 
     1134                                } 
     1135                                else { 
     1136                                        viewButton.hidden = true; 
     1137                                } 
     1138 
     1139                                document.getElementById('zotero-item-pane-content').selectedIndex = 2; 
     1140                        } 
     1141 
     1142                        else if(item.ref.isAttachment()) { 
     1143                                var attachmentBox = document.getElementById('zotero-attachment-box'); 
     1144                                attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view'; 
     1145                                attachmentBox.item = item.ref; 
     1146 
     1147                                document.getElementById('zotero-item-pane-content').selectedIndex = 3; 
     1148                        } 
     1149 
     1150                        // Regular item 
     1151                        else 
     1152                        { 
     1153                                document.getElementById('zotero-item-pane-content').selectedIndex = 1; 
     1154                                var pane = document.getElementById('zotero-view-tabbox').selectedIndex; 
     1155                                if (this.collectionsView.editable) { 
     1156                                        ZoteroItemPane.viewItem(item.ref, null, pane); 
     1157                                        tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex; 
     1158                                } 
     1159                                else { 
     1160                                        ZoteroItemPane.viewItem(item.ref, 'view', pane); 
     1161                                        tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex; 
     1162                                } 
     1163                        } 
     1164                } 
     1165                else 
     1166                { 
     1167                        document.getElementById('zotero-item-pane-content').selectedIndex = 0; 
     1168 
     1169                        var label = document.getElementById('zotero-view-selected-label'); 
     1170 
     1171                        if (this.itemsView && this.itemsView.selection.count) { 
     1172                                label.value = Zotero.getString('pane.item.selected.multiple', this.itemsView.selection.count); 
     1173                        } 
     1174                        else { 
     1175                                label.value = Zotero.getString('pane.item.selected.zero'); 
     1176                        } 
     1177                } 
     1178        } 
     1179 
     1180 
     1181        /** 
     1182         * Check if any selected items in the passed (trash) treeview are not deleted 
     1183         * 
     1184         * @param       {nsITreeView} 
     1185         * @return      {Boolean} 
     1186         */ 
     1187        function _nonDeletedItemsSelected(itemsView) { 
     1188                var start = {}; 
     1189                var end = {}; 
     1190                for (var i=0, len=itemsView.selection.getRangeCount(); i<len; i++) { 
     1191                        itemsView.selection.getRangeAt(i, start, end); 
     1192                        for (var j=start.value; j<=end.value; j++) { 
     1193                                if (!itemsView._getItemAtRow(j).ref.deleted) { 
     1194                                        return true; 
     1195                                } 
     1196                        } 
     1197                } 
     1198                return false; 
     1199        } 
     1200 
     1201 
     1202        this.updateNoteButtonMenu = function () { 
     1203                var items = ZoteroPane.getSelectedItems(); 
     1204                var button = document.getElementById('zotero-tb-add-child-note'); 
     1205                button.disabled = !this.canEdit() || 
     1206                        !(items.length == 1 && (items[0].isRegularItem() || !items[0].isTopLevelItem())); 
     1207        } 
     1208 
     1209 
     1210        this.updateAttachmentButtonMenu = function (popup) { 
     1211                var items = ZoteroPane.getSelectedItems(); 
     1212 
     1213                var disabled = !this.canEdit() || !(items.length == 1 && items[0].isRegularItem()); 
     1214 
     1215                if (disabled) { 
     1216                        for each(var node in popup.childNodes) { 
     1217                                node.disabled = true; 
     1218                        } 
     1219                        return; 
     1220                } 
     1221 
     1222                var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1223                var canEditFiles = this.canEditFiles(); 
     1224 
     1225                var prefix = "menuitem-iconic zotero-menuitem-attachments-"; 
     1226 
     1227                for (var i=0; i<popup.childNodes.length; i++) { 
     1228                        var node = popup.childNodes[i]; 
     1229 
     1230                        switch (node.className) { 
     1231                                case prefix + 'link': 
     1232                                        node.disabled = itemgroup.isWithinGroup(); 
     1233                                        break; 
     1234 
     1235                                case prefix + 'snapshot': 
     1236                                case prefix + 'file': 
     1237                                        node.disabled = !canEditFiles; 
     1238                                        break; 
     1239 
     1240                                case prefix + 'web-link': 
     1241                                        node.disabled = false; 
     1242                                        break; 
     1243 
     1244                                default: 
     1245                                        throw ("Invalid class name '" + node.className + "' in ZoteroPane.updateAttachmentButtonMenu()"); 
     1246                        } 
     1247                } 
     1248        } 
     1249 
     1250 
     1251        this.checkPDFConverter = function () { 
     1252                if (Zotero.Fulltext.pdfConverterIsRegistered()) { 
     1253                        return true; 
     1254                } 
     1255 
     1256                var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 
     1257                        .getService(Components.interfaces.nsIPromptService); 
     1258                var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 
     1259                        + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 
     1260                var index = ps.confirmEx( 
     1261                        null, 
     1262                        // TODO: localize 
     1263                        "PDF Tools Not Installed", 
     1264                        "To use this feature, you must first install the PDF tools in " 
     1265                                + "the Zotero preferences.", 
     1266                        buttonFlags, 
     1267                        "Open Preferences", 
     1268                        null, null, null, {} 
     1269                ); 
     1270                if (index == 0) { 
     1271                        ZoteroPane.openPreferences('zotero-prefpane-search', 'pdftools-install'); 
     1272                } 
     1273                return false; 
     1274        } 
     1275 
     1276 
     1277        function reindexItem() { 
     1278                var items = this.getSelectedItems(); 
     1279                if (!items) { 
     1280                        return; 
     1281                } 
     1282 
     1283                var itemIDs = []; 
     1284                var checkPDF = false; 
     1285                for (var i=0; i<items.length; i++) { 
     1286                        // If any PDFs, we need to make sure the converter is installed and 
     1287                        // prompt for installation if not 
     1288                        if (!checkPDF && items[i].attachmentMIMEType && items[i].attachmentMIMEType == "application/pdf") { 
     1289                                checkPDF = true; 
     1290                        } 
     1291                        itemIDs.push(items[i].id); 
     1292                } 
     1293 
     1294                if (checkPDF) { 
     1295                        var installed = this.checkPDFConverter(); 
     1296                        if (!installed) { 
     1297                                document.getElementById('zotero-attachment-box').updateItemIndexedState(); 
     1298                                return; 
     1299                        } 
     1300                } 
     1301 
     1302                Zotero.Fulltext.indexItems(itemIDs, true); 
     1303                document.getElementById('zotero-attachment-box').updateItemIndexedState(); 
     1304        } 
     1305 
     1306    function duplicateSubstitute() { 
     1307                var item, pos, len, itemGroup; 
     1308                var items = this.getSelectedItems(); 
     1309                if (items && items.length) { 
     1310                        item = items[0]; 
     1311                        itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     1312                        var duplicateItemIDs = itemGroup.duplicates._duplicateItemIDs; 
     1313                        var newID = false; 
     1314                        for (pos = 0, len = duplicateItemIDs[item.id].length; pos < len; pos += 1) { 
     1315                                if (!duplicateItemIDs[duplicateItemIDs[item.id][pos]]) { 
     1316                                        newID = duplicateItemIDs[item.id][pos]; 
     1317                                        break; 
     1318                                } 
     1319                        } 
     1320                        if (newID) { 
     1321                                var newItem = Zotero.Items.get(newID); 
     1322                                var targetCollectionIDs = item.getCollections(); 
     1323                                for (pos = 0, len = targetCollectionIDs.length; pos < len; pos += 1) { 
     1324                                        if (!newItem.inCollection(targetCollectionIDs[pos])) { 
     1325                                                var targetCollection = Zotero.Collections.get(targetCollectionIDs[pos]); 
     1326                                                targetCollection.addItem(newItem.id); 
     1327                                        } 
     1328                                } 
     1329                        } 
     1330                } 
     1331 
     1332                var ids = [item.id]; 
     1333 
     1334                if (itemGroup.isBucket()) { 
     1335                        itemGroup.ref.deleteItems(ids); 
     1336                } else if (itemGroup.isTrash()) { 
     1337                        Zotero.Items.erase(ids); 
     1338                } else if (itemGroup.isGroup() || (itemGroup.isWithinGroup())) { 
     1339                        Zotero.Items.erase(ids); 
     1340                } else if (itemGroup.isLibrary()) { 
     1341                        Zotero.Items.trash(ids); 
     1342                } 
     1343        } 
     1344 
     1345        function duplicateMarkItem() { 
     1346                var pos, len, item, itemGroup; 
     1347                var items = this.getSelectedItems(); 
     1348                itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     1349                Zotero.DB.beginTransaction(); 
     1350                for (pos = 0, len = items.length; pos < len; pos += 1) { 
     1351                        item = items[pos]; 
     1352                        if (item.isAttachment()) { 
     1353                                continue; 
     1354                        } 
     1355                        var title = "title"; 
     1356                        if (item.itemTypeID == 17) { 
     1357                                title = "caseName"; 
     1358                        } 
     1359                        Zotero.DB.query("DELETE FROM duplicateCheckList WHERE itemID=?", [item.id]); 
     1360                        Zotero.DB.query("INSERT INTO duplicateCheckList VALUES (?, ?)", [item.id, title]); 
     1361                        itemGroup.duplicates.markItem(item.id); 
     1362                } 
     1363                Zotero.DB.commitTransaction(); 
     1364                var savedSelection = this.itemsView.saveSelection(); 
     1365                this.itemsView._treebox.invalidate(); 
     1366                this.itemsView.refresh(); 
     1367                this.itemsView.sort(); 
     1368                this.itemsView.rememberSelection(savedSelection); 
     1369        } 
     1370 
     1371        function duplicateUnmarkItem() { 
     1372                var itemGroup, pos, len, checkID; 
     1373                if (this.itemsView && this.itemsView._itemGroup && this.itemsView._itemGroup.showDuplicates) { 
     1374                        var items = this.getSelectedItems(); 
     1375                        if (items && items.length) { 
     1376                                var item = items[0]; 
     1377                                Zotero.DB.beginTransaction(); 
     1378                                Zotero.DB.query("DELETE FROM duplicateCheckList WHERE itemID=?", [item.id]); 
     1379                                Zotero.DB.commitTransaction(); 
     1380                                itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     1381                                itemGroup.duplicates.deleteItem([item.id], true); 
     1382 
     1383                                var savedSelection = this.itemsView.saveSelection(); 
     1384                                this.itemsView._treebox.invalidate(); 
     1385                                this.itemsView.refresh(); 
     1386                                this.itemsView.sort(); 
     1387                                this.itemsView.rememberSelection(savedSelection); 
     1388                        } 
     1389                } 
     1390 
     1391        } 
     1392 
     1393        function duplicateUnmarkAll() { 
     1394                var pos, len, itemGroup, itemIDs, checkID, duplicateItemIDs, actionIDs, sql; 
     1395                if (this.itemsView && this.itemsView._itemGroup && this.itemsView._itemGroup.showDuplicates) { 
     1396                        itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     1397                        itemIDs = itemGroup.duplicates._itemIDs; 
     1398                        duplicateItemIDs = itemGroup.duplicates._duplicateItemIDs; 
     1399                        actionIDs = []; 
     1400                        for (pos = 0, len = itemIDs.length; pos < len; pos += 1) { 
     1401                                checkID = itemIDs[pos]; 
     1402                                if (duplicateItemIDs[checkID] && duplicateItemIDs[checkID].length === 0) { 
     1403                                        actionIDs.push(checkID); 
     1404                                } 
     1405                        } 
     1406                        if (actionIDs.length) { 
     1407                                sql = "DELETE FROM duplicateCheckList WHERE (itemID="; 
     1408                                sql = sql + actionIDs.join(" OR itemID=") + ")"; 
     1409                                Zotero.DB.beginTransaction(); 
     1410                                Zotero.DB.query(sql); 
     1411                                Zotero.DB.commitTransaction(); 
     1412 
     1413                                itemGroup.duplicates.deleteItem(actionIDs, true); 
     1414 
     1415                                var savedSelection = this.itemsView.saveSelection(); 
     1416                                this.itemsView._treebox.invalidate(); 
     1417                                this.itemsView.refresh(); 
     1418                                this.itemsView.sort(); 
     1419                                this.itemsView.rememberSelection(savedSelection); 
     1420                        } 
     1421                } 
     1422        } 
     1423 
     1424        function duplicateSelectedItem() { 
     1425                var pos, len, duplicateItemIDs, reverseLookup; 
     1426                if (!this.canEdit()) { 
     1427                        this.displayCannotEditLibraryMessage(); 
     1428                        return; 
     1429                } 
     1430 
     1431                var item = this.getSelectedItems()[0]; 
     1432                var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     1433 
     1434                Zotero.DB.beginTransaction(); 
     1435 
     1436                // Create new unsaved clone item in target library 
     1437                var newItem = new Zotero.Item(item.itemTypeID); 
     1438                newItem.libraryID = item.libraryID; 
     1439                // DEBUG: save here because clone() doesn't currently work on unsaved tagged items 
     1440                var id = newItem.save(); 
     1441 
     1442                var newItem = Zotero.Items.get(id); 
     1443                item.clone(false, newItem); 
     1444                newItem.save(); 
     1445 
     1446                // Adjust duplicates if in showDuplicates view. 
     1447                if (itemGroup.showDuplicates) { 
     1448                        itemGroup.duplicates.addItem(id, item.id); 
     1449                } 
     1450 
     1451                if (this.itemsView._itemGroup.isCollection() && !newItem.getSource()) { 
     1452                        this.itemsView._itemGroup.ref.addItem(newItem.id); 
     1453                } 
     1454 
     1455                Zotero.DB.commitTransaction(); 
     1456 
     1457                var savedSelection = this.itemsView.saveSelection(); 
     1458                this.itemsView._treebox.invalidate(); 
     1459                this.itemsView.refresh(); 
     1460                this.itemsView.sort(); 
     1461                this.itemsView.rememberSelection(savedSelection); 
     1462        } 
     1463 
     1464 
     1465        this.deleteSelectedItem = function () { 
     1466                Zotero.debug("ZoteroPane.deleteSelectedItem() is deprecated -- use ZoteroPane.deleteSelectedItems()"); 
     1467                this.deleteSelectedItems(); 
     1468        } 
     1469 
     1470        /* 
     1471         * Remove, trash, or delete item(s), depending on context 
     1472         * 
     1473         * @param       {Boolean}       [force=false]   Trash or delete even if in a collection or search, 
     1474         *                                                                              or trash without prompt in library 
     1475         */ 
     1476        this.deleteSelectedItems = function (force) { 
     1477                var pos, len; 
     1478                if (!this.itemsView || !this.itemsView.selection.count) { 
     1479                        return; 
     1480                } 
     1481                var itemGroup = this.itemsView._itemGroup; 
     1482 
     1483                if (!itemGroup.isTrash() && !itemGroup.isBucket() && !this.canEdit()) { 
     1484                        this.displayCannotEditLibraryMessage(); 
     1485                        return; 
     1486                } 
     1487 
     1488                var toTrash = { 
     1489                        title: Zotero.getString('pane.items.trash.title'), 
     1490                        text: Zotero.getString( 
     1491                                'pane.items.trash' + (this.itemsView.selection.count > 1 ? '.multiple' : '') 
     1492                        ) 
     1493                }; 
     1494                var toDelete = { 
     1495                        title: Zotero.getString('pane.items.delete.title'), 
     1496                        text: Zotero.getString( 
     1497                                'pane.items.delete' + (this.itemsView.selection.count > 1 ? '.multiple' : '') 
     1498                        ) 
     1499                }; 
     1500 
     1501                if (itemGroup.isLibrary()) { 
     1502                        // In library, don't prompt if meta key was pressed 
     1503                        var prompt = force ? false : toTrash; 
     1504                } 
     1505                else if (itemGroup.isCollection()) { 
     1506                        // In collection, only prompt if trashing 
     1507                        var prompt = force ? toTrash : false; 
     1508                } 
     1509                // This should be changed if/when groups get trash 
     1510                else if (itemGroup.isGroup()) { 
     1511                        var prompt = toDelete; 
     1512                } 
     1513                else if (itemGroup.isSearch()) { 
     1514                        if (!force) { 
     1515                                return; 
     1516                        } 
     1517                        var prompt = toTrash; 
     1518                } 
     1519                // Do nothing in share views 
     1520                else if (itemGroup.isShare()) { 
     1521                        return; 
     1522                } 
     1523                else if (itemGroup.isBucket()) { 
     1524                        var prompt = toDelete; 
     1525                } 
     1526                // Do nothing in trash view if any non-deleted items are selected 
     1527                else if (itemGroup.isTrash()) { 
     1528                        var start = {}; 
     1529                        var end = {}; 
     1530                        for (var i=0, len=this.itemsView.selection.getRangeCount(); i<len; i++) { 
     1531                                this.itemsView.selection.getRangeAt(i, start, end); 
     1532                                for (var j=start.value; j<=end.value; j++) { 
     1533                                        if (!this.itemsView._getItemAtRow(j).ref.deleted) { 
     1534                                                return; 
     1535                                        } 
     1536                                } 
     1537                        } 
     1538                        var prompt = toDelete; 
     1539                } 
     1540 
     1541                var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 
     1542                                                                                .getService(Components.interfaces.nsIPromptService); 
     1543                if (!prompt || promptService.confirm(window, prompt.title, prompt.text)) { 
     1544                        this.itemsView.deleteSelection(force); 
     1545                } 
     1546        } 
     1547 
     1548        function deleteSelectedCollection() 
     1549        { 
     1550                if (!this.canEdit()) { 
     1551                        this.displayCannotEditLibraryMessage(); 
     1552                        return; 
     1553                } 
     1554 
     1555                if (this.collectionsView.selection.count == 1) { 
     1556                        var row = 
     1557                                this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1558 
     1559                        if (row.isCollection()) 
     1560                        { 
     1561                                if (confirm(Zotero.getString('pane.collections.delete'))) 
     1562                                { 
     1563                                        this.collectionsView.deleteSelection(); 
     1564                                } 
     1565                        } 
     1566                        else if (row.isSearch()) 
     1567                        { 
     1568                                if (confirm(Zotero.getString('pane.collections.deleteSearch'))) 
     1569                                { 
     1570                                        this.collectionsView.deleteSelection(); 
     1571                                } 
     1572                        } 
     1573                } 
     1574        } 
     1575 
     1576 
     1577        this.restoreSelectedItems = function () { 
     1578                var items = this.getSelectedItems(); 
     1579                if (!items) { 
     1580                        return; 
     1581                } 
     1582 
     1583                Zotero.DB.beginTransaction(); 
     1584                for (var i=0; i<items.length; i++) { 
     1585                        items[i].deleted = false; 
     1586                        items[i].save(); 
     1587                } 
     1588                Zotero.DB.commitTransaction(); 
     1589        } 
     1590 
     1591 
     1592        this.emptyTrash = function () { 
     1593                var prompt = Components.classes["@mozilla.org/network/default-prompt;1"] 
     1594                                                                .getService(Components.interfaces.nsIPrompt); 
     1595 
     1596                var result = prompt.confirm("", 
     1597                        Zotero.getString('pane.collections.emptyTrash') + "\n\n" + 
     1598                        Zotero.getString('general.actionCannotBeUndone')); 
     1599                if (result) { 
     1600                        Zotero.Items.emptyTrash(); 
     1601                        Zotero.purgeDataObjects(true); 
     1602                } 
     1603        } 
     1604 
     1605        this.createCommonsBucket = function () { 
     1606                var prompt = Components.classes["@mozilla.org/network/default-prompt;1"] 
     1607                                                .createInstance(Components.interfaces.nsIPrompt); 
     1608 
     1609                var invalid = false; 
     1610 
     1611                while (true) { 
     1612                        if (invalid) { 
     1613                                // TODO: localize 
     1614                                prompt.alert("", "Invalid title. Please try again."); 
     1615                                invalid = false; 
     1616                        } 
     1617 
     1618                        var newTitle = {}; 
     1619                        var result = prompt.prompt( 
     1620                                "", 
     1621                                // TODO: localize 
     1622                                "Enter a title for this Zotero Commons collection:", 
     1623                                newTitle, 
     1624                                "", {} 
     1625                        ); 
     1626 
     1627                        if (!result) { 
     1628                                return; 
     1629                        } 
     1630 
     1631                        var title = Zotero.Utilities.prototype.trim(newTitle.value); 
     1632 
     1633                        if (!title) { 
     1634                                return; 
     1635                        } 
     1636 
     1637                        if (!Zotero.Commons.isValidBucketTitle(title)) { 
     1638                                invalid = true; 
     1639                                continue; 
     1640                        } 
     1641 
     1642                        break; 
     1643                } 
     1644 
     1645                invalid = false; 
     1646 
     1647                var origName = title.toLowerCase(); 
     1648                origName = origName.replace(/[^a-z0-9 ._-]/g, ''); 
     1649                origName = origName.replace(/ /g, '-'); 
     1650                origName = origName.substr(0, 32); 
     1651 
     1652                while (true) { 
     1653                        if (invalid) { 
     1654                                // TODO: localize 
     1655                                var msg = "'" + name + "' is not a valid Zotero Commons collection identifier.\n\n" 
     1656                                        + "Collection identifiers can contain basic Latin letters, numbers," 
     1657                                        + "hyphens, and underscores. Spaces and other characters are not allowed."; 
     1658                                prompt.alert("", msg); 
     1659                                invalid = false; 
     1660                        } 
     1661 
     1662                        var newName = { value: origName }; 
     1663                        var result = prompt.prompt( 
     1664                                "", 
     1665                                // TODO: localize 
     1666                                "Enter an identifier for the collection '" + title + "'.\n\n" 
     1667                                        + "The identifier will form the collection's URL " 
     1668                                        + "(e.g., http://www.archive.org/details/" + origName + ") " 
     1669                                        + "and can contain basic Latin letters, numbers, hyphens, and underscores. " 
     1670                                        + "Spaces and other characters are not allowed.", 
     1671                                newName, 
     1672                                "", {} 
     1673                        ); 
     1674 
     1675                        if (!result) { 
     1676                                return; 
     1677                        } 
     1678 
     1679                        var name = Zotero.Utilities.prototype.trim(newName.value); 
     1680 
     1681                        if (!name) { 
     1682                                return; 
     1683                        } 
     1684 
     1685                        if (!Zotero.Commons.isValidBucketName(name)) { 
     1686                                invalid = true; 
     1687                                continue; 
     1688                        } 
     1689 
     1690                        break; 
     1691                } 
     1692 
     1693                // TEMP 
     1694                var name = "zc-test-" + name; 
     1695 
     1696                // TODO: localize 
     1697                var progressWin = new Zotero.ProgressWindow(); 
     1698                progressWin.changeHeadline("Creating Zotero Commons Collection"); 
     1699                var icon = this.collectionsView.getImageSrc(this.collectionsView.selection.currentIndex); 
     1700                progressWin.addLines(title, icon) 
     1701                progressWin.show(); 
     1702 
     1703                Zotero.Commons.createBucket(name, title, function () { 
     1704                        progressWin.startCloseTimer(); 
     1705                }); 
     1706        } 
     1707 
     1708 
     1709        this.refreshCommonsBucket = function() { 
     1710                if (!this.collectionsView 
     1711                                || !this.collectionsView.selection 
     1712                                || this.collectionsView.selection.count != 1 
     1713                                || this.collectionsView.selection.currentIndex == -1) { 
     1714                        return false; 
     1715                } 
     1716 
     1717                var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1718                if (itemGroup && itemGroup.isBucket()) { 
     1719                        var self = this; 
     1720                        itemGroup.ref.refreshItems(function () { 
     1721                                self.itemsView.refresh(); 
     1722                                self.itemsView.sort(); 
     1723 
     1724                                // On a manual refresh, also check for new OCRed files 
     1725                                //Zotero.Commons.syncFiles(); 
     1726                        }); 
     1727                } 
     1728        } 
     1729 
     1730        function editSelectedCollection() 
     1731        { 
     1732                if (!this.canEdit()) { 
     1733                        this.displayCannotEditLibraryMessage(); 
     1734                        return; 
     1735                } 
     1736 
     1737                if (this.collectionsView.selection.count > 0) { 
     1738                        var row = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1739 
     1740                        if (row.isCollection()) { 
     1741                                var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 
     1742                                                                                .getService(Components.interfaces.nsIPromptService); 
     1743 
     1744                                var newName = { value: row.getName() }; 
     1745                                var result = promptService.prompt(window, "", 
     1746                                        Zotero.getString('pane.collections.rename'), newName, "", {}); 
     1747 
     1748                                if (result && newName.value) { 
     1749                                        row.ref.name = newName.value; 
     1750                                        row.ref.save(); 
     1751                                } 
     1752                        } 
     1753                        else { 
     1754                                var s = new Zotero.Search(); 
     1755                                s.id = row.ref.id; 
     1756                                var io = {dataIn: {search: s, name: row.getName()}, dataOut: null}; 
     1757                                window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io); 
     1758                                if (io.dataOut) { 
     1759                                        this.onCollectionSelected(); //reload itemsView 
     1760                                } 
     1761                        } 
     1762                } 
     1763        } 
     1764 
     1765 
     1766        function copySelectedItemsToClipboard(asCitations) { 
     1767                var items = this.getSelectedItems(); 
     1768                if (!items.length) { 
     1769                        return; 
     1770                } 
     1771 
     1772                // Make sure at least one item is a regular item 
     1773                // 
     1774                // DEBUG: We could copy notes via keyboard shortcut if we altered 
     1775                // Z_F_I.copyItemsToClipboard() to use Z.QuickCopy.getContentFromItems(), 
     1776                // but 1) we'd need to override that function's drag limit and 2) when I 
     1777                // tried it the OS X clipboard seemed to be getting text vs. HTML wrong, 
     1778                // automatically converting text/html to plaintext rather than using 
     1779                // text/unicode. (That may be fixable, however.) 
     1780                var canCopy = false; 
     1781                for each(var item in items) { 
     1782                        if (item.isRegularItem()) { 
     1783                                canCopy = true; 
     1784                                break; 
     1785                        } 
     1786                } 
     1787                if (!canCopy) { 
     1788                        var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     1789                                                .getService(Components.interfaces.nsIPrompt); 
     1790                        pr.alert("", Zotero.getString("fileInterface.noReferencesError")); 
     1791                        return; 
     1792                } 
     1793 
     1794                var url = window.content.location.href; 
     1795                var [mode, format] = Zotero.QuickCopy.getFormatFromURL(url).split('='); 
     1796                var [mode, contentType] = mode.split('/'); 
     1797 
     1798                if (mode == 'bibliography') { 
     1799                        if (asCitations) { 
     1800                                Zotero_File_Interface.copyCitationToClipboard(items, format, contentType == 'html'); 
     1801                        } 
     1802                        else { 
     1803                                Zotero_File_Interface.copyItemsToClipboard(items, format, contentType == 'html'); 
     1804                        } 
     1805                } 
     1806                else if (mode == 'export') { 
     1807                        // Copy citations doesn't work in export mode 
     1808                        if (asCitations) { 
     1809                                return; 
     1810                        } 
     1811                        else { 
     1812                                Zotero_File_Interface.exportItemsToClipboard(items, format); 
     1813                        } 
     1814                } 
     1815        } 
     1816 
     1817 
     1818        function clearQuicksearch() { 
     1819                var search = document.getElementById('zotero-tb-search'); 
     1820                if (search.value != '') { 
     1821                        search.value = ''; 
     1822                        search.doCommand('cmd_zotero_search'); 
     1823                } 
     1824        } 
     1825 
     1826 
     1827        function handleSearchKeypress(textbox, event) { 
     1828                // Events that turn find-as-you-type on 
     1829                if (event.keyCode == event.DOM_VK_ESCAPE) { 
     1830                        textbox.value = ''; 
     1831                        ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress')); 
     1832                        setTimeout("ZoteroPane.search(); ZoteroPane.clearItemsPaneMessage();", 1); 
     1833                } 
     1834                else if (event.keyCode == event.DOM_VK_RETURN || event.keyCode == event.DOM_VK_ENTER) { 
     1835                        ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress')); 
     1836                        setTimeout("ZoteroPane.search(true); ZoteroPane.clearItemsPaneMessage();", 1); 
     1837                } 
     1838        } 
     1839 
     1840 
     1841        function handleSearchInput(textbox, event) { 
     1842                // This is the new length, except, it seems, when the change is a 
     1843                // result of Undo or Redo 
     1844                if (!textbox.value.length) { 
     1845                        ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress')); 
     1846                        setTimeout("ZoteroPane.search(); ZoteroPane.clearItemsPaneMessage();", 1); 
     1847                } 
     1848                else if (textbox.value.indexOf('"') != -1) { 
     1849                        ZoteroPane.setItemsPaneMessage(Zotero.getString('advancedSearchMode')); 
     1850                } 
     1851        } 
     1852 
     1853 
     1854        function search(runAdvanced) 
     1855        { 
     1856                if (this.itemsView) { 
     1857                        var search = document.getElementById('zotero-tb-search'); 
     1858                        if (!runAdvanced && search.value.indexOf('"') != -1) { 
     1859                                return; 
     1860                        } 
     1861                        var searchVal = search.value; 
     1862                        this.itemsView.setFilter('search', searchVal); 
     1863                } 
     1864        } 
     1865 
     1866 
     1867        /* 
     1868         * Select item in current collection or, if not there, in Library 
     1869         * 
     1870         * If _inLibrary_, force switch to Library 
     1871         * If _expand_, open item if it's a container 
     1872         */ 
     1873        function selectItem(itemID, inLibrary, expand) 
     1874        { 
     1875                if (!itemID) { 
     1876                        return false; 
     1877                } 
     1878 
     1879                var item = Zotero.Items.get(itemID); 
     1880                if (!item) { 
     1881                        return false; 
     1882                } 
     1883 
     1884                if (!this.itemsView) { 
     1885                        Components.utils.reportError("Items view not set in ZoteroPane.selectItem()"); 
     1886                        return false; 
     1887                } 
     1888 
     1889                var currentLibraryID = this.getSelectedLibraryID(); 
     1890                // If in a different library 
     1891                if (item.libraryID != currentLibraryID) { 
     1892                        this.collectionsView.selectLibrary(item.libraryID); 
     1893                } 
     1894                // Force switch to library view 
     1895                else if (!this.itemsView._itemGroup.isLibrary() && inLibrary) { 
     1896                        this.collectionsView.selectLibrary(item.libraryID); 
     1897                } 
     1898 
     1899                var selected = this.itemsView.selectItem(itemID, expand); 
     1900                if (!selected) { 
     1901                        this.collectionsView.selectLibrary(item.libraryID); 
     1902                        this.itemsView.selectItem(itemID, expand); 
     1903                } 
     1904 
     1905                return true; 
     1906        } 
     1907 
     1908 
     1909        this.getSelectedLibraryID = function () { 
     1910                var group = this.getSelectedGroup(); 
     1911                if (group) { 
     1912                        return group.libraryID; 
     1913                } 
     1914                var collection = this.getSelectedCollection(); 
     1915                if (collection) { 
     1916                        return collection.libraryID; 
     1917                } 
     1918                return null; 
     1919        } 
     1920 
     1921 
     1922        function getSelectedCollection(asID) { 
     1923                if (this.collectionsView) { 
     1924                        return this.collectionsView.getSelectedCollection(asID); 
     1925                } 
     1926                return false; 
     1927        } 
     1928 
     1929 
     1930        function getSelectedSavedSearch(asID) 
     1931        { 
     1932                if (this.collectionsView.selection.count > 0 && this.collectionsView.selection.currentIndex != -1) { 
     1933                        var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1934                        if (collection && collection.isSearch()) { 
     1935                                return asID ? collection.ref.id : collection.ref; 
     1936                        } 
     1937                } 
     1938                return false; 
     1939        } 
     1940 
     1941 
     1942        /* 
     1943         * Return an array of Item objects for selected items 
     1944         * 
     1945         * If asIDs is true, return an array of itemIDs instead 
     1946         */ 
     1947        function getSelectedItems(asIDs) 
     1948        { 
     1949                if (!this.itemsView) { 
     1950                        return []; 
     1951                } 
     1952 
     1953                return this.itemsView.getSelectedItems(asIDs); 
     1954        } 
     1955 
     1956 
     1957        this.getSelectedGroup = function (asID) { 
     1958                if (this.collectionsView.selection 
     1959                                && this.collectionsView.selection.count > 0 
     1960                                && this.collectionsView.selection.currentIndex != -1) { 
     1961                        var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     1962                        if (itemGroup && itemGroup.isGroup()) { 
     1963                                return asID ? itemGroup.ref.id : itemGroup.ref; 
     1964                        } 
     1965                } 
     1966                return false; 
     1967        } 
     1968 
     1969 
     1970        /* 
     1971         * Returns an array of Zotero.Item objects of visible items in current sort order 
     1972         * 
     1973         * If asIDs is true, return an array of itemIDs instead 
     1974         */ 
     1975        function getSortedItems(asIDs) { 
     1976                if (!this.itemsView) { 
     1977                        return []; 
     1978                } 
     1979 
     1980                return this.itemsView.getSortedItems(asIDs); 
     1981        } 
     1982 
     1983 
     1984        function getSortField() { 
     1985                if (!this.itemsView) { 
     1986                        return false; 
     1987                } 
     1988 
     1989                return this.itemsView.getSortField(); 
     1990        } 
     1991 
     1992 
     1993        function getSortDirection() { 
     1994                if (!this.itemsView) { 
     1995                        return false; 
     1996                } 
     1997 
     1998                return this.itemsView.getSortDirection(); 
     1999        } 
     2000 
     2001 
     2002        this.buildCollectionContextMenu = function buildCollectionContextMenu() 
     2003        { 
     2004                var menu = document.getElementById('zotero-collectionmenu'); 
     2005                var m = { 
     2006                        newCollection: 0, 
     2007                        newSavedSearch: 1, 
     2008                        newSubcollection: 2, 
     2009                        sep1: 3, 
     2010                        editSelectedCollection: 4, 
     2011                        removeCollection: 5, 
     2012                        sep2: 6, 
     2013                        exportCollection: 7, 
     2014                        createBibCollection: 8, 
     2015                        exportFile: 9, 
     2016                        loadReport: 10, 
     2017                        emptyTrash: 11, 
     2018                        createCommonsBucket: 12, 
     2019                        refreshCommonsBucket: 13, 
     2020                        sep3: 14, 
     2021                        showDuplicates: 15, 
     2022                        unshowDuplicates: 16 
     2023                }; 
     2024 
     2025                var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     2026 
     2027                var enable = [], disable = [], show = []; 
     2028 
     2029                // Collection 
     2030                if (itemGroup.isCollection()) { 
     2031                        show = [ 
     2032                                m.newSubcollection, 
     2033                                m.sep1, 
     2034                                m.editSelectedCollection, 
     2035                                m.removeCollection, 
     2036                                m.sep2, 
     2037                                m.exportCollection, 
     2038                                m.createBibCollection, 
     2039                                m.loadReport 
     2040                        ]; 
     2041                        var s = [m.exportCollection, m.createBibCollection, m.loadReport]; 
     2042                        if (this.itemsView.rowCount>0) { 
     2043                                enable = s; 
     2044                        } 
     2045                        else if (!this.collectionsView.isContainerEmpty(this.collectionsView.selection.currentIndex)) { 
     2046                                enable = [m.exportCollection]; 
     2047                                disable = [m.createBibCollection, m.loadReport]; 
     2048                        } 
     2049                        else { 
     2050                                disable = s; 
     2051                        } 
     2052 
     2053                        // Adjust labels 
     2054                        menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.rename.collection')); 
     2055                        menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.collection')); 
     2056                        menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.collection')); 
     2057                        menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.collection')); 
     2058                        menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.collection')); 
     2059                } 
     2060                // Saved Search 
     2061                else if (itemGroup.isSearch()) { 
     2062                        show = [ 
     2063                                m.editSelectedCollection, 
     2064                                m.removeCollection, 
     2065                                m.sep2, 
     2066                                m.exportCollection, 
     2067                                m.createBibCollection, 
     2068                                m.loadReport 
     2069                        ]; 
     2070 
     2071                        var s = [m.exportCollection, m.createBibCollection, m.loadReport]; 
     2072                        if (this.itemsView.rowCount>0) { 
     2073                                enable = s; 
     2074                        } 
     2075                        else { 
     2076                                disable = s; 
     2077                        } 
     2078 
     2079                        // Adjust labels 
     2080                        menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch')); 
     2081                        menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch')); 
     2082                        menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.savedSearch')); 
     2083                        menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch')); 
     2084                        menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch')); 
     2085                } 
     2086                // Trash 
     2087                else if (itemGroup.isTrash()) { 
     2088                        show = [m.emptyTrash]; 
     2089                } 
     2090                else if (itemGroup.isHeader()) { 
     2091                        if (itemGroup.ref.id == 'commons-header') { 
     2092                                show = [m.createCommonsBucket]; 
     2093                        } 
     2094                } 
     2095                else if (itemGroup.isBucket()) { 
     2096                        show = [m.refreshCommonsBucket]; 
     2097                } 
     2098                // Group 
     2099                else if (itemGroup.isGroup()) { 
     2100                        if (itemGroup.editable) { 
     2101                        show = [m.newCollection, m.newSavedSearch]; 
     2102                                if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2103                                        show.push(m.sep3); 
     2104                                        if (itemGroup.showDuplicates) { 
     2105                                                show.push(m.unshowDuplicates); 
     2106                                        } else { 
     2107                                                show.push(m.showDuplicates); 
     2108                } 
     2109                                } 
     2110                        } 
     2111                } 
     2112                // Library 
     2113                else 
     2114                { 
     2115                        show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile]; 
     2116                        if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2117                                show.push(m.sep3); 
     2118                        if (itemGroup.showDuplicates) { 
     2119                                show.push(m.unshowDuplicates); 
     2120                        } else { 
     2121                                show.push(m.showDuplicates); 
     2122                } 
     2123                } 
     2124                } 
     2125 
     2126                // Disable some actions if user doesn't have write access 
     2127                var s = [m.editSelectedCollection, m.removeCollection, m.newCollection, m.newSavedSearch, m.newSubcollection]; 
     2128                if (itemGroup.isWithinGroup() && !itemGroup.editable) { 
     2129                        disable = disable.concat(s); 
     2130                } 
     2131                else { 
     2132                        enable = enable.concat(s); 
     2133                } 
     2134 
     2135                for (var i in disable) 
     2136                { 
     2137                        menu.childNodes[disable[i]].setAttribute('disabled', true); 
     2138                } 
     2139 
     2140                for (var i in enable) 
     2141                { 
     2142                        menu.childNodes[enable[i]].setAttribute('disabled', false); 
     2143                } 
     2144 
     2145                // Hide all items by default 
     2146                for each(var pos in m) { 
     2147                        menu.childNodes[pos].setAttribute('hidden', true); 
     2148                } 
     2149 
     2150                for (var i in show) 
     2151                { 
     2152                        menu.childNodes[show[i]].setAttribute('hidden', false); 
     2153                } 
     2154        } 
     2155 
     2156        function buildItemContextMenu() 
     2157        { 
     2158                var m = { 
     2159                        showInLibrary: 0, 
     2160                        sep1: 1, 
     2161                        addNote: 2, 
     2162                        addAttachments: 3, 
     2163                        sep2: 4, 
     2164                        duplicateItem: 5, 
     2165                        deleteItem: 6, 
     2166                        deleteFromLibrary: 7, 
     2167                        sep3: 8, 
     2168                        exportItems: 9, 
     2169                        createBib: 10, 
     2170                        loadReport: 11, 
     2171                        sep4: 12, 
     2172                        createParent: 13, 
     2173                        recognizePDF: 14, 
     2174                        renameAttachments: 15, 
     2175                        reindexItem: 16, 
     2176                        sep5: 17, 
     2177                        duplicateMark: 18, 
     2178                        duplicateSubstitute: 19, 
     2179                        duplicateUnmark: 20, 
     2180                        duplicateUnmarkAll: 21 
     2181                }; 
     2182 
     2183                var menu = document.getElementById('zotero-itemmenu'); 
     2184 
     2185                var enable = [], disable = [], show = [], hide = [], multiple = ''; 
     2186 
     2187                if (!this.itemsView) { 
     2188                        return; 
     2189                } 
     2190 
     2191                if (!Zotero.Prefs.get('debugShowDuplicates')) { 
     2192                        hide.push(m.sep5, m.duplicateMark); 
     2193                } 
     2194 
     2195                if (this.itemsView.selection.count > 0) { 
     2196                        var itemGroup = this.itemsView._itemGroup; 
     2197 
     2198                        enable.push(m.showInLibrary, m.addNote, m.addAttachments, 
     2199                                m.sep2, m.duplicateItem, m.deleteItem, m.deleteFromLibrary, 
     2200                                m.exportItems, m.createBib, m.loadReport); 
     2201 
     2202                        // Multiple items selected 
     2203                        if (this.itemsView.selection.count > 1) { 
     2204                                var multiple =  '.multiple'; 
     2205                                hide.push(m.showInLibrary, m.sep1, m.addNote, m.addAttachments, 
     2206                                        m.sep2, m.duplicateItem); 
     2207 
     2208                                // If all items can be reindexed, or all items can be recognized, show option 
     2209                                var items = this.getSelectedItems(); 
     2210                                var canIndex = true; 
     2211                                var canRecognize = true; 
     2212                                var canMark = false; 
     2213                                if (!Zotero.Fulltext.pdfConverterIsRegistered()) { 
     2214                                        canIndex = false; 
     2215                                } 
     2216                                for (var i=0; i<items.length; i++) { 
     2217                                        if (canIndex && !Zotero.Fulltext.canReindex(items[i].id)) { 
     2218                                                canIndex = false; 
     2219                                        } 
     2220                                        if (canRecognize && !Zotero_RecognizePDF.canRecognize(items[i])) { 
     2221                                                canRecognize = false; 
     2222                                        } 
     2223                                        if (!canMark && !items[i].isAttachment() && !this.itemsView._itemGroup.duplicates.isNew(items[i].id)) { 
     2224                                                canMark = true; 
     2225                                        } 
     2226                                        if (!canIndex && !canRecognize && canMark) { 
     2227                                                break; 
     2228                                        } 
     2229                                } 
     2230                                if (canIndex) { 
     2231                                        show.push(m.reindexItem); 
     2232                                } 
     2233                                else { 
     2234                                        hide.push(m.reindexItem); 
     2235                                } 
     2236                                if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2237                                if (!canMark) { 
     2238                                        disable.push(m.duplicateMark); 
     2239                                } else { 
     2240                                        enable.push(m.duplicateMark); 
     2241                                } 
     2242                                } 
     2243                                if (canRecognize) { 
     2244                                        show.push(m.recognizePDF); 
     2245                                        hide.push(m.createParent); 
     2246                                } 
     2247                                else { 
     2248                                        hide.push(m.recognizePDF); 
     2249 
     2250                                        var canCreateParent = true; 
     2251                                        for (var i=0; i<items.length; i++) { 
     2252                                                if (!items[i].isTopLevelItem() || items[i].isRegularItem() || Zotero_RecognizePDF.canRecognize(items[i])) { 
     2253                                                        canCreateParent = false; 
     2254                                                        break; 
     2255                                                } 
     2256                                        } 
     2257                                        if (canCreateParent) { 
     2258                                                show.push(m.createParent); 
     2259                                        } 
     2260                                        else { 
     2261                                                hide.push(m.createParent); 
     2262                                        } 
     2263                                } 
     2264 
     2265                                // If all items are child attachments, show rename option 
     2266                                var canRename = true; 
     2267                                for (var i=0; i<items.length; i++) { 
     2268                                        var item = items[i]; 
     2269                                        // Same check as in rename function 
     2270                                        if (!item.isAttachment() || !item.getSource() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     2271                                                canRename = false; 
     2272                                                break; 
     2273                                        } 
     2274                                } 
     2275                                if (canRename) { 
     2276                                        show.push(m.renameAttachments); 
     2277                                } 
     2278                                else { 
     2279                                        hide.push(m.renameAttachments); 
     2280                                } 
     2281 
     2282                                // Add in attachment separator 
     2283                                if (canCreateParent || canRecognize || canRename || canIndex) { 
     2284                                        show.push(m.sep4); 
     2285                                } 
     2286                                else { 
     2287                                        hide.push(m.sep4); 
     2288                                } 
     2289 
     2290                                // Block certain actions on files if no access and at least one item 
     2291                                // is an imported attachment 
     2292                                if (!itemGroup.filesEditable) { 
     2293                                        var hasImportedAttachment = false; 
     2294                                        for (var i=0; i<items.length; i++) { 
     2295                                                var item = items[i]; 
     2296                                                if (item.isImportedAttachment()) { 
     2297                                                        hasImportedAttachment = true; 
     2298                                                        break; 
     2299                                                } 
     2300                                        } 
     2301                                        if (hasImportedAttachment) { 
     2302                                                var d = [m.deleteFromLibrary, m.createParent, m.renameAttachments]; 
     2303                                                for each(var val in d) { 
     2304                                                        disable.push(val); 
     2305                                                        var index = enable.indexOf(val); 
     2306                                                        if (index != -1) { 
     2307                                                                enable.splice(index, 1); 
     2308                                                        } 
     2309                                                } 
     2310                                        } 
     2311                                } 
     2312                        } 
     2313 
     2314                        // Single item selected 
     2315                        else 
     2316                        { 
     2317                                var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex).ref; 
     2318                                var itemID = item.id; 
     2319                                menu.setAttribute('itemID', itemID); 
     2320 
     2321                                // Show in Library 
     2322                                if (!itemGroup.isLibrary() && !itemGroup.isWithinGroup()) { 
     2323                                        show.push(m.showInLibrary, m.sep1); 
     2324                                } 
     2325                                else { 
     2326                                        hide.push(m.showInLibrary, m.sep1); 
     2327                                } 
     2328 
     2329                                if (item.isRegularItem()) 
     2330                                { 
     2331                                        show.push(m.addNote, m.addAttachments, m.sep2); 
     2332                                } 
     2333                                else 
     2334                                { 
     2335                                        hide.push(m.addNote, m.addAttachments, m.sep2); 
     2336                                } 
     2337 
     2338 
     2339                                if (item.isAttachment()) { 
     2340                                        var showSep4 = false; 
     2341                                        hide.push(m.duplicateItem); 
     2342                                        if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2343                                        disable.push(m.duplicateMark); 
     2344                                        } 
     2345 
     2346                                        if (Zotero_RecognizePDF.canRecognize(item)) { 
     2347                                                show.push(m.recognizePDF); 
     2348                                                hide.push(m.createParent); 
     2349                                                showSep4 = true; 
     2350                                        } 
     2351                                        else { 
     2352                                                hide.push(m.recognizePDF); 
     2353 
     2354                                                // If not a PDF, allow parent item creation 
     2355                                                if (item.isTopLevelItem()) { 
     2356                                                        show.push(m.createParent); 
     2357                                                        showSep4 = true; 
     2358                                                } 
     2359                                                else { 
     2360                                                        hide.push(m.createParent); 
     2361                                                } 
     2362                                        } 
     2363 
     2364                                        // Attachment rename option 
     2365                                        if (item.getSource() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     2366                                                show.push(m.renameAttachments); 
     2367                                                showSep4 = true; 
     2368                                        } 
     2369                                        else { 
     2370                                                hide.push(m.renameAttachments); 
     2371                                        } 
     2372 
     2373                                        if (showSep4) { 
     2374                                                show.push(m.sep4); 
     2375                                        } 
     2376                                        else { 
     2377                                                hide.push(m.sep4); 
     2378                                        } 
     2379 
     2380                                        // If not linked URL, show reindex line 
     2381                                        if (Zotero.Fulltext.pdfConverterIsRegistered() 
     2382                                                        && Zotero.Fulltext.canReindex(item.id)) { 
     2383                                                show.push(m.reindexItem); 
     2384                                                showSep4 = true; 
     2385                                        } 
     2386                                        else { 
     2387                                                hide.push(m.reindexItem); 
     2388                                        } 
     2389                                } 
     2390                                else { // not an  attachment 
     2391                                        if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2392                                        if (!this.itemsView._itemGroup.duplicates.isNew(item.id)) { 
     2393                                        enable.push(m.duplicateMark); 
     2394                                        } else { 
     2395                                                disable.push(m.duplicateMark); 
     2396                                        } 
     2397                                        } 
     2398                                        if (item.isNote() && item.isTopLevelItem()) { 
     2399                                                show.push(m.sep4, m.createParent); 
     2400                                        } 
     2401                                        else { 
     2402                                                hide.push(m.sep4, m.createParent); 
     2403                                        } 
     2404 
     2405                                        show.push(m.duplicateItem); 
     2406                                        hide.push(m.recognizePDF, m.renameAttachments, m.reindexItem); 
     2407                                } 
     2408 
     2409                                // Update attachment submenu 
     2410                                var popup = document.getElementById('zotero-add-attachment-popup') 
     2411                                this.updateAttachmentButtonMenu(popup); 
     2412 
     2413                                // Block certain actions on files if no access 
     2414                                if (item.isImportedAttachment() && !itemGroup.filesEditable) { 
     2415                                        var d = [m.deleteFromLibrary, m.createParent, m.renameAttachments]; 
     2416                                        for each(var val in d) { 
     2417                                                disable.push(val); 
     2418                                                var index = enable.indexOf(val); 
     2419                                                if (index != -1) { 
     2420                                                        enable.splice(index, 1); 
     2421                                                } 
     2422                                        } 
     2423                                } 
     2424                        } 
     2425                } 
     2426                // No items selected 
     2427                else 
     2428                { 
     2429                        // Show in Library 
     2430                        if (!itemGroup.isLibrary()) { 
     2431                                show.push(m.showInLibrary, m.sep1); 
     2432                        } 
     2433                        else { 
     2434                                hide.push(m.showInLibrary, m.sep1); 
     2435                        } 
     2436 
     2437                        disable.push(m.showInLibrary, m.duplicateItem, m.deleteItem, 
     2438                                m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport); 
     2439                        hide.push(m.addNote, m.addAttachments, m.sep2, m.sep4, m.reindexItem, 
     2440                                m.createParent, m.recognizePDF, m.renameAttachments); 
     2441                } 
     2442 
     2443                // TODO: implement menu for remote items 
     2444                if (!itemGroup.editable) { 
     2445                        for (var i in m) { 
     2446                                // Still show export/bib/report for non-editable views 
     2447                                // (other than Commons buckets, which aren't real items) 
     2448                                if (!itemGroup.isBucket()) { 
     2449                                        switch (i) { 
     2450                                                case 'exportItems': 
     2451                                                case 'createBib': 
     2452                                                case 'loadReport': 
     2453                                                        continue; 
     2454                                        } 
     2455                                } 
     2456                                disable.push(m[i]); 
     2457                                var index = enable.indexOf(m[i]); 
     2458                                if (index != -1) { 
     2459                                        enable.splice(index, 1); 
     2460                                } 
     2461                        } 
     2462                } 
     2463 
     2464                // Remove from collection 
     2465                if (this.itemsView._itemGroup.isCollection() && !(item && item.getSource())) 
     2466                { 
     2467                        menu.childNodes[m.deleteItem].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple)); 
     2468                        show.push(m.deleteItem); 
     2469                } 
     2470                else 
     2471                { 
     2472                        hide.push(m.deleteItem); 
     2473                } 
     2474 
     2475                if (Zotero.Prefs.get('debugShowDuplicates')) { 
     2476                show.push(m.sep5); 
     2477                show.push(m.duplicateMark); 
     2478                } 
     2479 
     2480                if (this.itemsView._itemGroup.showDuplicates) { 
     2481                        var itemID = undefined; 
     2482                        var duplicateStatus = false; 
     2483                        var items = this.getSelectedItems(); 
     2484                        if (items.length) { 
     2485                                var item = items[0]; 
     2486                                itemID = items[0].id; 
     2487                                duplicateStatus = this.itemsView._itemGroup.duplicates.getDuplicateStatus(itemID); 
     2488                                show.push(m.duplicateSubstitute); 
     2489                                show.push(m.duplicateUnmark); 
     2490                                show.push(m.duplicateUnmarkAll); 
     2491 
     2492                                if (!duplicateStatus) { 
     2493                                        // Handled above with main menu items (actually, this 
     2494                                        // is probably never reached) 
     2495                                        // enable.push(m.duplicateMark); 
     2496                                        disable.push(m.duplicateUnmark); 
     2497                                } else { 
     2498                                        // Handled above with main menu items 
     2499                                        // disable.push(m.duplicateMark); 
     2500                                        enable.push(m.duplicateUnmark); 
     2501                                } 
     2502                                if (duplicateStatus === 1) { 
     2503                                        // For green items (new items with no match partners) 
     2504                                        enable.push(m.duplicateUnmarkAll); 
     2505                                } else { 
     2506                                        disable.push(m.duplicateUnmarkAll); 
     2507                                } 
     2508 
     2509                                // For yellow items (new items with non-new match partners) 
     2510                                if (duplicateStatus === 2 && item.numAttachments() === 0) { 
     2511                                        enable.push(m.duplicateSubstitute); 
     2512                                } else { 
     2513                                        disable.push(m.duplicateSubstitute); 
     2514                                } 
     2515                        } else { 
     2516                                // XXXX enable.push(m.duplicateMark); 
     2517                                hide.push(m.duplicateSubstitute); 
     2518                                hide.push(m.duplicateUnmark); 
     2519                                hide.push(m.duplicateUnmarkAll); 
     2520                        } 
     2521                } else { 
     2522                        // XXXXX: Todo: implement getNewStatus, to be checked from 
     2523                        // any group.  Needs duplicates object to be set on any 
     2524                        // group seen by getChildItems in itemTreeView 
     2525                        // 
     2526                        // Once that's in place, we'll need (a) a link-only option, and 
     2527                        // (b) a link-and-merge option, and we're golden for the basics. 
     2528                        // 
     2529                        // See above (main menu items area) for duplicateMark setting. 
     2530                        hide.push(m.duplicateSubstitute); 
     2531                        hide.push(m.duplicateUnmark); 
     2532                        hide.push(m.duplicateUnmarkAll); 
     2533                } 
     2534 
     2535                // Plural if necessary 
     2536                menu.childNodes[m.deleteFromLibrary].setAttribute('label', Zotero.getString('pane.items.menu.erase' + multiple)); 
     2537                menu.childNodes[m.exportItems].setAttribute('label', Zotero.getString('pane.items.menu.export' + multiple)); 
     2538                menu.childNodes[m.createBib].setAttribute('label', Zotero.getString('pane.items.menu.createBib' + multiple)); 
     2539                menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.items.menu.generateReport' + multiple)); 
     2540                menu.childNodes[m.createParent].setAttribute('label', Zotero.getString('pane.items.menu.createParent' + multiple)); 
     2541                menu.childNodes[m.recognizePDF].setAttribute('label', Zotero.getString('pane.items.menu.recognizePDF' + multiple)); 
     2542                menu.childNodes[m.renameAttachments].setAttribute('label', Zotero.getString('pane.items.menu.renameAttachments' + multiple)); 
     2543                menu.childNodes[m.reindexItem].setAttribute('label', Zotero.getString('pane.items.menu.reindexItem' + multiple)); 
     2544                menu.childNodes[m.duplicateMark].setAttribute('label', Zotero.getString('pane.items.menu.duplicateMark' + multiple)); 
     2545                menu.childNodes[m.duplicateSubstitute].setAttribute('label', Zotero.getString('pane.items.menu.duplicateSubstitute')); 
     2546                menu.childNodes[m.duplicateUnmark].setAttribute('label', Zotero.getString('pane.items.menu.duplicateUnmark')); 
     2547                menu.childNodes[m.duplicateUnmarkAll].setAttribute('label', Zotero.getString('pane.items.menu.duplicateUnmarkAll')); 
     2548 
     2549                for (var i in disable) 
     2550                { 
     2551                        menu.childNodes[disable[i]].setAttribute('disabled', true); 
     2552                } 
     2553 
     2554                for (var i in enable) 
     2555                { 
     2556                        menu.childNodes[enable[i]].setAttribute('disabled', false); 
     2557                } 
     2558 
     2559                for (var i in hide) 
     2560                { 
     2561                        menu.childNodes[hide[i]].setAttribute('hidden', true); 
     2562                } 
     2563 
     2564                for (var i in show) 
     2565                { 
     2566                        menu.childNodes[show[i]].setAttribute('hidden', false); 
     2567                } 
     2568        } 
     2569 
     2570 
     2571        // Adapted from: http://www.xulplanet.com/references/elemref/ref_tree.html#cmnote-9 
     2572        this.onTreeClick = function (event) { 
     2573                // We only care about primary button double and triple clicks 
     2574                if (!event || (event.detail != 2 && event.detail != 3) || event.button != 0) { 
     2575                        return; 
     2576                } 
     2577 
     2578                var t = event.originalTarget; 
     2579 
     2580                if (t.localName != 'treechildren') { 
     2581                        return; 
     2582                } 
     2583 
     2584                var tree = t.parentNode; 
     2585 
     2586                var row = {}, col = {}, obj = {}; 
     2587                tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj); 
     2588 
     2589                // obj.value == 'cell'/'text'/'image' 
     2590                if (!obj.value) { 
     2591                        return; 
     2592                } 
     2593 
     2594                if (tree.id == 'zotero-collections-tree') { 
     2595                        // Ignore triple clicks for collections 
     2596                        if (event.detail != 2) { 
     2597                                return; 
     2598                        } 
     2599 
     2600                        var itemGroup = ZoteroPane.collectionsView._getItemAtRow(tree.view.selection.currentIndex); 
     2601                        if (itemGroup.isLibrary()) { 
     2602                                var uri = Zotero.URI.getCurrentUserLibraryURI(); 
     2603                                if (uri) { 
     2604                                        ZoteroPane.loadURI(uri); 
     2605                                        event.stopPropagation(); 
     2606                                } 
     2607                                return; 
     2608                        } 
     2609 
     2610                        if (itemGroup.isSearch()) { 
     2611                                ZoteroPane.editSelectedCollection(); 
     2612                                return; 
     2613                        } 
     2614 
     2615                        if (itemGroup.isGroup()) { 
     2616                                var uri = Zotero.URI.getGroupURI(itemGroup.ref, true); 
     2617                                ZoteroPane.loadURI(uri); 
     2618                                event.stopPropagation(); 
     2619                                return; 
     2620                        } 
     2621 
     2622                        if (itemGroup.isHeader()) { 
     2623                                if (itemGroup.ref.id == 'group-libraries-header') { 
     2624                                        var uri = Zotero.URI.getGroupsURL(); 
     2625                                        ZoteroPane.loadURI(uri); 
     2626                                        event.stopPropagation(); 
     2627                                } 
     2628                                return; 
     2629                        } 
     2630 
     2631                        if (itemGroup.isBucket()) { 
     2632                                ZoteroPane.loadURI(itemGroup.ref.uri); 
     2633                                event.stopPropagation(); 
     2634                        } 
     2635                } 
     2636                else if (tree.id == 'zotero-items-tree') { 
     2637                        var viewOnDoubleClick = Zotero.Prefs.get('viewOnDoubleClick'); 
     2638 
     2639                        // Expand/collapse on triple-click 
     2640                        if (viewOnDoubleClick) { 
     2641                                if (event.detail == 3) { 
     2642                                        tree.view.toggleOpenState(tree.view.selection.currentIndex); 
     2643                                        return; 
     2644                                } 
     2645 
     2646                                // Don't expand/collapse on double-click 
     2647                                event.stopPropagation(); 
     2648                        } 
     2649 
     2650                        if (tree.view && tree.view.selection.currentIndex > -1) { 
     2651                                var item = ZoteroPane.getSelectedItems()[0]; 
     2652                                if (item) { 
     2653                                        if (item.isRegularItem()) { 
     2654                                                // Double-click on Commons item should load IA page 
     2655                                                var itemGroup = ZoteroPane.collectionsView._getItemAtRow( 
     2656                                                        ZoteroPane.collectionsView.selection.currentIndex 
     2657                                                ); 
     2658 
     2659                                                if (itemGroup.isBucket()) { 
     2660                                                        var uri = itemGroup.ref.getItemURI(item); 
     2661                                                        ZoteroPane.loadURI(uri); 
     2662                                                        event.stopPropagation(); 
     2663                                                        return; 
     2664                                                } 
     2665 
     2666                                                if (!viewOnDoubleClick) { 
     2667                                                        return; 
     2668                                                } 
     2669 
     2670                                                var uri = Components.classes["@mozilla.org/network/standard-url;1"]. 
     2671                                                                createInstance(Components.interfaces.nsIURI); 
     2672                                                var snapID = item.getBestAttachment(); 
     2673                                                if (snapID) { 
     2674                                                        spec = Zotero.Items.get(snapID).getLocalFileURL(); 
     2675                                                        if (spec) { 
     2676                                                                uri.spec = spec; 
     2677                                                                if (uri.scheme && uri.scheme == 'file') { 
     2678                                                                        ZoteroPane.viewAttachment(snapID, event); 
     2679                                                                        return; 
     2680                                                                } 
     2681                                                        } 
     2682                                                } 
     2683 
     2684                                                var uri = item.getField('url'); 
     2685                                                if (!uri) { 
     2686                                                        var doi = item.getField('DOI'); 
     2687                                                        if (doi) { 
     2688                                                                // Pull out DOI, in case there's a prefix 
     2689                                                                doi = Zotero.Utilities.prototype.cleanDOI(doi); 
     2690                                                                if (doi) { 
     2691                                                                        uri = "http://dx.doi.org/" + encodeURIComponent(doi); 
     2692                                                                } 
     2693                                                        } 
     2694                                                } 
     2695                                                if (uri) { 
     2696                                                        ZoteroPane.loadURI(uri); 
     2697                                                } 
     2698                                        } 
     2699                                        else if (item.isNote()) { 
     2700                                                if (!ZoteroPane.collectionsView.editable) { 
     2701                                                        return; 
     2702                                                } 
     2703                                                document.getElementById('zotero-view-note-button').doCommand(); 
     2704                                        } 
     2705                                        else if (item.isAttachment()) { 
     2706                                                ZoteroPane.viewSelectedAttachment(event); 
     2707                                        } 
     2708                                } 
     2709                        } 
     2710                } 
     2711        } 
     2712 
     2713 
     2714        this.startDrag = function (event, element) { 
     2715                element.onDragStart(event); 
     2716        } 
     2717 
     2718 
     2719        this.dragEnter = function (event, element) { 
     2720                return element.onDragEnter(event); 
     2721        } 
     2722 
     2723 
     2724        this.dragOver = function (event, element) { 
     2725                return element.onDragOver(event); 
     2726        } 
     2727 
     2728 
     2729        this.dragDrop = function (event, element) { 
     2730                return element.onDrop(event); 
     2731        } 
     2732 
     2733 
     2734        this.openPreferences = function (paneID, action) { 
     2735                var io = { 
     2736                        pane: paneID, 
     2737                        action: action 
     2738                }; 
     2739                window.openDialog('chrome://zotero/content/preferences/preferences.xul', 
     2740                        'zotero-prefs', 
     2741                        'chrome,titlebar,toolbar,centerscreen,' 
     2742                                + Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal', 
     2743                        io 
     2744                ); 
     2745        } 
     2746 
     2747 
     2748        /* 
     2749         * Loads a URL following the standard modifier key behavior 
     2750         *  (e.g. meta-click == new background tab, meta-shift-click == new front tab, 
     2751         *  shift-click == new window, no modifier == frontmost tab 
     2752         */ 
     2753        function loadURI(uri, event, data) { 
     2754                // Open in new tab 
     2755                if (event && (event.metaKey || (!Zotero.isMac && event.ctrlKey))) { 
     2756                        var tab = gBrowser.addTab(uri); 
     2757                        var browser = gBrowser.getBrowserForTab(tab); 
     2758 
     2759                        if (event.shiftKey) { 
     2760                                gBrowser.selectedTab = tab; 
     2761                        } 
     2762                } 
     2763                else if (event && event.shiftKey) { 
     2764                        window.open(uri, "zotero-loaded-page", 
     2765                                "menubar=yes,location=yes,toolbar=yes,personalbar=yes,resizable=yes,scrollbars=yes,status=yes"); 
     2766                } 
     2767                else { 
     2768                        window.loadURI(uri); 
     2769                } 
     2770        } 
     2771 
     2772 
     2773        function setItemsPaneMessage(msg, lock) { 
     2774                var elem = document.getElementById('zotero-items-pane-message-box'); 
     2775 
     2776                if (elem.getAttribute('locked') == 'true') { 
     2777                        return; 
     2778                } 
     2779 
     2780                while (elem.hasChildNodes()) { 
     2781                        elem.removeChild(elem.firstChild); 
     2782                } 
     2783                var msgParts = msg.split("\n\n"); 
     2784                for (var i=0; i<msgParts.length; i++) { 
     2785                        var desc = document.createElement('description'); 
     2786                        desc.appendChild(document.createTextNode(msgParts[i])); 
     2787                        elem.appendChild(desc); 
     2788                } 
     2789 
     2790                // Make message permanent 
     2791                if (lock) { 
     2792                        elem.setAttribute('locked', true); 
     2793                } 
     2794 
     2795                document.getElementById('zotero-items-pane-content').selectedIndex = 1; 
     2796        } 
     2797 
     2798 
     2799        function clearItemsPaneMessage() { 
     2800                // If message box is locked, don't clear 
     2801                var box = document.getElementById('zotero-items-pane-message-box'); 
     2802                if (box.getAttribute('locked') == 'true') { 
     2803                        return; 
     2804                } 
     2805 
     2806                document.getElementById('zotero-items-pane-content').selectedIndex = 0; 
     2807        } 
     2808 
     2809 
     2810        // Updates browser context menu options 
     2811        function contextPopupShowing() 
     2812        { 
     2813                if (!Zotero.Prefs.get('browserContentContextMenu')) { 
     2814                        return; 
     2815                } 
     2816 
     2817                var menuitem = document.getElementById("zotero-context-add-to-current-note"); 
     2818                var showing = false; 
     2819                if (menuitem){ 
     2820                        var items = ZoteroPane.getSelectedItems(); 
     2821                        if (ZoteroPane.itemsView.selection && ZoteroPane.itemsView.selection.count==1 
     2822                                && items[0] && items[0].isNote() 
     2823                                && window.gContextMenu.isTextSelected) 
     2824                        { 
     2825                                menuitem.hidden = false; 
     2826                                showing = true; 
     2827                        } 
     2828                        else 
     2829                        { 
     2830                                menuitem.hidden = true; 
     2831                        } 
     2832                } 
     2833 
     2834                var menuitem = document.getElementById("zotero-context-add-to-new-note"); 
     2835                if (menuitem){ 
     2836                        if (window.gContextMenu.isTextSelected) 
     2837                        { 
     2838                                menuitem.hidden = false; 
     2839                                showing = true; 
     2840                        } 
     2841                        else 
     2842                        { 
     2843                                menuitem.hidden = true; 
     2844                        } 
     2845                } 
     2846 
     2847                var menuitem = document.getElementById("zotero-context-save-link-as-item"); 
     2848                if (menuitem) { 
     2849                        if (window.gContextMenu.onLink) { 
     2850                                menuitem.hidden = false; 
     2851                                showing = true; 
     2852                        } 
     2853                        else { 
     2854                                menuitem.hidden = true; 
     2855                        } 
     2856                } 
     2857 
     2858                var menuitem = document.getElementById("zotero-context-save-image-as-item"); 
     2859                if (menuitem) { 
     2860                        // Not using window.gContextMenu.hasBGImage -- if the user wants it, 
     2861                        // they can use the Firefox option to view and then import from there 
     2862                        if (window.gContextMenu.onImage) { 
     2863                                menuitem.hidden = false; 
     2864                                showing = true; 
     2865                        } 
     2866                        else { 
     2867                                menuitem.hidden = true; 
     2868                        } 
     2869                } 
     2870 
     2871                // If Zotero is locked or library is read-only, disable menu items 
     2872                var menu = document.getElementById('zotero-content-area-context-menu'); 
     2873                menu.hidden = !showing; 
     2874                var disabled = Zotero.locked; 
     2875                if (!disabled && self.collectionsView.selection && self.collectionsView.selection.count) { 
     2876                        var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex); 
     2877                        disabled = !itemGroup.editable; 
     2878                } 
     2879                for each(var menuitem in menu.firstChild.childNodes) { 
     2880                        menuitem.disabled = disabled; 
     2881                } 
     2882        } 
     2883 
     2884 
     2885        this.newNote = function (popup, parent, text, citeURI) { 
     2886                if (!Zotero.stateCheck()) { 
     2887                        this.displayErrorMessage(true); 
     2888                        return; 
     2889                } 
     2890 
     2891                if (!this.canEdit()) { 
     2892                        this.displayCannotEditLibraryMessage(); 
     2893                        return; 
     2894                } 
     2895 
     2896                if (!popup) { 
     2897                        if (!text) { 
     2898                                text = ''; 
     2899                        } 
     2900                        text = Zotero.Utilities.prototype.trim(text); 
     2901 
     2902                        if (text) { 
     2903                                text = '<blockquote' 
     2904                                                + (citeURI ? ' cite="' + citeURI + '"' : '') 
     2905                                                + '>' + Zotero.Utilities.prototype.text2html(text) + "</blockquote>"; 
     2906                        } 
     2907 
     2908                        var item = new Zotero.Item('note'); 
     2909                        item.libraryID = this.getSelectedLibraryID(); 
     2910                        item.setNote(text); 
     2911                        if (parent) { 
     2912                                item.setSource(parent); 
     2913                        } 
     2914                        var itemID = item.save(); 
     2915 
     2916                        if (!parent && this.itemsView && this.itemsView._itemGroup.isCollection()) { 
     2917                                this.itemsView._itemGroup.ref.addItem(itemID); 
     2918                        } 
     2919 
     2920                        this.selectItem(itemID); 
     2921 
     2922                        document.getElementById('zotero-note-editor').focus(); 
     2923                } 
     2924                else 
     2925                { 
     2926                        // TODO: _text_ 
     2927                        var c = this.getSelectedCollection(); 
     2928                        if (c) { 
     2929                                this.openNoteWindow(null, c.id, parent); 
     2930                        } 
     2931                        else { 
     2932                                this.openNoteWindow(null, null, parent); 
     2933                        } 
     2934                } 
     2935        } 
     2936 
     2937 
     2938        function addTextToNote(text, citeURI) 
     2939        { 
     2940                if (!this.canEdit()) { 
     2941                        this.displayCannotEditLibraryMessage(); 
     2942                        return; 
     2943                } 
     2944 
     2945                if (!text) { 
     2946                        return false; 
     2947                } 
     2948 
     2949                text = Zotero.Utilities.prototype.trim(text); 
     2950 
     2951                if (!text.length) { 
     2952                        return false; 
     2953                } 
     2954 
     2955                text = '<blockquote' 
     2956                                        + (citeURI ? ' cite="' + citeURI + '"' : '') 
     2957                                        + '>' + Zotero.Utilities.prototype.text2html(text) + "</blockquote>"; 
     2958 
     2959                var items = this.getSelectedItems(); 
     2960                if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) { 
     2961                        var note = items[0].getNote() 
     2962 
     2963                        items[0].setNote(note + text); 
     2964                        items[0].save(); 
     2965 
     2966                        var noteElem = document.getElementById('zotero-note-editor') 
     2967                        noteElem.focus(); 
     2968                        return true; 
     2969                } 
     2970 
     2971                return false; 
     2972        } 
     2973 
     2974        function openNoteWindow(itemID, col, parentItemID) 
     2975        { 
     2976                if (!this.canEdit()) { 
     2977                        this.displayCannotEditLibraryMessage(); 
     2978                        return; 
     2979                } 
     2980 
     2981                var name = null; 
     2982 
     2983                if (itemID) { 
     2984                        // Create a name for this window so we can focus it later 
     2985                        // 
     2986                        // Collection is only used on new notes, so we don't need to 
     2987                        // include it in the name 
     2988                        name = 'zotero-note-' + itemID; 
     2989 
     2990                        var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 
     2991                                        .getService(Components.interfaces.nsIWindowMediator); 
     2992                        var e = wm.getEnumerator(''); 
     2993                        while (e.hasMoreElements()) { 
     2994                                var w = e.getNext(); 
     2995                                if (w.name == name) { 
     2996                                        w.focus(); 
     2997                                        return; 
     2998                                } 
     2999                        } 
     3000                } 
     3001 
     3002                window.open('chrome://zotero/content/note.xul?v=1' 
     3003                        + (itemID ? '&id=' + itemID : '') + (col ? '&coll=' + col : '') 
     3004                        + (parentItemID ? '&p=' + parentItemID : ''), 
     3005                        name, 'chrome,resizable,centerscreen'); 
     3006        } 
     3007 
     3008 
     3009        function addAttachmentFromDialog(link, id) 
     3010        { 
     3011                if (!this.canEdit()) { 
     3012                        this.displayCannotEditLibraryMessage(); 
     3013                        return; 
     3014                } 
     3015 
     3016                var itemGroup = ZoteroPane.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
     3017                if (link && itemGroup.isWithinGroup()) { 
     3018                        var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     3019                                                .getService(Components.interfaces.nsIPrompt); 
     3020                        pr.alert("", "Linked files cannot be added to group libraries."); 
     3021                        return; 
     3022                } 
     3023 
     3024                // TODO: disable in menu 
     3025                if (!this.canEditFiles()) { 
     3026                        this.displayCannotEditLibraryFilesMessage(); 
     3027                        return; 
     3028                } 
     3029 
     3030                var libraryID = itemGroup.ref.libraryID; 
     3031 
     3032                var nsIFilePicker = Components.interfaces.nsIFilePicker; 
     3033                var fp = Components.classes["@mozilla.org/filepicker;1"] 
     3034                                                .createInstance(nsIFilePicker); 
     3035                fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpenMultiple); 
     3036                fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll); 
     3037 
     3038                if(fp.show() == nsIFilePicker.returnOK) 
     3039                { 
     3040                        var files = fp.files; 
     3041                        while (files.hasMoreElements()){ 
     3042                                var file = files.getNext(); 
     3043                                file.QueryInterface(Components.interfaces.nsILocalFile); 
     3044                                var attachmentID; 
     3045                                if(link) 
     3046                                        attachmentID = Zotero.Attachments.linkFromFile(file, id); 
     3047                                else 
     3048                                        attachmentID = Zotero.Attachments.importFromFile(file, id, libraryID); 
     3049 
     3050                                if(attachmentID && !id) 
     3051                                { 
     3052                                        var c = this.getSelectedCollection(); 
     3053                                        if(c) 
     3054                                                c.addItem(attachmentID); 
     3055                                } 
     3056                        } 
     3057                } 
     3058        } 
     3059 
     3060 
     3061        this.addItemFromPage = function (itemType, saveSnapshot, row) { 
     3062                if (!this.canEdit(row)) { 
     3063                        this.displayCannotEditLibraryMessage(); 
     3064                        return; 
     3065                } 
     3066 
     3067                return this.addItemFromDocument(window.content.document, itemType, saveSnapshot, row); 
     3068        } 
     3069 
     3070 
     3071        /** 
     3072         * @param       {Document}                      doc 
     3073         * @param       {String|Integer}        [itemType='webpage']    Item type id or name 
     3074         * @param       {Boolean}                       [saveSnapshot]                  Force saving or non-saving of a snapshot, 
     3075         *                                                                                                              regardless of automaticSnapshots pref 
     3076         */ 
     3077        this.addItemFromDocument = function (doc, itemType, saveSnapshot, row) { 
     3078                var progressWin = new Zotero.ProgressWindow(); 
     3079                progressWin.changeHeadline(Zotero.getString('ingester.scraping')); 
     3080                var icon = 'chrome://zotero/skin/treeitem-webpage.png'; 
     3081                progressWin.addLines(doc.title, icon) 
     3082                progressWin.show(); 
     3083                progressWin.startCloseTimer(); 
     3084 
     3085                // Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled 
     3086                saveSnapshot = saveSnapshot || (saveSnapshot !== false && Zotero.Prefs.get('automaticSnapshots')); 
     3087 
     3088                // TODO: this, needless to say, is a temporary hack 
     3089                if (itemType == 'temporaryPDFHack') { 
     3090                        itemType = null; 
     3091                        var isPDF = false; 
     3092                        if (doc.title.indexOf('application/pdf') != -1) { 
     3093                                isPDF = true; 
     3094                        } 
     3095                        else { 
     3096                                var ios = Components.classes["@mozilla.org/network/io-service;1"]. 
     3097                                                        getService(Components.interfaces.nsIIOService); 
     3098                                try { 
     3099                                        var uri = ios.newURI(doc.location, null, null); 
     3100                                        if (uri.fileName && uri.fileName.match(/pdf$/)) { 
     3101                                                isPDF = true; 
     3102                                        } 
     3103                                } 
     3104                                catch (e) { 
     3105                                        Zotero.debug(e); 
     3106                                        Components.utils.reportError(e); 
     3107                                } 
     3108                        } 
     3109 
     3110                        if (isPDF && saveSnapshot) { 
     3111                                // 
     3112                                // Duplicate newItem() checks here 
     3113                                // 
     3114                                if (!Zotero.stateCheck()) { 
     3115                                        this.displayErrorMessage(true); 
     3116                                        return false; 
     3117                                } 
     3118 
     3119                                // Currently selected row 
     3120                                if (row === undefined) { 
     3121                                        row = this.collectionsView.selection.currentIndex; 
     3122                                } 
     3123 
     3124                                if (!this.canEdit(row)) { 
     3125                                        this.displayCannotEditLibraryMessage(); 
     3126                                        return; 
     3127                                } 
     3128 
     3129                                if (row !== undefined) { 
     3130                                        var itemGroup = this.collectionsView._getItemAtRow(row); 
     3131                                        var libraryID = itemGroup.ref.libraryID; 
     3132                                } 
     3133                                else { 
     3134                                        var libraryID = null; 
     3135                                        var itemGroup = null; 
     3136                                } 
     3137                                // 
     3138                                // 
     3139                                // 
     3140 
     3141                                if (!this.canEditFiles(row)) { 
     3142                                        this.displayCannotEditLibraryFilesMessage(); 
     3143                                        return; 
     3144                                } 
     3145 
     3146                                if (itemGroup && itemGroup.isCollection()) { 
     3147                                        var collectionID = itemGroup.ref.id; 
     3148                                } 
     3149                                else { 
     3150                                        var collectionID = false; 
     3151                                } 
     3152 
     3153                                var itemID = Zotero.Attachments.importFromDocument(doc, false, false, collectionID, null, libraryID); 
     3154 
     3155                                // importFromDocument() doesn't trigger the notifier for a second 
     3156                                // 
     3157                                // The one-second delay is weird but better than nothing 
     3158                                var self = this; 
     3159                                setTimeout(function () { 
     3160                                        self.selectItem(itemID); 
     3161                                }, 1001); 
     3162 
     3163                                return; 
     3164                        } 
     3165                } 
     3166 
     3167                // Save web page item by default 
     3168                if (!itemType) { 
     3169                        itemType = 'webpage'; 
     3170                } 
     3171                var data = { 
     3172                        title: doc.title, 
     3173                        url: doc.location.href, 
     3174                        accessDate: "CURRENT_TIMESTAMP" 
     3175                } 
     3176                itemType = Zotero.ItemTypes.getID(itemType); 
     3177                var item = this.newItem(itemType, data, row); 
     3178 
     3179                if (item.libraryID) { 
     3180                        var group = Zotero.Groups.getByLibraryID(item.libraryID); 
     3181                        filesEditable = group.filesEditable; 
     3182                } 
     3183                else { 
     3184                        filesEditable = true; 
     3185                } 
     3186 
     3187                if (saveSnapshot) { 
     3188                        var link = false; 
     3189 
     3190                        if (link) { 
     3191                                Zotero.Attachments.linkFromDocument(doc, item.id); 
     3192                        } 
     3193                        else if (filesEditable) { 
     3194                                Zotero.Attachments.importFromDocument(doc, item.id); 
     3195                        } 
     3196                } 
     3197 
     3198                return item.id; 
     3199        } 
     3200 
     3201 
     3202        this.addItemFromURL = function (url, itemType, saveSnapshot, row) { 
     3203                if (url == window.content.document.location.href) { 
     3204                        return this.addItemFromPage(itemType, saveSnapshot, row); 
     3205                } 
     3206 
     3207                var self = this; 
     3208 
     3209                Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) { 
     3210                        // If native type, save using a hidden browser 
     3211                        if (hasNativeHandler) { 
     3212                                var processor = function (doc) { 
     3213                                        ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot, row); 
     3214                                }; 
     3215 
     3216                                var done = function () {} 
     3217 
     3218                                var exception = function (e) { 
     3219                                        Zotero.debug(e); 
     3220                                } 
     3221 
     3222                                Zotero.Utilities.HTTP.processDocuments([url], processor, done, exception); 
     3223                        } 
     3224                        // Otherwise create placeholder item, attach attachment, and update from that 
     3225                        else { 
     3226                                // TODO: this, needless to say, is a temporary hack 
     3227                                if (itemType == 'temporaryPDFHack') { 
     3228                                        itemType = null; 
     3229 
     3230                                        if (mimeType == 'application/pdf') { 
     3231                                                // 
     3232                                                // Duplicate newItem() checks here 
     3233                                                // 
     3234                                                if (!Zotero.stateCheck()) { 
     3235                                                        ZoteroPane.displayErrorMessage(true); 
     3236                                                        return false; 
     3237                                                } 
     3238 
     3239                                                // Currently selected row 
     3240                                                if (row === undefined) { 
     3241                                                        row = ZoteroPane.collectionsView.selection.currentIndex; 
     3242                                                } 
     3243 
     3244                                                if (!ZoteroPane.canEdit(row)) { 
     3245                                                        ZoteroPane.displayCannotEditLibraryMessage(); 
     3246                                                        return; 
     3247                                                } 
     3248 
     3249                                                if (row !== undefined) { 
     3250                                                        var itemGroup = ZoteroPane.collectionsView._getItemAtRow(row); 
     3251                                                        var libraryID = itemGroup.ref.libraryID; 
     3252                                                } 
     3253                                                else { 
     3254                                                        var libraryID = null; 
     3255                                                        var itemGroup = null; 
     3256                                                } 
     3257                                                // 
     3258                                                // 
     3259                                                // 
     3260 
     3261                                                if (!ZoteroPane.canEditFiles(row)) { 
     3262                                                        ZoteroPane.displayCannotEditLibraryFilesMessage(); 
     3263                                                        return; 
     3264                                                } 
     3265 
     3266                                                if (itemGroup && itemGroup.isCollection()) { 
     3267                                                        var collectionID = itemGroup.ref.id; 
     3268                                                } 
     3269                                                else { 
     3270                                                        var collectionID = false; 
     3271                                                } 
     3272 
     3273                                                var attachmentItem = Zotero.Attachments.importFromURL(url, false, false, false, collectionID, mimeType, libraryID); 
     3274 
     3275                                                // importFromURL() doesn't trigger the notifier until 
     3276                                                // after download is complete 
     3277                                                // 
     3278                                                // TODO: add a callback to importFromURL() 
     3279                                                setTimeout(function () { 
     3280                                                        self.selectItem(attachmentItem.id); 
     3281                                                }, 1001); 
     3282 
     3283                                                return; 
     3284                                        } 
     3285                                } 
     3286 
     3287                                if (!itemType) { 
     3288                                        itemType = 'webpage'; 
     3289                                } 
     3290 
     3291                                var item = ZoteroPane.newItem(itemType, {}, row); 
     3292 
     3293                                if (item.libraryID) { 
     3294                                        var group = Zotero.Groups.getByLibraryID(item.libraryID); 
     3295                                        filesEditable = group.filesEditable; 
     3296                                } 
     3297                                else { 
     3298                                        filesEditable = true; 
     3299                                } 
     3300 
     3301                                // Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled 
     3302                                if (saveSnapshot || (saveSnapshot !== false && Zotero.Prefs.get('automaticSnapshots'))) { 
     3303                                        var link = false; 
     3304 
     3305                                        if (link) { 
     3306                                                //Zotero.Attachments.linkFromURL(doc, item.id); 
     3307                                        } 
     3308                                        else if (filesEditable) { 
     3309                                                var attachmentItem = Zotero.Attachments.importFromURL(url, item.id, false, false, false, mimeType); 
     3310                                                if (attachmentItem) { 
     3311                                                        item.setField('title', attachmentItem.getField('title')); 
     3312                                                        item.setField('url', attachmentItem.getField('url')); 
     3313                                                        item.setField('accessDate', attachmentItem.getField('accessDate')); 
     3314                                                        item.save(); 
     3315                                                } 
     3316                                        } 
     3317                                } 
     3318 
     3319                                return item.id; 
     3320 
     3321                        } 
     3322                }); 
     3323        } 
     3324 
     3325 
     3326        /* 
     3327         * Create an attachment from the current page 
     3328         * 
     3329         * |itemID|    -- itemID of parent item 
     3330         * |link|      -- create web link instead of snapshot 
     3331         */ 
     3332        this.addAttachmentFromPage = function (link, itemID) 
     3333        { 
     3334                if (!Zotero.stateCheck()) { 
     3335                        this.displayErrorMessage(true); 
     3336                        return; 
     3337                } 
     3338 
     3339                if (typeof itemID != 'number') { 
     3340                        throw ("itemID must be an integer in ZoteroPane.addAttachmentFromPage()"); 
     3341                } 
     3342 
     3343                var progressWin = new Zotero.ProgressWindow(); 
     3344                progressWin.changeHeadline(Zotero.getString('save.' + (link ? 'link' : 'attachment'))); 
     3345                var type = link ? 'web-link' : 'snapshot'; 
     3346                var icon = 'chrome://zotero/skin/treeitem-attachment-' + type + '.png'; 
     3347                progressWin.addLines(window.content.document.title, icon) 
     3348                progressWin.show(); 
     3349                progressWin.startCloseTimer(); 
     3350 
     3351                if (link) { 
     3352                        Zotero.Attachments.linkFromDocument(window.content.document, itemID); 
     3353                } 
     3354                else { 
     3355                        Zotero.Attachments.importFromDocument(window.content.document, itemID); 
     3356                } 
     3357        } 
     3358 
     3359 
     3360        function viewAttachment(itemID, event, noLocateOnMissing) { 
     3361                var attachment = Zotero.Items.get(itemID); 
     3362                if (!attachment.isAttachment()) { 
     3363                        throw ("Item " + itemID + " is not an attachment in ZoteroPane.viewAttachment()"); 
     3364                } 
     3365 
     3366                if (attachment.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     3367                        this.loadURI(attachment.getField('url'), event); 
     3368                        return; 
     3369                } 
     3370 
     3371                var file = attachment.getFile(); 
     3372                if (file) { 
     3373                        var mimeType = attachment.getAttachmentMIMEType(); 
     3374                        // If no MIME type specified, try to detect again (I guess in case 
     3375                        // we've gotten smarter since the file was imported?) 
     3376                        if (!mimeType) { 
     3377                                var mimeType = Zotero.MIME.getMIMETypeFromFile(file); 
     3378                                var ext = Zotero.File.getExtension(file); 
     3379 
     3380                                // TODO: update DB with new info 
     3381                        } 
     3382                        var ext = Zotero.File.getExtension(file); 
     3383                        var isNative = Zotero.MIME.hasNativeHandler(mimeType, ext); 
     3384                        var internal = Zotero.MIME.hasInternalHandler(mimeType, ext); 
     3385 
     3386                        if (isNative || 
     3387                                        (internal && !Zotero.Prefs.get('launchNonNativeFiles'))) { 
     3388 
     3389                                var url = 'zotero://attachment/' + itemID + '/'; 
     3390                                this.loadURI(url, event, { attachmentID: itemID}); 
     3391                        } 
     3392                        else { 
     3393                                var fileURL = attachment.getLocalFileURL(); 
     3394 
     3395                                // Some platforms don't have nsILocalFile.launch, so we just load it and 
     3396                                // let the Firefox external helper app window handle it 
     3397                                try { 
     3398                                        file.launch(); 
     3399                                } 
     3400                                catch (e) { 
     3401                                        window.loadURI(fileURL); 
     3402                                } 
     3403                        } 
     3404                } 
     3405                else { 
     3406                        this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing); 
     3407                } 
     3408        } 
     3409 
     3410 
     3411        function viewSelectedAttachment(event, noLocateOnMissing) 
     3412        { 
     3413                if (this.itemsView && this.itemsView.selection.count == 1) { 
     3414                        this.viewAttachment(this.getSelectedItems(true)[0], event, noLocateOnMissing); 
     3415                } 
     3416        } 
     3417 
     3418 
     3419        this.showAttachmentInFilesystem = function (itemID, noLocateOnMissing) { 
     3420                var attachment = Zotero.Items.get(itemID) 
     3421                if (attachment.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     3422                        var file = attachment.getFile(); 
     3423                        if (file){ 
     3424                                try { 
     3425                                        file.reveal(); 
     3426                                } 
     3427                                catch (e) { 
     3428                                        // On platforms that don't support nsILocalFile.reveal() (e.g. Linux), 
     3429                                        // "double-click" the parent directory 
     3430                                        try { 
     3431                                                var parent = file.parent.QueryInterface(Components.interfaces.nsILocalFile); 
     3432                                                parent.launch(); 
     3433                                        } 
     3434                                        // If launch also fails, try the OS handler 
     3435                                        catch (e) { 
     3436                                                var uri = Components.classes["@mozilla.org/network/io-service;1"]. 
     3437                                                                        getService(Components.interfaces.nsIIOService). 
     3438                                                                        newFileURI(parent); 
     3439                                                var protocolService = 
     3440                                                        Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]. 
     3441                                                                getService(Components.interfaces.nsIExternalProtocolService); 
     3442                                                protocolService.loadUrl(uri); 
     3443                                        } 
     3444                                } 
     3445                        } 
     3446                        else { 
     3447                                this.showAttachmentNotFoundDialog(attachment.id, noLocateOnMissing) 
     3448                        } 
     3449                } 
     3450        } 
     3451 
     3452 
     3453        /** 
     3454         * Test if the user can edit the currently selected library/collection, 
     3455         * and display an error if not 
     3456         * 
     3457         * @param       {Integer}       [row] 
     3458         * 
     3459         * @return      {Boolean}               TRUE if user can edit, FALSE if not 
     3460         */ 
     3461        this.canEdit = function (row) { 
     3462                // Currently selected row 
     3463                if (row === undefined) { 
     3464                        row = this.collectionsView.selection.currentIndex; 
     3465                } 
     3466 
     3467                var itemGroup = this.collectionsView._getItemAtRow(row); 
     3468                return itemGroup.editable; 
     3469        } 
     3470 
     3471 
     3472        /** 
     3473         * Test if the user can edit the currently selected library/collection, 
     3474         * and display an error if not 
     3475         * 
     3476         * @param       {Integer}       [row] 
     3477         * 
     3478         * @return      {Boolean}               TRUE if user can edit, FALSE if not 
     3479         */ 
     3480        this.canEditFiles = function (row) { 
     3481                // Currently selected row 
     3482                if (row === undefined) { 
     3483                        row = this.collectionsView.selection.currentIndex; 
     3484                } 
     3485 
     3486                var itemGroup = this.collectionsView._getItemAtRow(row); 
     3487                return itemGroup.filesEditable; 
     3488        } 
     3489 
     3490 
     3491        this.displayCannotEditLibraryMessage = function () { 
     3492                var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     3493                                        .getService(Components.interfaces.nsIPrompt); 
     3494                pr.alert("", "You cannot make changes to the currently selected library."); 
     3495        } 
     3496 
     3497 
     3498        this.displayCannotEditLibraryFilesMessage = function () { 
     3499                var pr = Components.classes["@mozilla.org/network/default-prompt;1"] 
     3500                                        .getService(Components.interfaces.nsIPrompt); 
     3501                pr.alert("", "You cannot add files to the currently selected library."); 
     3502        } 
     3503 
     3504 
     3505        function showAttachmentNotFoundDialog(itemID, noLocate) { 
     3506                var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. 
     3507                                createInstance(Components.interfaces.nsIPromptService); 
     3508 
     3509 
     3510                // Don't show Locate button 
     3511                if (noLocate) { 
     3512                        var index = ps.alert(null, 
     3513                                Zotero.getString('pane.item.attachments.fileNotFound.title'), 
     3514                                Zotero.getString('pane.item.attachments.fileNotFound.text') 
     3515                        ); 
     3516                        return; 
     3517                } 
     3518 
     3519                var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 
     3520                        + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); 
     3521                var index = ps.confirmEx(null, 
     3522                        Zotero.getString('pane.item.attachments.fileNotFound.title'), 
     3523                        Zotero.getString('pane.item.attachments.fileNotFound.text'), 
     3524                        buttonFlags, Zotero.getString('general.locate'), null, 
     3525                        null, null, {}); 
     3526 
     3527                if (index == 0) { 
     3528                        this.relinkAttachment(itemID); 
     3529                } 
     3530        } 
     3531 
     3532 
     3533        this.createParentItemsFromSelected = function () { 
     3534                if (!this.canEdit()) { 
     3535                        this.displayCannotEditLibraryMessage(); 
     3536                        return; 
     3537                } 
     3538 
     3539 
     3540                var items = this.getSelectedItems(); 
     3541                for (var i=0; i<items.length; i++) { 
     3542                        var item = items[i]; 
     3543                        if (!item.isTopLevelItem() || item.isRegularItem()) { 
     3544                                throw('Item ' + itemID + ' is not a top-level attachment or note in ZoteroPane.createParentItemsFromSelected()'); 
     3545                        } 
     3546 
     3547                        Zotero.DB.beginTransaction(); 
     3548                        // TODO: remove once there are no top-level web attachments 
     3549                        if (item.isWebAttachment()) { 
     3550                                var parent = new Zotero.Item('webpage'); 
     3551                        } 
     3552                        else { 
     3553                                var parent = new Zotero.Item('document'); 
     3554                        } 
     3555                        parent.libraryID = item.libraryID; 
     3556                        parent.setField('title', item.getField('title')); 
     3557                        if (item.isWebAttachment()) { 
     3558                                parent.setField('accessDate', item.getField('accessDate')); 
     3559                                parent.setField('url', item.getField('url')); 
     3560                        } 
     3561                        var itemID = parent.save(); 
     3562                        item.setSource(itemID); 
     3563                        item.save(); 
     3564                        Zotero.DB.commitTransaction(); 
     3565                } 
     3566        } 
     3567 
     3568 
     3569        this.renameSelectedAttachmentsFromParents = function () { 
     3570                if (!this.canEdit()) { 
     3571                        this.displayCannotEditLibraryMessage(); 
     3572                        return; 
     3573                } 
     3574 
     3575                var items = this.getSelectedItems(); 
     3576 
     3577                for (var i=0; i<items.length; i++) { 
     3578                        var item = items[i]; 
     3579 
     3580                        if (!item.isAttachment() || !item.getSource() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     3581                                throw('Item ' + itemID + ' is not a child file attachment in ZoteroPane.renameAttachmentFromParent()'); 
     3582                        } 
     3583 
     3584                        var file = item.getFile(); 
     3585                        if (!file) { 
     3586                                continue; 
     3587                        } 
     3588 
     3589                        var parentItemID = item.getSource(); 
     3590                        var newName = Zotero.Attachments.getFileBaseNameFromItem(parentItemID); 
     3591 
     3592                        var ext = file.leafName.match(/[^\.]+$/); 
     3593                        if (ext) { 
     3594                                newName = newName + '.' + ext; 
     3595                        } 
     3596 
     3597                        var renamed = item.renameAttachmentFile(newName); 
     3598                        if (renamed !== true) { 
     3599                                Zotero.debug("Could not rename file (" + renamed + ")"); 
     3600                                continue; 
     3601                        } 
     3602 
     3603                        item.setField('title', newName); 
     3604                        item.save(); 
     3605                } 
     3606 
     3607                return true; 
     3608        } 
     3609 
     3610 
     3611        function relinkAttachment(itemID) { 
     3612                if (!this.canEdit()) { 
     3613                        this.displayCannotEditLibraryMessage(); 
     3614                        return; 
     3615                } 
     3616 
     3617                var item = Zotero.Items.get(itemID); 
     3618                if (!item) { 
     3619                        throw('Item ' + itemID + ' not found in ZoteroPane.relinkAttachment()'); 
     3620                } 
     3621 
     3622                var nsIFilePicker = Components.interfaces.nsIFilePicker; 
     3623                var fp = Components.classes["@mozilla.org/filepicker;1"] 
     3624                                        .createInstance(nsIFilePicker); 
     3625                fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen); 
     3626 
     3627 
     3628                var file = item.getFile(false, true); 
     3629                var dir = Zotero.File.getClosestDirectory(file); 
     3630                if (dir) { 
     3631                        dir.QueryInterface(Components.interfaces.nsILocalFile); 
     3632                        fp.displayDirectory = dir; 
     3633                } 
     3634 
     3635                fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll); 
     3636 
     3637                if (fp.show() == nsIFilePicker.returnOK) { 
     3638                        var file = fp.file; 
     3639                        file.QueryInterface(Components.interfaces.nsILocalFile); 
     3640                        item.relinkAttachmentFile(file); 
     3641                } 
     3642        } 
     3643 
     3644 
     3645        function reportErrors() { 
     3646                var errors = Zotero.getErrors(true); 
     3647                var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] 
     3648                                   .getService(Components.interfaces.nsIWindowWatcher); 
     3649                var data = { 
     3650                        msg: Zotero.getString('errorReport.followingErrors', Zotero.appName), 
     3651                        e: errors.join('\n\n'), 
     3652                        askForSteps: true 
     3653                }; 
     3654                var io = { wrappedJSObject: { Zotero: Zotero, data:  data } }; 
     3655                var win = ww.openWindow(null, "chrome://zotero/content/errorReport.xul", 
     3656                                        "zotero-error-report", "chrome,centerscreen,modal", io); 
     3657        } 
     3658 
     3659 
     3660        /* 
     3661         * Display an error message saying that an error has occurred and Firefox 
     3662         * needs to be restarted. 
     3663         * 
     3664         * If |popup| is TRUE, display in popup progress window; otherwise, display 
     3665         * as items pane message 
     3666         */ 
     3667        function displayErrorMessage(popup) { 
     3668                var reportErrorsStr = Zotero.getString('errorReport.reportErrors'); 
     3669                var reportInstructions = 
     3670                        Zotero.getString('errorReport.reportInstructions', reportErrorsStr) 
     3671 
     3672                // Display as popup progress window 
     3673                if (popup) { 
     3674                        var pw = new Zotero.ProgressWindow(); 
     3675                        pw.changeHeadline(Zotero.getString('general.errorHasOccurred')); 
     3676                        var msg = Zotero.getString('general.restartFirefox') + ' ' 
     3677                                + reportInstructions; 
     3678                        pw.addDescription(msg); 
     3679                        pw.show(); 
     3680                        pw.startCloseTimer(8000); 
     3681                } 
     3682                // Display as items pane message 
     3683                else { 
     3684                        var msg = Zotero.getString('general.errorHasOccurred') + ' ' 
     3685                                + Zotero.getString('general.restartFirefox') + '\n\n' 
     3686                                + reportInstructions; 
     3687                        self.setItemsPaneMessage(msg, true); 
     3688                } 
     3689                Zotero.debug(msg, 1); 
     3690        } 
     3691} 
     3692 
     3693window.addEventListener("load", function(e) { ZoteroPane.onLoad(e); }, false); 
     3694window.addEventListener("unload", function(e) { ZoteroPane.onUnload(e); }, false); 
  • chrome/content/zotero/overlay.xul

    diff -u -r -N zotero-run/chrome/content/zotero/overlay.xul zotero-trunk/chrome/content/zotero/overlay.xul
    old new  
    109109                                        <menuitem label="&zotero.toolbar.emptyTrash.label;" oncommand="ZoteroPane.emptyTrash();"/> 
    110110                                        <menuitem label="&zotero.toolbar.newCollection.label;" oncommand="ZoteroPane.createCommonsBucket();"/><!--TODO localize --> 
    111111                                        <menuitem label="Refresh" oncommand="ZoteroPane.refreshCommonsBucket();"/><!--TODO localize --> 
    112                                         <!-- TODO: (moved from gear icon menu) localize <menuitem id="zotero-tb-actions-duplicate" label="&zotero.toolbar.duplicate.label;" oncommand="ZoteroPane.showDuplicates()"/>--> 
    113112                                        <menuseparator/> 
    114113                                        <menuitem label="Duplicates view" oncommand="ZoteroPane.showDuplicates();"/><!--TODO localize --> 
    115114                                        <menuitem label="Cancel duplicates view" oncommand="ZoteroPane.onCollectionSelected();"/><!--TODO localize --> 
     115                                        <menuitem label="Orphans view" oncommand="ZoteroPane.showOrphans();"/><!--TODO localize --> 
     116                                        <menuitem label="Cancel orphans view" oncommand="ZoteroPane.onCollectionSelected();"/><!--TODO localize --> 
    116117                                </popup> 
    117118                                <popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();"> 
    118119                                        <menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/> 
  • chrome/content/zotero/xpcom/collectionTreeView.js

    diff -u -r -N zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js
    old new  
    4141        this._highlightedRows = {}; 
    4242        this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']); 
    4343        this.showDuplicates = false; 
     44        this.showOrphans = false; 
    4445} 
    4546 
    4647/* 
     
    16331634        this.type = type; 
    16341635        this.ref = ref; 
    16351636        this.duplicates = new Zotero.Duplicate(); 
     1637        this.orphans = new Zotero.Orphan(); 
    16361638} 
    16371639 
    16381640Zotero.ItemGroup.prototype.isLibrary = function(includeGlobal) 
     
    18071809                                } else { 
    18081810                                        ids = this.duplicates._itemIDs; 
    18091811                } 
     1812                        } else if (this.showOrphans) { 
     1813                                var tmpTable = s.search(true); 
     1814                                ids = this.orphans.getIDs(tmpTable); 
     1815                                Zotero.DB.query("DROP TABLE " + tmpTable); 
    18101816                        } else { 
    18111817                                this.duplicates.setNewIDs(); 
    18121818                        ids = s.search(); 
  • chrome/content/zotero/xpcom/collectionTreeView.js.orig

    diff -u -r -N zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js.orig zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js.orig
    old new  
     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 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 General Public License for more details. 
     19     
     20    You should have received a copy of the GNU General Public License 
     21    along with Zotero.  If not, see <http://www.gnu.org/licenses/>. 
     22     
     23    ***** END LICENSE BLOCK ***** 
     24*/ 
     25 
     26//////////////////////////////////////////////////////////////////////////////// 
     27/// 
     28///  CollectionTreeView 
     29///    -- handles the link between an individual tree and the data layer 
     30///    -- displays only collections, in a hierarchy (no items) 
     31/// 
     32//////////////////////////////////////////////////////////////////////////////// 
     33 
     34/* 
     35 *  Constructor the the CollectionTreeView object 
     36 */ 
     37Zotero.CollectionTreeView = function() 
     38{ 
     39        this._treebox = null; 
     40        this.itemToSelect = null; 
     41        this._highlightedRows = {}; 
     42        this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']); 
     43        this.showDuplicates = false; 
     44} 
     45 
     46/* 
     47 *  Called by the tree itself 
     48 */ 
     49Zotero.CollectionTreeView.prototype.setTree = function(treebox) 
     50{ 
     51        if (this._treebox || !treebox) { 
     52                return; 
     53        } 
     54        this._treebox = treebox; 
     55         
     56        // Add a keypress listener for expand/collapse 
     57        var expandAllRows = this.expandAllRows; 
     58        var collapseAllRows = this.collapseAllRows; 
     59        var tree = this._treebox.treeBody.parentNode; 
     60        tree.addEventListener('keypress', function(event) { 
     61                var key = String.fromCharCode(event.which); 
     62                 
     63                if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) { 
     64                        expandAllRows(treebox); 
     65                        return; 
     66                } 
     67                else if (key == '-' && !(event.shiftKey || event.ctrlKey || 
     68                                event.altKey || event.metaKey)) { 
     69                        collapseAllRows(treebox); 
     70                        return; 
     71                } 
     72        }, false); 
     73         
     74        this.refresh(); 
     75         
     76        // Select the last-viewed collection 
     77        var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder'); 
     78        var matches = lastViewedFolder.match(/^(?:(C|S|G)([0-9]+)|L)$/); 
     79        var select = 0; 
     80        if (matches) { 
     81                if (matches[1] == 'C') { 
     82                        if (this._collectionRowMap[matches[2]]) { 
     83                                select = this._collectionRowMap[matches[2]]; 
     84                        } 
     85                        // Search recursively 
     86                        else { 
     87                                var path = []; 
     88                                var failsafe = 10; // Only go up ten levels 
     89                                var lastCol = matches[2]; 
     90                                do { 
     91                                        failsafe--; 
     92                                        var col = Zotero.Collections.get(lastCol); 
     93                                        if (!col) { 
     94                                                var msg = "Last-viewed collection not found"; 
     95                                                Zotero.debug(msg); 
     96                                                path = []; 
     97                                                break; 
     98                                        } 
     99                                        var par = col.getParent(); 
     100                                        if (!par) { 
     101                                                var msg = "Parent collection not found in " 
     102                                                        + "Zotero.CollectionTreeView.setTree()"; 
     103                                                Zotero.debug(msg, 1); 
     104                                                Components.utils.reportError(msg); 
     105                                                path = []; 
     106                                                break; 
     107                                        } 
     108                                        lastCol = par; 
     109                                        path.push(lastCol); 
     110                                } 
     111                                while (!this._collectionRowMap[lastCol] && failsafe > 0) 
     112                                if (path.length) { 
     113                                        for (var i=path.length-1; i>=0; i--) { 
     114                                                var id = path[i]; 
     115                                                var row = this._collectionRowMap[id]; 
     116                                                if (!row) { 
     117                                                        var msg = "Collection not found in tree in " 
     118                                                                + "Zotero.CollectionTreeView.setTree()"; 
     119                                                        Zotero.debug(msg, 1); 
     120                                                        Components.utils.reportError(msg); 
     121                                                        break; 
     122                                                } 
     123                                                if (!this.isContainerOpen(row)) { 
     124                                                        this.toggleOpenState(row); 
     125                                                        if (this._collectionRowMap[matches[2]]) { 
     126                                                                select = this._collectionRowMap[matches[2]]; 
     127                                                                break; 
     128                                                        } 
     129                                                } 
     130                                        } 
     131                                } 
     132                        } 
     133                } 
     134                else if (matches[1] == 'S' && this._searchRowMap[matches[2]]) { 
     135                        select = this._searchRowMap[matches[2]]; 
     136                } 
     137                else if (matches[1] == 'G' && this._groupRowMap[matches[2]]) { 
     138                        select = this._groupRowMap[matches[2]]; 
     139                } 
     140        } 
     141         
     142        this.selection.currentColumn = this._treebox.columns.getFirstColumn(); 
     143        this.selection.select(select); 
     144} 
     145 
     146/* 
     147 *  Reload the rows from the data access methods 
     148 *  (doesn't call the tree.invalidate methods, etc.) 
     149 */ 
     150Zotero.CollectionTreeView.prototype.refresh = function() 
     151{ 
     152        // Record open states before refreshing 
     153        if (this._dataItems) { 
     154                for (var i=0, len=this._dataItems.length; i<len; i++) { 
     155                        var itemGroup = this._dataItems[i][0] 
     156                        if (itemGroup.ref && itemGroup.ref.id == 'commons-header') { 
     157                                var commonsExpand = this.isContainerOpen(i); 
     158                        } 
     159                } 
     160        } 
     161         
     162        this.selection.clearSelection(); 
     163        var oldCount = this.rowCount; 
     164        this._dataItems = []; 
     165        this.rowCount = 0; 
     166         
     167        var self = this; 
     168        var library = { 
     169                id: null, 
     170                libraryID: null, 
     171                expand: function () { 
     172                        var newRows = 0; 
     173                         
     174                        var collections = Zotero.getCollections(); 
     175                        for (var i=0; i<collections.length; i++) { 
     176                                // Skip group collections 
     177                                if (collections[i].libraryID) { 
     178                                        continue; 
     179                                } 
     180                                self._showItem(new Zotero.ItemGroup('collection', collections[i]), 1, newRows+1); 
     181                                newRows++; 
     182                        } 
     183                         
     184                        var savedSearches = Zotero.Searches.getAll(); 
     185                        if (savedSearches) { 
     186                                for (var i=0; i<savedSearches.length; i++) { 
     187                                        self._showItem(new Zotero.ItemGroup('search', savedSearches[i]), 1, newRows+1); 
     188                                        newRows++; 
     189                                } 
     190                        } 
     191                         
     192                        var deletedItems = Zotero.Items.getDeleted(); 
     193                        if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) { 
     194                                self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1); 
     195                                newRows++; 
     196                        } 
     197                        self.trashNotEmpty = !!deletedItems; 
     198                         
     199                        return newRows; 
     200                } 
     201        }; 
     202        this._showItem(new Zotero.ItemGroup('library', library), 0, 1, 1); // itemgroup ref, level, beforeRow, startOpen 
     203        library.expand(); 
     204         
     205        var groups = Zotero.Groups.getAll(); 
     206        if (groups.length) { 
     207                this._showItem(new Zotero.ItemGroup('separator', false)); 
     208                var header = { 
     209                        id: "group-libraries-header", 
     210                        label: "Group Libraries", // TODO: localize 
     211                        expand: function (groups) { 
     212                                if (!groups) { 
     213                                        var groups = Zotero.Groups.getAll(); 
     214                                } 
     215                                 
     216                                for (var i=0; i<groups.length; i++) { 
     217                                        var startOpen = groups[i].hasCollections(); 
     218                                         
     219                                        self._showItem(new Zotero.ItemGroup('group', groups[i]), 1, null, startOpen); 
     220                                         
     221                                        var newRows = 0; 
     222                                         
     223                                        // Add group collections 
     224                                        var collections = groups[i].getCollections(); 
     225                                        for (var j=0; j<collections.length; j++) { 
     226                                                self._showItem(new Zotero.ItemGroup('collection', collections[j]), 2); 
     227                                                newRows++; 
     228                                        } 
     229                                         
     230                                        // Add group searches 
     231                                        var savedSearches = Zotero.Searches.getAll(groups[i].libraryID); 
     232                                        if (savedSearches) { 
     233                                                for (var j=0; j<savedSearches.length; j++) { 
     234                                                        self._showItem(new Zotero.ItemGroup('search', savedSearches[j]), 2); 
     235                                                        newRows++; 
     236                                                } 
     237                                        } 
     238                                } 
     239                        } 
     240                }; 
     241                this._showItem(new Zotero.ItemGroup('header', header), null, null, true); 
     242                header.expand(groups); 
     243        } 
     244         
     245        var shares = Zotero.Zeroconf.instances; 
     246        if (shares.length) { 
     247                this._showItem(new Zotero.ItemGroup('separator', false)); 
     248                for each(var share in shares) { 
     249                        this._showItem(new Zotero.ItemGroup('share', share)); 
     250                } 
     251        } 
     252         
     253        if (Zotero.Commons.enabled) { 
     254                this._showItem(new Zotero.ItemGroup('separator', false)); 
     255                var header = { 
     256                        id: "commons-header", 
     257                        label: "Commons", // TODO: localize 
     258                        expand: function (buckets) { 
     259                                if (!buckets) { 
     260                                        var buckets = Zotero.Commons.getBuckets(); 
     261                                } 
     262                                 
     263                                for each(var bucket in buckets) { 
     264                                        self._showItem(new Zotero.ItemGroup('bucket', bucket), 1); 
     265                                } 
     266                        } 
     267                }; 
     268                Zotero.debug('============='); 
     269                Zotero.debug(commonsExpand); 
     270                this._showItem(new Zotero.ItemGroup('header', header), null, null, commonsExpand); 
     271                if (commonsExpand) { 
     272                        header.expand(); 
     273                } 
     274        } 
     275         
     276        this._refreshHashMap(); 
     277         
     278        // Update the treebox's row count 
     279        var diff = this.rowCount - oldCount; 
     280        if (diff != 0) { 
     281                this._treebox.rowCountChanged(0, diff); 
     282        } 
     283} 
     284 
     285/* 
     286 *  Redisplay everything 
     287 */ 
     288Zotero.CollectionTreeView.prototype.reload = function() 
     289{ 
     290        var openCollections = []; 
     291         
     292        for (var i=0; i<this.rowCount; i++) { 
     293                if (this.isContainer(i) && this.isContainerOpen(i)) { 
     294                        openCollections.push(this._getItemAtRow(i).ref.id); 
     295                } 
     296        } 
     297         
     298        this._treebox.beginUpdateBatch(); 
     299        this.refresh(); 
     300         
     301        for(var i = 0; i < openCollections.length; i++) 
     302        { 
     303                var row = this._collectionRowMap[openCollections[i]]; 
     304                if (row != null) { 
     305                        this.toggleOpenState(row); 
     306                } 
     307        } 
     308        this._treebox.invalidate(); 
     309        this._treebox.endUpdateBatch(); 
     310} 
     311 
     312/* 
     313 *  Called by Zotero.Notifier on any changes to collections in the data layer 
     314 */ 
     315Zotero.CollectionTreeView.prototype.notify = function(action, type, ids) 
     316{ 
     317        if (!ids || ids.length == 0) { 
     318                return; 
     319        } 
     320         
     321        if (!this._collectionRowMap) { 
     322                Zotero.debug("Collection row map didn't exist in collectionTreeView.notify()"); 
     323                return; 
     324        } 
     325         
     326        this.selection.selectEventsSuppressed = true; 
     327        var savedSelection = this.saveSelection(); 
     328         
     329        var madeChanges = false; 
     330         
     331        if (action == 'delete') { 
     332                var selectedIndex = this.selection.count ? this.selection.selectedIndex : 0; 
     333                 
     334                //Since a delete involves shifting of rows, we have to do it in order 
     335                 
     336                //sort the ids by row 
     337                var rows = new Array(); 
     338                for (var i in ids) 
     339                { 
     340                        switch (type) 
     341                        { 
     342                                case 'collection': 
     343                                        if(this._collectionRowMap[ids[i]] != null) 
     344                                        { 
     345                                                rows.push(this._collectionRowMap[ids[i]]); 
     346                                        } 
     347                                        break; 
     348                                 
     349                                case 'search': 
     350                                        if(this._searchRowMap[ids[i]] != null) 
     351                                        { 
     352                                                rows.push(this._searchRowMap[ids[i]]); 
     353                                        } 
     354                                        break; 
     355                                 
     356                                case 'group': 
     357                                        //if (this._groupRowMap[ids[i]] != null) { 
     358                                        //      rows.push(this._groupRowMap[ids[i]]); 
     359                                        //} 
     360                                         
     361                                        // For now, just reload if a group is removed, since otherwise 
     362                                        // we'd have to remove collections too 
     363                                        this.reload(); 
     364                                        this.rememberSelection(savedSelection); 
     365                                        break; 
     366                        } 
     367                } 
     368                 
     369                if(rows.length > 0) 
     370                { 
     371                        rows.sort(function(a,b) { return a-b }); 
     372                         
     373                        for(var i=0, len=rows.length; i<len; i++) 
     374                        { 
     375                                var row = rows[i]; 
     376                                this._hideItem(row-i); 
     377                                this._treebox.rowCountChanged(row-i,-1); 
     378                        } 
     379                         
     380                        this._refreshHashMap(); 
     381                } 
     382                 
     383                if (!this.selection.count) { 
     384                        this.selection.select(selectedIndex) 
     385                } 
     386        } 
     387        else if(action == 'move') 
     388        { 
     389                for (var i=0; i<ids.length; i++) { 
     390                        // Open the parent collection if closed 
     391                        var collection = Zotero.Collections.get(ids[i]); 
     392                        var parentID = collection.parent; 
     393                        if (parentID && this._collectionRowMap[parentID] && 
     394                                        !this.isContainerOpen(this._collectionRowMap[parentID])) { 
     395                                this.toggleOpenState(this._collectionRowMap[parentID]); 
     396                        } 
     397                } 
     398                 
     399                this.reload(); 
     400                this.rememberSelection(savedSelection); 
     401        } 
     402        else if (action == 'modify' || action == 'refresh') { 
     403                if (type != 'bucket') { 
     404                        this.reload(); 
     405                } 
     406                this.rememberSelection(savedSelection); 
     407        } 
     408        else if(action == 'add') 
     409        { 
     410                // Multiple adds not currently supported 
     411                ids = ids[0]; 
     412                 
     413                switch (type) 
     414                { 
     415                        case 'collection': 
     416                                var collection = Zotero.Collections.get(ids); 
     417                                var collectionID = collection.id; 
     418                                // Open container if creating subcollection 
     419                                var parentID = collection.getParent(); 
     420                                if (parentID) { 
     421                                        if (!this.isContainerOpen(this._collectionRowMap[parentID])){ 
     422                                                this.toggleOpenState(this._collectionRowMap[parentID]); 
     423                                        } 
     424                                } 
     425                                 
     426                                this.reload(); 
     427                                if (Zotero.suppressUIUpdates) { 
     428                                        this.rememberSelection(savedSelection); 
     429                                        break; 
     430                                } 
     431                                this.selection.select(this._collectionRowMap[collectionID]); 
     432                                break; 
     433                                 
     434                        case 'search': 
     435                                this.reload(); 
     436                                if (Zotero.suppressUIUpdates) { 
     437                                        this.rememberSelection(savedSelection); 
     438                                        break; 
     439                                } 
     440                                this.selection.select(this._searchRowMap[ids]); 
     441                                break; 
     442                         
     443                        case 'group': 
     444                                this.reload(); 
     445                                // Groups can only be created during sync 
     446                                this.rememberSelection(savedSelection); 
     447                                break; 
     448 
     449                        case 'bucket': 
     450                                this.reload(); 
     451                                this.rememberSelection(savedSelection); 
     452                                break; 
     453                } 
     454        } 
     455         
     456        this.selection.selectEventsSuppressed = false; 
     457} 
     458 
     459 
     460/* 
     461 * Set the rows that should be highlighted -- actual highlighting is done 
     462 * by getRowProperties based on the array set here 
     463 */ 
     464Zotero.CollectionTreeView.prototype.setHighlightedRows = function (ids) { 
     465        this._highlightedRows = {}; 
     466        this._treebox.invalidate(); 
     467         
     468        for each(var id in ids) { 
     469                this.expandToCollection(id); 
     470                this._highlightedRows[this._collectionRowMap[id]] = true; 
     471                this._treebox.invalidateRow(this._collectionRowMap[id]); 
     472        } 
     473} 
     474 
     475 
     476/* 
     477 *  Unregisters view from Zotero.Notifier (called on window close) 
     478 */ 
     479Zotero.CollectionTreeView.prototype.unregister = function() 
     480{ 
     481        Zotero.Notifier.unregisterObserver(this._unregisterID); 
     482} 
     483 
     484Zotero.CollectionTreeView.prototype.isLibrary = function(row) 
     485{ 
     486        return this._getItemAtRow(row).isLibrary(); 
     487} 
     488 
     489Zotero.CollectionTreeView.prototype.isCollection = function(row) 
     490{ 
     491        return this._getItemAtRow(row).isCollection(); 
     492} 
     493 
     494Zotero.CollectionTreeView.prototype.isSearch = function(row) 
     495{ 
     496        return this._getItemAtRow(row).isSearch(); 
     497} 
     498 
     499 
     500//////////////////////////////////////////////////////////////////////////////// 
     501/// 
     502///  nsITreeView functions 
     503///  http://www.xulplanet.com/references/xpcomref/ifaces/nsITreeView.html 
     504/// 
     505//////////////////////////////////////////////////////////////////////////////// 
     506 
     507Zotero.CollectionTreeView.prototype.getCellText = function(row, column) 
     508{ 
     509        var obj = this._getItemAtRow(row); 
     510         
     511        if(column.id == "zotero-collections-name-column") 
     512                return obj.getName(); 
     513        else 
     514                return ""; 
     515} 
     516 
     517Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col) 
     518{ 
     519        var source = this._getItemAtRow(row); 
     520        var collectionType = source.type; 
     521        switch (collectionType) { 
     522                case 'trash': 
     523                        if (this.trashNotEmpty) { 
     524                                collectionType += '-full'; 
     525                        } 
     526                        break; 
     527                 
     528                case 'collection': 
     529                        // TODO: group collection 
     530                        return "chrome://zotero-platform/content/treesource-collection.png"; 
     531                 
     532                case 'search': 
     533                        // TODO: is this platform independent? 
     534                        return "chrome://zotero-platform/content/treesource-search.png"; 
     535                 
     536                case 'header': 
     537                        if (source.ref.id == 'group-libraries-header') { 
     538                                collectionType = 'groups'; 
     539                        } 
     540                        else if (source.ref.id == 'commons-header') { 
     541                                collectionType = 'commons'; 
     542                        } 
     543                        break; 
     544                 
     545                case 'group': 
     546                        collectionType = 'library'; 
     547                        break; 
     548        } 
     549        return "chrome://zotero/skin/treesource-" + collectionType + ".png"; 
     550} 
     551 
     552Zotero.CollectionTreeView.prototype.isContainer = function(row) 
     553{ 
     554        var itemGroup = this._getItemAtRow(row); 
     555        return itemGroup.isLibrary(true) || itemGroup.isCollection() || itemGroup.isHeader() || itemGroup.isBucket(); 
     556} 
     557 
     558Zotero.CollectionTreeView.prototype.isContainerOpen = function(row) 
     559{ 
     560        return this._dataItems[row][1]; 
     561} 
     562 
     563/* 
     564 * Returns true if the collection has no child collections 
     565 */ 
     566Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row) 
     567{ 
     568        var itemGroup = this._getItemAtRow(row); 
     569        if (itemGroup.isLibrary()) { 
     570                return false; 
     571        } 
     572        if (itemGroup.isHeader()) { 
     573                return false; 
     574        } 
     575        if (itemGroup.isBucket()) { 
     576                return true; 
     577        } 
     578        if (itemGroup.isGroup()) { 
     579                return !itemGroup.ref.hasCollections(); 
     580        } 
     581        if (itemGroup.isCollection()) { 
     582                return !itemGroup.ref.hasChildCollections(); 
     583        } 
     584        return true; 
     585} 
     586 
     587Zotero.CollectionTreeView.prototype.getLevel = function(row) 
     588{ 
     589        return this._dataItems[row][2]; 
     590} 
     591 
     592Zotero.CollectionTreeView.prototype.getParentIndex = function(row) 
     593{ 
     594        var thisLevel = this.getLevel(row); 
     595        if(thisLevel == 0) return -1; 
     596        for(var i = row - 1; i >= 0; i--) 
     597                if(this.getLevel(i) < thisLevel) 
     598                        return i; 
     599        return -1; 
     600} 
     601 
     602Zotero.CollectionTreeView.prototype.hasNextSibling = function(row, afterIndex) 
     603{ 
     604        var thisLevel = this.getLevel(row); 
     605        for(var i = afterIndex + 1; i < this.rowCount; i++) 
     606        {        
     607                var nextLevel = this.getLevel(i); 
     608                if(nextLevel == thisLevel) return true; 
     609                else if(nextLevel < thisLevel) return false; 
     610        } 
     611} 
     612 
     613/* 
     614 *  Opens/closes the specified row 
     615 */ 
     616Zotero.CollectionTreeView.prototype.toggleOpenState = function(row) 
     617{ 
     618        var count = 0;          //used to tell the tree how many rows were added/removed 
     619        var thisLevel = this.getLevel(row); 
     620 
     621        this._treebox.beginUpdateBatch(); 
     622        if (this.isContainerOpen(row)) { 
     623                while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel)) 
     624                { 
     625                        this._hideItem(row+1); 
     626                        count--;        //count is negative when closing a container because we are removing rows 
     627                } 
     628        } 
     629        else { 
     630                var itemGroup = this._getItemAtRow(row); 
     631                 
     632                if (itemGroup.type == 'header') { 
     633                        itemGroup.ref.expand(); 
     634                } 
     635                else if(itemGroup.type == 'bucket') { 
     636                } 
     637                else { 
     638                        if (itemGroup.isLibrary()) { 
     639                                count = itemGroup.ref.expand(); 
     640                        } 
     641                        else { 
     642                                if (itemGroup.isGroup()) { 
     643                                        var collections = itemGroup.ref.getCollections(); 
     644                                } 
     645                                else { 
     646                                        var collections = Zotero.getCollections(itemGroup.ref.id); 
     647                                } 
     648                                // Add child collections 
     649                                for (var i=0; i<collections.length; i++) { 
     650                                        this._showItem(new Zotero.ItemGroup('collection', collections[i]), thisLevel + 1, row + count + 1); 
     651                                        count++; 
     652                                } 
     653                                 
     654                                // Add group searches 
     655                                if (itemGroup.isGroup()) { 
     656                                        var savedSearches = Zotero.Searches.getAll(itemGroup.ref.libraryID); 
     657                                        if (savedSearches) { 
     658                                                for (var i=0; i<savedSearches.length; i++) { 
     659                                                        this._showItem(new Zotero.ItemGroup('search', savedSearches[i]), thisLevel + 1, row + count + 1); 
     660                                                        count; 
     661                                                } 
     662                                        } 
     663                                } 
     664                        } 
     665                } 
     666        } 
     667        this._dataItems[row][1] = !this._dataItems[row][1];  //toggle container open value 
     668 
     669        this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these 
     670        this._treebox.invalidateRow(row); 
     671        this._treebox.endUpdateBatch();  
     672        this._refreshHashMap(); 
     673} 
     674 
     675 
     676Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) { 
     677        var itemGroup = this._getItemAtRow(row); 
     678        switch (itemGroup.type) { 
     679                case 'separator': 
     680                        return false; 
     681        } 
     682        return true; 
     683} 
     684 
     685 
     686Zotero.CollectionTreeView.prototype.__defineGetter__('editable', function () { 
     687        return this._getItemAtRow(this.selection.currentIndex).editable; 
     688}); 
     689 
     690 
     691Zotero.CollectionTreeView.prototype.expandAllRows = function(treebox) { 
     692        var view = treebox.view; 
     693        treebox.beginUpdateBatch(); 
     694        for (var i=0; i<view.rowCount; i++) { 
     695                if (view.isContainer(i) && !view.isContainerOpen(i)) { 
     696                        view.toggleOpenState(i); 
     697                } 
     698        } 
     699        treebox.endUpdateBatch(); 
     700} 
     701 
     702 
     703Zotero.CollectionTreeView.prototype.expandToCollection = function(collectionID) { 
     704        var col = Zotero.Collections.get(collectionID); 
     705        if (!col) { 
     706                Zotero.debug("Cannot expand to nonexistent collection " + collectionID, 2); 
     707                return false; 
     708        } 
     709        var row = this._collectionRowMap[collectionID]; 
     710        if (row) { 
     711                return true; 
     712        } 
     713        var path = []; 
     714        var parent; 
     715        while (parent = col.getParent()) { 
     716                path.unshift(parent); 
     717                col = Zotero.Collections.get(parent); 
     718        } 
     719        for each(var id in path) { 
     720                row = this._collectionRowMap[id]; 
     721                if (!this.isContainerOpen(row)) { 
     722                        this.toggleOpenState(row); 
     723                } 
     724        } 
     725        return true; 
     726} 
     727 
     728 
     729Zotero.CollectionTreeView.prototype.collapseAllRows = function(treebox) { 
     730        var view = treebox.view; 
     731        treebox.beginUpdateBatch(); 
     732        for (var i=0; i<view.rowCount; i++) { 
     733                if (view.isContainer(i) && view.isContainerOpen(i)) { 
     734                        view.toggleOpenState(i); 
     735                } 
     736        } 
     737        treebox.endUpdateBatch(); 
     738} 
     739 
     740 
     741//////////////////////////////////////////////////////////////////////////////// 
     742/// 
     743///  Additional functions for managing data in the tree 
     744/// 
     745//////////////////////////////////////////////////////////////////////////////// 
     746/** 
     747 * @param       {Integer|null}          libraryID               Library to select, or null for local library 
     748 */ 
     749Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) { 
     750        if (Zotero.suppressUIUpdates) { 
     751                Zotero.debug("UI updates suppressed -- not changing library selection"); 
     752                return false; 
     753        } 
     754         
     755        // Select local library 
     756        if (!libraryID) { 
     757                this.selection.select(0); 
     758                return true; 
     759        } 
     760         
     761        // Already selected 
     762        var itemGroup = this._getItemAtRow(this.selection.currentIndex); 
     763        if (itemGroup.ref.libraryID == libraryID) { 
     764                return true; 
     765        } 
     766         
     767        // Find library 
     768        for (var i=0, rows=this.rowCount; i<rows.length; i++) { 
     769                var itemGroup = this._getItemAtRow(this.selection.currentIndex); 
     770                if (itemGroup.ref && itemGroup.ref.libraryID == libraryID) { 
     771                        this.selection.select(i); 
     772                        return true; 
     773                } 
     774        } 
     775         
     776        return false; 
     777} 
     778 
     779 
     780/* 
     781 *  Delete the selection 
     782 */ 
     783Zotero.CollectionTreeView.prototype.deleteSelection = function() 
     784{ 
     785        if(this.selection.count == 0) 
     786                return; 
     787 
     788        //collapse open collections 
     789        for(var i=0; i<this.rowCount; i++) 
     790                if(this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i)) 
     791                        this.toggleOpenState(i); 
     792        this._refreshHashMap(); 
     793         
     794        //create an array of collections 
     795        var rows = new Array(); 
     796        var start = new Object(); 
     797        var end = new Object(); 
     798        for (var i=0, len=this.selection.getRangeCount(); i<len; i++) 
     799        { 
     800                this.selection.getRangeAt(i,start,end); 
     801                for (var j=start.value; j<=end.value; j++) 
     802                        if(!this._getItemAtRow(j).isLibrary()) 
     803                                rows.push(j); 
     804        } 
     805         
     806        //iterate and erase... 
     807        this._treebox.beginUpdateBatch(); 
     808        for (var i=0; i<rows.length; i++) 
     809        { 
     810                //erase collection from DB: 
     811                var group = this._getItemAtRow(rows[i]-i); 
     812                if(group.isCollection()) 
     813                { 
     814                        group.ref.erase(); 
     815                } 
     816                else if(group.isSearch()) 
     817                { 
     818                        Zotero.Searches.erase(group.ref['id']); 
     819                } 
     820        } 
     821        this._treebox.endUpdateBatch(); 
     822         
     823        if(end.value < this.rowCount) 
     824                this.selection.select(end.value); 
     825        else 
     826                this.selection.select(this.rowCount-1); 
     827} 
     828 
     829/* 
     830 *  Called by various view functions to show a row 
     831 *  
     832 *      itemGroup:      reference to the ItemGroup 
     833 *      level:  the indent level of the row 
     834 *      beforeRow:      row index to insert new row before 
     835 */ 
     836Zotero.CollectionTreeView.prototype._showItem = function(itemGroup, level, beforeRow, startOpen) 
     837{ 
     838        if (!level) { 
     839                level = 0; 
     840        } 
     841         
     842        if (!beforeRow) { 
     843                beforeRow = this._dataItems.length; 
     844        } 
     845         
     846        if (!startOpen) { 
     847                startOpen = false; 
     848        } 
     849         
     850        this._dataItems.splice(beforeRow, 0, [itemGroup, startOpen, level]); 
     851        this.rowCount++; 
     852} 
     853 
     854/* 
     855 *  Called by view to hide specified row 
     856 */ 
     857Zotero.CollectionTreeView.prototype._hideItem = function(row) 
     858{ 
     859        this._dataItems.splice(row,1); this.rowCount--; 
     860} 
     861 
     862/* 
     863 *  Returns a reference to the collection at row (see Zotero.Collection in data_access.js) 
     864 */ 
     865Zotero.CollectionTreeView.prototype._getItemAtRow = function(row) 
     866{ 
     867        return this._dataItems[row][0]; 
     868} 
     869 
     870 
     871/* 
     872 *  Saves the ids of the currently selected item for later 
     873 */ 
     874Zotero.CollectionTreeView.prototype.saveSelection = function() 
     875{ 
     876        for (var i=0, len=this.rowCount; i<len; i++) { 
     877                if (this.selection.isSelected(i)) { 
     878                        var itemGroup = this._getItemAtRow(i); 
     879                        if (itemGroup.isLibrary()) { 
     880                                return 'L'; 
     881                        } 
     882                        else if (itemGroup.isCollection()) { 
     883                                return 'C' + itemGroup.ref.id; 
     884                        } 
     885                        else if (itemGroup.isSearch()) { 
     886                                return 'S' + itemGroup.ref.id; 
     887                        } 
     888                        else if (itemGroup.isTrash()) { 
     889                                return 'T'; 
     890                        } 
     891                        else if (itemGroup.isGroup()) { 
     892                                return 'G' + itemGroup.ref.id; 
     893                        } 
     894                } 
     895        } 
     896        return false; 
     897} 
     898 
     899/* 
     900 *  Sets the selection based on saved selection ids (see above) 
     901 */ 
     902Zotero.CollectionTreeView.prototype.rememberSelection = function(selection) 
     903{ 
     904        if (!selection) { 
     905                return; 
     906        } 
     907         
     908        var id = selection.substr(1); 
     909        switch (selection.substr(0, 1)) { 
     910                // Library 
     911                case 'L': 
     912                        this.selection.select(0); 
     913                        break; 
     914                 
     915                // Collection 
     916                case 'C': 
     917                        // This only selects the collection if it's still visible, 
     918                        // so we open the parent in notify() 
     919                        if (this._collectionRowMap[id] != undefined) { 
     920                                this.selection.select(this._collectionRowMap[id]); 
     921                        } 
     922                        break; 
     923                 
     924                // Saved search 
     925                case 'S': 
     926                        if (this._searchRowMap[id] != undefined) { 
     927                                this.selection.select(this._searchRowMap[id]); 
     928                        } 
     929                        break; 
     930                 
     931                // Trash 
     932                case 'T': 
     933                        if (this._getItemAtRow(this.rowCount-1).isTrash()){ 
     934                                this.selection.select(this.rowCount-1); 
     935                        } 
     936                        else { 
     937                                this.selection.select(0); 
     938                        } 
     939                        break; 
     940                 
     941                // Group 
     942                case 'G': 
     943                        if (this._groupRowMap[id] != undefined) { 
     944                                this.selection.select(this._groupRowMap[i]); 
     945                        } 
     946                        break; 
     947        } 
     948} 
     949         
     950         
     951Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) { 
     952        if (this.selection 
     953                        && this.selection.count > 0 
     954                        && this.selection.currentIndex != -1) { 
     955                var collection = this._getItemAtRow(this.selection.currentIndex); 
     956                if (collection && collection.isCollection()) { 
     957                        return asID ? collection.ref.id : collection.ref; 
     958                } 
     959        } 
     960        return false; 
     961} 
     962 
     963 
     964 
     965/* 
     966 * Creates hash map of collection and search ids to row indexes 
     967 * e.g., var rowForID = this._collectionRowMap[] 
     968 */ 
     969Zotero.CollectionTreeView.prototype._refreshHashMap = function() 
     970{        
     971        this._collectionRowMap = []; 
     972        this._searchRowMap = []; 
     973        this._groupRowMap = []; 
     974        for(var i=0; i < this.rowCount; i++){ 
     975                var itemGroup = this._getItemAtRow(i); 
     976                if (itemGroup.isCollection(i)) { 
     977                        this._collectionRowMap[itemGroup.ref.id] = i; 
     978                } 
     979                else if (itemGroup.isSearch(i)) { 
     980                        this._searchRowMap[itemGroup.ref.id] = i; 
     981                } 
     982                else if (itemGroup.isGroup(i)) { 
     983                        this._groupRowMap[itemGroup.ref.id] = i; 
     984                } 
     985        } 
     986} 
     987 
     988//////////////////////////////////////////////////////////////////////////////// 
     989/// 
     990///  Command Controller: 
     991///             for Select All, etc. 
     992/// 
     993//////////////////////////////////////////////////////////////////////////////// 
     994 
     995Zotero.CollectionTreeCommandController = function(tree) 
     996{ 
     997        this.tree = tree; 
     998} 
     999 
     1000Zotero.CollectionTreeCommandController.prototype.supportsCommand = function(cmd) 
     1001{ 
     1002} 
     1003 
     1004Zotero.CollectionTreeCommandController.prototype.isCommandEnabled = function(cmd) 
     1005{ 
     1006} 
     1007 
     1008Zotero.CollectionTreeCommandController.prototype.doCommand = function(cmd) 
     1009{ 
     1010} 
     1011 
     1012Zotero.CollectionTreeCommandController.prototype.onEvent = function(evt) 
     1013{ 
     1014} 
     1015 
     1016//////////////////////////////////////////////////////////////////////////////// 
     1017/// 
     1018///  Drag-and-drop functions: 
     1019///             canDrop() and drop() are for nsITreeView 
     1020///             onDragStart(), getSupportedFlavours(), and onDrop() for nsDragAndDrop.js 
     1021/// 
     1022//////////////////////////////////////////////////////////////////////////////// 
     1023 
     1024 
     1025/** 
     1026 * Start a drag using HTML 5 Drag and Drop 
     1027 */ 
     1028Zotero.CollectionTreeView.prototype.onDragStart = function(event, transferData, action) { 
     1029        var itemGroup = this._getItemAtRow(this.selection.currentIndex); 
     1030        if (!itemGroup.isCollection()) { 
     1031                return false; 
     1032        } 
     1033        var collectionID = itemGroup.ref.id; 
     1034         
     1035        event.dataTransfer.setData("zotero/collection", collectionID); 
     1036} 
     1037 
     1038 
     1039/** 
     1040 * Returns the supported drag flavors 
     1041 * 
     1042 * Called by nsDragAndDrop.js 
     1043 */ 
     1044Zotero.CollectionTreeView.prototype.getSupportedFlavours = function () { 
     1045        var flavors = new FlavourSet(); 
     1046        flavors.appendFlavour("zotero/collection"); 
     1047        flavors.appendFlavour("zotero/item"); 
     1048        flavors.appendFlavour("zotero/item-xml"); 
     1049        flavors.appendFlavour("text/x-moz-url"); 
     1050        flavors.appendFlavour("application/x-moz-file", "nsIFile"); 
     1051        return flavors;  
     1052} 
     1053 
     1054 
     1055/* 
     1056 *  Called while a drag is over the tree. 
     1057 */ 
     1058Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData) 
     1059{ 
     1060        //Zotero.debug("Row is " + row + "; orient is " + orient); 
     1061         
     1062        // Two different services call canDrop, nsDragAndDrop and the tree 
     1063        // This is for the former, used when dragging between windows 
     1064        if (typeof row == 'object') { 
     1065                return false; 
     1066        } 
     1067         
     1068        if (!dragData || !dragData.data) { 
     1069                var dragData = Zotero.DragDrop.getDragData(this); 
     1070        } 
     1071        if (!dragData) { 
     1072                return false; 
     1073        } 
     1074        var dataType = dragData.dataType; 
     1075        var data = dragData.data; 
     1076         
     1077        // For dropping collections onto root level 
     1078        if (orient == 1 && row == 0 && dataType == 'zotero/collection') { 
     1079                return true; 
     1080        } 
     1081        else if(orient == 0)    //directly on a row... 
     1082        { 
     1083                var itemGroup = this._getItemAtRow(row); //the collection we are dragging over 
     1084                 
     1085                if (dataType == 'zotero/item' && itemGroup.isBucket()) { 
     1086                        return true; 
     1087                } 
     1088                 
     1089                if (!itemGroup.editable) { 
     1090                        return false; 
     1091                } 
     1092                 
     1093                if (dataType == 'zotero/item') { 
     1094                        var ids = data; 
     1095                        var items = Zotero.Items.get(ids); 
     1096                        var skip = true; 
     1097                        for each(var item in items) { 
     1098                                // Can only drag top-level items 
     1099                                if (!item.isTopLevelItem()) { 
     1100                                        return false 
     1101                                } 
     1102                                 
     1103                                if (itemGroup.isWithinGroup() && item.isAttachment()) { 
     1104                                        // Linked files can't be added to groups 
     1105                                        if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) { 
     1106                                                return false; 
     1107                                        } 
     1108                                        if (!itemGroup.filesEditable) { 
     1109                                                return false; 
     1110                                        } 
     1111                                        skip = false; 
     1112                                        continue; 
     1113                                } 
     1114                                 
     1115                                // Cross-library drag 
     1116                                if (itemGroup.ref.libraryID != item.libraryID) { 
     1117                                        // Only allow cross-library drag to root library and collections 
     1118                                        if (!(itemGroup.isLibrary(true) || itemGroup.isCollection())) { 
     1119                                                return false; 
     1120                                        } 
     1121                                         
     1122                                        var linkedItem = item.getLinkedItem(itemGroup.ref.libraryID); 
     1123                                        if (linkedItem) { 
     1124                                                // For drag to root, skip if linked item exists 
     1125                                                if (itemGroup.isLibrary(true)) { 
     1126                                                        continue; 
     1127                                                } 
     1128                                                // For drag to collection 
     1129                                                else if (itemGroup.isCollection()) { 
     1130                                                        // skip if linked item is already in it 
     1131                                                        if (itemGroup.ref.hasItem(linkedItem.id)) { 
     1132                                                                continue; 
     1133                                                        } 
     1134                                                        // or if linked item is a child item 
     1135                                                        else if (linkedItem.getSource()) { 
     1136                                                                continue; 
     1137                                                        } 
     1138                                                } 
     1139                                        } 
     1140                                        skip = false; 
     1141                                        continue; 
     1142                                } 
     1143                                 
     1144                                // Intra-library drag 
     1145                                 
     1146                                // Don't allow drag onto root of same library 
     1147                                if (itemGroup.isLibrary(true)) { 
     1148                                        return false; 
     1149                                } 
     1150                                 
     1151                                // Make sure there's at least one item that's not already 
     1152                                // in this collection 
     1153                                if (itemGroup.isCollection() && !itemGroup.ref.hasItem(item.id)) { 
     1154                                        skip = false; 
     1155                                        continue; 
     1156                                } 
     1157                        } 
     1158                        if (skip) { 
     1159                                return false; 
     1160                        } 
     1161                        return true; 
     1162                } 
     1163                else if (dataType == 'zotero/item-xml') { 
     1164                        var xml = new XML(data.data); 
     1165                        for each(var xmlNode in xml.items.item) { 
     1166                                var item = Zotero.Sync.Server.Data.xmlToItem(xmlNode); 
     1167                                if (item.isRegularItem() || !item.getSource()) { 
     1168                                        return true; 
     1169                                } 
     1170                        } 
     1171                        return false; 
     1172                } 
     1173                else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') { 
     1174                        if (itemGroup.isSearch()) { 
     1175                                return false; 
     1176                        } 
     1177                        if (dataType == 'application/x-moz-file') { 
     1178                                // Don't allow folder drag 
     1179                                if (data[0].isDirectory()) { 
     1180                                        return false; 
     1181                                } 
     1182                                // Don't allow drop if no permissions 
     1183                                if (!itemGroup.filesEditable) { 
     1184                                        return false; 
     1185                                } 
     1186                        } 
     1187                         
     1188                        return true; 
     1189                } 
     1190                else if (dataType == 'zotero/collection') { 
     1191                        // Collections cannot be dropped on themselves 
     1192                        if (data[0] == itemGroup.ref.id) { 
     1193                                return false; 
     1194                        } 
     1195                         
     1196                        // Nor in their children 
     1197                        if (Zotero.Collections.get(data[0]).hasDescendent('collection', itemGroup.ref.id)) { 
     1198                                return false; 
     1199                        } 
     1200                         
     1201                        var col = Zotero.Collections.get(data[0]); 
     1202                         
     1203                        // Nor, at least for now, on another group 
     1204                        if (itemGroup.isWithinGroup()) { 
     1205                                if (itemGroup.ref.libraryID != col.libraryID) { 
     1206                                        return false; 
     1207                                } 
     1208                        } 
     1209                        // Nor from a group library to the local library 
     1210                        else if (col.libraryID) { 
     1211                                return false; 
     1212                        } 
     1213                         
     1214                        return true; 
     1215                } 
     1216        } 
     1217        return false; 
     1218} 
     1219 
     1220/* 
     1221 *  Called when something's been dropped on or next to a row 
     1222 */ 
     1223Zotero.CollectionTreeView.prototype.drop = function(row, orient) 
     1224{ 
     1225        var dragData = Zotero.DragDrop.getDragData(this); 
     1226         
     1227        if (!this.canDrop(row, orient, dragData)) { 
     1228                return false; 
     1229        } 
     1230         
     1231        var dataType = dragData.dataType; 
     1232        var data = dragData.data; 
     1233        var itemGroup = this._getItemAtRow(row); 
     1234         
     1235        if(dataType == 'zotero/collection') 
     1236        { 
     1237                var targetCollectionID; 
     1238                if (itemGroup.isCollection()) { 
     1239                        targetCollectionID = itemGroup.ref.id; 
     1240                } 
     1241                var droppedCollection = Zotero.Collections.get(data[0]); 
     1242                droppedCollection.parent = targetCollectionID; 
     1243                droppedCollection.save(); 
     1244        } 
     1245        else if (dataType == 'zotero/item') { 
     1246                var ids = data; 
     1247                if (ids.length < 1) { 
     1248                        return; 
     1249                } 
     1250                 
     1251                if (itemGroup.isWithinGroup()) { 
     1252                        var targetLibraryID = itemGroup.ref.libraryID; 
     1253                } 
     1254                else { 
     1255                        var targetLibraryID = null; 
     1256                } 
     1257                 
     1258                if(itemGroup.isBucket()) { 
     1259                        itemGroup.ref.uploadItems(ids); 
     1260                        return; 
     1261                } 
     1262                 
     1263                Zotero.DB.beginTransaction(); 
     1264                 
     1265                var items = Zotero.Items.get(ids); 
     1266                if (!items) { 
     1267                        return; 
     1268                } 
     1269                 
     1270                var newItems = []; 
     1271                var newIDs = []; 
     1272                // TODO: support items coming from different sources? 
     1273                if (items[0].libraryID == targetLibraryID) { 
     1274                        var sameLibrary = true; 
     1275                } 
     1276                else { 
     1277                        var sameLibrary = false; 
     1278                } 
     1279                 
     1280                for each(var item in items) { 
     1281                        if (!(item.isRegularItem() || !item.getSource())) { 
     1282                                continue; 
     1283                        } 
     1284                         
     1285                        if (sameLibrary) { 
     1286                                newIDs.push(item.id); 
     1287                        } 
     1288                        else { 
     1289                                newItems.push(item); 
     1290                        } 
     1291                } 
     1292                 
     1293                if (!sameLibrary) { 
     1294                        var toReconcile = []; 
     1295                         
     1296                        for each(var item in newItems) { 
     1297                                // Check if there's already a copy of this item in the library 
     1298                                var linkedItem = item.getLinkedItem(targetLibraryID); 
     1299                                if (linkedItem) { 
     1300                                        // Add linked item to target collection rather than copying 
     1301                                        if (itemGroup.isCollection()) { 
     1302                                                itemGroup.ref.addItem(linkedItem.id); 
     1303                                                continue; 
     1304                                        } 
     1305                                         
     1306                                        /* 
     1307                                        // TODO: support tags, related, attachments, etc. 
     1308                                         
     1309                                        // Overlay source item fields on unsaved clone of linked item 
     1310                                        var newItem = item.clone(false, linkedItem.clone(true)); 
     1311                                        newItem.setField('dateAdded', item.dateAdded); 
     1312                                        newItem.setField('dateModified', item.dateModified); 
     1313                                         
     1314                                        var diff = newItem.diff(linkedItem, false, ["dateAdded", "dateModified"]); 
     1315                                        if (!diff) { 
     1316                                                // Check if creators changed 
     1317                                                var creatorsChanged = false; 
     1318                                                 
     1319                                                var creators = item.getCreators(); 
     1320                                                var linkedCreators = linkedItem.getCreators(); 
     1321                                                if (creators.length != linkedCreators.length) { 
     1322                                                        Zotero.debug('Creators have changed'); 
     1323                                                        creatorsChanged = true; 
     1324                                                } 
     1325                                                else { 
     1326                                                        for (var i=0; i<creators.length; i++) { 
     1327                                                                if (!creators[i].ref.equals(linkedCreators[i].ref)) { 
     1328                                                                        Zotero.debug('changed'); 
     1329                                                                        creatorsChanged = true; 
     1330                                                                        break; 
     1331                                                                } 
     1332                                                        } 
     1333                                                } 
     1334                                                if (!creatorsChanged) { 
     1335                                                        Zotero.debug("Linked item hasn't changed -- skipping conflict resolution"); 
     1336                                                        continue; 
     1337                                                } 
     1338                                        } 
     1339                                        toReconcile.push([newItem, linkedItem]); 
     1340                                        continue; 
     1341                                        */ 
     1342                                } 
     1343                                 
     1344                                // Standalone attachment 
     1345                                if (item.isAttachment()) { 
     1346                                        var id = Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID); 
     1347                                        newIDs.push(id); 
     1348                                        continue; 
     1349                                } 
     1350                                 
     1351                                // Create new unsaved clone item in target library 
     1352                                var newItem = new Zotero.Item(item.itemTypeID); 
     1353                                newItem.libraryID = targetLibraryID; 
     1354                                // DEBUG: save here because clone() doesn't currently work on unsaved tagged items 
     1355                                var id = newItem.save(); 
     1356                                newItem = Zotero.Items.get(id); 
     1357                                item.clone(false, newItem); 
     1358                                newItem.save(); 
     1359                                //var id = newItem.save(); 
     1360                                //var newItem = Zotero.Items.get(id); 
     1361                                 
     1362                                // Record link 
     1363                                item.addLinkedItem(newItem); 
     1364                                newIDs.push(id); 
     1365                                 
     1366                                if (item.isNote()) { 
     1367                                        continue; 
     1368                                } 
     1369                                 
     1370                                // For regular items, add child items if prefs and permissions allow 
     1371                                 
     1372                                // Child notes 
     1373                                if (Zotero.Prefs.get('groups.copyChildNotes')) { 
     1374                                        var noteIDs = item.getNotes(); 
     1375                                        var notes = Zotero.Items.get(noteIDs); 
     1376                                        for each(var note in notes) { 
     1377                                                var newNote = new Zotero.Item('note'); 
     1378                                                newNote.libraryID = targetLibraryID; 
     1379                                                // DEBUG: save here because clone() doesn't currently work on unsaved tagged items 
     1380                                                var id = newNote.save(); 
     1381                                                newNote = Zotero.Items.get(id); 
     1382                                                note.clone(false, newNote); 
     1383                                                newNote.setSource(newItem.id); 
     1384                                                newNote.save(); 
     1385                                                 
     1386                                                note.addLinkedItem(newNote); 
     1387                                        } 
     1388                                } 
     1389                                 
     1390                                // Child attachments 
     1391                                var copyChildLinks = Zotero.Prefs.get('groups.copyChildLinks'); 
     1392                                var copyChildFileAttachments = Zotero.Prefs.get('groups.copyChildFileAttachments'); 
     1393                                if (copyChildLinks || copyChildFileAttachments) { 
     1394                                        var attachmentIDs = item.getAttachments(); 
     1395                                        var attachments = Zotero.Items.get(attachmentIDs); 
     1396                                        for each(var attachment in attachments) { 
     1397                                                var linkMode = attachment.attachmentLinkMode; 
     1398                                                 
     1399                                                // Skip linked files 
     1400                                                if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) { 
     1401                                                        continue; 
     1402                                                } 
     1403                                                 
     1404                                                // Skip imported files if we don't have pref and permissions 
     1405                                                if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { 
     1406                                                        if (!copyChildLinks) { 
     1407                                                                Zotero.debug("Skipping child link attachment on drag"); 
     1408                                                                continue; 
     1409                                                        } 
     1410                                                } 
     1411                                                else { 
     1412                                                        if (!copyChildFileAttachments || !itemGroup.filesEditable) { 
     1413                                                                Zotero.debug("Skipping child file attachment on drag"); 
     1414                                                                continue; 
     1415                                                        } 
     1416                                                } 
     1417                                                 
     1418                                                var id = Zotero.Attachments.copyAttachmentToLibrary(attachment, targetLibraryID, newItem.id); 
     1419                                        } 
     1420                                } 
     1421                        } 
     1422                         
     1423                        if (toReconcile.length) { 
     1424                                var sourceName = items[0].libraryID ? Zotero.Libraries.getName(items[0].libraryID) 
     1425                                                                        : Zotero.getString('pane.collections.library'); 
     1426                                var targetName = targetLibraryID ? Zotero.Libraries.getName(libraryID) 
     1427                                                                        : Zotero.getString('pane.collections.library'); 
     1428                                 
     1429                                var io = { 
     1430                                        dataIn: { 
     1431                                                type: "item", 
     1432                                                captions: [ 
     1433                                                        // TODO: localize 
     1434                                                        sourceName, 
     1435                                                        targetName, 
     1436                                                        "Merged Item" 
     1437                                                ], 
     1438                                                objects: toReconcile 
     1439                                        } 
     1440                                }; 
     1441                                 
     1442                                /* 
     1443                                if (type == 'item') { 
     1444                                        if (!Zotero.Utilities.prototype.isEmpty(changedCreators)) { 
     1445                                                io.dataIn.changedCreators = changedCreators; 
     1446                                        } 
     1447                                } 
     1448                                */ 
     1449                                 
     1450                                var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 
     1451                                                   .getService(Components.interfaces.nsIWindowMediator); 
     1452                                var lastWin = wm.getMostRecentWindow("navigator:browser"); 
     1453                                lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io); 
     1454                                 
     1455                                for each(var obj in io.dataOut) { 
     1456                                        obj.ref.save(); 
     1457                                } 
     1458                        } 
     1459                } 
     1460                 
     1461                if (newIDs.length && itemGroup.isCollection()) { 
     1462                        itemGroup.ref.addItems(newIDs); 
     1463                } 
     1464                 
     1465                Zotero.DB.commitTransaction(); 
     1466        } 
     1467        else if (dataType == 'zotero/item-xml') { 
     1468                Zotero.DB.beginTransaction(); 
     1469                var xml = new XML(data.data); 
     1470                var toAdd = []; 
     1471                for each(var xmlNode in xml.items.item) { 
     1472                        var item = Zotero.Sync.Server.Data.xmlToItem(xmlNode, false, true); 
     1473                        if (item.isRegularItem() || !item.getSource()) { 
     1474                                var id = item.save(); 
     1475                                toAdd.push(id); 
     1476                        } 
     1477                } 
     1478                if (toAdd.length > 0) { 
     1479                        this._getItemAtRow(row).ref.addItems(toAdd); 
     1480                } 
     1481                 
     1482                Zotero.DB.commitTransaction(); 
     1483                return; 
     1484        } 
     1485        else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') { 
     1486                if (itemGroup.isWithinGroup()) { 
     1487                        var targetLibraryID = itemGroup.ref.libraryID; 
     1488                } 
     1489                else { 
     1490                        var targetLibraryID = null; 
     1491                } 
     1492                 
     1493                if (itemGroup.isCollection()) { 
     1494                        var parentCollectionID = itemGroup.ref.id; 
     1495                } 
     1496                else { 
     1497                        var parentCollectionID = false; 
     1498                } 
     1499                 
     1500                var unlock = Zotero.Notifier.begin(true); 
     1501                try { 
     1502                        for (var i=0; i<data.length; i++) { 
     1503                                var file = data[i]; 
     1504                                 
     1505                                if (dataType == 'text/x-moz-url') { 
     1506                                        var url = data[i]; 
     1507                                         
     1508                                        if (url.indexOf('file:///') == 0) { 
     1509                                                var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 
     1510                                                                   .getService(Components.interfaces.nsIWindowMediator); 
     1511                                                var win = wm.getMostRecentWindow("navigator:browser"); 
     1512                                                // If dragging currently loaded page, only convert to 
     1513                                                // file if not an HTML document 
     1514                                                if (win.content.location.href != url || 
     1515                                                                win.content.document.contentType != 'text/html') { 
     1516                                                        var nsIFPH = Components.classes["@mozilla.org/network/protocol;1?name=file"] 
     1517                                                                        .getService(Components.interfaces.nsIFileProtocolHandler); 
     1518                                                        try { 
     1519                                                                var file = nsIFPH.getFileFromURLSpec(url); 
     1520                                                        } 
     1521                                                        catch (e) { 
     1522                                                                Zotero.debug(e); 
     1523                                                        } 
     1524                                                } 
     1525                                        } 
     1526                                         
     1527                                        // Still string, so remote URL 
     1528                                        if (typeof file == 'string') { 
     1529                                                var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 
     1530                                                                   .getService(Components.interfaces.nsIWindowMediator); 
     1531                                                var win = wm.getMostRecentWindow("navigator:browser"); 
     1532                                                win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack', null, row); // TODO: don't do this 
     1533                                                continue; 
     1534                                        } 
     1535                                         
     1536                                        // Otherwise file, so fall through 
     1537                                } 
     1538                                 
     1539                                try { 
     1540                                        Zotero.DB.beginTransaction(); 
     1541                                        var itemID = Zotero.Attachments.importFromFile(file, false, targetLibraryID); 
     1542                                        if (parentCollectionID) { 
     1543                                                var col = Zotero.Collections.get(parentCollectionID); 
     1544                                                if (col) { 
     1545                                                        col.addItem(itemID); 
     1546                                                } 
     1547                                        } 
     1548                                        Zotero.DB.commitTransaction(); 
     1549                                } 
     1550                                catch (e) { 
     1551                                        Zotero.DB.rollbackTransaction(); 
     1552                                        throw (e); 
     1553                                } 
     1554                        } 
     1555                } 
     1556                finally { 
     1557                        Zotero.Notifier.commit(unlock); 
     1558                } 
     1559        } 
     1560} 
     1561 
     1562 
     1563/* 
     1564 * Called by HTML 5 Drag and Drop when dragging over the tree 
     1565 */ 
     1566Zotero.CollectionTreeView.prototype.onDragEnter = function (event) { 
     1567        //Zotero.debug("Storing current drag data"); 
     1568        Zotero.DragDrop.currentDataTransfer = event.dataTransfer; 
     1569} 
     1570 
     1571 
     1572/* 
     1573 * Called by HTML 5 Drag and Drop when dragging over the tree 
     1574 */ 
     1575Zotero.CollectionTreeView.prototype.onDragOver = function (event, dropdata, session) { 
     1576        return false; 
     1577} 
     1578 
     1579 
     1580/* 
     1581 * Called by HTML 5 Drag and Drop when dropping onto the tree 
     1582 */ 
     1583Zotero.CollectionTreeView.prototype.onDrop = function (event, dropdata, session) { 
     1584        return false; 
     1585} 
     1586 
     1587Zotero.CollectionTreeView.prototype.onDragExit = function (event) { 
     1588        //Zotero.debug("Clearing drag data"); 
     1589        Zotero.DragDrop.currentDataTransfer = null; 
     1590} 
     1591 
     1592 
     1593 
     1594 
     1595//////////////////////////////////////////////////////////////////////////////// 
     1596/// 
     1597///  Functions for nsITreeView that we have to stub out. 
     1598/// 
     1599//////////////////////////////////////////////////////////////////////////////// 
     1600 
     1601Zotero.CollectionTreeView.prototype.isSorted = function()                                                       { return false; } 
     1602Zotero.CollectionTreeView.prototype.isEditable = function(row, idx)                             { return false; } 
     1603 
     1604/* Set 'highlighted' property on rows set by setHighlightedRows */ 
     1605Zotero.CollectionTreeView.prototype.getRowProperties = function(row, props) { 
     1606        if (this._highlightedRows[row]) { 
     1607                var aServ = Components.classes["@mozilla.org/atom-service;1"]. 
     1608                        getService(Components.interfaces.nsIAtomService); 
     1609                props.AppendElement(aServ.getAtom("highlighted")); 
     1610        } 
     1611} 
     1612 
     1613Zotero.CollectionTreeView.prototype.getColumnProperties = function(col, prop)           { } 
     1614Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop)        { } 
     1615Zotero.CollectionTreeView.prototype.isSeparator = function(index) { 
     1616        var source = this._getItemAtRow(index); 
     1617        return source.type == 'separator'; 
     1618} 
     1619Zotero.CollectionTreeView.prototype.performAction = function(action)                            { } 
     1620Zotero.CollectionTreeView.prototype.performActionOnCell = function(action, row, col)    { } 
     1621Zotero.CollectionTreeView.prototype.getProgressMode = function(row, col)                        { } 
     1622Zotero.CollectionTreeView.prototype.cycleHeader = function(column)                                      { } 
     1623 
     1624//////////////////////////////////////////////////////////////////////////////// 
     1625/// 
     1626///  Zotero ItemGroup -- a sort of "super class" for collection, library, 
     1627///     and saved search 
     1628/// 
     1629//////////////////////////////////////////////////////////////////////////////// 
     1630 
     1631Zotero.ItemGroup = function(type, ref) 
     1632{ 
     1633        this.type = type; 
     1634        this.ref = ref; 
     1635        this.duplicates = new Zotero.Duplicate(); 
     1636} 
     1637 
     1638Zotero.ItemGroup.prototype.isLibrary = function(includeGlobal) 
     1639{ 
     1640        if (includeGlobal) { 
     1641                return this.type == 'library' || this.type == 'group'; 
     1642        } 
     1643        return this.type == 'library'; 
     1644} 
     1645 
     1646Zotero.ItemGroup.prototype.isCollection = function() 
     1647{ 
     1648        return this.type == 'collection'; 
     1649} 
     1650 
     1651Zotero.ItemGroup.prototype.isSearch = function() 
     1652{ 
     1653        return this.type == 'search'; 
     1654} 
     1655 
     1656Zotero.ItemGroup.prototype.isShare = function() 
     1657{ 
     1658        return this.type == 'share'; 
     1659} 
     1660 
     1661Zotero.ItemGroup.prototype.isBucket = function() 
     1662{ 
     1663        return this.type == 'bucket'; 
     1664} 
     1665 
     1666Zotero.ItemGroup.prototype.isTrash = function() 
     1667{ 
     1668        return this.type == 'trash'; 
     1669} 
     1670 
     1671Zotero.ItemGroup.prototype.isGroup = function() { 
     1672        return this.type == 'group'; 
     1673} 
     1674 
     1675Zotero.ItemGroup.prototype.isHeader = function () { 
     1676        return this.type == 'header'; 
     1677} 
     1678 
     1679Zotero.ItemGroup.prototype.isSeparator = function () { 
     1680        return this.type == 'separator'; 
     1681} 
     1682 
     1683 
     1684// Special 
     1685Zotero.ItemGroup.prototype.isWithinGroup = function () { 
     1686        return this.ref && !!this.ref.libraryID; 
     1687} 
     1688 
     1689Zotero.ItemGroup.prototype.__defineGetter__('editable', function () { 
     1690        if (this.isTrash() || this.isShare() || this.isBucket()) { 
     1691                return false; 
     1692        } 
     1693        if (!this.isWithinGroup()) { 
     1694                return true; 
     1695        } 
     1696        var libraryID = this.ref.libraryID; 
     1697        if (this.isGroup()) { 
     1698                return this.ref.editable; 
     1699        } 
     1700        if (this.isCollection() || this.isSearch()) { 
     1701                var type = Zotero.Libraries.getType(libraryID); 
     1702                if (type == 'group') { 
     1703                        var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); 
     1704                        var group = Zotero.Groups.get(groupID); 
     1705                        return group.editable; 
     1706                } 
     1707                throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.editable"); 
     1708        } 
     1709        return false; 
     1710}); 
     1711 
     1712Zotero.ItemGroup.prototype.__defineGetter__('filesEditable', function () { 
     1713        if (this.isTrash() || this.isShare()) { 
     1714                return false; 
     1715        } 
     1716        if (!this.isWithinGroup()) { 
     1717                return true; 
     1718        } 
     1719        var libraryID = this.ref.libraryID; 
     1720        if (this.isGroup()) { 
     1721                return this.ref.filesEditable; 
     1722        } 
     1723        if (this.isCollection()) { 
     1724                var type = Zotero.Libraries.getType(libraryID); 
     1725                if (type == 'group') { 
     1726                        var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); 
     1727                        var group = Zotero.Groups.get(groupID); 
     1728                        return group.filesEditable; 
     1729                } 
     1730                throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.filesEditable"); 
     1731        } 
     1732        return false; 
     1733}); 
     1734 
     1735Zotero.ItemGroup.prototype.getName = function() 
     1736{ 
     1737        switch (this.type) { 
     1738                case 'collection': 
     1739                        return this.ref.name; 
     1740                 
     1741                case 'library': 
     1742                        return Zotero.getString('pane.collections.library'); 
     1743                 
     1744                case 'search': 
     1745                        return this.ref.name; 
     1746                 
     1747                case 'share': 
     1748                        return this.ref.name; 
     1749 
     1750                case 'bucket': 
     1751                        return this.ref.name; 
     1752                 
     1753                case 'trash': 
     1754                        return Zotero.getString('pane.collections.trash'); 
     1755                 
     1756                case 'group': 
     1757                        return this.ref.name; 
     1758                 
     1759                case 'header': 
     1760                        return this.ref.label; 
     1761                 
     1762                default: 
     1763                        return ""; 
     1764        } 
     1765} 
     1766 
     1767Zotero.ItemGroup.prototype.getChildItems = function(reduplicate) 
     1768{ 
     1769        switch (this.type) { 
     1770                // Fake results if this is a shared library 
     1771                case 'share': 
     1772                        return this.ref.getAll(); 
     1773 
     1774                case 'bucket': 
     1775                        return this.ref.getItems(); 
     1776                 
     1777                case 'header': 
     1778                        return []; 
     1779        } 
     1780         
     1781        var s = this.getSearchObject(); 
     1782         
     1783        // FIXME: Hack to exclude group libraries for now 
     1784        if (this.isSearch()) { 
     1785                var currentLibraryID = this.ref.libraryID; 
     1786                if (currentLibraryID) { 
     1787                        s.addCondition('libraryID', 'is', currentLibraryID); 
     1788                } 
     1789                else { 
     1790                        var groups = Zotero.Groups.getAll(); 
     1791                        for each(var group in groups) { 
     1792                                s.addCondition('libraryID', 'isNot', group.libraryID); 
     1793                        } 
     1794                } 
     1795        } 
     1796         
     1797        try { 
     1798                var ids; 
     1799                if (Zotero.Prefs.get('debugShowDuplicates')) { 
     1800                if (this.showDuplicates) { 
     1801                                // This object knows about duplicate pairings, so we need to be 
     1802                                // able to peek inside it from elsewhere. 
     1803                                if (!this.duplicates._itemIDs || reduplicate) { 
     1804                        var tmpTable = s.search(true); 
     1805                                        ids = this.duplicates.getIDs(tmpTable); 
     1806                        Zotero.DB.query("DROP TABLE " + tmpTable); 
     1807                                } else { 
     1808                                        ids = this.duplicates._itemIDs; 
     1809                } 
     1810                        } else { 
     1811                                this.duplicates.setNewIDs(); 
     1812                        ids = s.search(); 
     1813                } 
     1814                } else { 
     1815                        ids = s.search(); 
     1816        } 
     1817        } catch (e) { 
     1818                Zotero.DB.rollbackAllTransactions(); 
     1819                Zotero.debug(e, 2); 
     1820                throw (e); 
     1821        } 
     1822        return Zotero.Items.get(ids); 
     1823} 
     1824 
     1825 
     1826/* 
     1827 * Returns the search object for the currently display 
     1828 * 
     1829 * This accounts for the collection, saved search, quicksearch, tags, etc. 
     1830 */ 
     1831Zotero.ItemGroup.prototype.getSearchObject = function() { 
     1832        var includeScopeChildren = false; 
     1833         
     1834        // Create/load the inner search 
     1835        var s = new Zotero.Search(); 
     1836        if (this.isLibrary()) { 
     1837                s.addCondition('libraryID', 'is', null); 
     1838                s.addCondition('noChildren', 'true'); 
     1839                includeScopeChildren = true; 
     1840        } 
     1841        else if (this.isGroup()) { 
     1842                s.addCondition('libraryID', 'is', this.ref.libraryID); 
     1843                s.addCondition('noChildren', 'true'); 
     1844                includeScopeChildren = true; 
     1845        } 
     1846        else if (this.isCollection()) { 
     1847                s.addCondition('noChildren', 'true'); 
     1848                s.addCondition('collectionID', 'is', this.ref.id); 
     1849                if (Zotero.Prefs.get('recursiveCollections')) { 
     1850                        s.addCondition('recursive', 'true'); 
     1851                } 
     1852                includeScopeChildren = true; 
     1853        } 
     1854        else if (this.isTrash()) { 
     1855                s.addCondition('deleted', 'true'); 
     1856        } 
     1857        else if (this.isSearch()) { 
     1858                s.id = this.ref.id; 
     1859        } 
     1860        else { 
     1861                throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()'); 
     1862        } 
     1863         
     1864        // Create the outer (filter) search 
     1865        var s2 = new Zotero.Search(); 
     1866        if (this.isTrash()) { 
     1867                s2.addCondition('deleted', 'true'); 
     1868        } 
     1869        s2.setScope(s, includeScopeChildren); 
     1870         
     1871        if (this.searchText) { 
     1872                s2.addCondition('quicksearch', 'contains', this.searchText); 
     1873        } 
     1874         
     1875        if (this.tags){ 
     1876                for (var tag in this.tags){ 
     1877                        if (this.tags[tag]){ 
     1878                                s2.addCondition('tag', 'is', tag); 
     1879                        } 
     1880                } 
     1881        } 
     1882         
     1883        return s2; 
     1884} 
     1885 
     1886 
     1887/* 
     1888 * Returns all the tags used by items in the current view 
     1889 */ 
     1890Zotero.ItemGroup.prototype.getChildTags = function() { 
     1891        switch (this.type) { 
     1892                // TODO: implement? 
     1893                case 'share': 
     1894                        return false; 
     1895 
     1896                case 'bucket': 
     1897                        return false; 
     1898                 
     1899                case 'header': 
     1900                        return false; 
     1901        } 
     1902 
     1903         
     1904        var s = this.getSearchObject(); 
     1905        return Zotero.Tags.getAllWithinSearch(s); 
     1906} 
     1907 
     1908 
     1909Zotero.ItemGroup.prototype.setSearch = function(searchText) 
     1910{ 
     1911        this.searchText = searchText; 
     1912} 
     1913 
     1914Zotero.ItemGroup.prototype.setTags = function(tags) 
     1915{ 
     1916        this.tags = tags; 
     1917} 
     1918 
     1919/* 
     1920 * Returns TRUE if saved search, quicksearch or tag filter 
     1921 */ 
     1922Zotero.ItemGroup.prototype.isSearchMode = function() { 
     1923        switch (this.type) { 
     1924                case 'search': 
     1925                case 'trash': 
     1926                        return true; 
     1927        } 
     1928         
     1929        // Quicksearch 
     1930        if (this.searchText != '') { 
     1931                return true; 
     1932        } 
     1933         
     1934        // Tag filter 
     1935        if (this.tags) { 
     1936                for (var i in this.tags) { 
     1937                        return true; 
     1938                } 
     1939        } 
     1940} 
  • chrome/content/zotero/xpcom/orphan.js

    diff -u -r -N zotero-run/chrome/content/zotero/xpcom/orphan.js zotero-trunk/chrome/content/zotero/xpcom/orphan.js
    old new  
     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 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 General Public License for more details. 
     19 
     20    You should have received a copy of the GNU General Public License 
     21    along with Zotero.  If not, see <http://www.gnu.org/licenses/>. 
     22 
     23    ***** END LICENSE BLOCK ***** 
     24*/ 
     25 
     26 
     27Zotero.Orphan = function() { 
     28        this._itemIDs= false; 
     29        this._duplicateItemIDs = false; 
     30} 
     31 
     32Zotero.Orphan.prototype.__defineGetter__('id', function () { return this._id; }); 
     33 
     34Zotero.Orphan.prototype.getIDs = function(idsTable) { 
     35        var res, ret, pos, len; 
     36 
     37        ret = []; 
     38 
     39        if (!idsTable)  { 
     40                return ret; 
     41        } 
     42 
     43        var sql = "SELECT itemID FROM " + idsTable + " " 
     44                          + "WHERE NOT itemID IN (SELECT itemID FROM collectionItems) AND " 
     45                          + "NOT itemID IN (" 
     46                                  + "SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL " 
     47                                  + "UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL" 
     48                          + ")"; 
     49 
     50 
     51        res = Zotero.DB.query(sql); 
     52 
     53        for (pos = 0, len = res.length; pos < len; pos += 1) { 
     54                ret.push(res[pos].itemID); 
     55        } 
     56 
     57        return ret; 
     58} 
  • components/zotero-service.js

    diff -u -r -N zotero-run/components/zotero-service.js zotero-trunk/components/zotero-service.js
    old new  
    9191        'mime', 
    9292        'mimeTypeHandler', 
    9393        'notifier', 
     94        'orphan', 
    9495        'progressWindow', 
    9596        'proxy', 
    9697        'quickCopy', 
  • orphans.patch

    diff -u -r -N zotero-run/orphans.patch zotero-trunk/orphans.patch
    old new  
    1 diff -u -w -r -N --exclude='*.patch' zotero-trunk/chrome/content/zotero/overlay.js zotero-run/chrome/content/zotero/overlay.js 
    2 --- zotero-trunk/chrome/content/zotero/overlay.js       2010-08-07 06:05:40.000000000 +0900 
    3 +++ zotero-run/chrome/content/zotero/overlay.js 2010-08-07 09:32:38.000000000 +0900 
     1diff -u -r zotero-run/chrome/content/zotero/overlay.js zotero-trunk/chrome/content/zotero/overlay.js 
     2--- zotero-run/chrome/content/zotero/overlay.js 2010-08-08 21:12:27.000000000 +0900 
     3+++ zotero-trunk/chrome/content/zotero/overlay.js       2010-08-08 21:16:54.000000000 +0900 
    44@@ -1036,6 +1036,7 @@ 
    55                itemgroup.setSearch(''); 
    66                itemgroup.setTags(getTagSelection()); 
    77                itemgroup.showDuplicates = false; 
    88+               itemgroup.showOrphans = false; 
    9                  
     9  
    1010                try { 
    1111                        Zotero.UnresponsiveScriptIndicator.disable(); 
    1212@@ -1083,6 +1084,23 @@ 
     
    4242+                       showOrphans: 17, 
    4343+                       unshowOrphans: 18 
    4444                }; 
    45                  
     45  
    4646                var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); 
    47 @@ -2098,12 +2118,24 @@ 
     47@@ -2098,14 +2118,29 @@ 
    4848                // Group 
    4949                else if (itemGroup.isGroup()) { 
    50                         show = [m.newCollection, m.newSavedSearch]; 
    51 -                       if (itemGroup.editable) { 
    52 -                               show.push(m.sep3); 
    53                                 if (itemGroup.showDuplicates) { 
    54                                         show.push(m.unshowDuplicates); 
    55                                 } else { 
    56                                         show.push(m.showDuplicates); 
    57 +                               if (itemGroup.showOrphans) { 
    58 +                                       disable.push(m.showDuplicates); 
    59 +                               } else { 
    60 +                                       enable.push(m.showDuplicates); 
    61 +                               } 
    62 +                       } 
    63 +                       if (itemGroup.showOrphans) { 
    64 +                               show.push(m.unshowOrphans); 
    65 +                       } else { 
    66 +                               show.push(m.showOrphans); 
    67 +                               if (itemGroup.showDuplicates) { 
    68 +                                       disable.push(m.showOrphans); 
    69 +                               } else { 
    70 +                                       enable.push(m.showOrphans); 
    71                 } 
     50                        if (itemGroup.editable) { 
     51-                       show = [m.newCollection, m.newSavedSearch]; 
     52+                               show = [m.newCollection, m.newSavedSearch]; 
     53                                if (Zotero.Prefs.get('debugShowDuplicates')) { 
     54                                        show.push(m.sep3); 
     55                                        if (itemGroup.showDuplicates) { 
     56                                                show.push(m.unshowDuplicates); 
     57                                        } else { 
     58                                                show.push(m.showDuplicates); 
     59-               } 
     60+                                               if (itemGroup.showOrphans) { 
     61+                                                       disable.push(m.showDuplicates); 
     62+                                               } else { 
     63+                                                       enable.push(m.showDuplicates); 
     64+                                               } 
     65+                                       } 
     66+                                       if (itemGroup.showOrphans) { 
     67+                                               show.push(m.unshowOrphans); 
     68+                                       } else { 
     69+                                               show.push(m.showOrphans); 
     70+                                               if (itemGroup.showDuplicates) { 
     71+                                                       disable.push(m.showOrphans); 
     72+                                               } else { 
     73+                                                       enable.push(m.showOrphans); 
     74+                                               } 
     75+                                       } 
     76                                } 
    7277                        } 
    7378                } 
    74 @@ -2115,6 +2147,21 @@ 
     79@@ -2119,6 +2154,21 @@ 
    7580                                show.push(m.unshowDuplicates); 
    7681                        } else { 
    7782                                show.push(m.showDuplicates); 
     
    9297+                               } 
    9398                } 
    9499                } 
    95                  
    96 diff -u -w -r -N --exclude='*.patch' zotero-trunk/chrome/content/zotero/overlay.xul zotero-run/chrome/content/zotero/overlay.xul 
    97 --- zotero-trunk/chrome/content/zotero/overlay.xul      2010-08-07 06:05:40.000000000 +0900 
    98 +++ zotero-run/chrome/content/zotero/overlay.xul        2010-08-07 06:14:27.000000000 +0900 
     100                } 
     101Only in zotero-trunk/chrome/content/zotero: overlay.js.orig 
     102diff -u -r zotero-run/chrome/content/zotero/overlay.xul zotero-trunk/chrome/content/zotero/overlay.xul 
     103--- zotero-run/chrome/content/zotero/overlay.xul        2010-08-08 21:12:27.000000000 +0900 
     104+++ zotero-trunk/chrome/content/zotero/overlay.xul      2010-08-08 21:13:50.000000000 +0900 
    99105@@ -109,10 +109,11 @@ 
    100106                                        <menuitem label="&zotero.toolbar.emptyTrash.label;" oncommand="ZoteroPane.emptyTrash();"/> 
    101107                                        <menuitem label="&zotero.toolbar.newCollection.label;" oncommand="ZoteroPane.createCommonsBucket();"/><!--TODO localize --> 
     
    109115                                </popup> 
    110116                                <popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();"> 
    111117                                        <menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/> 
    112 diff -u -w -r -N --exclude='*.patch' zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js 
    113 --- zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js      2010-08-07 06:05:40.000000000 +0900 
    114 +++ zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js        2010-08-07 09:36:46.000000000 +0900 
     118diff -u -r zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js 
     119--- zotero-run/chrome/content/zotero/xpcom/collectionTreeView.js        2010-08-08 21:12:27.000000000 +0900 
     120+++ zotero-trunk/chrome/content/zotero/xpcom/collectionTreeView.js      2010-08-08 21:13:50.000000000 +0900 
    115121@@ -41,6 +41,7 @@ 
    116122        this._highlightedRows = {}; 
    117123        this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']); 
     
    128134 } 
    129135  
    130136 Zotero.ItemGroup.prototype.isLibrary = function(includeGlobal) 
    131 @@ -1808,6 +1810,10 @@ 
     137@@ -1807,6 +1809,10 @@ 
    132138                                } else { 
    133139                                        ids = this.duplicates._itemIDs; 
    134140                } 
     
    139145                        } else { 
    140146                                this.duplicates.setNewIDs(); 
    141147                        ids = s.search(); 
    142 diff -u -w -r -N --exclude='*.patch' zotero-trunk/chrome/content/zotero/xpcom/orphan.js zotero-run/chrome/content/zotero/xpcom/orphan.js 
    143 --- zotero-trunk/chrome/content/zotero/xpcom/orphan.js  1970-01-01 09:00:00.000000000 +0900 
    144 +++ zotero-run/chrome/content/zotero/xpcom/orphan.js    2010-08-07 06:06:08.000000000 +0900 
    145 @@ -0,0 +1,58 @@ 
    146 +/* 
    147 +    ***** BEGIN LICENSE BLOCK ***** 
    148 + 
    149 +    Copyright © 2009 Center for History and New Media 
    150 +                     George Mason University, Fairfax, Virginia, USA 
    151 +                     http://zotero.org 
    152 + 
    153 +    This file is part of Zotero. 
    154 + 
    155 +    Zotero is free software: you can redistribute it and/or modify 
    156 +    it under the terms of the GNU General Public License as published by 
    157 +    the Free Software Foundation, either version 3 of the License, or 
    158 +    (at your option) any later version. 
    159 + 
    160 +    Zotero is distributed in the hope that it will be useful, 
    161 +    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    162 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    163 +    GNU General Public License for more details. 
    164 + 
    165 +    You should have received a copy of the GNU General Public License 
    166 +    along with Zotero.  If not, see <http://www.gnu.org/licenses/>. 
    167 + 
    168 +    ***** END LICENSE BLOCK ***** 
    169 +*/ 
    170 + 
    171 + 
    172 +Zotero.Orphan = function() { 
    173 +       this._itemIDs= false; 
    174 +       this._duplicateItemIDs = false; 
    175 +} 
    176 + 
    177 +Zotero.Orphan.prototype.__defineGetter__('id', function () { return this._id; }); 
    178 + 
    179 +Zotero.Orphan.prototype.getIDs = function(idsTable) { 
    180 +       var res, ret, pos, len; 
    181 + 
    182 +       ret = []; 
    183 + 
    184 +       if (!idsTable)  { 
    185 +               return ret; 
    186 +       } 
    187 + 
    188 +       var sql = "SELECT itemID FROM " + idsTable + " " 
    189 +                         + "WHERE NOT itemID IN (SELECT itemID FROM collectionItems) AND " 
    190 +                         + "NOT itemID IN (" 
    191 +                                 + "SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL " 
    192 +                                 + "UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL" 
    193 +                         + ")"; 
    194 + 
    195 + 
    196 +       res = Zotero.DB.query(sql); 
    197 + 
    198 +       for (pos = 0, len = res.length; pos < len; pos += 1) { 
    199 +               ret.push(res[pos].itemID); 
    200 +       } 
    201 + 
    202 +       return ret; 
    203 +} 
    204 diff -u -w -r -N --exclude='*.patch' zotero-trunk/components/zotero-service.js zotero-run/components/zotero-service.js 
    205 --- zotero-trunk/components/zotero-service.js   2010-08-07 06:05:08.000000000 +0900 
    206 +++ zotero-run/components/zotero-service.js     2010-08-07 06:14:27.000000000 +0900 
     148Only in zotero-trunk/chrome/content/zotero/xpcom: collectionTreeView.js.orig 
     149Only in zotero-trunk/chrome/content/zotero/xpcom: orphan.js 
     150diff -u -r zotero-run/components/zotero-service.js zotero-trunk/components/zotero-service.js 
     151--- zotero-run/components/zotero-service.js     2010-08-08 21:12:27.000000000 +0900 
     152+++ zotero-trunk/components/zotero-service.js   2010-08-08 21:13:50.000000000 +0900 
    207153@@ -91,6 +91,7 @@ 
    208154        'mime', 
    209155        'mimeTypeHandler',