From 61c3da14b400c62c5ee3dfc7cbf4f19bd56fea85 Mon Sep 17 00:00:00 2001 From: djmaze Date: Mon, 18 Jan 2021 16:47:10 +0100 Subject: [PATCH] Draft of new Filters design --- dev/Model/SieveScript.js | 81 +++++++++ dev/Settings/User/Filters.js | 68 +++---- dev/Stores/User/Sieve.js | 12 ++ dev/View/Popup/SieveScript.js | 169 ++++++++++++++++++ .../Providers/Filters/SieveStorage.php | 12 ++ .../app/localization/webmail/_source.en.yml | 1 + .../Views/User/PopupsSieveScript.html | 104 +++++++++++ .../templates/Views/User/SettingsFilters.html | 113 ++++-------- 8 files changed, 446 insertions(+), 114 deletions(-) create mode 100644 dev/Model/SieveScript.js create mode 100644 dev/Stores/User/Sieve.js create mode 100644 dev/View/Popup/SieveScript.js create mode 100644 snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html diff --git a/dev/Model/SieveScript.js b/dev/Model/SieveScript.js new file mode 100644 index 000000000..4dd1866b2 --- /dev/null +++ b/dev/Model/SieveScript.js @@ -0,0 +1,81 @@ +import ko from 'ko'; + +import { AbstractModel } from 'Knoin/AbstractModel'; +import { FilterModel } from 'Model/Filter'; + +class SieveScriptModel extends AbstractModel +{ + constructor() { + super(); + + this.addObservables({ + name: '', + nameError: false, + nameFocused: false, + + active: false, + + body: '', + + deleteAccess: false, + canBeDeleted: false + }); + + this.filters = ko.observableArray([]); + + this.addSubscribables({ + name: sValue => this.nameError(!sValue) + }); + } + + setFilters() { + /*let tree = */window.Sieve.parseScript(this.body); +// this.filters = ko.observableArray(tree); + } + + verify() { + if (!this.name()) { + this.nameError(true); + return false; + } + this.nameError(false); + return true; + } + + toJson() { + return { + name: this.name(), + active: this.active ? '1' : '0', + body: this.body, +// filters: this.filters() + }; + } + + /** + * Only 'rainloop.user' script supports filters + */ + allowFilters() { + return 'rainloop.user' === this.name(); + } + + /** + * @static + * @param {FetchJsonScript} json + * @returns {?SieveScriptModel} + */ + static reviveFromJson(json) { + const script = super.reviveFromJson(json); + if (script) { + script.filters([]); + if (script.allowFilters() && Array.isNotEmpty(json.filters)) { + script.filters( + json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v) + ); + } + } + return script; + } + +} + +export { SieveScriptModel, SieveScriptModel as default }; diff --git a/dev/Settings/User/Filters.js b/dev/Settings/User/Filters.js index 8dea23d2c..26205a2b7 100644 --- a/dev/Settings/User/Filters.js +++ b/dev/Settings/User/Filters.js @@ -1,13 +1,14 @@ import ko from 'ko'; -import { delegateRunOnDestroy } from 'Common/UtilsUser'; import { StorageResultType, Notification } from 'Common/Enums'; import { getNotification } from 'Common/Translator'; import FilterStore from 'Stores/User/Filter'; +import SieveStore from 'Stores/User/Sieve'; import Remote from 'Remote/User/Fetch'; import { FilterModel } from 'Model/Filter'; +import { SieveScriptModel } from 'Model/SieveScript'; import { showScreenPopup, command } from 'Knoin/Knoin'; @@ -15,6 +16,8 @@ class FiltersUserSettings { constructor() { this.modules = FilterStore.modules; this.filters = FilterStore.filters; + this.sieve = SieveStore; + this.scripts = SieveStore.scripts; ko.addObservablesTo(this, { inited: false, @@ -94,6 +97,7 @@ class FiltersUserSettings { Remote.filtersGet((result, data) => { this.filters.loading(false); this.serverError(false); + this.scripts([]); if (StorageResultType.Success === result && data && data.Result && Array.isArray(data.Result.Filters)) { this.inited(true); @@ -105,6 +109,17 @@ class FiltersUserSettings { this.modules(data.Result.Capa); + this.sieve.capa(data.Result.Capa); +/* + this.scripts( + data.Result.Scripts.map(aItem => SieveScriptModel.reviveFromJson(aItem)).filter(v => v) + ); +*/ + Object.values(data.Result.Scripts).forEach(value => { + value = SieveScriptModel.reviveFromJson(value); + value && this.scripts.push(value) + }); + this.filterRaw(data.Result.Scripts['rainloop.user.raw'].body); this.filterRaw.capa(data.Result.Capa.join(' ')); this.filterRaw.active(data.Result.Scripts['rainloop.user.raw'].active); @@ -112,6 +127,9 @@ class FiltersUserSettings { } else { this.filters([]); this.modules({}); + + this.sieve.capa([]); + this.filterRaw(''); this.filterRaw.capa({}); @@ -126,50 +144,38 @@ class FiltersUserSettings { } } - deleteFilter(filter) { - this.filters.remove(filter); - delegateRunOnDestroy(filter); - } - - addFilter() { - const filter = new FilterModel(); - - filter.generateID(); - showScreenPopup(require('View/Popup/Filter'), [ - filter, + addScript() { + const script = new SieveScriptModel(); + showScreenPopup(require('View/Popup/SieveScript'), [ + script, () => { - this.filters.push(filter); - this.filterRaw.active(false); + if (!this.scripts[script.name]) { + this.scripts[script.name] = script.name; + } }, false ]); } - editFilter(filter) { - const clonedFilter = filter.cloneSelf(); - - showScreenPopup(require('View/Popup/Filter'), [ - clonedFilter, + editScript(script) { + showScreenPopup(require('View/Popup/SieveScript'), [ + script, () => { - const filters = this.filters(), - index = filters.indexOf(filter); - - if (-1 < index && filters[index]) { - delegateRunOnDestroy(filters[index]); - filters[index] = clonedFilter; - - this.filters(filters); - this.haveChanges(true); - } + // TODO on save }, true ]); } + deleteScript() { + // TODO + } + onBuild(oDom) { oDom.addEventListener('click', event => { - const el = event.target.closestWithin('.filter-item .e-action', oDom); - el && ko.dataFor(el) && this.editFilter(ko.dataFor(el)); + const el = event.target.closestWithin('.script-item .e-action', oDom), + script = el && ko.dataFor(el); + script && this.editScript(script); }); } diff --git a/dev/Stores/User/Sieve.js b/dev/Stores/User/Sieve.js new file mode 100644 index 000000000..c2b1ef06c --- /dev/null +++ b/dev/Stores/User/Sieve.js @@ -0,0 +1,12 @@ +import ko from 'ko'; + +class SieveUserStore { + constructor() { + // capabilities + this.capa = ko.observableArray([]); + // Sieve scripts SieveScriptModel + this.scripts = ko.observableArray([]); + } +} + +export default new SieveUserStore(); diff --git a/dev/View/Popup/SieveScript.js b/dev/View/Popup/SieveScript.js new file mode 100644 index 000000000..a0fc4816e --- /dev/null +++ b/dev/View/Popup/SieveScript.js @@ -0,0 +1,169 @@ +import ko from 'ko'; + +import { delegateRunOnDestroy } from 'Common/UtilsUser'; +import { StorageResultType, Notification } from 'Common/Enums'; +import { getNotification } from 'Common/Translator'; + +import Remote from 'Remote/User/Fetch'; +import FilterStore from 'Stores/User/Filter'; +import FilterModel from 'Model/Filter'; + +import { popup, showScreenPopup, command } from 'Knoin/Knoin'; +import { AbstractViewNext } from 'Knoin/AbstractViewNext'; + +@popup({ + name: 'View/Popup/SieveScript', + templateID: 'PopupsSieveScript' +}) +class SieveScriptPopupView extends AbstractViewNext { + constructor() { + super(); + + this.modules = FilterStore.modules; + this.filters = FilterStore.filters; + + this.script = { + filters: FilterStore.filters, + saving: () => FilterStore.filters.saving() + }; + + ko.addObservablesTo(this, { + isNew: true, + inited: false, + serverError: false, + serverErrorDesc: '', + haveChanges: false, + + saveErrorText: '' + }); + + this.serverError.subscribe(value => value || this.serverErrorDesc(''), this); + + this.filterRaw = FilterStore.raw; + this.filterRaw.capa = FilterStore.capa; + this.filterRaw.active = ko.observable(false); + this.filterRaw.allow = ko.observable(false); + this.filterRaw.error = ko.observable(false); + + this.filterForDeletion = ko.observable(null).deleteAccessHelper(); + + this.filters.subscribe(() => this.haveChanges(true)); + + this.filterRaw.subscribe(() => { + this.haveChanges(true); + this.filterRaw.error(false); + }); + + this.haveChanges.subscribe(() => this.saveErrorText('')); + + this.filterRaw.active.subscribe(() => { + this.haveChanges(true); + this.filterRaw.error(false); + }); + } + + @command((self) => self.haveChanges()) + saveScriptCommand() { + if (!this.filters.saving()) { + if (this.filterRaw.active() && !this.filterRaw().trim()) { + this.filterRaw.error(true); + return false; + } + + this.filters.saving(true); + this.saveErrorText(''); + + Remote.filtersSave( + (result, data) => { + this.filters.saving(false); + + if (StorageResultType.Success === result && data && data.Result) { + this.haveChanges(false); + } else if (data && data.ErrorCode) { + this.saveErrorText(data.ErrorMessageAdditional || getNotification(data.ErrorCode)); + } else { + this.saveErrorText(getNotification(Notification.CantSaveFilters)); + } + }, + this.filters(), + this.filterRaw(), + this.filterRaw.active() + ); + } + + return true; + } + + deleteFilter(filter) { + this.filters.remove(filter); + delegateRunOnDestroy(filter); + } + + addFilter() { + const filter = new FilterModel(); + + filter.generateID(); + showScreenPopup(require('View/Popup/Filter'), [ + filter, + () => { + this.filters.push(filter); + this.filterRaw.active(false); + }, + false + ]); + } + + editFilter(filter) { + const clonedFilter = filter.cloneSelf(); + + showScreenPopup(require('View/Popup/Filter'), [ + clonedFilter, + () => { + const filters = this.filters(), + index = filters.indexOf(filter); + + if (-1 < index && filters[index]) { + delegateRunOnDestroy(filters[index]); + filters[index] = clonedFilter; + + this.filters(filters); + this.haveChanges(true); + } + }, + true + ]); + } + + onBuild(oDom) { + oDom.addEventListener('click', event => { + const el = event.target.closestWithin('.filter-item .e-action', oDom), + filter = el && ko.dataFor(el); + filter && this.editFilter(ko.dataFor(el)); + }); + } + + onShow(oScript, fTrueCallback, bEdit) { + this.clearPopup(); + + this.fTrueCallback = fTrueCallback; + this.filters(oScript.filters()); + + this.isNew(!bEdit); + + if (!bEdit && oScript) { +// oScript.nameFocused(true); + } + } + + onShowWithDelay() { + } + + clearPopup() { + this.isNew(true); + this.fTrueCallback = null; + this.filters([]); + } + +} + +export { SieveScriptPopupView, SieveScriptPopupView as default }; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php index 29bad1452..eabd77d81 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php @@ -64,10 +64,19 @@ class SieveStorage implements FiltersInterface foreach ($aList as $name => $active) { if ($name != self::SIEVE_FILE_NAME) { $aScripts[$name] = array( + '@Object' => 'Object/SieveScript', 'name' => $name, 'active' => $active, 'body' => $oSieveClient->GetScript($name) // \trim() ? ); + } else { + $aScripts[$name] = array( + '@Object' => 'Object/SieveScript', + 'name' => $name, + 'active' => $active, + 'body' => $oSieveClient->GetScript($name), // \trim() ? + 'filters' => $aFilters + ); } } } @@ -77,12 +86,15 @@ class SieveStorage implements FiltersInterface if (!isset($aList[self::SIEVE_FILE_NAME_RAW])) { $aScripts[$name] = array( + '@Object' => 'Object/SieveScript', 'name' => self::SIEVE_FILE_NAME_RAW, 'active' => false, 'body' => '' ); } + \ksort($aScripts); + return array( 'RawIsAllow' => $bAllowRaw, 'Filters' => $aFilters, diff --git a/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml b/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml index eecbd76a1..6db80cdf9 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml @@ -431,6 +431,7 @@ en: BUTTON_BACK: "Back" SETTINGS_FILTERS: LEGEND_FILTERS: "Filters" + LEGEND_SIEVE_SCRIPT: "Sieve Script" BUTTON_SAVE: "Save" BUTTON_ADD_FILTER: "Add a Filter" BUTTON_DELETE: "Delete" diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html new file mode 100644 index 000000000..fff832c6b --- /dev/null +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html @@ -0,0 +1,104 @@ + diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsFilters.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsFilters.html index ab642ef6d..7ed360176 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsFilters.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsFilters.html @@ -6,37 +6,6 @@ -
- -
-
-
-
-
- -    - -
-
-
@@ -46,60 +15,38 @@
-
-
-
-
-
-						:
-						
-					
- -
-
-
-
- - - - - - - - - - - - - - - - - -
- - - - - - - -    - - - - - - - - - -
-
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +