Move search code into separate files in xpcom/data
This commit is contained in:
parent
0769a84a00
commit
8f38b01712
4 changed files with 865 additions and 817 deletions
|
@ -1592,819 +1592,3 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
||||||
this._sql = sql;
|
this._sql = sql;
|
||||||
this._sqlParams = sqlParams.length ? sqlParams : false;
|
this._sqlParams = sqlParams.length ? sqlParams : false;
|
||||||
});
|
});
|
||||||
|
|
||||||
Zotero.Searches = function() {
|
|
||||||
this.constructor = null;
|
|
||||||
|
|
||||||
this._ZDO_object = 'search';
|
|
||||||
this._ZDO_id = 'savedSearchID';
|
|
||||||
this._ZDO_table = 'savedSearches';
|
|
||||||
|
|
||||||
this._primaryDataSQLParts = {
|
|
||||||
savedSearchID: "O.savedSearchID",
|
|
||||||
name: "O.savedSearchName AS name",
|
|
||||||
libraryID: "O.libraryID",
|
|
||||||
key: "O.key",
|
|
||||||
version: "O.version",
|
|
||||||
synced: "O.synced"
|
|
||||||
}
|
|
||||||
|
|
||||||
this._primaryDataSQLFrom = "FROM savedSearches O";
|
|
||||||
|
|
||||||
this.init = Zotero.Promise.coroutine(function* () {
|
|
||||||
yield Zotero.DataObjects.prototype.init.apply(this);
|
|
||||||
yield Zotero.SearchConditions.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of Zotero.Search objects, ordered by name
|
|
||||||
*
|
|
||||||
* @param {Integer} [libraryID]
|
|
||||||
*/
|
|
||||||
this.getAll = Zotero.Promise.coroutine(function* (libraryID) {
|
|
||||||
var sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
|
|
||||||
var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
|
|
||||||
if (!ids.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
var searches = this.get(ids);
|
|
||||||
// Do proper collation sort
|
|
||||||
var collation = Zotero.getLocaleCollation();
|
|
||||||
searches.sort(function (a, b) {
|
|
||||||
return collation.compareString(1, a.name, b.name);
|
|
||||||
});
|
|
||||||
return searches;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
this.getPrimaryDataSQL = function () {
|
|
||||||
// This should be the same as the query in Zotero.Search.loadPrimaryData(),
|
|
||||||
// just without a specific savedSearchID
|
|
||||||
return "SELECT "
|
|
||||||
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
|
|
||||||
+ "FROM savedSearches O WHERE 1";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this._loadConditions = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
|
|
||||||
var sql = "SELECT savedSearchID, searchConditionID, condition, operator, value, required "
|
|
||||||
+ "FROM savedSearches LEFT JOIN savedSearchConditions USING (savedSearchID) "
|
|
||||||
+ "WHERE libraryID=?" + idSQL
|
|
||||||
+ "ORDER BY savedSearchID, searchConditionID";
|
|
||||||
var params = [libraryID];
|
|
||||||
var lastID = null;
|
|
||||||
var rows = [];
|
|
||||||
var setRows = function (searchID, rows) {
|
|
||||||
var search = this._objectCache[searchID];
|
|
||||||
if (!search) {
|
|
||||||
throw new Error("Search " + searchID + " not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
search._conditions = {};
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
search._maxSearchConditionID = rows[rows.length - 1].searchConditionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindex conditions, in case they're not contiguous in the DB
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
|
||||||
let condition = rows[i];
|
|
||||||
|
|
||||||
// Parse "condition[/mode]"
|
|
||||||
let [conditionName, mode] = Zotero.SearchConditions.parseCondition(condition.condition);
|
|
||||||
|
|
||||||
let cond = Zotero.SearchConditions.get(conditionName);
|
|
||||||
if (!cond || cond.noLoad) {
|
|
||||||
Zotero.debug("Invalid saved search condition '" + conditionName + "' -- skipping", 2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert itemTypeID to itemType
|
|
||||||
//
|
|
||||||
// TEMP: This can be removed at some point
|
|
||||||
if (conditionName == 'itemTypeID') {
|
|
||||||
conditionName = 'itemType';
|
|
||||||
condition.value = Zotero.ItemTypes.getName(condition.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
search._conditions[i] = {
|
|
||||||
id: i,
|
|
||||||
condition: conditionName,
|
|
||||||
mode: mode,
|
|
||||||
operator: condition.operator,
|
|
||||||
value: condition.value,
|
|
||||||
required: !!condition.required
|
|
||||||
};
|
|
||||||
}
|
|
||||||
search._loaded.conditions = true;
|
|
||||||
search._clearChanged('conditions');
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
yield Zotero.DB.queryAsync(
|
|
||||||
sql,
|
|
||||||
params,
|
|
||||||
{
|
|
||||||
noCache: ids.length != 1,
|
|
||||||
onRow: function (row) {
|
|
||||||
let searchID = row.getResultByIndex(0);
|
|
||||||
|
|
||||||
if (lastID && searchID != lastID) {
|
|
||||||
setRows(lastID, rows);
|
|
||||||
rows = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
lastID = searchID;
|
|
||||||
let searchConditionID = row.getResultByIndex(1);
|
|
||||||
// No conditions
|
|
||||||
if (searchConditionID === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rows.push({
|
|
||||||
searchConditionID,
|
|
||||||
condition: row.getResultByIndex(2),
|
|
||||||
operator: row.getResultByIndex(3),
|
|
||||||
value: row.getResultByIndex(4),
|
|
||||||
required: row.getResultByIndex(5)
|
|
||||||
});
|
|
||||||
}.bind(this)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (lastID) {
|
|
||||||
setRows(lastID, rows);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Zotero.DataObjects.call(this);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Zotero.SearchConditions = new function(){
|
|
||||||
this.get = get;
|
|
||||||
this.getStandardConditions = getStandardConditions;
|
|
||||||
this.hasOperator = hasOperator;
|
|
||||||
this.getLocalizedName = getLocalizedName;
|
|
||||||
this.parseSearchString = parseSearchString;
|
|
||||||
this.parseCondition = parseCondition;
|
|
||||||
|
|
||||||
var _initialized = false;
|
|
||||||
var _conditions;
|
|
||||||
var _standardConditions;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define the advanced search operators
|
|
||||||
*/
|
|
||||||
var _operators = {
|
|
||||||
// Standard -- these need to match those in zoterosearch.xml
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
beginsWith: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true,
|
|
||||||
isLessThan: true,
|
|
||||||
isGreaterThan: true,
|
|
||||||
isBefore: true,
|
|
||||||
isAfter: true,
|
|
||||||
isInTheLast: true,
|
|
||||||
|
|
||||||
// Special
|
|
||||||
any: true,
|
|
||||||
all: true,
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define and set up the available advanced search conditions
|
|
||||||
*
|
|
||||||
* Flags:
|
|
||||||
* - special (don't show in search window menu)
|
|
||||||
* - template (special handling)
|
|
||||||
* - noLoad (can't load from saved search)
|
|
||||||
*/
|
|
||||||
this.init = Zotero.Promise.coroutine(function* () {
|
|
||||||
var conditions = [
|
|
||||||
//
|
|
||||||
// Special conditions
|
|
||||||
//
|
|
||||||
{
|
|
||||||
name: 'deleted',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Don't include child items
|
|
||||||
{
|
|
||||||
name: 'noChildren',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'unfiled',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'includeParentsAndChildren',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'includeParents',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'includeChildren',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Search recursively within collections
|
|
||||||
{
|
|
||||||
name: 'recursive',
|
|
||||||
operators: {
|
|
||||||
true: true,
|
|
||||||
false: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Join mode
|
|
||||||
{
|
|
||||||
name: 'joinMode',
|
|
||||||
operators: {
|
|
||||||
any: true,
|
|
||||||
all: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'quicksearch-titleCreatorYear',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'quicksearch-fields',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'quicksearch-everything',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
{
|
|
||||||
name: 'quicksearch',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Quicksearch block markers
|
|
||||||
{
|
|
||||||
name: 'blockStart',
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'blockEnd',
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Shortcuts for adding collections and searches by id
|
|
||||||
{
|
|
||||||
name: 'collectionID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'savedSearchID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Standard conditions
|
|
||||||
//
|
|
||||||
|
|
||||||
// Collection id to search within
|
|
||||||
{
|
|
||||||
name: 'collection',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'collectionItems',
|
|
||||||
field: 'collectionID'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Saved search to search within
|
|
||||||
{
|
|
||||||
name: 'savedSearch',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
special: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'dateAdded',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
isBefore: true,
|
|
||||||
isAfter: true,
|
|
||||||
isInTheLast: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'dateAdded'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'dateModified',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
isBefore: true,
|
|
||||||
isAfter: true,
|
|
||||||
isInTheLast: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'dateModified'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
{
|
|
||||||
name: 'itemTypeID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'itemTypeID',
|
|
||||||
special: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'itemType',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'typeName'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'fileTypeID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'itemAttachments',
|
|
||||||
field: 'fileTypeID'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'tagID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'itemTags',
|
|
||||||
field: 'tagID',
|
|
||||||
special: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'tag',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemTags',
|
|
||||||
field: 'name'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'note',
|
|
||||||
operators: {
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemNotes',
|
|
||||||
field: 'note'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'childNote',
|
|
||||||
operators: {
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'note'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'creator',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemCreators',
|
|
||||||
field: "TRIM(firstName || ' ' || lastName)"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'lastName',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemCreators',
|
|
||||||
field: 'lastName',
|
|
||||||
special: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'field',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemData',
|
|
||||||
field: 'value',
|
|
||||||
aliases: yield Zotero.DB.columnQueryAsync("SELECT fieldName FROM fieldsCombined "
|
|
||||||
+ "WHERE fieldName NOT IN ('accessDate', 'date', 'pages', "
|
|
||||||
+ "'section','seriesNumber','issue')"),
|
|
||||||
template: true // mark for special handling
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'datefield',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
isBefore: true,
|
|
||||||
isAfter: true,
|
|
||||||
isInTheLast: true
|
|
||||||
},
|
|
||||||
table: 'itemData',
|
|
||||||
field: 'value',
|
|
||||||
aliases: ['accessDate', 'date', 'dateDue', 'accepted'], // TEMP - NSF
|
|
||||||
template: true // mark for special handling
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'year',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'itemData',
|
|
||||||
field: 'SUBSTR(value, 1, 4)',
|
|
||||||
special: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'numberfield',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true,
|
|
||||||
isLessThan: true,
|
|
||||||
isGreaterThan: true
|
|
||||||
},
|
|
||||||
table: 'itemData',
|
|
||||||
field: 'value',
|
|
||||||
aliases: ['pages', 'numPages', 'numberOfVolumes', 'section', 'seriesNumber','issue'],
|
|
||||||
template: true // mark for special handling
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'libraryID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'libraryID',
|
|
||||||
special: true,
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true,
|
|
||||||
beginsWith: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'key',
|
|
||||||
special: true,
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'itemID',
|
|
||||||
operators: {
|
|
||||||
is: true,
|
|
||||||
isNot: true
|
|
||||||
},
|
|
||||||
table: 'items',
|
|
||||||
field: 'itemID',
|
|
||||||
special: true,
|
|
||||||
noLoad: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'annotation',
|
|
||||||
operators: {
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'annotations',
|
|
||||||
field: 'text'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'fulltextWord',
|
|
||||||
operators: {
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
table: 'fulltextItemWords',
|
|
||||||
field: 'word',
|
|
||||||
flags: {
|
|
||||||
leftbound: true
|
|
||||||
},
|
|
||||||
special: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'fulltextContent',
|
|
||||||
operators: {
|
|
||||||
contains: true,
|
|
||||||
doesNotContain: true
|
|
||||||
},
|
|
||||||
special: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'tempTable',
|
|
||||||
operators: {
|
|
||||||
is: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Index conditions by name and aliases
|
|
||||||
_conditions = {};
|
|
||||||
for (var i in conditions) {
|
|
||||||
_conditions[conditions[i]['name']] = conditions[i];
|
|
||||||
if (conditions[i]['aliases']) {
|
|
||||||
for (var j in conditions[i]['aliases']) {
|
|
||||||
// TEMP - NSF
|
|
||||||
switch (conditions[i]['aliases'][j]) {
|
|
||||||
case 'dateDue':
|
|
||||||
case 'accepted':
|
|
||||||
if (!Zotero.ItemTypes.getID('nsfReviewer')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_conditions[conditions[i]['aliases'][j]] = conditions[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_conditions[conditions[i]['name']] = conditions[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_standardConditions = [];
|
|
||||||
|
|
||||||
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
|
||||||
var locale = Zotero.locale;
|
|
||||||
|
|
||||||
// Separate standard conditions for menu display
|
|
||||||
for (var i in _conditions){
|
|
||||||
var fieldID = false;
|
|
||||||
if (['field', 'datefield', 'numberfield'].indexOf(_conditions[i]['name']) != -1) {
|
|
||||||
fieldID = Zotero.ItemFields.getID(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If explicitly special...
|
|
||||||
if (_conditions[i]['special'] ||
|
|
||||||
// or a template master (e.g. 'field')...
|
|
||||||
(_conditions[i]['template'] && i==_conditions[i]['name']) ||
|
|
||||||
// or no table and not explicitly unspecial...
|
|
||||||
(!_conditions[i]['table'] &&
|
|
||||||
typeof _conditions[i]['special'] == 'undefined') ||
|
|
||||||
// or field is a type-specific version of a base field...
|
|
||||||
(fieldID && baseMappedFields.indexOf(fieldID) != -1)) {
|
|
||||||
// ...then skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let localized = self.getLocalizedName(i);
|
|
||||||
// Hack to use a different name for "issue" in French locale,
|
|
||||||
// where 'number' and 'issue' are translated the same
|
|
||||||
// https://forums.zotero.org/discussion/14942/
|
|
||||||
if (fieldID == 5 && locale.substr(0, 2).toLowerCase() == 'fr') {
|
|
||||||
localized = "Num\u00E9ro (p\u00E9riodique)";
|
|
||||||
}
|
|
||||||
|
|
||||||
_standardConditions.push({
|
|
||||||
name: i,
|
|
||||||
localized: localized,
|
|
||||||
operators: _conditions[i]['operators'],
|
|
||||||
flags: _conditions[i]['flags']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var collation = Zotero.getLocaleCollation();
|
|
||||||
_standardConditions.sort(function(a, b) {
|
|
||||||
return collation.compareString(1, a.localized, b.localized);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get condition data
|
|
||||||
*/
|
|
||||||
function get(condition){
|
|
||||||
return _conditions[condition];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns array of possible conditions
|
|
||||||
*
|
|
||||||
* Does not include special conditions, only ones that would show in a drop-down list
|
|
||||||
*/
|
|
||||||
function getStandardConditions(){
|
|
||||||
// TODO: return copy instead
|
|
||||||
return _standardConditions;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if an operator is valid for a given condition
|
|
||||||
*/
|
|
||||||
function hasOperator(condition, operator){
|
|
||||||
var [condition, mode] = this.parseCondition(condition);
|
|
||||||
|
|
||||||
if (!_conditions) {
|
|
||||||
throw new Zotero.Exception.UnloadedDataException("Search conditions not yet loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_conditions[condition]){
|
|
||||||
let e = new Error("Invalid condition '" + condition + "' in hasOperator()");
|
|
||||||
e.name = "ZoteroUnknownFieldError";
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!operator && typeof _conditions[condition]['operators'] == 'undefined'){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!_conditions[condition]['operators'][operator];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getLocalizedName(str) {
|
|
||||||
// TEMP
|
|
||||||
if (str == 'itemType') {
|
|
||||||
str = 'itemTypeID';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Zotero.getString('searchConditions.' + str)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return Zotero.ItemFields.getLocalizedString(null, str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two API JSON condition objects
|
|
||||||
*/
|
|
||||||
this.equals = function (data1, data2) {
|
|
||||||
return data1.condition === data2.condition
|
|
||||||
&& data1.operator === data2.operator
|
|
||||||
&& data1.value === data2.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parses a search into words and "double-quoted phrases"
|
|
||||||
*
|
|
||||||
* Also strips unpaired quotes at the beginning and end of words
|
|
||||||
*
|
|
||||||
* Returns array of objects containing 'text' and 'inQuotes'
|
|
||||||
*/
|
|
||||||
function parseSearchString(str) {
|
|
||||||
var parts = str.split(/\s*("[^"]*")\s*|"\s|\s"|^"|"$|'\s|\s'|^'|'$|\s/m);
|
|
||||||
var parsed = [];
|
|
||||||
|
|
||||||
for (var i in parts) {
|
|
||||||
var part = parts[i];
|
|
||||||
if (!part || !part.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.charAt(0)=='"' && part.charAt(part.length-1)=='"') {
|
|
||||||
parsed.push({
|
|
||||||
text: part.substring(1, part.length-1),
|
|
||||||
inQuotes: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
parsed.push({
|
|
||||||
text: part,
|
|
||||||
inQuotes: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function parseCondition(condition){
|
|
||||||
var mode = false;
|
|
||||||
var pos = condition.indexOf('/');
|
|
||||||
if (pos != -1){
|
|
||||||
mode = condition.substr(pos+1);
|
|
||||||
condition = condition.substr(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [condition, mode];
|
|
||||||
}
|
|
||||||
}
|
|
690
chrome/content/zotero/xpcom/data/searchConditions.js
Normal file
690
chrome/content/zotero/xpcom/data/searchConditions.js
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2006-2016 Center for History and New Media
|
||||||
|
George Mason University, Fairfax, Virginia, USA
|
||||||
|
https://zotero.org
|
||||||
|
|
||||||
|
This file is part of Zotero.
|
||||||
|
|
||||||
|
Zotero is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Zotero is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
Zotero.SearchConditions = new function(){
|
||||||
|
this.get = get;
|
||||||
|
this.getStandardConditions = getStandardConditions;
|
||||||
|
this.hasOperator = hasOperator;
|
||||||
|
this.getLocalizedName = getLocalizedName;
|
||||||
|
this.parseSearchString = parseSearchString;
|
||||||
|
this.parseCondition = parseCondition;
|
||||||
|
|
||||||
|
var _initialized = false;
|
||||||
|
var _conditions;
|
||||||
|
var _standardConditions;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define the advanced search operators
|
||||||
|
*/
|
||||||
|
var _operators = {
|
||||||
|
// Standard -- these need to match those in zoterosearch.xml
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
beginsWith: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true,
|
||||||
|
isLessThan: true,
|
||||||
|
isGreaterThan: true,
|
||||||
|
isBefore: true,
|
||||||
|
isAfter: true,
|
||||||
|
isInTheLast: true,
|
||||||
|
|
||||||
|
// Special
|
||||||
|
any: true,
|
||||||
|
all: true,
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define and set up the available advanced search conditions
|
||||||
|
*
|
||||||
|
* Flags:
|
||||||
|
* - special (don't show in search window menu)
|
||||||
|
* - template (special handling)
|
||||||
|
* - noLoad (can't load from saved search)
|
||||||
|
*/
|
||||||
|
this.init = Zotero.Promise.coroutine(function* () {
|
||||||
|
var conditions = [
|
||||||
|
//
|
||||||
|
// Special conditions
|
||||||
|
//
|
||||||
|
{
|
||||||
|
name: 'deleted',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Don't include child items
|
||||||
|
{
|
||||||
|
name: 'noChildren',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'unfiled',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'includeParentsAndChildren',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'includeParents',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'includeChildren',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search recursively within collections
|
||||||
|
{
|
||||||
|
name: 'recursive',
|
||||||
|
operators: {
|
||||||
|
true: true,
|
||||||
|
false: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Join mode
|
||||||
|
{
|
||||||
|
name: 'joinMode',
|
||||||
|
operators: {
|
||||||
|
any: true,
|
||||||
|
all: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'quicksearch-titleCreatorYear',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'quicksearch-fields',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'quicksearch-everything',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
{
|
||||||
|
name: 'quicksearch',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quicksearch block markers
|
||||||
|
{
|
||||||
|
name: 'blockStart',
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'blockEnd',
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Shortcuts for adding collections and searches by id
|
||||||
|
{
|
||||||
|
name: 'collectionID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'savedSearchID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Standard conditions
|
||||||
|
//
|
||||||
|
|
||||||
|
// Collection id to search within
|
||||||
|
{
|
||||||
|
name: 'collection',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'collectionItems',
|
||||||
|
field: 'collectionID'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Saved search to search within
|
||||||
|
{
|
||||||
|
name: 'savedSearch',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
special: false
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'dateAdded',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
isBefore: true,
|
||||||
|
isAfter: true,
|
||||||
|
isInTheLast: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'dateAdded'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'dateModified',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
isBefore: true,
|
||||||
|
isAfter: true,
|
||||||
|
isInTheLast: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'dateModified'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
{
|
||||||
|
name: 'itemTypeID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'itemTypeID',
|
||||||
|
special: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'itemType',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'typeName'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'fileTypeID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'itemAttachments',
|
||||||
|
field: 'fileTypeID'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'tagID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'itemTags',
|
||||||
|
field: 'tagID',
|
||||||
|
special: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'tag',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemTags',
|
||||||
|
field: 'name'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'note',
|
||||||
|
operators: {
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemNotes',
|
||||||
|
field: 'note'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'childNote',
|
||||||
|
operators: {
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'note'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'creator',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemCreators',
|
||||||
|
field: "TRIM(firstName || ' ' || lastName)"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'lastName',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemCreators',
|
||||||
|
field: 'lastName',
|
||||||
|
special: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'field',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemData',
|
||||||
|
field: 'value',
|
||||||
|
aliases: yield Zotero.DB.columnQueryAsync("SELECT fieldName FROM fieldsCombined "
|
||||||
|
+ "WHERE fieldName NOT IN ('accessDate', 'date', 'pages', "
|
||||||
|
+ "'section','seriesNumber','issue')"),
|
||||||
|
template: true // mark for special handling
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'datefield',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
isBefore: true,
|
||||||
|
isAfter: true,
|
||||||
|
isInTheLast: true
|
||||||
|
},
|
||||||
|
table: 'itemData',
|
||||||
|
field: 'value',
|
||||||
|
aliases: ['accessDate', 'date', 'dateDue', 'accepted'], // TEMP - NSF
|
||||||
|
template: true // mark for special handling
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'year',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'itemData',
|
||||||
|
field: 'SUBSTR(value, 1, 4)',
|
||||||
|
special: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'numberfield',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true,
|
||||||
|
isLessThan: true,
|
||||||
|
isGreaterThan: true
|
||||||
|
},
|
||||||
|
table: 'itemData',
|
||||||
|
field: 'value',
|
||||||
|
aliases: ['pages', 'numPages', 'numberOfVolumes', 'section', 'seriesNumber','issue'],
|
||||||
|
template: true // mark for special handling
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'libraryID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'libraryID',
|
||||||
|
special: true,
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true,
|
||||||
|
beginsWith: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'key',
|
||||||
|
special: true,
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'itemID',
|
||||||
|
operators: {
|
||||||
|
is: true,
|
||||||
|
isNot: true
|
||||||
|
},
|
||||||
|
table: 'items',
|
||||||
|
field: 'itemID',
|
||||||
|
special: true,
|
||||||
|
noLoad: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'annotation',
|
||||||
|
operators: {
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'annotations',
|
||||||
|
field: 'text'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'fulltextWord',
|
||||||
|
operators: {
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
table: 'fulltextItemWords',
|
||||||
|
field: 'word',
|
||||||
|
flags: {
|
||||||
|
leftbound: true
|
||||||
|
},
|
||||||
|
special: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'fulltextContent',
|
||||||
|
operators: {
|
||||||
|
contains: true,
|
||||||
|
doesNotContain: true
|
||||||
|
},
|
||||||
|
special: false
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'tempTable',
|
||||||
|
operators: {
|
||||||
|
is: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Index conditions by name and aliases
|
||||||
|
_conditions = {};
|
||||||
|
for (var i in conditions) {
|
||||||
|
_conditions[conditions[i]['name']] = conditions[i];
|
||||||
|
if (conditions[i]['aliases']) {
|
||||||
|
for (var j in conditions[i]['aliases']) {
|
||||||
|
// TEMP - NSF
|
||||||
|
switch (conditions[i]['aliases'][j]) {
|
||||||
|
case 'dateDue':
|
||||||
|
case 'accepted':
|
||||||
|
if (!Zotero.ItemTypes.getID('nsfReviewer')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_conditions[conditions[i]['aliases'][j]] = conditions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_conditions[conditions[i]['name']] = conditions[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
_standardConditions = [];
|
||||||
|
|
||||||
|
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
||||||
|
var locale = Zotero.locale;
|
||||||
|
|
||||||
|
// Separate standard conditions for menu display
|
||||||
|
for (var i in _conditions){
|
||||||
|
var fieldID = false;
|
||||||
|
if (['field', 'datefield', 'numberfield'].indexOf(_conditions[i]['name']) != -1) {
|
||||||
|
fieldID = Zotero.ItemFields.getID(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If explicitly special...
|
||||||
|
if (_conditions[i]['special'] ||
|
||||||
|
// or a template master (e.g. 'field')...
|
||||||
|
(_conditions[i]['template'] && i==_conditions[i]['name']) ||
|
||||||
|
// or no table and not explicitly unspecial...
|
||||||
|
(!_conditions[i]['table'] &&
|
||||||
|
typeof _conditions[i]['special'] == 'undefined') ||
|
||||||
|
// or field is a type-specific version of a base field...
|
||||||
|
(fieldID && baseMappedFields.indexOf(fieldID) != -1)) {
|
||||||
|
// ...then skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let localized = self.getLocalizedName(i);
|
||||||
|
// Hack to use a different name for "issue" in French locale,
|
||||||
|
// where 'number' and 'issue' are translated the same
|
||||||
|
// https://forums.zotero.org/discussion/14942/
|
||||||
|
if (fieldID == 5 && locale.substr(0, 2).toLowerCase() == 'fr') {
|
||||||
|
localized = "Num\u00E9ro (p\u00E9riodique)";
|
||||||
|
}
|
||||||
|
|
||||||
|
_standardConditions.push({
|
||||||
|
name: i,
|
||||||
|
localized: localized,
|
||||||
|
operators: _conditions[i]['operators'],
|
||||||
|
flags: _conditions[i]['flags']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
_standardConditions.sort(function(a, b) {
|
||||||
|
return collation.compareString(1, a.localized, b.localized);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get condition data
|
||||||
|
*/
|
||||||
|
function get(condition){
|
||||||
|
return _conditions[condition];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns array of possible conditions
|
||||||
|
*
|
||||||
|
* Does not include special conditions, only ones that would show in a drop-down list
|
||||||
|
*/
|
||||||
|
function getStandardConditions(){
|
||||||
|
// TODO: return copy instead
|
||||||
|
return _standardConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if an operator is valid for a given condition
|
||||||
|
*/
|
||||||
|
function hasOperator(condition, operator){
|
||||||
|
var [condition, mode] = this.parseCondition(condition);
|
||||||
|
|
||||||
|
if (!_conditions) {
|
||||||
|
throw new Zotero.Exception.UnloadedDataException("Search conditions not yet loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_conditions[condition]){
|
||||||
|
let e = new Error("Invalid condition '" + condition + "' in hasOperator()");
|
||||||
|
e.name = "ZoteroUnknownFieldError";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!operator && typeof _conditions[condition]['operators'] == 'undefined'){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!_conditions[condition]['operators'][operator];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getLocalizedName(str) {
|
||||||
|
// TEMP
|
||||||
|
if (str == 'itemType') {
|
||||||
|
str = 'itemTypeID';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Zotero.getString('searchConditions.' + str)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return Zotero.ItemFields.getLocalizedString(null, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two API JSON condition objects
|
||||||
|
*/
|
||||||
|
this.equals = function (data1, data2) {
|
||||||
|
return data1.condition === data2.condition
|
||||||
|
&& data1.operator === data2.operator
|
||||||
|
&& data1.value === data2.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parses a search into words and "double-quoted phrases"
|
||||||
|
*
|
||||||
|
* Also strips unpaired quotes at the beginning and end of words
|
||||||
|
*
|
||||||
|
* Returns array of objects containing 'text' and 'inQuotes'
|
||||||
|
*/
|
||||||
|
function parseSearchString(str) {
|
||||||
|
var parts = str.split(/\s*("[^"]*")\s*|"\s|\s"|^"|"$|'\s|\s'|^'|'$|\s/m);
|
||||||
|
var parsed = [];
|
||||||
|
|
||||||
|
for (var i in parts) {
|
||||||
|
var part = parts[i];
|
||||||
|
if (!part || !part.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.charAt(0)=='"' && part.charAt(part.length-1)=='"') {
|
||||||
|
parsed.push({
|
||||||
|
text: part.substring(1, part.length-1),
|
||||||
|
inQuotes: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parsed.push({
|
||||||
|
text: part,
|
||||||
|
inQuotes: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parseCondition(condition){
|
||||||
|
var mode = false;
|
||||||
|
var pos = condition.indexOf('/');
|
||||||
|
if (pos != -1){
|
||||||
|
mode = condition.substr(pos+1);
|
||||||
|
condition = condition.substr(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [condition, mode];
|
||||||
|
}
|
||||||
|
}
|
172
chrome/content/zotero/xpcom/data/searches.js
Normal file
172
chrome/content/zotero/xpcom/data/searches.js
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2006-2016 Center for History and New Media
|
||||||
|
George Mason University, Fairfax, Virginia, USA
|
||||||
|
https://zotero.org
|
||||||
|
|
||||||
|
This file is part of Zotero.
|
||||||
|
|
||||||
|
Zotero is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Zotero is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
Zotero.Searches = function() {
|
||||||
|
this.constructor = null;
|
||||||
|
|
||||||
|
this._ZDO_object = 'search';
|
||||||
|
this._ZDO_id = 'savedSearchID';
|
||||||
|
this._ZDO_table = 'savedSearches';
|
||||||
|
|
||||||
|
this._primaryDataSQLParts = {
|
||||||
|
savedSearchID: "O.savedSearchID",
|
||||||
|
name: "O.savedSearchName AS name",
|
||||||
|
libraryID: "O.libraryID",
|
||||||
|
key: "O.key",
|
||||||
|
version: "O.version",
|
||||||
|
synced: "O.synced"
|
||||||
|
}
|
||||||
|
|
||||||
|
this._primaryDataSQLFrom = "FROM savedSearches O";
|
||||||
|
|
||||||
|
this.init = Zotero.Promise.coroutine(function* () {
|
||||||
|
yield Zotero.DataObjects.prototype.init.apply(this);
|
||||||
|
yield Zotero.SearchConditions.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of Zotero.Search objects, ordered by name
|
||||||
|
*
|
||||||
|
* @param {Integer} [libraryID]
|
||||||
|
*/
|
||||||
|
this.getAll = Zotero.Promise.coroutine(function* (libraryID) {
|
||||||
|
var sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
|
||||||
|
var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
|
||||||
|
if (!ids.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var searches = this.get(ids);
|
||||||
|
// Do proper collation sort
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
searches.sort(function (a, b) {
|
||||||
|
return collation.compareString(1, a.name, b.name);
|
||||||
|
});
|
||||||
|
return searches;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.getPrimaryDataSQL = function () {
|
||||||
|
// This should be the same as the query in Zotero.Search.loadPrimaryData(),
|
||||||
|
// just without a specific savedSearchID
|
||||||
|
return "SELECT "
|
||||||
|
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
|
||||||
|
+ "FROM savedSearches O WHERE 1";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this._loadConditions = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
|
||||||
|
var sql = "SELECT savedSearchID, searchConditionID, condition, operator, value, required "
|
||||||
|
+ "FROM savedSearches LEFT JOIN savedSearchConditions USING (savedSearchID) "
|
||||||
|
+ "WHERE libraryID=?" + idSQL
|
||||||
|
+ "ORDER BY savedSearchID, searchConditionID";
|
||||||
|
var params = [libraryID];
|
||||||
|
var lastID = null;
|
||||||
|
var rows = [];
|
||||||
|
var setRows = function (searchID, rows) {
|
||||||
|
var search = this._objectCache[searchID];
|
||||||
|
if (!search) {
|
||||||
|
throw new Error("Search " + searchID + " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
search._conditions = {};
|
||||||
|
|
||||||
|
if (rows.length) {
|
||||||
|
search._maxSearchConditionID = rows[rows.length - 1].searchConditionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reindex conditions, in case they're not contiguous in the DB
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
let condition = rows[i];
|
||||||
|
|
||||||
|
// Parse "condition[/mode]"
|
||||||
|
let [conditionName, mode] = Zotero.SearchConditions.parseCondition(condition.condition);
|
||||||
|
|
||||||
|
let cond = Zotero.SearchConditions.get(conditionName);
|
||||||
|
if (!cond || cond.noLoad) {
|
||||||
|
Zotero.debug("Invalid saved search condition '" + conditionName + "' -- skipping", 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert itemTypeID to itemType
|
||||||
|
//
|
||||||
|
// TEMP: This can be removed at some point
|
||||||
|
if (conditionName == 'itemTypeID') {
|
||||||
|
conditionName = 'itemType';
|
||||||
|
condition.value = Zotero.ItemTypes.getName(condition.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
search._conditions[i] = {
|
||||||
|
id: i,
|
||||||
|
condition: conditionName,
|
||||||
|
mode: mode,
|
||||||
|
operator: condition.operator,
|
||||||
|
value: condition.value,
|
||||||
|
required: !!condition.required
|
||||||
|
};
|
||||||
|
}
|
||||||
|
search._loaded.conditions = true;
|
||||||
|
search._clearChanged('conditions');
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
yield Zotero.DB.queryAsync(
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
{
|
||||||
|
noCache: ids.length != 1,
|
||||||
|
onRow: function (row) {
|
||||||
|
let searchID = row.getResultByIndex(0);
|
||||||
|
|
||||||
|
if (lastID && searchID != lastID) {
|
||||||
|
setRows(lastID, rows);
|
||||||
|
rows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
lastID = searchID;
|
||||||
|
let searchConditionID = row.getResultByIndex(1);
|
||||||
|
// No conditions
|
||||||
|
if (searchConditionID === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rows.push({
|
||||||
|
searchConditionID,
|
||||||
|
condition: row.getResultByIndex(2),
|
||||||
|
operator: row.getResultByIndex(3),
|
||||||
|
value: row.getResultByIndex(4),
|
||||||
|
required: row.getResultByIndex(5)
|
||||||
|
});
|
||||||
|
}.bind(this)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (lastID) {
|
||||||
|
setRows(lastID, rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Zotero.DataObjects.call(this);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
|
@ -82,6 +82,9 @@ const xpcomFilesLocal = [
|
||||||
'data/groups',
|
'data/groups',
|
||||||
'data/itemFields',
|
'data/itemFields',
|
||||||
'data/relations',
|
'data/relations',
|
||||||
|
'data/search',
|
||||||
|
'data/searchConditions',
|
||||||
|
'data/searches',
|
||||||
'data/tags',
|
'data/tags',
|
||||||
'db',
|
'db',
|
||||||
'duplicates',
|
'duplicates',
|
||||||
|
@ -98,7 +101,6 @@ const xpcomFilesLocal = [
|
||||||
'report',
|
'report',
|
||||||
'router',
|
'router',
|
||||||
'schema',
|
'schema',
|
||||||
'search',
|
|
||||||
'server',
|
'server',
|
||||||
'style',
|
'style',
|
||||||
'sync',
|
'sync',
|
||||||
|
|
Loading…
Reference in a new issue