Fixes #616, Tags don't appear in tag selector for child items

Also, child items in search results now always display in context, with the non-matching rows in gray. May later implement Elena's idea to show an ellipsis to toggle non-matching items.

New method Search.setScope(searchObj) to pass one search as the scope for another -- used to make quicksearch and tag selector properly search within the current view.

Note: These changes need testing -- I may not have gotten all the various conditions (view mode, quicksearch, tag selector, advanced search, drag-and-drop) exactly right.
This commit is contained in:
Dan Stillman 2007-04-16 04:35:56 +00:00
parent c6f4a0d023
commit 0dbf98ccf4
6 changed files with 199 additions and 57 deletions

View File

@ -27,6 +27,7 @@ var ZoteroAdvancedSearch = new function() {
// A minimal implementation of Zotero.CollectionTreeView
var itemGroup = {
isSearchMode: function() { return true; },
getChildItems: function () {
var ids = _searchBox.search.search();
return Zotero.Items.get(ids);

View File

@ -834,51 +834,42 @@ Zotero.ItemGroup.prototype.getChildItems = function()
* This accounts for the collection, saved search, quicksearch, tags, etc.
*/
Zotero.ItemGroup.prototype.getSearchObject = function() {
// Create/load the inner search
var s = new Zotero.Search();
if (this.searchText){
if (this.isCollection())
{
s.addCondition('collectionID', 'is', this.ref.getID());
if (Zotero.Prefs.get('recursiveCollections')) {
s.addCondition('recursive', 'true');
}
}
else if (this.isSearch())
{
s.addCondition('savedSearchID', 'is', this.ref['id']);
}
s.addCondition('quicksearch', 'contains', this.searchText);
if (this.isLibrary()) {
s.addCondition('noChildren', true);
}
else
{
if (this.isLibrary()){
s.addCondition('noChildren', 'true');
}
else if (this.isCollection()){
s.addCondition('noChildren', 'true');
s.addCondition('collectionID', 'is', this.ref.getID());
if (Zotero.Prefs.get('recursiveCollections')) {
s.addCondition('recursive', 'true');
}
}
else if (this.isSearch()){
s.load(this.ref['id']);
}
else {
return null;
else if (this.isCollection()) {
s.addCondition('noChildren', true);
s.addCondition('collectionID', 'is', this.ref.getID());
if (Zotero.Prefs.get('recursiveCollections')) {
s.addCondition('recursive', 'true');
}
}
else if (this.isSearch()){
s.load(this.ref['id']);
}
else {
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
}
// Create the outer (filter) search
var s2 = new Zotero.Search();
s2.setScope(s);
if (this.searchText) {
s2.addCondition('quicksearch', 'contains', this.searchText);
}
if (this.tags){
for (var tag in this.tags){
if (this.tags[tag]){
s.addCondition('tag', 'is', tag);
s2.addCondition('tag', 'is', tag);
}
}
}
return s;
return s2;
}
@ -899,4 +890,17 @@ Zotero.ItemGroup.prototype.setSearch = function(searchText)
Zotero.ItemGroup.prototype.setTags = function(tags)
{
this.tags = tags;
}
/*
* Returns TRUE if using quicksearch or tag filter
*/
Zotero.ItemGroup.prototype.isSearchMode = function() {
if (this.tags) {
for (var i in this.tags) {
var hasTags = true;
break;
}
}
return this.searchText != '' || hasTags;
}

View File

@ -3671,13 +3671,25 @@ Zotero.Tags = new function(){
var tags = Zotero.DB.query(sql);
}
else {
var sql = "CREATE TEMPORARY TABLE tmpSearchResults AS " + search.getSQL();
Zotero.DB.query(sql, search.getSQLParams());
sql = "CREATE INDEX tmpSearchResults_itemID ON tmpSearchResults(itemID)";
Zotero.DB.query(sql);
var sql = "SELECT DISTINCT tagID, tag, tagType FROM itemTags "
+ "NATURAL JOIN tags WHERE itemID IN (" + search.getSQL() + ") ";
+ "NATURAL JOIN tags WHERE ("
+ "itemID IN (SELECT itemID FROM tmpSearchResults) OR "
+ "itemID IN (SELECT itemID FROM itemNotes WHERE sourceItemID IN (SELECT itemID FROM tmpSearchResults)) OR "
+ "itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IN (SELECT itemID FROM tmpSearchResults))"
+ ") ";
if (types) {
sql += "AND tagType IN (" + types.join() + ") ";
}
sql += "ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql, search.getSQLParams());
var tags = Zotero.DB.query(sql);
sql = "DROP TABLE tmpSearchResults";
Zotero.DB.query(sql);
}
var indexed = {};

View File

@ -117,6 +117,7 @@ Zotero.ItemTreeView.prototype.setTree = function(treebox)
}, false);
obj.sort();
obj.expandMatchParents();
//Zotero.debug('Running callbacks in itemTreeView.setTree()', 4);
obj._runCallbacks();
@ -143,8 +144,12 @@ Zotero.ItemTreeView.prototype.setTree = function(treebox)
*/
Zotero.ItemTreeView.prototype.refresh = function()
{
this._searchMode = this._itemGroup.isSearchMode();
var oldRows = this.rowCount;
this._dataItems = [];
this._searchItemIDs = {}; // items matching the search
this._searchParentIDs = {};
this.rowCount = 0;
var cacheFields = ['title', 'date'];
@ -156,7 +161,8 @@ Zotero.ItemTreeView.prototype.refresh = function()
catch (e) {
return;
}
for each(var field in visibleFields) {
for (var i=0; i<visibleFields.length; i++) {
var field = visibleFields[i];
if (field == 'year') {
field = 'date';
}
@ -167,10 +173,33 @@ Zotero.ItemTreeView.prototype.refresh = function()
Zotero.Items.cacheFields(cacheFields);
var newRows = this._itemGroup.getChildItems();
var added = 0;
for (var i=0, len=newRows.length; i < len; i++) {
if (newRows[i] &&
(!this._sourcesOnly || (!newRows[i].isAttachment() && !newRows[i].isNote()))) {
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), i+1); //item ref, before row
// Only add regular items if sourcesOnly is set
if (this._sourcesOnly && !newRows[i].isRegularItem()) {
continue;
}
// Don't add child items directly
var sourceItemID = newRows[i].getSource();
if (sourceItemID) {
this._searchParentIDs[sourceItemID] = true;
}
// Add top-level items
else {
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), added + 1); //item ref, before row
added++;
}
this._searchItemIDs[newRows[i].getID()] = true;
}
// Add parents of matches if not matches themselves
for (var id in this._searchParentIDs) {
if (!this._searchItemIDs[id]) {
var item = Zotero.Items.get(id);
this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), added + 1); //item ref, before row
added++;
}
}
@ -667,8 +696,8 @@ Zotero.ItemTreeView.prototype.cycleHeader = function(column)
var savedSelection = this.saveSelection();
this.sort();
this.rememberSelection(savedSelection);
this.selection.selectEventsSuppressed = false;
this._treebox.invalidate();
this.selection.selectEventsSuppressed = false;
}
/*
@ -1053,10 +1082,12 @@ Zotero.ItemTreeView.prototype.setFilter = function(type, data) {
this.sort();
this.rememberOpenState(savedOpenState);
this.expandMatchParents();
this.rememberFirstRow(savedFirstRow);
this.rememberSelection(savedSelection);
this.selection.selectEventsSuppressed = false;
this._treebox.invalidate();
this.selection.selectEventsSuppressed = false;
//Zotero.debug('Running callbacks in itemTreeView.setFilter()', 4);
this._runCallbacks();
}
@ -1186,6 +1217,19 @@ Zotero.ItemTreeView.prototype.rememberOpenState = function(ids) {
}
Zotero.ItemTreeView.prototype.expandMatchParents = function () {
// Expand parents of child matches
if (this._searchMode) {
var view = this._treebox.view;
for (var id in this._searchParentIDs) {
if (!view.isContainerOpen(this._itemRowMap[id])) {
view.toggleOpenState(this._itemRowMap[id]);
}
}
}
}
Zotero.ItemTreeView.prototype.saveFirstRow = function() {
var row = this._treebox.getFirstVisibleRow();
if (row) {
@ -1609,9 +1653,17 @@ Zotero.ItemTreeView.prototype.onDragOver = function (evt,dropdata,session) { }
////////////////////////////////////////////////////////////////////////////////
Zotero.ItemTreeView.prototype.isSeparator = function(row) { return false; }
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) { }
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) { }
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) { }
/* Mark items not matching search as context rows, displayed in gray */
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
if (this._searchMode && !this._searchItemIDs[this._getItemAtRow(row).ref.getID()]) {
var aServ = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("contextRow"));
}
}
Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
{

View File

@ -21,6 +21,7 @@
*/
Zotero.Search = function(savedSearchID){
this._scope = null;
this._sql = null;
this._sqlParams = null;
this._maxSearchConditionID = 0;
@ -231,6 +232,14 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
}
/*
* Sets scope of search to the results of the passed Search object
*/
Zotero.Search.prototype.setScope = function (searchObj) {
this._scope = searchObj;
}
Zotero.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value, required){
if (typeof this._conditions[searchConditionID] == 'undefined'){
throw ('Invalid searchConditionID ' + searchConditionID + ' in updateCondition()');
@ -314,7 +323,56 @@ Zotero.Search.prototype.search = function(){
this._buildQuery();
}
var ids = Zotero.DB.columnQuery(this._sql, this._sqlParams);
if (this._scope) {
// If subsearch has post-search filter, run and insert ids into temp table
if (this._scope.hasPostSearchFilter()) {
var ids = this._scope.search();
if (!ids) {
return false;
}
Zotero.DB.query("DROP TABLE IF EXISTS tmpSearchResults");
var sql = "CREATE TEMPORARY TABLE tmpSearchResults (itemID INT)";
Zotero.DB.query(sql);
var sql = "INSERT INTO tmpSearchResults VALUES (?)";
var insertStatement = Zotero.DB.getStatement(sql);
for (var i=0; i<ids.length; i++) {
insertStatement.bindInt32Parameter(1, ids[i]);
try {
insertStatement.execute();
}
catch (e) {
throw (Zotero.DB.getLastErrorString());
}
}
insertStatement.reset();
}
// Otherwise, just copy to temp table directly
else {
var sql = "CREATE TEMPORARY TABLE tmpSearchResults AS "
+ this._scope.getSQL();
Zotero.DB.query(sql, this._scope.getSQLParams());
}
var sql = "CREATE INDEX tmpSearchResults_itemID ON tmpSearchResults(itemID)";
Zotero.DB.query(sql);
var sql = "SELECT itemID FROM items WHERE itemID IN (" + this._sql + ") "
+ "AND ("
+ "itemID IN (SELECT itemID FROM tmpSearchResults) OR "
+ "itemID IN (SELECT itemID FROM itemAttachments"
+ " WHERE sourceItemID IN (SELECT itemID FROM tmpSearchResults)) OR "
+ "itemID IN (SELECT itemID FROM itemNotes"
+ " WHERE sourceItemID IN (SELECT itemID FROM tmpSearchResults))"
+ ")";
var ids = Zotero.DB.columnQuery(sql, this._sqlParams);
Zotero.DB.query("DROP TABLE tmpSearchResults");
}
else {
var ids = Zotero.DB.columnQuery(this._sql, this._sqlParams);
}
//Zotero.debug('IDs from main search: ');
//Zotero.debug(ids);
@ -348,6 +406,10 @@ Zotero.Search.prototype.search = function(){
// (a separate fulltext word search filtered by fulltext content)
for each(var condition in this._conditions){
if (condition['condition']=='fulltextContent'){
if (this._scope) {
throw ("Cannot perform fulltext content search with custom scope in Zotero.Search.search()");
}
//Zotero.debug('Running subsearch against fulltext word index');
// Run a new search against the fulltext word index
@ -900,10 +962,12 @@ Zotero.Search.prototype._buildQuery = function(){
}
// Keep non-required conditions separate if in ANY mode
else if (!condition['required'] && joinMode == 'ANY') {
var nonQSConditions = true;
anySQL += condSQL + ' OR ';
anySQLParams = anySQLParams.concat(condSQLParams);
}
else {
var nonQSConditions = true;
condSQL += ' AND ';
sql += condSQL;
sqlParams = sqlParams.concat(condSQLParams);
@ -917,23 +981,20 @@ Zotero.Search.prototype._buildQuery = function(){
sql = sql.substring(0, sql.length-4); // remove last ' OR '
sql += ')';
}
else {
else if (nonQSConditions) {
sql = sql.substring(0, sql.length-5); // remove last ' AND '
}
else {
sql = sql.substring(0, sql.length-7); // remove ' WHERE '
}
// Add on quicksearch conditions -- this requires searching against
// children, so we repeat the main query for note and attachment
// sourceItemIDs before ANDing the quicksearch block
// Add on quicksearch conditions
if (quicksearchSQLSet) {
sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + " UNION "
+ "SELECT itemID FROM itemNotes WHERE sourceItemID IN "
+ "(" + sql + ") UNION "
+ "SELECT itemID FROM itemAttachments WHERE sourceItemID IN "
+ "(" + sql + ")) AND ((" + quicksearchSQLSet.join(') AND (') + "))";
sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + ") "
+ "AND ((" + quicksearchSQLSet.join(') AND (') + "))";
sqlParams = sqlParams.concat(sqlParams).concat(sqlParams);
for each(var p in quicksearchParamsSet) {
sqlParams = sqlParams.concat(p);
for (var k=0; k<quicksearchParamsSet.length; k++) {
sqlParams = sqlParams.concat(quicksearchParamsSet[k]);
}
}
}

View File

@ -39,7 +39,7 @@
margin-right: 5px;
}
/* Set by setHighlightedRows() and getCellProperties() in collectionTreeView.js) */
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
{
background: #FFFF99;
@ -59,6 +59,18 @@
margin-right: 5px;
}
/* Style search results, display non-matches in gray */
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow) {
color: gray;
}
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow, selected, focus) {
/* This is the default color, not the (platform-specific) highlight color, but at least it
helps to differentiate when clicking on a context row. */
color: inherit;
}
#zotero-items-pane
{
min-width: 300px;