diff options
author | Lauri Ojansivu <x@xet7.org> | 2018-05-24 11:04:31 +0300 |
---|---|---|
committer | Lauri Ojansivu <x@xet7.org> | 2018-05-24 11:04:31 +0300 |
commit | a69997d31e4d73a671cd3d30afc3b7f9e179ef2c (patch) | |
tree | bfb75f515ca09fe54a52677c1e1d5c6cdccb0bd4 /client | |
parent | e5c6da5a3919158d8164ae1e0b4a8567da71249d (diff) | |
parent | 3bcd578fcbf4b852ee34d3d33c43e3097ab45313 (diff) | |
download | wekan-a69997d31e4d73a671cd3d30afc3b7f9e179ef2c.tar.gz wekan-a69997d31e4d73a671cd3d30afc3b7f9e179ef2c.tar.bz2 wekan-a69997d31e4d73a671cd3d30afc3b7f9e179ef2c.zip |
Merge remote-tracking branch 'upstream/devel' into devel
Diffstat (limited to 'client')
-rw-r--r-- | client/components/cards/minicard.jade | 10 | ||||
-rw-r--r-- | client/components/cards/minicard.styl | 7 | ||||
-rw-r--r-- | client/components/sidebar/sidebarFilters.jade | 4 | ||||
-rw-r--r-- | client/components/sidebar/sidebarFilters.js | 5 | ||||
-rw-r--r-- | client/lib/filter.js | 300 |
5 files changed, 315 insertions, 11 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..fa139cfe 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -79,6 +79,279 @@ 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 +363,7 @@ Filter = { labelIds: new SetFilter(), members: new SetFilter(), customFields: new SetFilter('_id'), + advanced: new AdvancedFilter(), _fields: ['labelIds', 'members', 'customFields'], @@ -102,7 +376,7 @@ Filter = { isActive() { return _.any(this._fields, (fieldName) => { return this[fieldName]._isActive(); - }); + }) || this.advanced._isActive(); }, _getMongoSelector() { @@ -115,12 +389,10 @@ Filter = { this._fields.forEach((fieldName) => { const filter = this[fieldName]; if (filter._isActive()) { - if (filter.subField !== '') - { + if (filter.subField !== '') { filterSelector[`${fieldName}.${filter.subField}`] = filter._getMongoSelector(); } - else - { + else { filterSelector[fieldName] = filter._getMongoSelector(); } emptySelector[fieldName] = filter._getEmptySelector(); @@ -130,13 +402,18 @@ Filter = { } }); - const exceptionsSelector = {_id: {$in: this._exceptions}}; + 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) { @@ -144,7 +421,7 @@ Filter = { if (_.isUndefined(additionalSelector)) return filterSelector; else - return {$and: [filterSelector, additionalSelector]}; + return { $and: [filterSelector, additionalSelector] }; }, reset() { @@ -152,6 +429,7 @@ Filter = { const filter = this[fieldName]; filter.reset(); }); + this.advanced.reset(); this.resetExceptions(); }, |