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:
Dan Stillman 2011-02-14 03:59:32 +00:00
parent 1908001230
commit 1e60f8947a
7 changed files with 228 additions and 99 deletions

View file

@ -74,76 +74,13 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox)
this.refresh(); 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.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 * Reload the rows from the data access methods
* (doesn't call the tree.invalidate methods, etc.) * (doesn't call the tree.invalidate methods, etc.)
@ -165,6 +102,13 @@ Zotero.CollectionTreeView.prototype.refresh = function()
this._dataItems = []; this._dataItems = [];
this.rowCount = 0; this.rowCount = 0;
try {
var unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(',');
}
catch (e) {
unfiledLibraries = [];
}
var self = this; var self = this;
var library = { var library = {
id: null, 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(); var deletedItems = Zotero.Items.getDeleted();
if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) { if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) {
self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1); self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1);
@ -236,6 +192,18 @@ Zotero.CollectionTreeView.prototype.refresh = function()
newRows++; 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); this._showItem(new Zotero.ItemGroup('header', header), null, null, commonsExpand);
if (commonsExpand) { if (commonsExpand) {
header.expand(); header.expand();
@ -781,6 +747,78 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
return false; 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 * Delete the selection
@ -1790,6 +1828,7 @@ Zotero.ItemGroup.prototype.getChildItems = function()
Zotero.debug(e, 2); Zotero.debug(e, 2);
throw (e); throw (e);
} }
return Zotero.Items.get(ids); return Zotero.Items.get(ids);
} }
@ -1826,7 +1865,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
s.addCondition('deleted', 'true'); s.addCondition('deleted', 'true');
} }
else if (this.isSearch()) { else if (this.isSearch()) {
s.id = this.ref.id; var s = this.ref;
} }
else { else {
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()'); throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');

View file

@ -1015,6 +1015,11 @@ Zotero.Search.prototype._buildQuery = function(){
var includeChildren = this._conditions[i]['operator'] == 'true'; var includeChildren = this._conditions[i]['operator'] == 'true';
continue; continue;
case 'unfiled':
this._conditions[i]['operator']
var unfiled = this._conditions[i]['operator'] == 'true';
continue;
// Search subfolders // Search subfolders
case 'recursive': case 'recursive':
var recursive = this._conditions[i]['operator']=='true'; var recursive = this._conditions[i]['operator']=='true';
@ -1063,6 +1068,15 @@ Zotero.Search.prototype._buildQuery = function(){
+ "WHERE sourceItemID IS NOT NULL))"; + "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) { if (this._hasPrimaryConditions) {
sql += " AND "; sql += " AND ";
@ -1753,6 +1767,14 @@ Zotero.SearchConditions = new function(){
} }
}, },
{
name: 'unfiled',
operators: {
true: true,
false: true
}
},
{ {
name: 'includeParentsAndChildren', name: 'includeParentsAndChildren',
operators: { operators: {
@ -2071,7 +2093,6 @@ Zotero.SearchConditions = new function(){
special: true special: true
}, },
{ {
name: 'fulltextContent', name: 'fulltextContent',
operators: { operators: {

View file

@ -56,7 +56,6 @@ var ZoteroPane = new function()
this.itemSelected = itemSelected; this.itemSelected = itemSelected;
this.reindexItem = reindexItem; this.reindexItem = reindexItem;
this.duplicateSelectedItem = duplicateSelectedItem; this.duplicateSelectedItem = duplicateSelectedItem;
this.deleteSelectedCollection = deleteSelectedCollection;
this.editSelectedCollection = editSelectedCollection; this.editSelectedCollection = editSelectedCollection;
this.copySelectedItemsToClipboard = copySelectedItemsToClipboard; this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
this.clearQuicksearch = clearQuicksearch; this.clearQuicksearch = clearQuicksearch;
@ -714,6 +713,55 @@ var ZoteroPane = new function()
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io); 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 () { this.openLookupWindow = function () {
if (!Zotero.stateCheck()) { 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()) { if (!this.canEdit()) {
this.displayCannotEditLibraryMessage(); this.displayCannotEditLibraryMessage();
return; return;
} }
if (this.collectionsView.selection.count == 1) { if (this.collectionsView.selection.count == 1) {
var row =
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
if (row.isCollection()) if (row.isCollection())
{ {
if (confirm(Zotero.getString('pane.collections.delete'))) if (confirm(Zotero.getString('pane.collections.delete')))
@ -1797,16 +1848,17 @@ var ZoteroPane = new function()
newSavedSearch: 1, newSavedSearch: 1,
newSubcollection: 2, newSubcollection: 2,
sep1: 3, sep1: 3,
editSelectedCollection: 4, showUnfiled: 4,
removeCollection: 5, editSelectedCollection: 5,
sep2: 6, removeCollection: 6,
exportCollection: 7, sep2: 7,
createBibCollection: 8, exportCollection: 8,
exportFile: 9, createBibCollection: 9,
loadReport: 10, exportFile: 10,
emptyTrash: 11, loadReport: 11,
createCommonsBucket: 12, emptyTrash: 12,
refreshCommonsBucket: 13 createCommonsBucket: 13,
refreshCommonsBucket: 14
}; };
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
@ -1846,14 +1898,27 @@ var ZoteroPane = new function()
} }
// Saved Search // Saved Search
else if (itemGroup.isSearch()) { else if (itemGroup.isSearch()) {
show = [ // Unfiled items view
m.editSelectedCollection, if ((itemGroup.ref.id + "").match(/^8634533000/)) { // 'UNFILED000'
m.removeCollection, show = [
m.sep2, m.removeCollection
m.exportCollection, ];
m.createBibCollection,
m.loadReport 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]; var s = [m.exportCollection, m.createBibCollection, m.loadReport];
if (this.itemsView.rowCount>0) { if (this.itemsView.rowCount>0) {
@ -1865,7 +1930,6 @@ var ZoteroPane = new function()
// Adjust labels // Adjust labels
menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch')); 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.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.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch'));
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch')); menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
@ -1884,12 +1948,12 @@ var ZoteroPane = new function()
} }
// Group // Group
else if (itemGroup.isGroup()) { else if (itemGroup.isGroup()) {
show = [m.newCollection, m.newSavedSearch]; show = [m.newCollection, m.newSavedSearch, m.sep1, m.showUnfiled];
} }
// Library // Library
else 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 // Disable some actions if user doesn't have write access

View file

@ -216,6 +216,7 @@
<menuitem label="&zotero.toolbar.newSavedSearch.label;" oncommand="ZoteroPane.newSearch()"/> <menuitem label="&zotero.toolbar.newSavedSearch.label;" oncommand="ZoteroPane.newSearch()"/>
<menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane.newCollection(ZoteroPane.getSelectedCollection().id)"/> <menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane.newCollection(ZoteroPane.getSelectedCollection().id)"/>
<menuseparator/> <menuseparator/>
<menuitem label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane.setUnfiled(ZoteroPane.getSelectedLibraryID(), true)"/>
<menuitem oncommand="ZoteroPane.editSelectedCollection();"/> <menuitem oncommand="ZoteroPane.editSelectedCollection();"/>
<menuitem oncommand="ZoteroPane.deleteSelectedCollection();"/> <menuitem oncommand="ZoteroPane.deleteSelectedCollection();"/>
<menuseparator/> <menuseparator/>

View file

@ -6,7 +6,7 @@
<!ENTITY zotero.search.joinMode.suffix "of the following:"> <!ENTITY zotero.search.joinMode.suffix "of the following:">
<!ENTITY zotero.search.recursive.label "Search subfolders"> <!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.includeParentsAndChildren "Include parent and child items of matching items">
<!ENTITY zotero.search.textModes.phrase "Phrase"> <!ENTITY zotero.search.textModes.phrase "Phrase">

View file

@ -37,6 +37,8 @@
<!ENTITY zotero.tabs.related.label "Related"> <!ENTITY zotero.tabs.related.label "Related">
<!ENTITY zotero.notes.separate "Edit in a separate window"> <!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.itemType "Item Type">
<!ENTITY zotero.items.type_column "Type"> <!ENTITY zotero.items.type_column "Type">
<!ENTITY zotero.items.title_column "Title"> <!ENTITY zotero.items.title_column "Title">

View file

@ -33,6 +33,7 @@ general.create = Create
general.seeForMoreInformation = See %S for more information. general.seeForMoreInformation = See %S for more information.
general.enable = Enable general.enable = Enable
general.disable = Disable general.disable = Disable
general.remove = Remove
general.operationInProgress = A Zotero operation is currently in progress. general.operationInProgress = A Zotero operation is currently in progress.
general.operationInProgress.waitUntilFinished = Please wait until it has finished. 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.library = My Library
pane.collections.trash = Trash pane.collections.trash = Trash
pane.collections.untitled = Untitled pane.collections.untitled = Untitled
pane.collections.unfiled = Unfiled Items
pane.collections.menu.rename.collection = Rename Collection... pane.collections.menu.rename.collection = Rename Collection...
pane.collections.menu.edit.savedSearch = Edit Saved Search pane.collections.menu.edit.savedSearch = Edit Saved Search