summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2018-05-20 22:53:36 +0300
committerLauri Ojansivu <x@xet7.org>2018-05-20 22:53:36 +0300
commite8206f298325093d02280780006ccf87724afba5 (patch)
tree697f9dcca12cc240f45dc28fa5dbcce2f4fb36e5
parent1133b7a04a99f4068298d7bc2c1f3428b1bba539 (diff)
parent2119a6568b36591f84df29e0305fda9c12ec207a (diff)
downloadwekan-e8206f298325093d02280780006ccf87724afba5.tar.gz
wekan-e8206f298325093d02280780006ccf87724afba5.tar.bz2
wekan-e8206f298325093d02280780006ccf87724afba5.zip
Merge branch 'feature-advanced-filter' of https://github.com/feuerball11/wekan into feuerball11-feature-advanced-filter
-rw-r--r--client/components/cards/minicard.jade10
-rw-r--r--client/components/cards/minicard.styl7
-rw-r--r--client/components/sidebar/sidebarFilters.jade4
-rw-r--r--client/components/sidebar/sidebarFilters.js5
-rw-r--r--client/lib/filter.js313
-rw-r--r--i18n/en.i18n.json2
6 files changed, 336 insertions, 5 deletions
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index 9fa4dd57..aa0708dd 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -20,10 +20,20 @@ template(name="minicard")
.date
+cardSpentTime
+ .minicard-custom-fields
+ each customFieldsWD
+ if definition.showOnCard
+ .minicard-custom-field
+ .minicard-custom-field-item
+ = definition.name
+ .minicard-custom-field-item
+ = value
+
if members
.minicard-members.js-minicard-members
each members
+userAvatar(userId=this)
+
.badges
if comments.count
.badge(title="{{_ 'card-comments-title' comments.count }}")
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index d59f1f63..38f829d0 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -77,6 +77,13 @@
height: @width
border-radius: 2px
margin-left: 3px
+ .minicard-custom-fields
+ display:block;
+ .minicard-custom-field
+ display:flex;
+ .minicard-custom-field-item
+ max-width:50%;
+ flex-grow:1;
.minicard-title
p:last-child
margin-bottom: 0
diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade
index 5f9fcf72..514870b8 100644
--- a/client/components/sidebar/sidebarFilters.jade
+++ b/client/components/sidebar/sidebarFilters.jade
@@ -55,6 +55,10 @@ template(name="filterSidebar")
{{ name }}
if Filter.customFields.isSelected _id
i.fa.fa-check
+ hr
+ span {{_ 'advanced-filter-label'}}
+ input.js-field-advanced-filter(type="text")
+ span {{_ 'advanced-filter-description'}}
if Filter.isActive
hr
a.sidebar-btn.js-clear-all
diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js
index ba2633de..fd8229e4 100644
--- a/client/components/sidebar/sidebarFilters.js
+++ b/client/components/sidebar/sidebarFilters.js
@@ -16,6 +16,11 @@ BlazeComponent.extendComponent({
Filter.customFields.toggle(this.currentData()._id);
Filter.resetExceptions();
},
+ 'change .js-field-advanced-filter'(evt) {
+ evt.preventDefault();
+ Filter.advanced.set(this.find('.js-field-advanced-filter').value.trim());
+ Filter.resetExceptions();
+ },
'click .js-clear-all'(evt) {
evt.preventDefault();
Filter.reset();
diff --git a/client/lib/filter.js b/client/lib/filter.js
index f68c9711..c5f8fe7e 100644
--- a/client/lib/filter.js
+++ b/client/lib/filter.js
@@ -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.
// 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
@@ -90,6 +386,7 @@ Filter = {
labelIds: new SetFilter(),
members: new SetFilter(),
customFields: new SetFilter('_id'),
+ advanced: new AdvancedFilter(),
_fields: ['labelIds', 'members', 'customFields'],
@@ -102,7 +399,7 @@ Filter = {
isActive() {
return _.any(this._fields, (fieldName) => {
return this[fieldName]._isActive();
- });
+ }) || this.advanced._isActive();
},
_getMongoSelector() {
@@ -133,10 +430,15 @@ Filter = {
const exceptionsSelector = {_id: {$in: this._exceptions}};
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) {
@@ -152,6 +454,7 @@ Filter = {
const filter = this[fieldName];
filter.reset();
});
+ this.advanced.reset();
this.resetExceptions();
},
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index e2d6ddce..f44dba23 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -246,6 +246,8 @@
"filter-on": "Filter is on",
"filter-on-desc": "You are filtering cards on this board. Click here to edit filter.",
"filter-to-selection": "Filter to selection",
+ "advanced-filter-label": "Advanced Filter",
+ "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A Space is used as seperator between the operators. You can filter for all custom fields by simply typing there names and values. For example: Field1 == Value1 Note: If fields or values contains spaces, you need to encapsulate them into single quetes. For example: 'Field 1' == 'Value 1' Also you can combine multiple Conditions. For Example: F1 == V1 || F1 = V2 Normaly all Operators are interpreted from left to right. You can change the order of that by placing brakets. For Example: F1 == V1 and ( F2 == V2 || F2 == V3 )",
"fullname": "Full Name",
"header-logo-title": "Go back to your boards page.",
"hide-system-messages": "Hide system messages",