Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/marius-wieschollek/passwords-webextension.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarius David Wieschollek <passwords.public@mdns.eu>2021-12-15 00:03:14 +0300
committerMarius David Wieschollek <passwords.public@mdns.eu>2021-12-15 00:03:14 +0300
commit485bf5d647f367435487ac00adcdc2b9f77fed08 (patch)
treea4d570501fc0339e83b5c69e1956fa9be50219ab
parentccf33f8cdab77cc4a0c0d1979090898977863ae5 (diff)
Add new search enginesearch
Signed-off-by: Marius David Wieschollek <passwords.public@mdns.eu>
-rw-r--r--src/js/NextSearch/Condition/AbstractSearchCondition.js80
-rw-r--r--src/js/NextSearch/Condition/AndCondition.js25
-rw-r--r--src/js/NextSearch/Condition/ConditionBuilder.js79
-rw-r--r--src/js/NextSearch/Condition/OrCondition.js28
-rw-r--r--src/js/NextSearch/Condition/XorCondition.js31
-rw-r--r--src/js/NextSearch/Field/AbstractSearchField.js41
-rw-r--r--src/js/NextSearch/Field/FieldBuilder.js147
-rw-r--r--src/js/NextSearch/Field/FieldContains.js24
-rw-r--r--src/js/NextSearch/Field/FieldEquals.js20
-rw-r--r--src/js/NextSearch/Field/FieldIn.js23
-rw-r--r--src/js/NextSearch/Field/FieldMatches.js25
-rw-r--r--src/js/NextSearch/Field/FieldNotContains.js24
-rw-r--r--src/js/NextSearch/Field/FieldNotEquals.js20
-rw-r--r--src/js/NextSearch/Field/FieldNotIn.js23
-rw-r--r--src/js/NextSearch/Field/FieldNotMatches.js26
-rw-r--r--src/js/NextSearch/Search.js307
-rw-r--r--src/js/NextSearch/Sort/AbstractSearchSort.js75
-rw-r--r--src/js/NextSearch/Sort/SortAscending.js17
-rw-r--r--src/js/NextSearch/Sort/SortDescending.js17
19 files changed, 1032 insertions, 0 deletions
diff --git a/src/js/NextSearch/Condition/AbstractSearchCondition.js b/src/js/NextSearch/Condition/AbstractSearchCondition.js
new file mode 100644
index 0000000..1e85e1c
--- /dev/null
+++ b/src/js/NextSearch/Condition/AbstractSearchCondition.js
@@ -0,0 +1,80 @@
+export default class AbstractSearchCondition {
+
+ /**
+ * @return {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'abstract';
+ }
+
+ /**
+ * @return {Number}
+ */
+ get length() {
+ return this._conditions.length;
+ }
+
+ /**
+ *
+ * @return {(AbstractSearchCondition|AbstractSearchField)[]}
+ */
+ get conditions() {
+ return this._conditions;
+ }
+
+ /**
+ * @param {(AbstractSearchCondition|AbstractSearchField)[]} value
+ */
+ set conditions(value) {
+ this._conditions = value;
+ }
+
+ /**
+ *
+ * @param {AbstractSearchCondition|AbstractSearchField} conditions
+ */
+ constructor(...conditions) {
+ if(!Array.isArray(conditions)) conditions = [];
+
+ this._conditions = conditions;
+ }
+
+ /**
+ *
+ * @param {(AbstractSearchField|AbstractSearchCondition)} conditions
+ * @return {AbstractSearchCondition}
+ */
+ add(...conditions) {
+ this._conditions.push(...conditions);
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {Object} item
+ * @return {({checks: number, passed: boolean, matches: number}|{passed: false})}
+ */
+ evaluate(item) {
+ return {matches: 0, checks: 0, passed: false};
+ }
+
+ /**
+ *
+ * @return {{type: String, conditions: [], operator: String}}
+ */
+ export() {
+ let data = {
+ type : 'condition',
+ operator : this.TYPE,
+ conditions: []
+ };
+
+ for(let condition of this._conditions) {
+ data.conditions.push(condition.export());
+ }
+
+ return data;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Condition/AndCondition.js b/src/js/NextSearch/Condition/AndCondition.js
new file mode 100644
index 0000000..8e4d36a
--- /dev/null
+++ b/src/js/NextSearch/Condition/AndCondition.js
@@ -0,0 +1,25 @@
+import AbstractSearchCondition from '@js/NextSearch/Condition/AbstractSearchCondition';
+
+export default class AndCondition extends AbstractSearchCondition {
+
+ get TYPE() {
+ return 'and';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let result = {matches: 0, checks: 0, passed: true};
+
+ for(let condition of this._conditions) {
+ let partialResult = condition.evaluate(item);
+
+ if(!partialResult.passed) return {passed: false};
+ result.matches += partialResult.matches;
+ result.checks += partialResult.checks;
+ }
+
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Condition/ConditionBuilder.js b/src/js/NextSearch/Condition/ConditionBuilder.js
new file mode 100644
index 0000000..02d8d51
--- /dev/null
+++ b/src/js/NextSearch/Condition/ConditionBuilder.js
@@ -0,0 +1,79 @@
+import FieldBuilder from '@js/NextSearch/Field/FieldBuilder';
+import AndCondition from '@js/NextSearch/Condition/AndCondition';
+import OrCondition from '@js/NextSearch/Condition/OrCondition';
+import XorCondition from '@js/NextSearch/Condition/XorCondition';
+
+export default class ConditionBuilder {
+
+ /**
+ *
+ * @param {AbstractSearchCondition} condition
+ */
+ constructor(condition) {
+ this._condition = condition;
+ }
+
+ /**
+ *
+ * @param {(String|Function)} [param]
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ where(param) {
+ if(typeof param === 'string') {
+ return new FieldBuilder(param, this._condition, this);
+ } else if(typeof param === 'function') {
+ param(this);
+ }
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {(String|Function)} [param]
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ and(param) {
+ return this._createCondition(param, AndCondition);
+ }
+
+ /**
+ *
+ * @param {(String|Function)} [param]
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ or(param) {
+ return this._createCondition(param, OrCondition);
+ }
+
+ /**
+ *
+ * @param {(String|Function)} [param]
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ xor(param) {
+ return this._createCondition(param, XorCondition);
+ }
+
+ /**
+ *
+ * @param param
+ * @param conditionClass
+ * @returns {FieldBuilder|ConditionBuilder}
+ * @private
+ */
+ _createCondition(param, conditionClass) {
+ let condition = new conditionClass(),
+ builder = new ConditionBuilder(condition);
+
+ this._condition.add(condition);
+ if(typeof param === 'string') {
+ return builder.where(param);
+ } else if(typeof param === 'function') {
+ param(builder);
+ return this;
+ }
+
+ return builder;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Condition/OrCondition.js b/src/js/NextSearch/Condition/OrCondition.js
new file mode 100644
index 0000000..c711cf7
--- /dev/null
+++ b/src/js/NextSearch/Condition/OrCondition.js
@@ -0,0 +1,28 @@
+import AbstractSearchCondition from '@js/NextSearch/Condition/AbstractSearchCondition';
+
+export default class OrCondition extends AbstractSearchCondition {
+
+ get TYPE() {
+ return 'or';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let result = {matches: 0, checks: 0, passed: false};
+
+ for(let condition of this._conditions) {
+ let partialResult = condition.evaluate(item);
+
+ if(partialResult.passed) {
+ result.passed = true;
+ result.matches += partialResult.matches;
+ result.checks += partialResult.checks;
+ }
+ }
+
+ if(!result.passed) return {passed: false};
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Condition/XorCondition.js b/src/js/NextSearch/Condition/XorCondition.js
new file mode 100644
index 0000000..56a336e
--- /dev/null
+++ b/src/js/NextSearch/Condition/XorCondition.js
@@ -0,0 +1,31 @@
+import AbstractSearchCondition from '@js/NextSearch/Condition/AbstractSearchCondition';
+
+export default class XorCondition extends AbstractSearchCondition {
+
+ get TYPE() {
+ return 'xor';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let result = {matches: 0, checks: 0, passed: false};
+
+ for(let condition of this._conditions) {
+ let partialResult = condition.evaluate(item);
+
+ if(partialResult.passed) {
+ if(result.passed) return {passed: false};
+
+ result.passed = true;
+ result.matches = partialResult.matches;
+ result.checks = partialResult.checks;
+ }
+ }
+
+ if(!result.passed) return {passed: false};
+
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/AbstractSearchField.js b/src/js/NextSearch/Field/AbstractSearchField.js
new file mode 100644
index 0000000..4f1e0d2
--- /dev/null
+++ b/src/js/NextSearch/Field/AbstractSearchField.js
@@ -0,0 +1,41 @@
+export default class AbstractSearchField {
+
+ /**
+ * @return {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'abstract';
+ }
+
+ /**
+ *
+ * @param {String} field
+ * @param {*} value
+ */
+ constructor(field, value) {
+ this._name = field;
+ this._value = value;
+ }
+
+ /**
+ *
+ * @param {AbstractModel} item
+ * @return {({checks: number, passed: boolean, matches: number}|{passed: false})}
+ */
+ evaluate(item) {
+ return {matches: 0, checks: 0, passed: false};
+ }
+
+ /**
+ * @return {{field: String, type: String, value: *, operator: String}}
+ */
+ export() {
+ return {
+ type : 'field',
+ operator: this.TYPE,
+ value : this._value,
+ field : this._name
+ };
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldBuilder.js b/src/js/NextSearch/Field/FieldBuilder.js
new file mode 100644
index 0000000..8dbe743
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldBuilder.js
@@ -0,0 +1,147 @@
+import FieldEquals from '@js/NextSearch/Field/FieldEquals';
+import FieldContains from '@js/NextSearch/Field/FieldContains';
+import FieldMatches from '@js/NextSearch/Field/FieldMatches';
+import FieldIn from '@js/NextSearch/Field/FieldIn';
+import FieldNotEquals from '@js/NextSearch/Field/FieldNotEquals';
+import FieldNotContains from '@js/NextSearch/Field/FieldNotContains';
+import FieldNotMatches from '@js/NextSearch/Field/FieldNotMatches';
+import FieldNotIn from '@js/NextSearch/Field/FieldNotIn';
+
+export default class FieldBuilder {
+
+ /**
+ *
+ * @param {String} field
+ * @param {AbstractSearchCondition} condition
+ * @param {ConditionBuilder} query
+ */
+ constructor(field, condition, query) {
+ this._field = field;
+ this._query = query;
+ this._condition = condition;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ equals(value) {
+ this._condition.add(new FieldEquals(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ contains(value) {
+ this._condition.add(new FieldContains(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ matches(value) {
+ this._condition.add(new FieldMatches(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String[]} values
+ * @returns {FieldBuilder}
+ */
+ in(values) {
+ this._condition.add(new FieldIn(this._field, values));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ notEquals(value) {
+ this._condition.add(new FieldNotEquals(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ notContains(value) {
+ this._condition.add(new FieldNotContains(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String} value
+ * @returns {FieldBuilder}
+ */
+ notMatches(value) {
+ this._condition.add(new FieldNotMatches(this._field, value));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param {String[]} values
+ * @returns {FieldBuilder}
+ */
+ notIn(values) {
+ this._condition.add(new FieldNotIn(this._field, values));
+
+ return this;
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ where(...props) {
+ return this._query.where(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ and(...props) {
+ return this._query.and(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ or(...props) {
+ return this._query.or(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ xor(...props) {
+ return this._query.xor(...props);
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldContains.js b/src/js/NextSearch/Field/FieldContains.js
new file mode 100644
index 0000000..1ab66e1
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldContains.js
@@ -0,0 +1,24 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldContains extends AbstractSearchField {
+
+ get TYPE() {
+ return 'contains';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {passed: false};
+
+ let search = this._value.toString().toLowerCase();
+ if(value.toString().toLowerCase().indexOf(search) !== -1) {
+ return {matches: 1, checks: 1, passed: true};
+ }
+
+ return {passed: false};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldEquals.js b/src/js/NextSearch/Field/FieldEquals.js
new file mode 100644
index 0000000..08f694e
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldEquals.js
@@ -0,0 +1,20 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldEquals extends AbstractSearchField {
+
+ get TYPE() {
+ return 'equals';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {passed: false};
+ if(value === this._value) return {matches: 1, checks: 1, passed: true};
+
+ return {passed: false};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldIn.js b/src/js/NextSearch/Field/FieldIn.js
new file mode 100644
index 0000000..aeebc67
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldIn.js
@@ -0,0 +1,23 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldIn extends AbstractSearchField {
+
+ get TYPE() {
+ return 'in';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {passed: false};
+
+ if(this._value.indexOf(value) !== -1) {
+ return {matches: 1, checks: 1, passed: true};
+ }
+
+ return {passed: false};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldMatches.js b/src/js/NextSearch/Field/FieldMatches.js
new file mode 100644
index 0000000..18f14ef
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldMatches.js
@@ -0,0 +1,25 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldMatches extends AbstractSearchField {
+
+ get TYPE() {
+ return 'matches';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {passed: false};
+
+ let regexp = new RegExp(this._value, 'g'),
+ matches = regexp.exec(value);
+ if(matches.length > 1) {
+ return {matches: 1, checks: 1, passed: true};
+ }
+
+ return {passed: false};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldNotContains.js b/src/js/NextSearch/Field/FieldNotContains.js
new file mode 100644
index 0000000..d52d5f2
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldNotContains.js
@@ -0,0 +1,24 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldNotContains extends AbstractSearchField {
+
+ get TYPE() {
+ return 'notContains';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {matches: 1, checks: 1, passed: true};
+
+ let search = this._value.toString().toLowerCase();
+ if(value.toString().toLowerCase().indexOf(search) !== -1) {
+ return {passed: false};
+ }
+
+ return {matches: 1, checks: 1, passed: true};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldNotEquals.js b/src/js/NextSearch/Field/FieldNotEquals.js
new file mode 100644
index 0000000..21973b1
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldNotEquals.js
@@ -0,0 +1,20 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldNotEquals extends AbstractSearchField {
+
+ get TYPE() {
+ return 'notEquals';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {matches: 1, checks: 1, passed: true};
+ if(value === this._value) return {passed: false};
+
+ return {matches: 1, checks: 1, passed: true};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldNotIn.js b/src/js/NextSearch/Field/FieldNotIn.js
new file mode 100644
index 0000000..ffa73e5
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldNotIn.js
@@ -0,0 +1,23 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldNotIn extends AbstractSearchField {
+
+ get TYPE() {
+ return 'notIn';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {matches: 1, checks: 1, passed: true};
+
+ if(this._value.indexOf(value) !== -1) {
+ return {passed: false};
+ }
+
+ return {matches: 1, checks: 1, passed: true};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Field/FieldNotMatches.js b/src/js/NextSearch/Field/FieldNotMatches.js
new file mode 100644
index 0000000..8eb4ed2
--- /dev/null
+++ b/src/js/NextSearch/Field/FieldNotMatches.js
@@ -0,0 +1,26 @@
+import AbstractSearchField from '@js/NextSearch/Field/AbstractSearchField';
+
+export default class FieldNotMatches extends AbstractSearchField {
+
+ get TYPE() {
+ return 'notMatches';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ evaluate(item) {
+ let value = item.getProperty(this._name);
+
+ if(!value) return {matches: 1, checks: 1, passed: true};
+
+ let regexp = new RegExp(this._value, 'g');
+ let matches = regexp.exec(value);
+
+ if(matches.length > 1) {
+ return {passed: false};
+ }
+
+ return {matches: 1, checks: 1, passed: true};
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Search.js b/src/js/NextSearch/Search.js
new file mode 100644
index 0000000..43389c4
--- /dev/null
+++ b/src/js/NextSearch/Search.js
@@ -0,0 +1,307 @@
+import AndCondition from '@js/NextSearch/Condition/AndCondition';
+import OrCondition from '@js/NextSearch/Condition/OrCondition';
+import XorCondition from '@js/NextSearch/Condition/XorCondition';
+import SortAscending from '@js/NextSearch/Sort/SortAscending';
+import SortDescending from '@js/NextSearch/Sort/SortDescending';
+import ConditionBuilder from '@js/NextSearch/Condition/ConditionBuilder';
+
+export default class Search {
+
+ /**
+ *
+ * @return {Boolean}
+ */
+ get sorted() {
+ return this._sort.length !== 0;
+ }
+
+ /**
+ *
+ * @return {Boolean}
+ */
+ get filtered() {
+ return this._condition.length !== 0;
+ }
+
+ /**
+ *
+ * @return {AbstractSearchSort[]}
+ */
+ get sort() {
+ return this._sort;
+ }
+
+ /**
+ *
+ * @param {AbstractSearchSort[]} value
+ */
+ set sort(value) {
+ this._sort = value;
+ }
+
+ /**
+ *
+ * @return {AbstractSearchCondition[]}
+ */
+ get conditions() {
+ return this._condition.conditions;
+ }
+
+ /**
+ *
+ * @param {AbstractSearchCondition[]} value
+ */
+ set conditions(value) {
+ this._condition.conditions = value;
+ }
+
+ /**
+ * @return {Number}
+ */
+ get limit() {
+ return this._limit;
+ }
+
+ /**
+ * @param {Number} value
+ */
+ set limit(value) {
+ this._limit = value;
+ }
+
+ /**
+ * @return {Number}
+ */
+ get score() {
+ return this._score;
+ }
+
+ /**
+ * @param {Number} value
+ */
+ set score(value) {
+ this._score = value;
+ }
+
+ get type() {
+ return this._condition.TYPE;
+ }
+
+ /**
+ *
+ * @param value
+ */
+ set type(value) {
+ let conditions = this.conditions;
+ this._condition = this._makeCondition(value);
+ this._builder = new ConditionBuilder(this._condition);
+ this.conditions = conditions;
+ }
+
+ /**
+ *
+ * @param {String} [condition=and]
+ */
+ constructor(condition = 'and') {
+ this._condition = this._makeCondition(condition);
+ this._builder = new ConditionBuilder(this._condition);
+ this._sort = [];
+ this._score = 0;
+ this._limit = 0;
+ }
+
+ clear() {
+ this._condition.conditions = [];
+ this._sort = [];
+ this._score = 0;
+ this._limit = 0;
+ }
+
+ /**
+ * @api
+ * @public
+ *
+ * @param {String} field
+ * @param {Boolean} ascending
+ * @return {Search}
+ */
+ sortBy(field, ascending = false) {
+ if(ascending) {
+ this._sort.push(new SortAscending(field));
+ } else {
+ this._sort.push(new SortDescending(field));
+ }
+
+ return this;
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ where(...props) {
+ return this._builder.where(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ and(...props) {
+ return this._builder.and(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ or(...props) {
+ return this._builder.or(...props);
+ }
+
+ /**
+ *
+ * @param props
+ * @returns {FieldBuilder|ConditionBuilder}
+ */
+ xor(...props) {
+ return this._builder.xor(...props);
+ }
+
+ /**
+ * @api
+ * @public
+ *
+ * @param {(AbstractView|AbstractCollection)} view
+ *
+ * @return {AbstractModel[]}
+ */
+ execute(view) {
+ let items = view.getClone(),
+ matches = [];
+
+ for(let item of items) {
+ let result = this._condition.evaluate(item);
+
+ if(result.passed) {
+ let score = result.checks === 0 ? 0 : result.matches / result.checks;
+ if(score >= this._score) {
+ matches.push({item, score});
+ }
+ }
+ }
+
+ if(matches.length !== 0 && this._sort.length !== 0) {
+ matches.sort(
+ (a, b) => { return this._sortFunction(a, b); }
+ );
+ }
+
+ if(this._limit > 0 && matches.length > this._limit) {
+ matches = matches.splice(0, this._limit);
+ }
+
+ let result = [];
+ for(let match of matches) {
+ result.push(match.item);
+ }
+
+ return result;
+ }
+
+ /**
+ *
+ * @return {{score: Number, condition: {type: String, conditions: *[], operator: String}, limit: Number, sort: []}}
+ */
+ export() {
+ let data = {
+ condition: this._condition.export(),
+ limit : this._limit,
+ score : this._score,
+ sort : []
+ };
+
+ for(let sort of this._sort) {
+ data.sort.push(sort.export());
+ }
+
+ return data;
+ }
+
+ /**
+ *
+ * @return {{score: Number, condition: {type: String, conditions: *[], operator: String}, limit: Number, sort: []}}
+ */
+ import(data) {
+ if(data.hasOwnProperty('limit')) this._limit = data.limit;
+ if(data.hasOwnProperty('score')) this._score = data.score;
+
+ if(data.hasOwnProperty('sort')) {
+ this._sort = [];
+ for(let sort of data.sort) {
+ this.sortBy(sort.field, sort.ascending);
+ }
+ }
+
+ if(data.hasOwnProperty('condition')) {
+ this._condition = this._makeCondition(data.condition.operator);
+ this._builder = new ConditionBuilder(this._condition);
+
+ this._importConditions(this._builder, data.condition.conditions);
+ }
+ }
+
+ /**
+ *
+ * @param {ConditionBuilder} builder
+ * @param {Object[]} conditions
+ * @private
+ */
+ _importConditions(builder, conditions) {
+ for(let condition of conditions) {
+ if(condition.type === 'field') {
+ builder.where(condition.field)[condition.operator](condition.value);
+ } else {
+ builder[condition.operator]((condBuilder) => {
+ this._importConditions(condBuilder, condition.conditions);
+ });
+ }
+ }
+ }
+
+
+ /**
+ *
+ * @param {AbstractModel} a
+ * @param {AbstractModel} b
+ * @return {Number}
+ * @private
+ */
+ _sortFunction(a, b) {
+ for(let sort of this._sort) {
+ let result = sort.compare(a, b);
+ if(result !== 0) return result;
+ }
+
+ return 0;
+ }
+
+ /**
+ *
+ * @param type
+ * @return {OrCondition|XorCondition|AndCondition}
+ * @private
+ */
+ _makeCondition(type) {
+ if(type === 'or') {
+ return new OrCondition();
+ } else if(type === 'xor') {
+ return new XorCondition();
+ } else {
+ return new AndCondition();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Sort/AbstractSearchSort.js b/src/js/NextSearch/Sort/AbstractSearchSort.js
new file mode 100644
index 0000000..1e8352f
--- /dev/null
+++ b/src/js/NextSearch/Sort/AbstractSearchSort.js
@@ -0,0 +1,75 @@
+export default class AbstractSearchSort {
+
+ /**
+ * @return {String}
+ * @constructor
+ */
+ get TYPE() {
+ return 'abstract';
+ }
+
+ /**
+ * @return {String}
+ */
+ get field() {
+ return this._field;
+ }
+
+ /**
+ *
+ * @param {String} field
+ */
+ constructor(field) {
+ this._field = field;
+ }
+
+ /**
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Number}
+ */
+ compare(a, b) {
+ let valueA = this._getFieldValue(a),
+ valueB = this._getFieldValue(b);
+
+ return this._compareValues(valueA, valueB);
+ }
+
+ /**
+ *
+ * @return {{field: String, type: string, ascending: boolean}}
+ */
+ export() {
+ return {
+ type : 'sort',
+ ascending: this.TYPE === 'ascending',
+ field : this._field
+ };
+ }
+
+ /**
+ *
+ * @param {*} a
+ * @param {*} b
+ * @return {Number}
+ * @private
+ */
+ _compareValues(a, b) {
+ return 0;
+ }
+
+ /**
+ *
+ * @param {Object} item
+ * @return {*}
+ * @private
+ */
+ _getFieldValue(item) {
+ if(this._field === 'score') {
+ return item.score;
+ }
+
+ return item.item.getProperty(this._field);
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Sort/SortAscending.js b/src/js/NextSearch/Sort/SortAscending.js
new file mode 100644
index 0000000..b86d131
--- /dev/null
+++ b/src/js/NextSearch/Sort/SortAscending.js
@@ -0,0 +1,17 @@
+import AbstractSearchSort from '@js/NextSearch/Sort/AbstractSearchSort';
+
+export default class SortAscending extends AbstractSearchSort {
+
+ get TYPE() {
+ return 'ascending';
+ }
+
+ _compareValues(a, b) {
+ if(a === b) return 0;
+ if(typeof a === 'string') {
+ if(b === null) return -1;
+ return a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'});
+ }
+ return a < b ? -1 : 1;
+ }
+} \ No newline at end of file
diff --git a/src/js/NextSearch/Sort/SortDescending.js b/src/js/NextSearch/Sort/SortDescending.js
new file mode 100644
index 0000000..2b75024
--- /dev/null
+++ b/src/js/NextSearch/Sort/SortDescending.js
@@ -0,0 +1,17 @@
+import AbstractSearchSort from '@js/NextSearch/Sort/AbstractSearchSort';
+
+export default class SortDescending extends AbstractSearchSort {
+
+ get TYPE() {
+ return 'descending';
+ }
+
+ _compareValues(a, b) {
+ if(a === b) return 0;
+ if(typeof a === 'string') {
+ if(b === null) return 1;
+ return b.localeCompare(a, undefined, {numeric: true, sensitivity: 'base'});
+ }
+ return b < a ? -1 : 1;
+ }
+} \ No newline at end of file