Closes #745, Add Unfiled search condition
Adds "Show Unfiled Items" context menu to libraries, which adds a virtual saved search for unfiled items. Right-click, Remove to hide. Per-library visibility is saved in prefs and persists across restarts. Implemented as 'unfiled' search condition, but not accessible via search UI Should probably use a different color icon to differentiate from real saved searches
This commit is contained in:
parent
1908001230
commit
1e60f8947a
7 changed files with 228 additions and 99 deletions
|
@ -74,76 +74,13 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox)
|
|||
|
||||
this.refresh();
|
||||
|
||||
// Select the last-viewed collection
|
||||
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
|
||||
var matches = lastViewedFolder.match(/^(?:(C|S|G)([0-9]+)|L)$/);
|
||||
var select = 0;
|
||||
if (matches) {
|
||||
if (matches[1] == 'C') {
|
||||
if (this._collectionRowMap[matches[2]]) {
|
||||
select = this._collectionRowMap[matches[2]];
|
||||
}
|
||||
// Search recursively
|
||||
else {
|
||||
var path = [];
|
||||
var failsafe = 10; // Only go up ten levels
|
||||
var lastCol = matches[2];
|
||||
do {
|
||||
failsafe--;
|
||||
var col = Zotero.Collections.get(lastCol);
|
||||
if (!col) {
|
||||
var msg = "Last-viewed collection not found";
|
||||
Zotero.debug(msg);
|
||||
path = [];
|
||||
break;
|
||||
}
|
||||
var par = col.getParent();
|
||||
if (!par) {
|
||||
var msg = "Parent collection not found in "
|
||||
+ "Zotero.CollectionTreeView.setTree()";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
path = [];
|
||||
break;
|
||||
}
|
||||
lastCol = par;
|
||||
path.push(lastCol);
|
||||
}
|
||||
while (!this._collectionRowMap[lastCol] && failsafe > 0)
|
||||
if (path.length) {
|
||||
for (var i=path.length-1; i>=0; i--) {
|
||||
var id = path[i];
|
||||
var row = this._collectionRowMap[id];
|
||||
if (!row) {
|
||||
var msg = "Collection not found in tree in "
|
||||
+ "Zotero.CollectionTreeView.setTree()";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
break;
|
||||
}
|
||||
if (!this.isContainerOpen(row)) {
|
||||
this.toggleOpenState(row);
|
||||
if (this._collectionRowMap[matches[2]]) {
|
||||
select = this._collectionRowMap[matches[2]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (matches[1] == 'S' && this._searchRowMap[matches[2]]) {
|
||||
select = this._searchRowMap[matches[2]];
|
||||
}
|
||||
else if (matches[1] == 'G' && this._groupRowMap[matches[2]]) {
|
||||
select = this._groupRowMap[matches[2]];
|
||||
}
|
||||
}
|
||||
|
||||
this.selection.currentColumn = this._treebox.columns.getFirstColumn();
|
||||
this.selection.select(select);
|
||||
|
||||
var row = this.getLastViewedRow();
|
||||
this.selection.select(row);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Reload the rows from the data access methods
|
||||
* (doesn't call the tree.invalidate methods, etc.)
|
||||
|
@ -165,6 +102,13 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
this._dataItems = [];
|
||||
this.rowCount = 0;
|
||||
|
||||
try {
|
||||
var unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(',');
|
||||
}
|
||||
catch (e) {
|
||||
unfiledLibraries = [];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var library = {
|
||||
id: null,
|
||||
|
@ -190,6 +134,18 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
|
||||
// Unfiled items
|
||||
if (unfiledLibraries.indexOf('0') != -1) {
|
||||
var s = new Zotero.Search;
|
||||
// Give virtual search an id so it can be reselected automatically
|
||||
s.id = 86345330000; // 'UNFILED' + '000' + libraryID
|
||||
s.name = Zotero.getString('pane.collections.unfiled');
|
||||
s.addCondition('libraryID', 'is', null);
|
||||
s.addCondition('unfiled', 'true');
|
||||
self._showItem(new Zotero.ItemGroup('search', s), 1, newRows+1);
|
||||
newRows++;
|
||||
}
|
||||
|
||||
var deletedItems = Zotero.Items.getDeleted();
|
||||
if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) {
|
||||
self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1);
|
||||
|
@ -236,6 +192,18 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
newRows++;
|
||||
}
|
||||
}
|
||||
|
||||
// Unfiled items
|
||||
if (unfiledLibraries.indexOf(groups[i].libraryID + '') != -1) {
|
||||
var s = new Zotero.Search;
|
||||
s.id = parseInt('8634533000' + groups[i].libraryID); // 'UNFILED' + '000' + libraryID
|
||||
s.libraryID = groups[i].libraryID;
|
||||
s.name = Zotero.getString('pane.collections.unfiled');
|
||||
s.addCondition('libraryID', 'is', groups[i].libraryID);
|
||||
s.addCondition('unfiled', 'true');
|
||||
self._showItem(new Zotero.ItemGroup('search', s), 2);
|
||||
newRows++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -270,8 +238,6 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
};
|
||||
Zotero.debug('=============');
|
||||
Zotero.debug(commonsExpand);
|
||||
this._showItem(new Zotero.ItemGroup('header', header), null, null, commonsExpand);
|
||||
if (commonsExpand) {
|
||||
header.expand();
|
||||
|
@ -781,6 +747,78 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the last-viewed source
|
||||
*/
|
||||
Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
|
||||
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
|
||||
var matches = lastViewedFolder.match(/^(?:(C|S|G)([0-9]+)|L)$/);
|
||||
var select = 0;
|
||||
if (matches) {
|
||||
if (matches[1] == 'C') {
|
||||
if (this._collectionRowMap[matches[2]]) {
|
||||
select = this._collectionRowMap[matches[2]];
|
||||
}
|
||||
// Search recursively
|
||||
else {
|
||||
var path = [];
|
||||
var failsafe = 10; // Only go up ten levels
|
||||
var lastCol = matches[2];
|
||||
do {
|
||||
failsafe--;
|
||||
var col = Zotero.Collections.get(lastCol);
|
||||
if (!col) {
|
||||
var msg = "Last-viewed collection not found";
|
||||
Zotero.debug(msg);
|
||||
path = [];
|
||||
break;
|
||||
}
|
||||
var par = col.getParent();
|
||||
if (!par) {
|
||||
var msg = "Parent collection not found in "
|
||||
+ "Zotero.CollectionTreeView.setTree()";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
path = [];
|
||||
break;
|
||||
}
|
||||
lastCol = par;
|
||||
path.push(lastCol);
|
||||
}
|
||||
while (!this._collectionRowMap[lastCol] && failsafe > 0)
|
||||
if (path.length) {
|
||||
for (var i=path.length-1; i>=0; i--) {
|
||||
var id = path[i];
|
||||
var row = this._collectionRowMap[id];
|
||||
if (!row) {
|
||||
var msg = "Collection not found in tree in "
|
||||
+ "Zotero.CollectionTreeView.setTree()";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
break;
|
||||
}
|
||||
if (!this.isContainerOpen(row)) {
|
||||
this.toggleOpenState(row);
|
||||
if (this._collectionRowMap[matches[2]]) {
|
||||
select = this._collectionRowMap[matches[2]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (matches[1] == 'S' && this._searchRowMap[matches[2]]) {
|
||||
select = this._searchRowMap[matches[2]];
|
||||
}
|
||||
else if (matches[1] == 'G' && this._groupRowMap[matches[2]]) {
|
||||
select = this._groupRowMap[matches[2]];
|
||||
}
|
||||
}
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Delete the selection
|
||||
|
@ -1790,6 +1828,7 @@ Zotero.ItemGroup.prototype.getChildItems = function()
|
|||
Zotero.debug(e, 2);
|
||||
throw (e);
|
||||
}
|
||||
|
||||
return Zotero.Items.get(ids);
|
||||
}
|
||||
|
||||
|
@ -1826,7 +1865,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
|
|||
s.addCondition('deleted', 'true');
|
||||
}
|
||||
else if (this.isSearch()) {
|
||||
s.id = this.ref.id;
|
||||
var s = this.ref;
|
||||
}
|
||||
else {
|
||||
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
|
||||
|
|
|
@ -1015,6 +1015,11 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
var includeChildren = this._conditions[i]['operator'] == 'true';
|
||||
continue;
|
||||
|
||||
case 'unfiled':
|
||||
this._conditions[i]['operator']
|
||||
var unfiled = this._conditions[i]['operator'] == 'true';
|
||||
continue;
|
||||
|
||||
// Search subfolders
|
||||
case 'recursive':
|
||||
var recursive = this._conditions[i]['operator']=='true';
|
||||
|
@ -1063,6 +1068,15 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
+ "WHERE sourceItemID IS NOT NULL))";
|
||||
}
|
||||
|
||||
if (unfiled) {
|
||||
sql += " AND (itemID NOT IN (SELECT itemID FROM collectionItems) "
|
||||
// Exclude children
|
||||
+ "AND itemID NOT IN "
|
||||
+ "(SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL "
|
||||
+ "UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL)"
|
||||
+ ")";
|
||||
}
|
||||
|
||||
if (this._hasPrimaryConditions) {
|
||||
sql += " AND ";
|
||||
|
||||
|
@ -1753,6 +1767,14 @@ Zotero.SearchConditions = new function(){
|
|||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'unfiled',
|
||||
operators: {
|
||||
true: true,
|
||||
false: true
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'includeParentsAndChildren',
|
||||
operators: {
|
||||
|
@ -2071,7 +2093,6 @@ Zotero.SearchConditions = new function(){
|
|||
special: true
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
name: 'fulltextContent',
|
||||
operators: {
|
||||
|
|
|
@ -56,7 +56,6 @@ var ZoteroPane = new function()
|
|||
this.itemSelected = itemSelected;
|
||||
this.reindexItem = reindexItem;
|
||||
this.duplicateSelectedItem = duplicateSelectedItem;
|
||||
this.deleteSelectedCollection = deleteSelectedCollection;
|
||||
this.editSelectedCollection = editSelectedCollection;
|
||||
this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
|
||||
this.clearQuicksearch = clearQuicksearch;
|
||||
|
@ -714,6 +713,55 @@ var ZoteroPane = new function()
|
|||
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
||||
}
|
||||
|
||||
this.setUnfiled = function (libraryID, show) {
|
||||
try {
|
||||
var ids = Zotero.Prefs.get('unfiledLibraries').split(',');
|
||||
}
|
||||
catch (e) {
|
||||
var ids = [];
|
||||
}
|
||||
|
||||
if (!libraryID) {
|
||||
libraryID = 0;
|
||||
}
|
||||
|
||||
var newids = [];
|
||||
for each(var id in ids) {
|
||||
id = parseInt(id);
|
||||
if (isNaN(id)) {
|
||||
continue;
|
||||
}
|
||||
// Remove current library if hiding
|
||||
if (id == libraryID && !show) {
|
||||
continue;
|
||||
}
|
||||
// Remove libraryIDs that no longer exist
|
||||
if (id != 0 && !Zotero.Libraries.exists(id)) {
|
||||
continue;
|
||||
}
|
||||
newids.push(id);
|
||||
}
|
||||
|
||||
// Add the current library if it's not already set
|
||||
if (show && newids.indexOf(libraryID) == -1) {
|
||||
newids.push(libraryID);
|
||||
}
|
||||
|
||||
newids.sort();
|
||||
|
||||
Zotero.Prefs.set('unfiledLibraries', newids.join());
|
||||
|
||||
if (show) {
|
||||
// 'UNFILED' + '000' + libraryID
|
||||
Zotero.Prefs.set('lastViewedFolder', 'S' + '8634533000' + libraryID);
|
||||
}
|
||||
|
||||
this.collectionsView.refresh();
|
||||
|
||||
// Select new row
|
||||
var row = this.collectionsView.getLastViewedRow();
|
||||
this.collectionsView.selection.select(row);
|
||||
}
|
||||
|
||||
this.openLookupWindow = function () {
|
||||
if (!Zotero.stateCheck()) {
|
||||
|
@ -1306,17 +1354,20 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
|
||||
function deleteSelectedCollection()
|
||||
{
|
||||
this.deleteSelectedCollection = function () {
|
||||
// Remove virtual Unfiled search
|
||||
var row = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
|
||||
if (row.isSearch() && (row.ref.id + "").match(/^8634533000/)) { // 'UNFILED000'
|
||||
this.setUnfiled(row.ref.libraryID, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.collectionsView.selection.count == 1) {
|
||||
var row =
|
||||
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
|
||||
|
||||
if (row.isCollection())
|
||||
{
|
||||
if (confirm(Zotero.getString('pane.collections.delete')))
|
||||
|
@ -1797,16 +1848,17 @@ var ZoteroPane = new function()
|
|||
newSavedSearch: 1,
|
||||
newSubcollection: 2,
|
||||
sep1: 3,
|
||||
editSelectedCollection: 4,
|
||||
removeCollection: 5,
|
||||
sep2: 6,
|
||||
exportCollection: 7,
|
||||
createBibCollection: 8,
|
||||
exportFile: 9,
|
||||
loadReport: 10,
|
||||
emptyTrash: 11,
|
||||
createCommonsBucket: 12,
|
||||
refreshCommonsBucket: 13
|
||||
showUnfiled: 4,
|
||||
editSelectedCollection: 5,
|
||||
removeCollection: 6,
|
||||
sep2: 7,
|
||||
exportCollection: 8,
|
||||
createBibCollection: 9,
|
||||
exportFile: 10,
|
||||
loadReport: 11,
|
||||
emptyTrash: 12,
|
||||
createCommonsBucket: 13,
|
||||
refreshCommonsBucket: 14
|
||||
};
|
||||
|
||||
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
|
||||
|
@ -1846,14 +1898,27 @@ var ZoteroPane = new function()
|
|||
}
|
||||
// Saved Search
|
||||
else if (itemGroup.isSearch()) {
|
||||
show = [
|
||||
m.editSelectedCollection,
|
||||
m.removeCollection,
|
||||
m.sep2,
|
||||
m.exportCollection,
|
||||
m.createBibCollection,
|
||||
m.loadReport
|
||||
];
|
||||
// Unfiled items view
|
||||
if ((itemGroup.ref.id + "").match(/^8634533000/)) { // 'UNFILED000'
|
||||
show = [
|
||||
m.removeCollection
|
||||
];
|
||||
|
||||
menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('general.remove'));
|
||||
}
|
||||
// Normal search view
|
||||
else {
|
||||
show = [
|
||||
m.editSelectedCollection,
|
||||
m.removeCollection,
|
||||
m.sep2,
|
||||
m.exportCollection,
|
||||
m.createBibCollection,
|
||||
m.loadReport
|
||||
];
|
||||
|
||||
menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch'));
|
||||
}
|
||||
|
||||
var s = [m.exportCollection, m.createBibCollection, m.loadReport];
|
||||
if (this.itemsView.rowCount>0) {
|
||||
|
@ -1865,7 +1930,6 @@ var ZoteroPane = new function()
|
|||
|
||||
// Adjust labels
|
||||
menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch'));
|
||||
menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch'));
|
||||
menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.savedSearch'));
|
||||
menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch'));
|
||||
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
|
||||
|
@ -1884,12 +1948,12 @@ var ZoteroPane = new function()
|
|||
}
|
||||
// Group
|
||||
else if (itemGroup.isGroup()) {
|
||||
show = [m.newCollection, m.newSavedSearch];
|
||||
show = [m.newCollection, m.newSavedSearch, m.sep1, m.showUnfiled];
|
||||
}
|
||||
// Library
|
||||
else
|
||||
{
|
||||
show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile];
|
||||
show = [m.newCollection, m.newSavedSearch, m.sep1, m.showUnfiled, m.sep2, m.exportFile];
|
||||
}
|
||||
|
||||
// Disable some actions if user doesn't have write access
|
||||
|
|
|
@ -216,6 +216,7 @@
|
|||
<menuitem label="&zotero.toolbar.newSavedSearch.label;" oncommand="ZoteroPane.newSearch()"/>
|
||||
<menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane.newCollection(ZoteroPane.getSelectedCollection().id)"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane.setUnfiled(ZoteroPane.getSelectedLibraryID(), true)"/>
|
||||
<menuitem oncommand="ZoteroPane.editSelectedCollection();"/>
|
||||
<menuitem oncommand="ZoteroPane.deleteSelectedCollection();"/>
|
||||
<menuseparator/>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!ENTITY zotero.search.joinMode.suffix "of the following:">
|
||||
|
||||
<!ENTITY zotero.search.recursive.label "Search subfolders">
|
||||
<!ENTITY zotero.search.noChildren "Only show top-level items">
|
||||
<!ENTITY zotero.search.noChildren "Show only top-level items">
|
||||
<!ENTITY zotero.search.includeParentsAndChildren "Include parent and child items of matching items">
|
||||
|
||||
<!ENTITY zotero.search.textModes.phrase "Phrase">
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
<!ENTITY zotero.tabs.related.label "Related">
|
||||
<!ENTITY zotero.notes.separate "Edit in a separate window">
|
||||
|
||||
<!ENTITY zotero.collections.showUnfiledItems "Show Unfiled Items">
|
||||
|
||||
<!ENTITY zotero.items.itemType "Item Type">
|
||||
<!ENTITY zotero.items.type_column "Type">
|
||||
<!ENTITY zotero.items.title_column "Title">
|
||||
|
|
|
@ -33,6 +33,7 @@ general.create = Create
|
|||
general.seeForMoreInformation = See %S for more information.
|
||||
general.enable = Enable
|
||||
general.disable = Disable
|
||||
general.remove = Remove
|
||||
|
||||
general.operationInProgress = A Zotero operation is currently in progress.
|
||||
general.operationInProgress.waitUntilFinished = Please wait until it has finished.
|
||||
|
@ -108,6 +109,7 @@ pane.collections.rename = Rename collection:
|
|||
pane.collections.library = My Library
|
||||
pane.collections.trash = Trash
|
||||
pane.collections.untitled = Untitled
|
||||
pane.collections.unfiled = Unfiled Items
|
||||
|
||||
pane.collections.menu.rename.collection = Rename Collection...
|
||||
pane.collections.menu.edit.savedSearch = Edit Saved Search
|
||||
|
|
Loading…
Add table
Reference in a new issue