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._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/itemFields',
|
||||
'data/relations',
|
||||
'data/search',
|
||||
'data/searchConditions',
|
||||
'data/searches',
|
||||
'data/tags',
|
||||
'db',
|
||||
'duplicates',
|
||||
|
@ -98,7 +101,6 @@ const xpcomFilesLocal = [
|
|||
'report',
|
||||
'router',
|
||||
'schema',
|
||||
'search',
|
||||
'server',
|
||||
'style',
|
||||
'sync',
|
||||
|
|
Loading…
Reference in a new issue