Split Sieve/Filters code from app.js so that i can work on the new Sieve GUI

This commit is contained in:
djmaze 2022-03-11 10:26:25 +01:00
parent ec6a79d9d6
commit d6dc4d291c
23 changed files with 460 additions and 490 deletions

121
dev/Sieve/Model/Abstract.js Normal file
View file

@ -0,0 +1,121 @@
import { forEachObjectValue, forEachObjectEntry, koComputable } from 'Sieve/Utils';
function typeCast(curValue, newValue) {
if (null != curValue) {
switch (typeof curValue)
{
case 'boolean': return 0 != newValue && !!newValue;
case 'number': return isFinite(newValue) ? parseFloat(newValue) : 0;
case 'string': return null != newValue ? '' + newValue : '';
case 'object':
if (curValue.constructor.reviveFromJson) {
return curValue.constructor.reviveFromJson(newValue);
}
if (Array.isArray(curValue) && !Array.isArray(newValue))
return [];
}
}
return newValue;
}
export class AbstractModel {
constructor() {
/*
if (new.target === AbstractModel) {
throw new Error("Can't instantiate AbstractModel!");
}
*/
this.disposables = [];
}
addObservables(observables) {
forEachObjectEntry(observables, (key, value) =>
this[key] || (this[key] = /*isArray(value) ? ko.observableArray(value) :*/ ko.observable(value))
);
}
addComputables(computables) {
forEachObjectEntry(computables, (key, fn) => this[key] = koComputable(fn));
}
addSubscribables(subscribables) {
forEachObjectEntry(subscribables, (key, fn) => this.disposables.push( this[key].subscribe(fn) ) );
}
/** Called by delegateRunOnDestroy */
onDestroy() {
/** dispose ko subscribables */
this.disposables.forEach(disposable => {
disposable && typeof disposable.dispose === 'function' && disposable.dispose();
});
/** clear object entries */
// forEachObjectEntry(this, (key, value) => {
forEachObjectValue(this, value => {
/** clear CollectionModel */
let arr = ko.isObservableArray(value) ? value() : value;
arr && arr.onDestroy && arr.onDestroy();
/** destroy ko.observable/ko.computed? */
// dispose(value);
/** clear object value */
// this[key] = null; // TODO: issue with Contacts view
});
// this.disposables = [];
}
/**
* @static
* @param {FetchJson} json
* @returns {boolean}
*/
static validJson(json) {
return !!(json && ('Object/'+this.name.replace('Model', '') === json['@Object']));
}
/**
* @static
* @param {FetchJson} json
* @returns {*Model}
*/
static reviveFromJson(json) {
let obj = this.validJson(json) ? new this() : null;
obj && obj.revivePropertiesFromJson(json);
return obj;
}
revivePropertiesFromJson(json) {
let model = this.constructor;
if (!model.validJson(json)) {
return false;
}
forEachObjectEntry(json, (key, value) => {
if ('@' !== key[0]) try {
key = key[0].toLowerCase() + key.slice(1);
switch (typeof this[key])
{
case 'function':
if (ko.isObservable(this[key])) {
this[key](typeCast(this[key](), value));
// console.log('Observable ' + (typeof this[key]()) + ' ' + (model.name) + '.' + key + ' revived');
}
// else console.log(model.name + '.' + key + ' is a function');
break;
case 'boolean':
case 'number':
case 'object':
case 'string':
this[key] = typeCast(this[key], value);
break;
// fall through
case 'undefined':
default:
// console.log((typeof this[key])+' '+(model.name)+'.'+key+' not revived');
}
} catch (e) {
console.log(model.name + '.' + key);
console.error(e);
}
});
return true;
}
}

267
dev/Sieve/Model/Filter.js Normal file
View file

@ -0,0 +1,267 @@
import { koArrayWithDestroy } from 'Sieve/Utils';
//import { getFolderFromCacheList } from 'Common/Cache';
//import { AccountUserStore } from 'Stores/User/Account';
import { FilterConditionModel } from 'Sieve/Model/FilterCondition';
import { AbstractModel } from 'Sieve/Model/Abstract';
/**
* @enum {string}
*/
export const FilterAction = {
None: 'None',
MoveTo: 'MoveTo',
Discard: 'Discard',
Vacation: 'Vacation',
Reject: 'Reject',
Forward: 'Forward'
};
/**
* @enum {string}
*/
const FilterRulesType = {
All: 'All',
Any: 'Any'
};
export class FilterModel extends AbstractModel {
constructor() {
super();
this.id = '';
this.addObservables({
enabled: true,
askDelete: false,
canBeDeleted: true,
name: '',
nameError: false,
nameFocused: false,
conditionsType: FilterRulesType.Any,
// Actions
actionValue: '',
actionValueError: false,
actionValueSecond: '',
actionValueThird: '',
actionValueFourth: '',
actionValueFourthError: false,
actionMarkAsRead: false,
actionKeep: true,
actionNoStop: false,
actionType: FilterAction.MoveTo
});
this.conditions = koArrayWithDestroy();
const fGetRealFolderName = folderFullName => {
// const folder = getFolderFromCacheList(folderFullName);
// return folder ? folder.fullName.replace('.' === folder.delimiter ? /\./ : /[\\/]+/, ' / ') : folderFullName;
return folderFullName;
};
this.addComputables({
nameSub: () => {
let result = '';
const actionValue = this.actionValue(), root = 'SETTINGS_FILTERS/SUBNAME_';
switch (this.actionType()) {
case FilterAction.MoveTo:
result = rl.i18n(root + 'MOVE_TO', {
FOLDER: fGetRealFolderName(actionValue)
});
break;
case FilterAction.Forward:
result = rl.i18n(root + 'FORWARD_TO', {
EMAIL: actionValue
});
break;
case FilterAction.Vacation:
result = rl.i18n(root + 'VACATION_MESSAGE');
break;
case FilterAction.Reject:
result = rl.i18n(root + 'REJECT');
break;
case FilterAction.Discard:
result = rl.i18n(root + 'DISCARD');
break;
// no default
}
return result ? '(' + result + ')' : '';
},
actionTemplate: () => {
const result = 'SettingsFiltersAction';
switch (this.actionType()) {
case FilterAction.Forward:
return result + 'Forward';
case FilterAction.Vacation:
return result + 'Vacation';
case FilterAction.Reject:
return result + 'Reject';
case FilterAction.None:
return result + 'None';
case FilterAction.Discard:
return result + 'Discard';
case FilterAction.MoveTo:
default:
return result + 'MoveToFolder';
}
}
});
this.addSubscribables({
name: sValue => this.nameError(!sValue),
actionValue: sValue => this.actionValueError(!sValue),
actionType: () => {
this.actionValue('');
this.actionValueError(false);
this.actionValueSecond('');
this.actionValueThird('');
this.actionValueFourth('');
this.actionValueFourthError(false);
}
});
}
generateID() {
this.id = Jua.randomId();
}
verify() {
if (!this.name()) {
this.nameError(true);
return false;
}
if (this.conditions.length && this.conditions.find(cond => cond && !cond.verify())) {
return false;
}
if (!this.actionValue()) {
if ([
FilterAction.MoveTo,
FilterAction.Forward,
FilterAction.Reject,
FilterAction.Vacation
].includes(this.actionType())
) {
this.actionValueError(true);
return false;
}
}
if (FilterAction.Forward === this.actionType() && !this.actionValue().includes('@')) {
this.actionValueError(true);
return false;
}
if (
FilterAction.Vacation === this.actionType() &&
this.actionValueFourth() &&
!this.actionValueFourth().includes('@')
) {
this.actionValueFourthError(true);
return false;
}
this.nameError(false);
this.actionValueError(false);
return true;
}
toJson() {
return {
// '@Object': 'Object/Filter',
ID: this.id,
Enabled: this.enabled() ? 1 : 0,
Name: this.name(),
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(),
Keep: this.actionKeep() ? 1 : 0,
Stop: this.actionNoStop() ? 0 : 1,
MarkAsRead: this.actionMarkAsRead() ? 1 : 0
};
}
addCondition() {
this.conditions.push(new FilterConditionModel());
}
removeCondition(oConditionToDelete) {
this.conditions.remove(oConditionToDelete);
}
setRecipients() {
// this.actionValueFourth(AccountUserStore.getEmailAddresses().join(', '));
}
/**
* @static
* @param {FetchJsonFilter} json
* @returns {?FilterModel}
*/
static reviveFromJson(json) {
const filter = super.reviveFromJson(json);
if (filter) {
filter.id = filter.id ? '' + filter.id : '';
filter.conditions(
json.Conditions ? json.Conditions.map(aData => FilterConditionModel.reviveFromJson(aData)).filter(v => v) : []
);
filter.actionKeep(0 != json.Keep);
filter.actionNoStop(0 == json.Stop);
filter.actionMarkAsRead(1 == json.MarkAsRead);
}
return filter;
}
cloneSelf() {
const filter = new FilterModel();
filter.id = this.id;
filter.enabled(this.enabled());
filter.name(this.name());
filter.nameError(this.nameError());
filter.conditionsType(this.conditionsType());
filter.actionMarkAsRead(this.actionMarkAsRead());
filter.actionType(this.actionType());
filter.actionValue(this.actionValue());
filter.actionValueError(this.actionValueError());
filter.actionValueSecond(this.actionValueSecond());
filter.actionValueThird(this.actionValueThird());
filter.actionValueFourth(this.actionValueFourth());
filter.actionKeep(this.actionKeep());
filter.actionNoStop(this.actionNoStop());
filter.conditions(this.conditions.map(item => item.cloneSelf()));
return filter;
}
}

View file

@ -0,0 +1,104 @@
import { koComputable } from 'Sieve/Utils';
import { AbstractModel } from 'Sieve/Model/Abstract';
/**
* @enum {string}
*/
export const FilterConditionField = {
From: 'From',
Recipient: 'Recipient',
Subject: 'Subject',
Header: 'Header',
Body: 'Body',
Size: 'Size'
};
/**
* @enum {string}
*/
export const FilterConditionType = {
Contains: 'Contains',
NotContains: 'NotContains',
EqualTo: 'EqualTo',
NotEqualTo: 'NotEqualTo',
Regex: 'Regex',
Over: 'Over',
Under: 'Under',
Text: 'Text',
Raw: 'Raw'
};
export class FilterConditionModel extends AbstractModel {
constructor() {
super();
this.addObservables({
field: FilterConditionField.From,
type: FilterConditionType.Contains,
value: '',
valueError: false,
valueSecond: '',
valueSecondError: false
});
this.template = koComputable(() => {
const template = 'SettingsFiltersCondition';
switch (this.field()) {
case FilterConditionField.Body:
return template + 'Body';
case FilterConditionField.Size:
return template + 'Size';
case FilterConditionField.Header:
return template + 'More';
default:
return template + 'Default';
}
});
this.addSubscribables({
field: () => {
this.value('');
this.valueSecond('');
}
});
}
verify() {
if (!this.value()) {
this.valueError(true);
return false;
}
if (FilterConditionField.Header === this.field() && !this.valueSecond()) {
this.valueSecondError(true);
return false;
}
return true;
}
// static reviveFromJson(json) {}
toJson() {
return {
// '@Object': 'Object/FilterCondition',
Field: this.field(),
Type: this.type(),
Value: this.value(),
ValueSecond: this.valueSecond()
};
}
cloneSelf() {
const filterCond = new FilterConditionModel();
filterCond.field(this.field());
filterCond.type(this.type());
filterCond.value(this.value());
filterCond.valueSecond(this.valueSecond());
return filterCond;
}
}

333
dev/Sieve/Model/Script.js Normal file
View file

@ -0,0 +1,333 @@
import { AbstractModel } from 'Sieve/Model/Abstract';
import { FilterModel } from 'Sieve/Model/Filter';
import { koArrayWithDestroy } from 'Sieve/Utils';
const SIEVE_FILE_NAME = 'rainloop.user';
// collectionToFileString
function filtersToSieveScript(filters)
{
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 = string => '"' + string.trim().replace(/(\\|")/, '\\\\$1') + '"';
const StripSpaces = string => string.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 = [],
conditions = filter.conditions();
const errorAction = type => result.push(sTab + '# @Error (' + type + '): empty action value');
// 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
block ? result.push('{') : (sTab = '');
if (filter.actionMarkAsRead() && ['None','MoveTo','Forward'].includes(filter.actionType())) {
require.imap4flags = 1;
result.push(sTab + 'addflag "\\\\Seen";');
}
let value = filter.actionValue().trim();
value = value.length ? quote(value) : 0;
switch (filter.actionType())
{
case 'None':
break;
case 'Discard':
result.push(sTab + 'discard;');
break;
case 'Vacation':
if (value) {
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 + value + ';');
} else {
errorAction('vacation');
}
break;
case 'Reject': {
if (value) {
require.reject = 1;
result.push(sTab + 'reject ' + value + ';');
} else {
errorAction('reject');
}
break; }
case 'Forward':
if (value) {
if (filter.actionKeep()) {
require.fileinto = 1;
result.push(sTab + 'fileinto "INBOX";');
}
result.push(sTab + 'redirect ' + value + ';');
} else {
errorAction('redirect');
}
break;
case 'MoveTo':
if (value) {
require.fileinto = 1;
result.push(sTab + 'fileinto ' + value + ';');
} else {
errorAction('fileinto');
}
break;
}
filter.actionNoStop() || result.push(sTab + 'stop;');
block && result.push('}');
return result.join(eol);
};
filters.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);
}
// fileStringToCollection
function sieveScriptToFilters(script)
{
let regex = /BEGIN:HEADER([\s\S]+?)END:HEADER/gm,
filters = [],
json,
filter;
if (script.length && script.includes('RAINLOOP:SIEVE')) {
while ((json = regex.exec(script))) {
json = decodeURIComponent(escape(atob(json[1].replace(/\s+/g, ''))));
if (json && json.length && (json = JSON.parse(json))) {
json['@Object'] = 'Object/Filter';
json.Conditions.forEach(condition => condition['@Object'] = 'Object/FilterCondition');
filter = FilterModel.reviveFromJson(json);
filter && filters.push(filter);
}
}
}
return filters;
}
export class SieveScriptModel extends AbstractModel
{
constructor() {
super();
this.addObservables({
name: '',
active: false,
body: '',
exists: false,
nameError: false,
askDelete: false,
canBeDeleted: true,
hasChanges: false
});
this.filters = koArrayWithDestroy();
// this.saving = ko.observable(false).extend({ debounce: 200 });
this.addSubscribables({
name: () => this.hasChanges(true),
filters: () => this.hasChanges(true),
body: () => this.hasChanges(true)
});
}
filtersToRaw() {
return filtersToSieveScript(this.filters);
// this.body(filtersToSieveScript(this.filters));
}
rawToFilters() {
return sieveScriptToFilters(this.body());
// this.filters(sieveScriptToFilters(this.body()));
}
verify() {
this.nameError(!this.name().trim());
return !this.nameError();
}
toJson() {
return {
name: this.name(),
active: this.active() ? 1 : 0,
body: this.body(),
filters: this.filters.map(item => item.toJson())
};
}
/**
* Only 'rainloop.user' script supports filters
*/
allowFilters() {
return SIEVE_FILE_NAME === this.name();
}
/**
* @static
* @param {FetchJsonScript} json
* @returns {?SieveScriptModel}
*/
static reviveFromJson(json) {
const script = super.reviveFromJson(json);
if (script) {
if (script.allowFilters()) {
script.filters(
Array.isArray(json.filters) && json.filters.length
? json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v)
: sieveScriptToFilters(script.body())
);
} else {
script.filters([]);
}
script.canBeDeleted(SIEVE_FILE_NAME !== json.name);
script.exists(true);
script.hasChanges(false);
}
return script;
}
}

View file

@ -1,4 +1,89 @@
import { SieveScriptModel } from 'Sieve/Model/Script';
export const
// import { i18n } from 'Common/Translator';
i18n = rl.i18n,
// import { forEachObjectValue, forEachObjectEntry } from 'Common/Utils';
forEachObjectValue = (obj, fn) => Object.values(obj).forEach(fn),
forEachObjectEntry = (obj, fn) => Object.entries(obj).forEach(([key, value]) => fn(key, value)),
// import { koArrayWithDestroy } from 'External/ko';
// With this we don't need delegateRunOnDestroy
koArrayWithDestroy = data => {
data = ko.observableArray(data);
data.subscribe(changes =>
changes.forEach(item =>
'deleted' === item.status && null == item.moved && item.value.onDestroy && item.value.onDestroy()
)
, data, 'arrayChange');
return data;
},
// import { koComputable } from 'External/ko';
koComputable = fn => ko.computed(fn, {'pure':true}),
arrayToString = (arr, separator) =>
arr.map(item => item.toString ? item.toString() : item).join(separator);
arr.map(item => item.toString ? item.toString() : item).join(separator),
/*
getNotificationMessage = code => {
let key = getKeyByValue(Notification, code);
return key ? I18N_DATA.NOTIFICATIONS[i18nKey(key).replace('_NOTIFICATION', '_ERROR')] : '';
rl.i18n('NOTIFICATIONS/')
},
getNotification = (code, message = '', defCode = 0) => {
code = parseInt(code, 10) || 0;
if (Notification.ClientViewError === code && message) {
return message;
}
return getNotificationMessage(code)
|| getNotificationMessage(parseInt(defCode, 10))
|| '';
},
*/
getNotification = code => 'ERROR ' + code,
Remote = rl.app.Remote,
// capabilities
capa = ko.observableArray(),
// Sieve scripts SieveScriptModel
scripts = koArrayWithDestroy(),
loading = ko.observable(false),
serverError = ko.observable(false),
serverErrorDesc = ko.observable(''),
setError = text => {
serverError(true);
serverErrorDesc(text);
},
updateList = () => {
if (!loading()) {
loading(true);
serverError(false);
Remote.request('Filters', (iError, data) => {
loading(false);
scripts([]);
if (iError) {
capa([]);
setError(getNotification(iError));
} else {
capa(data.Result.Capa);
/*
scripts(
data.Result.Scripts.map(aItem => SieveScriptModel.reviveFromJson(aItem)).filter(v => v)
);
*/
forEachObjectValue(data.Result.Scripts, value => {
value = SieveScriptModel.reviveFromJson(value);
value && scripts.push(value)
});
}
});
}
};

204
dev/Sieve/View/Filter.js Normal file
View file

@ -0,0 +1,204 @@
import { FilterAction } from 'Sieve/Model/Filter';
import { FilterConditionField, FilterConditionType } from 'Sieve/Model/FilterCondition';
/*
import { SettingsUserStore } from 'Stores/User/Settings';
*/
import {
capa,
i18n,
koComputable
} from 'Sieve/Utils';
const
// import { defaultOptionsAfterRender } from 'Common/Utils';
defaultOptionsAfterRender = (domItem, item) =>
domItem && item && undefined !== item.disabled
&& domItem.classList.toggle('disabled', domItem.disabled = item.disabled),
// import { folderListOptionsBuilder } from 'Common/Folders';
/**
* @param {Array=} aDisabled
* @param {Array=} aHeaderLines
* @param {Function=} fRenameCallback
* @returns {Array}
*/
folderListOptionsBuilder = (
aDisabled,
aHeaderLines,
fRenameCallback
) => {
const
aResult = [],
sDeepPrefix = '\u00A0\u00A0\u00A0',
showUnsubscribed = true/*!SettingsUserStore.hideUnsubscribed()*/,
foldersWalk = folders => {
folders.forEach(oItem => {
if (showUnsubscribed || oItem.hasSubscriptions() || !oItem.exists) {
aResult.push({
id: oItem.fullName,
name:
sDeepPrefix.repeat(oItem.deep) +
fRenameCallback(oItem),
system: false,
disabled: !oItem.selectable() || aDisabled.includes(oItem.fullName)
});
}
if (oItem.subFolders.length) {
foldersWalk(oItem.subFolders());
}
});
};
fRenameCallback = fRenameCallback || (oItem => oItem.name());
Array.isArray(aDisabled) || (aDisabled = []);
Array.isArray(aHeaderLines) && aHeaderLines.forEach(line =>
aResult.push({
id: line[0],
name: line[1],
system: false,
disabled: false
})
);
// FolderUserStore.folderList()
foldersWalk(window.Sieve.folderList() || []);
return aResult;
};
export class FilterPopupView extends rl.pluginPopupView {
constructor() {
super('Filter');
this.addObservables({
isNew: true,
filter: null,
allowMarkAsRead: false,
selectedFolderValue: ''
});
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
this.folderSelectList = koComputable(() =>
folderListOptionsBuilder(
[rl.settings.get('SieveAllowFileintoInbox') ? '' : 'INBOX'],
[['', '']],
item => item ? item.localName() : ''
)
);
this.selectedFolderValue.subscribe(() => this.filter().actionValueError(false));
['actionTypeOptions','fieldOptions','typeOptions','typeOptionsSize','typeOptionsBody'].forEach(
key => this[key] = ko.observableArray()
);
this.populateOptions();
}
saveFilter() {
if (FilterAction.MoveTo === this.filter().actionType()) {
this.filter().actionValue(this.selectedFolderValue());
}
if (this.filter().verify()) {
this.fTrueCallback();
this.close();
}
}
populateOptions() {
this.actionTypeOptions([]);
let i18nFilter = key => i18n('POPUPS_FILTER/SELECT_' + key);
this.fieldOptions([
{ id: FilterConditionField.From, name: i18n('GLOBAL/FROM') },
{ id: FilterConditionField.Recipient, name: i18nFilter('FIELD_RECIPIENTS') },
{ id: FilterConditionField.Subject, name: i18n('GLOBAL/SUBJECT') },
{ id: FilterConditionField.Size, name: i18nFilter('FIELD_SIZE') },
{ id: FilterConditionField.Header, name: i18nFilter('FIELD_HEADER') }
]);
this.typeOptions([
{ id: FilterConditionType.Contains, name: i18nFilter('TYPE_CONTAINS') },
{ id: FilterConditionType.NotContains, name: i18nFilter('TYPE_NOT_CONTAINS') },
{ id: FilterConditionType.EqualTo, name: i18nFilter('TYPE_EQUAL_TO') },
{ id: FilterConditionType.NotEqualTo, name: i18nFilter('TYPE_NOT_EQUAL_TO') }
]);
// this.actionTypeOptions.push({id: FilterAction.None,
// name: i18n('GLOBAL/NONE')});
if (capa) {
this.allowMarkAsRead(capa.includes('imap4flags'));
if (capa.includes('fileinto')) {
this.actionTypeOptions.push({
id: FilterAction.MoveTo,
name: i18nFilter('ACTION_MOVE_TO')
});
this.actionTypeOptions.push({
id: FilterAction.Forward,
name: i18nFilter('ACTION_FORWARD_TO')
});
}
if (capa.includes('reject')) {
this.actionTypeOptions.push({ id: FilterAction.Reject, name: i18nFilter('ACTION_REJECT') });
}
if (capa.includes('vacation')) {
this.actionTypeOptions.push({
id: FilterAction.Vacation,
name: i18nFilter('ACTION_VACATION_MESSAGE')
});
}
if (capa.includes('body')) {
this.fieldOptions.push({ id: FilterConditionField.Body, name: i18nFilter('FIELD_BODY') });
}
if (capa.includes('regex')) {
this.typeOptions.push({ id: FilterConditionType.Regex, name: 'Regex' });
}
}
this.actionTypeOptions.push({ id: FilterAction.Discard, name: i18nFilter('ACTION_DISCARD') });
this.typeOptionsSize([
{ id: FilterConditionType.Over, name: i18nFilter('TYPE_OVER') },
{ id: FilterConditionType.Under, name: i18nFilter('TYPE_UNDER') }
]);
this.typeOptionsBody([
{ id: FilterConditionType.Text, name: i18nFilter('TYPE_TEXT') },
{ id: FilterConditionType.Raw, name: i18nFilter('TYPE_RAW') }
]);
}
removeCondition(oConditionToDelete) {
this.filter().removeCondition(oConditionToDelete);
}
beforeShow(oFilter, fTrueCallback, bEdit) {
// onShow(oFilter, fTrueCallback, bEdit) {
this.isNew(!bEdit);
this.fTrueCallback = fTrueCallback;
this.filter(oFilter);
this.selectedFolderValue(oFilter.actionValue());
bEdit || oFilter.nameFocused(true);
this.populateOptions();
}
afterShow() {
this.isNew() && this.filter().nameFocused(true);
}
}

136
dev/Sieve/View/Script.js Normal file
View file

@ -0,0 +1,136 @@
import { FilterModel } from 'Sieve/Model/Filter';
import { SieveScriptModel } from 'Sieve/Model/Script';
import { FilterPopupView } from 'Sieve/View/Filter';
//import { parseScript } from 'Sieve/Parser';
import {
capa,
scripts,
getNotification,
Remote
} from 'Sieve/Utils';
export class SieveScriptPopupView extends rl.pluginPopupView {
constructor() {
super('SieveScript');
this.addObservables({
saveError: false,
saveErrorText: '',
rawActive: false,
allowToggle: false,
script: null
});
this.sieveCapabilities = capa.join(' ');
this.saving = false;
this.filterForDeletion = ko.observable(null).askDeleteHelper();
}
saveScript() {
let self = this,
script = self.script();
if (!self.saving/* && script.hasChanges()*/) {
if (!script.verify()) {
return;
}
if (!script.exists() && scripts.find(item => item.name() === script.name())) {
script.nameError(true);
return;
}
self.saving = true;
self.saveError(false);
if (self.allowToggle()) {
script.body(script.filtersToRaw());
}
Remote.request('FiltersScriptSave',
(iError, data) => {
self.saving = false;
if (iError) {
self.saveError(true);
self.saveErrorText((data && data.ErrorMessageAdditional) || getNotification(iError));
} else {
script.exists() || scripts.push(script);
script.exists(true);
script.hasChanges(false);
}
},
script.toJson()
);
}
}
deleteFilter(filter) {
this.script().filters.remove(filter);
}
addFilter() {
/* this = SieveScriptModel */
const filter = new FilterModel();
filter.generateID();
FilterPopupView.showModal([
filter,
() => this.filters.push(filter)
]);
}
editFilter(filter) {
const clonedFilter = filter.cloneSelf();
FilterPopupView.showModal([
clonedFilter,
() => {
const script = this.script(),
filters = script.filters(),
index = filters.indexOf(filter);
if (-1 < index) {
// script.filters.splice(index, 1, clonedFilter);
filters[index] = clonedFilter;
script.filters(filters);
}
},
true
]);
}
toggleFiltersRaw() {
let script = this.script(), notRaw = !this.rawActive();
if (notRaw) {
script.body(script.filtersToRaw());
script.hasChanges(script.hasChanges());
}
this.rawActive(notRaw);
}
onBuild(oDom) {
oDom.addEventListener('click', event => {
const el = event.target.closestWithin('td.e-action', oDom),
filter = el && ko.dataFor(el);
filter && this.editFilter(filter);
});
}
beforeShow(oScript) {
// onShow(oScript) {
oScript = oScript || new SieveScriptModel();
let raw = !oScript.allowFilters();
this.script(oScript);
this.rawActive(raw);
this.allowToggle(!raw);
this.saveError(false);
/*
// TODO: Sieve GUI
let tree = parseScript(oScript.body(), oScript.name());
console.dir(tree);
console.log(tree.join('\r\n'));
*/
}
}