mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-18 02:54:24 +08:00
Added: rainloop.user filters to sieve script
This commit is contained in:
parent
76bc8aa107
commit
c7d6426c88
5 changed files with 260 additions and 13 deletions
|
@ -176,20 +176,21 @@ class FilterModel extends AbstractModel {
|
|||
|
||||
toJson() {
|
||||
return {
|
||||
// '@Object': 'Object/Filter',
|
||||
ID: this.id,
|
||||
Enabled: this.enabled() ? '1' : '0',
|
||||
Name: this.name(),
|
||||
ConditionsType: this.conditionsType(),
|
||||
Conditions: this.conditions().map(item => item.toJson()),
|
||||
ConditionsType: this.conditionsType(),
|
||||
|
||||
ActionType: this.actionType(),
|
||||
ActionValue: this.actionValue(),
|
||||
ActionValueSecond: this.actionValueSecond(),
|
||||
ActionValueThird: this.actionValueThird(),
|
||||
ActionValueFourth: this.actionValueFourth(),
|
||||
ActionType: this.actionType(),
|
||||
|
||||
Stop: this.actionNoStop() ? '0' : '1',
|
||||
Keep: this.actionKeep() ? '1' : '0',
|
||||
Stop: this.actionNoStop() ? '0' : '1',
|
||||
MarkAsRead: this.actionMarkAsRead() ? '1' : '0'
|
||||
};
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ class FilterConditionModel extends AbstractModel {
|
|||
|
||||
toJson() {
|
||||
return {
|
||||
// '@Object': 'Object/FilterCondition',
|
||||
Field: this.field(),
|
||||
Type: this.type(),
|
||||
Value: this.value(),
|
||||
|
|
|
@ -3,6 +3,241 @@ import ko from 'ko';
|
|||
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
import { FilterModel } from 'Model/Filter';
|
||||
|
||||
// collectionToFileString
|
||||
function filtersToSieveScript(aFilters)
|
||||
{
|
||||
let eol = '\r\n',
|
||||
split = /.{0,74}/g,
|
||||
require = {},
|
||||
parts = [
|
||||
'# This is SnappyMail sieve script.',
|
||||
'# Please don\'t change anything here.',
|
||||
'# RAINLOOP:SIEVE',
|
||||
''
|
||||
];
|
||||
|
||||
const quote = sValue => '"' + sValue.trim().replace(/(\\|")/, '\\\\$1') + '"';
|
||||
const StripSpaces = sValue => sValue.replace(/\s+/, ' ').trim();
|
||||
|
||||
// conditionToSieveScript
|
||||
const conditionToString = (condition, require) =>
|
||||
{
|
||||
let result = '',
|
||||
type = condition.type(),
|
||||
field = condition.field(),
|
||||
value = condition.value().trim(),
|
||||
valueSecond = condition.valueSecond().trim();
|
||||
|
||||
if (value.length && ('Header' !== field || valueSecond.length)) {
|
||||
switch (type)
|
||||
{
|
||||
case 'NotEqualTo':
|
||||
result += 'not ';
|
||||
type = ':is';
|
||||
break;
|
||||
case 'EqualTo':
|
||||
type = ':is';
|
||||
break;
|
||||
case 'NotContains':
|
||||
result += 'not ';
|
||||
type = ':contains';
|
||||
break;
|
||||
case 'Text':
|
||||
case 'Raw':
|
||||
case 'Over':
|
||||
case 'Under':
|
||||
case 'Contains':
|
||||
type = ':' + type.toLowerCase();
|
||||
break;
|
||||
case 'Regex':
|
||||
type = ':regex';
|
||||
require.regex = 1;
|
||||
break;
|
||||
default:
|
||||
return '/* @Error: unknown type value ' + type + '*/ false';
|
||||
}
|
||||
|
||||
switch (field)
|
||||
{
|
||||
case 'From':
|
||||
result += 'header ' + type + ' ["From"]';
|
||||
break;
|
||||
case 'Recipient':
|
||||
result += 'header ' + type + ' ["To", "CC"]';
|
||||
break;
|
||||
case 'Subject':
|
||||
result += 'header ' + type + ' ["Subject"]';
|
||||
break;
|
||||
case 'Header':
|
||||
result += 'header ' + type + ' [' + quote(valueSecond) + ']';
|
||||
break;
|
||||
case 'Body':
|
||||
// :text :raw :content
|
||||
result += 'body ' + type + ' :contains';
|
||||
require.body = 1;
|
||||
break;
|
||||
case 'Size':
|
||||
result += 'size ' + type;
|
||||
break;
|
||||
default:
|
||||
return '/* @Error: unknown field value ' + field + ' */ false';
|
||||
}
|
||||
|
||||
if (('From' === field || 'Recipient' === field) && value.includes(','))
|
||||
{
|
||||
result += ' [' + value.split(',').map(value => quote(value)).join(', ').trim() + ']';
|
||||
}
|
||||
else if ('Size' === field)
|
||||
{
|
||||
result += ' ' + value;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += ' ' + quote(value);
|
||||
}
|
||||
|
||||
return StripSpaces(result);
|
||||
}
|
||||
|
||||
return '/* @Error: empty condition value */ false';
|
||||
};
|
||||
|
||||
// filterToSieveScript
|
||||
const filterToString = (filter, require) =>
|
||||
{
|
||||
let sTab = ' ',
|
||||
block = true,
|
||||
result = [];
|
||||
|
||||
const errorAction = type => result.push(sTab + '# @Error (' + type + '): empty action value');
|
||||
|
||||
// Conditions
|
||||
let conditions = filter.conditions();
|
||||
if (1 < conditions.length) {
|
||||
result.push('Any' === filter.conditionsType()
|
||||
? 'if anyof('
|
||||
: 'if allof('
|
||||
);
|
||||
result.push(conditions.map(condition => sTab + conditionToString(condition, require)).join(',' + eol));
|
||||
result.push(')');
|
||||
} else if (conditions.length) {
|
||||
result.push('if ' + conditionToString(conditions[0], require));
|
||||
} else {
|
||||
block = false;
|
||||
}
|
||||
|
||||
// actions
|
||||
if (block) {
|
||||
result.push('{');
|
||||
} else {
|
||||
sTab = '';
|
||||
}
|
||||
|
||||
if (filter.actionMarkAsRead() && ['None','MoveTo','Forward'].includes(filter.actionType())) {
|
||||
require.imap4flags = 1;
|
||||
result.push(sTab + 'addflag "\\\\Seen";');
|
||||
}
|
||||
|
||||
switch (filter.actionType())
|
||||
{
|
||||
// case FiltersAction.None:
|
||||
case 'None':
|
||||
break;
|
||||
case 'Discard':
|
||||
result.push(sTab + 'discard;');
|
||||
break;
|
||||
case 'Vacation': {
|
||||
let value = filter.actionValue().trim();
|
||||
if (value.length) {
|
||||
require.vacation = 1;
|
||||
|
||||
let days = 1,
|
||||
subject = '',
|
||||
addresses = '',
|
||||
paramValue = filter.actionValueSecond().trim();
|
||||
|
||||
if (paramValue.length) {
|
||||
subject = ':subject ' + quote(StripSpaces(paramValue)) + ' ';
|
||||
}
|
||||
|
||||
paramValue = filter.actionValueThird().trim();
|
||||
if (paramValue.length) {
|
||||
days = Math.max(1, parseInt(paramValue, 10));
|
||||
}
|
||||
|
||||
paramValue = filter.actionValueFourth().trim()
|
||||
if (paramValue.length) {
|
||||
paramValue = paramValue.split(',').map(email =>
|
||||
email.trim().length ? quote(email) : ''
|
||||
).filter(email => email.length);
|
||||
if (paramValue.length) {
|
||||
addresses = ':addresses [' + paramValue.join(', ') + '] ';
|
||||
}
|
||||
}
|
||||
|
||||
result.push(sTab + 'vacation :days ' + days + ' ' + addresses + subject + quote(value) + ';');
|
||||
} else {
|
||||
errorAction('vacation');
|
||||
}
|
||||
break; }
|
||||
case 'Reject': {
|
||||
let value = filter.actionValue().trim();
|
||||
if (value.length) {
|
||||
require.reject = 1;
|
||||
result.push(sTab + 'reject ' + quote(value) + ';');
|
||||
} else {
|
||||
errorAction('reject');
|
||||
}
|
||||
break; }
|
||||
case 'Forward': {
|
||||
let value = filter.actionValue();
|
||||
if (value.length) {
|
||||
if (filter.actionKeep()) {
|
||||
require.fileinto = 1;
|
||||
result.push(sTab + 'fileinto "INBOX";');
|
||||
}
|
||||
result.push(sTab + 'redirect ' + quote(value) + ';');
|
||||
} else {
|
||||
errorAction('redirect');
|
||||
}
|
||||
break; }
|
||||
case 'MoveTo': {
|
||||
let value = filter.actionValue();
|
||||
if (value.length) {
|
||||
require.fileinto = 1;
|
||||
result.push(sTab + 'fileinto ' + quote(value) + ';');
|
||||
} else {
|
||||
errorAction('fileinto');
|
||||
}
|
||||
break; }
|
||||
}
|
||||
|
||||
filter.actionNoStop() || result.push(sTab + 'stop;');
|
||||
|
||||
block && result.push('}');
|
||||
|
||||
return result.join(eol);
|
||||
};
|
||||
|
||||
aFilters.forEach(filter => {
|
||||
parts.push([
|
||||
'/*',
|
||||
'BEGIN:FILTER:' + filter.id,
|
||||
'BEGIN:HEADER',
|
||||
btoa(unescape(encodeURIComponent(JSON.stringify(filter.toJson())))).match(split).join(eol) + 'END:HEADER',
|
||||
'*/',
|
||||
filter.enabled() ? '' : '/* @Filter is disabled ',
|
||||
filterToString(filter, require),
|
||||
filter.enabled() ? '' : '*/',
|
||||
'/* END:FILTER */',
|
||||
''
|
||||
].join(eol));
|
||||
});
|
||||
|
||||
require = Object.keys(require);
|
||||
return (require.length ? 'require ' + JSON.stringify(require) + ';' + eol : '') + eol + parts.join(eol);
|
||||
}
|
||||
|
||||
class SieveScriptModel extends AbstractModel
|
||||
{
|
||||
constructor() {
|
||||
|
@ -36,6 +271,10 @@ class SieveScriptModel extends AbstractModel
|
|||
// this.filters = ko.observableArray(tree);
|
||||
}
|
||||
|
||||
filtersToRaw() {
|
||||
return filtersToSieveScript(this.filters);
|
||||
}
|
||||
|
||||
verify() {
|
||||
this.nameError(!this.name().trim());
|
||||
this.bodyError(this.allowFilters() ? !this.filters().length : !this.body().trim());
|
||||
|
|
|
@ -48,6 +48,7 @@ class SieveScriptPopupView extends AbstractViewNext {
|
|||
|
||||
this.saving = true;
|
||||
this.saveError(false);
|
||||
// script.body(script.filtersToRaw());
|
||||
|
||||
Remote.filtersScriptSave(
|
||||
(result, data) => {
|
||||
|
@ -108,6 +109,16 @@ class SieveScriptPopupView extends AbstractViewNext {
|
|||
]);
|
||||
}
|
||||
|
||||
toggleFiltersRaw() {
|
||||
if (!this.rawActive()) {
|
||||
let script = this.script(),
|
||||
changed = script.hasChanges();
|
||||
script.body(script.filtersToRaw());
|
||||
script.hasChanges(changed);
|
||||
}
|
||||
this.rawActive(!this.rawActive());
|
||||
}
|
||||
|
||||
onBuild(oDom) {
|
||||
oDom.addEventListener('click', event => {
|
||||
const el = event.target.closestWithin('.filter-item .e-action', oDom),
|
||||
|
|
|
@ -38,16 +38,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="row" data-bind="visible: allowFilters">
|
||||
<div class="span5 width100-on-mobile">
|
||||
<a class="btn" data-bind="click: function () { $root.rawActive(!$root.rawActive()) },
|
||||
css: {'active': $root.rawActive }, tooltip: 'POPUPS_SIEVE_SCRIPT/BUTTON_RAW_SCRIPT'">
|
||||
<i class="icon-file-code"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="row">
|
||||
<div class="span8 width100-on-mobile">
|
||||
<div class="control-group" data-bind="visible: $root.rawActive">
|
||||
|
@ -107,6 +98,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn" data-bind="visible: allowFilters, click: function() { $root.toggleFiltersRaw(); },
|
||||
css: {'active': $root.rawActive }, tooltip: 'POPUPS_SIEVE_SCRIPT/BUTTON_RAW_SCRIPT'">
|
||||
<i class="icon-file-code"></i>
|
||||
</a>
|
||||
<a class="btn buttonSave" data-bind="visible: hasChanges, click: function() { $root.saveScriptCommand(); }, css: {'btn-danger': $root.saveError}">
|
||||
<i data-bind="css: {'icon-floppy': !$root.saving, 'icon-spinner animated': $root.saving}"></i>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue