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();
// 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()');

View file

@ -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: {

View file

@ -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

View file

@ -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/>

View file

@ -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">

View file

@ -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">

View file

@ -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