|
@@ -79,6 +79,302 @@ class SetFilter {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+// Advanced filter forms a MongoSelector from a users String.
|
|
|
|
+// Build by: Ignatz 19.05.2018 (github feuerball11)
|
|
|
|
+class AdvancedFilter {
|
|
|
|
+ constructor() {
|
|
|
|
+ this._dep = new Tracker.Dependency();
|
|
|
|
+ this._filter = '';
|
|
|
|
+ this._lastValide={};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ set(str)
|
|
|
|
+ {
|
|
|
|
+ this._filter = str;
|
|
|
|
+ this._dep.changed();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ reset() {
|
|
|
|
+ this._filter = '';
|
|
|
|
+ this._lastValide={};
|
|
|
|
+ this._dep.changed();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _isActive() {
|
|
|
|
+ this._dep.depend();
|
|
|
|
+ return this._filter !== '';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _filterToCommands(){
|
|
|
|
+ const commands = [];
|
|
|
|
+ let current = '';
|
|
|
|
+ let string = false;
|
|
|
|
+ let wasString = false;
|
|
|
|
+ let ignore = false;
|
|
|
|
+ for (let i = 0; i < this._filter.length; i++)
|
|
|
|
+ {
|
|
|
|
+ const char = this._filter.charAt(i);
|
|
|
|
+ if (ignore)
|
|
|
|
+ {
|
|
|
|
+ ignore = false;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ if (char === '\'')
|
|
|
|
+ {
|
|
|
|
+ string = !string;
|
|
|
|
+ if (string) wasString = true;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ if (char === '\\')
|
|
|
|
+ {
|
|
|
|
+ ignore = true;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ if (char === ' ' && !string)
|
|
|
|
+ {
|
|
|
|
+ commands.push({'cmd':current, 'string':wasString});
|
|
|
|
+ wasString = false;
|
|
|
|
+ current = '';
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ current += char;
|
|
|
|
+ }
|
|
|
|
+ if (current !== '')
|
|
|
|
+ {
|
|
|
|
+ commands.push({'cmd':current, 'string':wasString});
|
|
|
|
+ }
|
|
|
|
+ return commands;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _fieldNameToId(field)
|
|
|
|
+ {
|
|
|
|
+ const found = CustomFields.findOne({'name':field});
|
|
|
|
+ return found._id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _arrayToSelector(commands)
|
|
|
|
+ {
|
|
|
|
+ try {
|
|
|
|
+ //let changed = false;
|
|
|
|
+ this._processSubCommands(commands);
|
|
|
|
+ }
|
|
|
|
+ catch (e){return this._lastValide;}
|
|
|
|
+ this._lastValide = {$or: commands};
|
|
|
|
+ return {$or: commands};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _processSubCommands(commands)
|
|
|
|
+ {
|
|
|
|
+ const subcommands = [];
|
|
|
|
+ let level = 0;
|
|
|
|
+ let start = -1;
|
|
|
|
+ for (let i = 0; i < commands.length; i++)
|
|
|
|
+ {
|
|
|
|
+ if (commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ switch (commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ case '(':
|
|
|
|
+ {
|
|
|
|
+ level++;
|
|
|
|
+ if (start === -1) start = i;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ case ')':
|
|
|
|
+ {
|
|
|
|
+ level--;
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ i--;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ {
|
|
|
|
+ if (level > 0)
|
|
|
|
+ {
|
|
|
|
+ subcommands.push(commands[i]);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ i--;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (start !== -1)
|
|
|
|
+ {
|
|
|
|
+ this._processSubCommands(subcommands);
|
|
|
|
+ if (subcommands.length === 1)
|
|
|
|
+ commands.splice(start, 0, subcommands[0]);
|
|
|
|
+ else
|
|
|
|
+ commands.splice(start, 0, subcommands);
|
|
|
|
+ }
|
|
|
|
+ this._processConditions(commands);
|
|
|
|
+ this._processLogicalOperators(commands);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _processConditions(commands)
|
|
|
|
+ {
|
|
|
|
+ for (let i = 0; i < commands.length; i++)
|
|
|
|
+ {
|
|
|
|
+ if (!commands[i].string && commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ switch (commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ case '=':
|
|
|
|
+ case '==':
|
|
|
|
+ case '===':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value':str};
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case '!=':
|
|
|
|
+ case '!==':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $not: str }};
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case '>':
|
|
|
|
+ case 'gt':
|
|
|
|
+ case 'Gt':
|
|
|
|
+ case 'GT':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $gt: str } };
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case '>=':
|
|
|
|
+ case '>==':
|
|
|
|
+ case 'gte':
|
|
|
|
+ case 'Gte':
|
|
|
|
+ case 'GTE':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $gte: str } };
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case '<':
|
|
|
|
+ case 'lt':
|
|
|
|
+ case 'Lt':
|
|
|
|
+ case 'LT':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $lt: str } };
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case '<=':
|
|
|
|
+ case '<==':
|
|
|
|
+ case 'lte':
|
|
|
|
+ case 'Lte':
|
|
|
|
+ case 'LTE':
|
|
|
|
+ {
|
|
|
|
+ const field = commands[i-1].cmd;
|
|
|
|
+ const str = commands[i+1].cmd;
|
|
|
|
+ commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $lte: str } };
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _processLogicalOperators(commands)
|
|
|
|
+ {
|
|
|
|
+ for (let i = 0; i < commands.length; i++)
|
|
|
|
+ {
|
|
|
|
+ if (!commands[i].string && commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ switch (commands[i].cmd)
|
|
|
|
+ {
|
|
|
|
+ case 'or':
|
|
|
|
+ case 'Or':
|
|
|
|
+ case 'OR':
|
|
|
|
+ case '|':
|
|
|
|
+ case '||':
|
|
|
|
+ {
|
|
|
|
+ const op1 = commands[i-1];
|
|
|
|
+ const op2 = commands[i+1];
|
|
|
|
+ commands[i] = {$or: [op1, op2]};
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ case 'and':
|
|
|
|
+ case 'And':
|
|
|
|
+ case 'AND':
|
|
|
|
+ case '&':
|
|
|
|
+ case '&&':
|
|
|
|
+ {
|
|
|
|
+ const op1 = commands[i-1];
|
|
|
|
+ const op2 = commands[i+1];
|
|
|
|
+ commands[i] = {$and: [op1, op2]};
|
|
|
|
+ commands.splice(i-1, 1);
|
|
|
|
+ commands.splice(i, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ case 'not':
|
|
|
|
+ case 'Not':
|
|
|
|
+ case 'NOT':
|
|
|
|
+ case '!':
|
|
|
|
+ {
|
|
|
|
+ const op1 = commands[i+1];
|
|
|
|
+ commands[i] = {$not: op1};
|
|
|
|
+ commands.splice(i+1, 1);
|
|
|
|
+ //changed = true;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _getMongoSelector() {
|
|
|
|
+ this._dep.depend();
|
|
|
|
+ const commands = this._filterToCommands();
|
|
|
|
+ return this._arrayToSelector(commands);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
// The global Filter object.
|
|
// The global Filter object.
|
|
// XXX It would be possible to re-write this object more elegantly, and removing
|
|
// XXX It would be possible to re-write this object more elegantly, and removing
|
|
// the need to provide a list of `_fields`. We also should move methods into the
|
|
// the need to provide a list of `_fields`. We also should move methods into the
|
|
@@ -90,6 +386,7 @@ Filter = {
|
|
labelIds: new SetFilter(),
|
|
labelIds: new SetFilter(),
|
|
members: new SetFilter(),
|
|
members: new SetFilter(),
|
|
customFields: new SetFilter('_id'),
|
|
customFields: new SetFilter('_id'),
|
|
|
|
+ advanced: new AdvancedFilter(),
|
|
|
|
|
|
_fields: ['labelIds', 'members', 'customFields'],
|
|
_fields: ['labelIds', 'members', 'customFields'],
|
|
|
|
|
|
@@ -102,7 +399,7 @@ Filter = {
|
|
isActive() {
|
|
isActive() {
|
|
return _.any(this._fields, (fieldName) => {
|
|
return _.any(this._fields, (fieldName) => {
|
|
return this[fieldName]._isActive();
|
|
return this[fieldName]._isActive();
|
|
- });
|
|
|
|
|
|
+ }) || this.advanced._isActive();
|
|
},
|
|
},
|
|
|
|
|
|
_getMongoSelector() {
|
|
_getMongoSelector() {
|
|
@@ -133,10 +430,15 @@ Filter = {
|
|
const exceptionsSelector = {_id: {$in: this._exceptions}};
|
|
const exceptionsSelector = {_id: {$in: this._exceptions}};
|
|
this._exceptionsDep.depend();
|
|
this._exceptionsDep.depend();
|
|
|
|
|
|
- if (includeEmptySelectors)
|
|
|
|
- return {$or: [filterSelector, exceptionsSelector, emptySelector]};
|
|
|
|
- else
|
|
|
|
- return {$or: [filterSelector, exceptionsSelector]};
|
|
|
|
|
|
+ const selectors = [exceptionsSelector];
|
|
|
|
+
|
|
|
|
+ if (_.any(this._fields, (fieldName) => {
|
|
|
|
+ return this[fieldName]._isActive();
|
|
|
|
+ })) selectors.push(filterSelector);
|
|
|
|
+ if (includeEmptySelectors) selectors.push(emptySelector);
|
|
|
|
+ if (this.advanced._isActive()) selectors.push(this.advanced._getMongoSelector());
|
|
|
|
+
|
|
|
|
+ return {$or: selectors};
|
|
},
|
|
},
|
|
|
|
|
|
mongoSelector(additionalSelector) {
|
|
mongoSelector(additionalSelector) {
|
|
@@ -152,6 +454,7 @@ Filter = {
|
|
const filter = this[fieldName];
|
|
const filter = this[fieldName];
|
|
filter.reset();
|
|
filter.reset();
|
|
});
|
|
});
|
|
|
|
+ this.advanced.reset();
|
|
this.resetExceptions();
|
|
this.resetExceptions();
|
|
},
|
|
},
|
|
|
|
|