Sieve filters (interface/vacation action)

This commit is contained in:
RainLoop Team 2015-01-20 01:20:12 +04:00
parent 54edeaa645
commit 68cc322bd5
26 changed files with 235 additions and 104 deletions

View file

@ -252,9 +252,7 @@
Utils.initNotificationLanguage();
}, null);
_.delay(function () {
Utils.windowResize();
}, 1000);
_.delay(Utils.windowResizeCallback, 1000);
ssm.addState({
'id': 'mobile',

View file

@ -306,6 +306,7 @@
'None': 'None',
'MoveTo': 'MoveTo',
'Discard': 'Discard',
'Vacation': 'Vacation',
'Forward': 'Forward'
};

View file

@ -52,6 +52,10 @@
}
}, 50);
Utils.windowResizeCallback = function () {
Utils.windowResize();
};
/**
* @param {(string|number)} mValue
* @param {boolean=} bIncludeZero

4
dev/External/ko.js vendored
View file

@ -266,9 +266,7 @@
'keyboard': false,
'show': ko.unwrap(fValueAccessor())
})
.on('shown.koModal', function () {
Utils.windowResize();
})
.on('shown.koModal', Utils.windowResizeCallback)
.find('.close').on('click.koModal', function () {
fValueAccessor()(false);
});

View file

@ -9,6 +9,9 @@
Enums = require('Common/Enums'),
Utils = require('Common/Utils'),
Cache = require('Storage/User/Cache'),
FilterConditionModel = require('Model/FilterCondition'),
AbstractModel = require('Knoin/AbstractModel')
@ -29,13 +32,15 @@
this.name.error = ko.observable(false);
this.name.focused = ko.observable(false);
this.raw = ko.observable('');
this.conditions = ko.observableArray([]);
this.conditionsType = ko.observable(Enums.FilterRulesType.Any);
// Actions
this.actionValue = ko.observable('');
this.actionValue.error = ko.observable(false);
this.actionValueSecond = ko.observable('');
this.actionMarkAsRead = ko.observable(false);
this.actionSkipOthers = ko.observable(false);
@ -44,8 +49,43 @@
this.actionType = ko.observable(Enums.FiltersAction.MoveTo);
this.actionType.subscribe(function (sValue) {
this.actionType.subscribe(function () {
this.actionValue('');
this.actionValue.error(false);
this.actionValueSecond('');
}, this);
var fGetRealFolderName = function (sFolderFullNameRaw) {
var oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
return oFolder ? oFolder.fullName.replace(
'.' === oFolder.delimiter ? /\./ : /[\\\/]+/, ' / ') : sFolderFullNameRaw;
};
this.nameSub = ko.computed(function () {
var
sResult = '',
sActionValue = this.actionValue()
;
switch (this.actionType())
{
case Enums.FiltersAction.MoveTo:
sResult = 'MoveTo $i18n "' + fGetRealFolderName(sActionValue) + '"';
break;
case Enums.FiltersAction.Forward:
sResult = 'Forward @i18n "' + sActionValue + '"';
break;
case Enums.FiltersAction.Vacation:
sResult = 'Vacation message @i18n';
break;
case Enums.FiltersAction.Discard:
sResult = 'Discard @i18n';
break;
}
return sResult ? '(' + sResult + ')' : '';
}, this);
this.actionTemplate = ko.computed(function () {
@ -55,17 +95,19 @@
{
default:
case Enums.FiltersAction.MoveTo:
sTemplate = 'SettingsFiltersActionValueAsFolders';
sTemplate = 'SettingsFiltersActionMoveToFolder';
break;
// case Enums.FiltersAction.Forward:
// sTemplate = 'SettingsFiltersActionWithValue';
// break;
case Enums.FiltersAction.Forward:
sTemplate = 'SettingsFiltersActionForward';
break;
case Enums.FiltersAction.Vacation:
sTemplate = 'SettingsFiltersActionVacation';
break;
case Enums.FiltersAction.None:
sTemplate = 'SettingsFiltersActionNone';
break;
case Enums.FiltersAction.Discard:
sTemplate = 'SettingsFiltersActionNoValue';
sTemplate = 'SettingsFiltersActionDiscard';
break;
}
@ -73,14 +115,16 @@
}, this);
this.regDisposables(this.conditions.subscribe(function () {
Utils.windowResize();
}));
this.regDisposables(this.conditions.subscribe(Utils.windowResizeCallback));
this.regDisposables(this.name.subscribe(function (sValue) {
this.name.error('' === sValue);
}, this));
this.regDisposables(this.actionValue.subscribe(function (sValue) {
this.actionValue.error('' === sValue);
}, this));
this.regDisposables([this.actionTemplate]);
this.deleteAccess = ko.observable(false);
@ -94,6 +138,40 @@
this.id = Utils.fakeMd5();
};
FilterModel.prototype.verify = function ()
{
if ('' === this.name())
{
this.name.error(true);
return false;
}
if ('' === this.actionValue())
{
if (-1 < Utils.inArray(this.actionType(), [
Enums.FiltersAction.MoveTo,
Enums.FiltersAction.Forward,
Enums.FiltersAction.Vacation
]))
{
this.actionValue.error(true);
return false;
}
}
if (Enums.FiltersAction.Forward === this.actionType() &&
-1 === this.actionValue().indexOf('@'))
{
this.actionValue.error(true);
return false;
}
this.name.error(false);
this.actionValue.error(false);
return true;
};
FilterModel.prototype.toJson = function ()
{
return {
@ -106,10 +184,9 @@
}),
'ActionValue': this.actionValue(),
'ActionValueSecond': this.actionValueSecond(),
'ActionType': this.actionType(),
'Raw': this.raw(),
'MarkAsRead': this.actionMarkAsRead() ? '1' : '0',
'KeepForward': this.keepForward() ? '1' : '0',
'SkipOthers': this.actionSkipOthers() ? '1' : '0'
@ -152,17 +229,18 @@
oClone.name(this.name());
oClone.name.error(this.name.error());
oClone.raw(this.raw());
oClone.conditionsType(this.conditionsType());
oClone.actionMarkAsRead(this.actionMarkAsRead());
oClone.actionSkipOthers(this.actionSkipOthers());
oClone.actionValue(this.actionValue());
oClone.actionType(this.actionType());
oClone.actionValue(this.actionValue());
oClone.actionValue.error(this.actionValue.error());
oClone.actionValueSecond(this.actionValueSecond());
oClone.keepForward(this.keepForward());
oClone.conditions(_.map(this.conditions(), function (oCondition) {

View file

@ -99,15 +99,9 @@
MailBoxUserScreen.prototype.onStart = function ()
{
var
fResizeFunction = function () {
Utils.windowResize();
}
;
Data.folderList.subscribe(fResizeFunction);
Data.messageList.subscribe(fResizeFunction);
Data.message.subscribe(fResizeFunction);
Data.folderList.subscribe(Utils.windowResizeCallback);
Data.messageList.subscribe(Utils.windowResizeCallback);
Data.message.subscribe(Utils.windowResizeCallback);
Data.layout.subscribe(function (nValue) {

View file

@ -28,9 +28,7 @@
this.filters.loading = ko.observable(false).extend({'throttle': 200});
this.filters.saving = ko.observable(false).extend({'throttle': 200});
this.filters.subscribe(function () {
Utils.windowResize();
});
this.filters.subscribe(Utils.windowResizeCallback);
this.processText = ko.computed(function () {
return this.filters.loading() ? Utils.i18n('SETTINGS_FILTERS/LOADING_PROCESS') : '';
@ -136,7 +134,7 @@
self.haveChanges(true);
}
}, false]);
}, true]);
};
FiltersUserSettings.prototype.onBuild = function (oDom)

View file

@ -239,11 +239,11 @@
}, this);
this.folderMenuForFilters = ko.computed(function () {
return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(), [
'INBOX'
], null, null, null, null, function (oItem) {
return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(),
['INBOX'], [['', '']], null, null, null, function (oItem) {
return oItem ? oItem.localName() : '';
});
}
);
}, this);
// message list

View file

@ -21,13 +21,17 @@
margin-right: 5px;
}
.filter-name {
.filter-name, .filter-sub-name {
display: inline-block;
word-break: break-all;
box-sizing: border-box;
line-height: 22px;
cursor: pointer;
}
.filter-sub-name {
color: #aaa;
}
}
.filter-item {

View file

@ -51,9 +51,6 @@
var
self = this,
fResizeSub = function () {
Utils.windowResize();
},
fCcAndBccCheckHelper = function (aValue) {
if (false === self.showCcAndBcc() && 0 < aValue.length)
{
@ -134,8 +131,8 @@
this.attachmentsPlace = ko.observable(false);
this.attachments.subscribe(fResizeSub);
this.attachmentsPlace.subscribe(fResizeSub);
this.attachments.subscribe(Utils.windowResizeCallback);
this.attachmentsPlace.subscribe(Utils.windowResizeCallback);
this.attachmentsInErrorCount.subscribe(function (iN) {
if (0 === iN)

View file

@ -178,13 +178,8 @@
this.reloadContactList();
}, this);
this.contacts.subscribe(function () {
Utils.windowResize();
}, this);
this.viewProperties.subscribe(function () {
Utils.windowResize();
}, this);
this.contacts.subscribe(Utils.windowResizeCallback);
this.viewProperties.subscribe(Utils.windowResizeCallback);
this.contactsChecked = ko.computed(function () {
return _.filter(this.contacts(), function (oItem) {

View file

@ -30,31 +30,33 @@
this.fTrueCallback = null;
this.filter = ko.observable(null);
this.selectedFolderValue = ko.observable(Consts.Values.UnuseOptionValue);
this.folderSelectList = Data.folderMenuForFilters;
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
this.folderSelectList = Data.folderMenuForFilters;
this.selectedFolderValue = ko.observable('');
this.selectedFolderValue.subscribe(function() {
if (this.filter())
{
this.filter().actionValue.error(false);
}
}, this);
this.saveFilter = Utils.createCommand(this, function () {
if (this.filter())
{
if ('' === this.filter().name())
if (Enums.FiltersAction.MoveTo === this.filter().actionType())
{
this.filter().actionValue(this.selectedFolderValue());
}
if (!this.filter().verify())
{
this.filter().name.error(true);
return false;
}
if (this.fTrueCallback)
{
if (Enums.FiltersAction.MoveTo === this.filter().actionType())
{
this.filter().actionValue(this.selectedFolderValue());
}
else if (Enums.FiltersAction.Forward !== this.filter().actionType())
{
this.filter().actionValue('');
}
this.fTrueCallback(this.filter());
}
@ -68,9 +70,10 @@
});
this.actionTypeOptions = [
{'id': Enums.FiltersAction.None, 'name': 'None @i18n'},
// {'id': Enums.FiltersAction.None, 'name': 'None @i18n'},
{'id': Enums.FiltersAction.MoveTo, 'name': ' Move to @i18n'},
{'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'},
{'id': Enums.FiltersAction.Vacation, 'name': 'Vacation message @i18n'},
{'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'}
];

View file

@ -292,12 +292,7 @@
Utils.windowResize();
});
this.messageLoadingThrottle.subscribe(function (bV) {
if (bV)
{
Utils.windowResize();
}
});
this.messageLoadingThrottle.subscribe(Utils.windowResizeCallback);
this.goUpCommand = Utils.createCommand(this, function () {
Events.pub('mailbox.message-list.selector.go-up');

View file

@ -39,6 +39,11 @@ class Filter
*/
private $sActionValue;
/**
* @var string
*/
private $sActionValueSecond;
/**
* @var bool
*/
@ -72,6 +77,7 @@ class Filter
$this->sActionType = \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
$this->sActionValue = '';
$this->sActionValueSecond = '';
$this->bMarkAsRead = false;
$this->bSkipOthers = false;
@ -134,6 +140,14 @@ class Filter
return $this->sActionValue;
}
/**
* @return string
*/
public function ActionValueSecond()
{
return $this->sActionValueSecond;
}
/**
* @return bool
*/
@ -201,6 +215,7 @@ class Filter
\RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
$this->sActionValue = isset($aFilter['ActionValue']) ? $aFilter['ActionValue'] : '';
$this->sActionValueSecond = isset($aFilter['ActionValueSecond']) ? $aFilter['ActionValueSecond'] : '';
$this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? $aFilter['MarkAsRead'] : false;
$this->bSkipOthers = isset($aFilter['SkipOthers']) ? $aFilter['SkipOthers'] : false;
@ -239,6 +254,7 @@ class Filter
'ConditionsType' => $this->ConditionsType(),
'ActionType' => $this->ActionType(),
'ActionValue' => $this->ActionValue(),
'ActionValueSecond' => $this->ActionValueSecond(),
'MarkAsRead' => $this->MarkAsRead(),
'SkipOthers' => $this->SkipOthers()
);

View file

@ -7,5 +7,6 @@ class ActionType
const NONE = 'None';
const MOVE_TO = 'MoveTo';
const DISCARD = 'Discard';
const VACATION = 'Vacation';
const FORWARD = 'Forward';
}

View file

@ -195,6 +195,28 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
$aResult[] = $sTab.'discard;';
$aResult[] = $sTab.'stop;';
break;
case \RainLoop\Providers\Filters\Enumerations\ActionType::VACATION:
$sValue = \trim($oFilter->ActionValue());
$sValueSecond = \trim($oFilter->ActionValueSecond());
if (0 < \strlen($sValue))
{
$aCapa['vacation'] = true;
$sSubject = '';
if (0 < \strlen($sValueSecond))
{
$sSubject = ':subject "'.$this->quote(
\preg_replace('/[\s]+/u', ' ', $sValueSecond)).'" ';
}
$aResult[] = $sTab.'vacation :days 1 '.$sSubject.'"'.$this->quote($sValue).'";';
$aResult[] = $sTab.'stop;';
}
else
{
$aResult[] = $sTab.'# @Error (vacation): empty action value';
}
break;
case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD:
$sValue = $oFilter->ActionValue();
if (0 < \strlen($sValue))

View file

@ -14,7 +14,7 @@
<div class="control-group" data-bind="css: {'error': name.error}">
<div class="controls">
<input type="text" class="span5" data-bind="value: name, hasFocus: name.focused" placeholder="Name"
<input type="text" class="span5" data-bind="value: name, hasFocus: name.focused" placeholder="Name @i18n"
autocorrect="off" autocapitalize="off" spellcheck="false" />
</div>
</div>
@ -51,17 +51,10 @@
<br />
<div class="legend">
<span>
Actions
Actions @i18n
</span>
</div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'Mark as read @i18n',
value: actionMarkAsRead
}
}"></div>
<br />
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>
<div data-bind="template: {'name': actionTemplate()}"></div>
</div>
</div>

View file

@ -42,6 +42,8 @@
</td>
<td class="e-action">
<span class="filter-name" data-bind="text: name()"></span>
&nbsp;
<span class="filter-sub-name" data-bind="text: nameSub()"></span>
</td>
<td>
<a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oFilter) { $root.deleteFilter(oFilter); }">

View file

@ -1,11 +1,23 @@
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp;
<input type="text" data-bind="value: actionValue" />
&nbsp;
<div data-bind="component: {
<div class="control-group" data-bind="css: {'error': actionValue.error}">
<div class="controls">
<input type="text" data-bind="value: actionValue" placeholder="Email @i18n" />
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'keep in INBOX @i18n',
value: keepForward
}
}"></div>
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'Mark as read @i18n',
value: actionMarkAsRead
}
}"></div>
</div>
</div>

View file

@ -0,0 +1,17 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}">
<div class="controls">
<select data-bind="options: $root.folderSelectList, value: $root.selectedFolderValue,
optionsText: 'name', optionsValue: 'id', optionsAfterRender: $root.defautOptionsAfterRender"></select>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'Mark as read @i18n',
value: actionMarkAsRead
}
}"></div>
</div>
</div>

View file

@ -1 +0,0 @@
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>

View file

@ -0,0 +1,11 @@
<br />
<div class="control-group">
<div class="controls">
<input type="text" class="span5" data-bind="value: actionValueSecond" placeholder="Subject (optional) @i18n" />
</div>
</div>
<div class="control-group" data-bind="css: {'error': actionValue.error}">
<div class="controls">
<textarea class="span5" data-bind="value: actionValue" style="height: 100px;" placeholder="Message @i18n"></textarea>
</div>
</div>

View file

@ -1,4 +0,0 @@
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp;
<select data-bind="options: $root.folderSelectList, value: $root.selectedFolderValue,
optionsText: 'name', optionsValue: 'id', optionsAfterRender: $root.defautOptionsAfterRender"></select>

View file

@ -1,3 +0,0 @@
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>
&nbsp;
<input type="text" data-bind="value: actionValue" />