Monotone-Parent: 6364fd26aed4532b46cd1c779ef5e9f5f6c0971c
Monotone-Revision: fd35d485688e2370ae5250cfa15931e93abfd567 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2010-03-02T19:57:30 Monotone-Branch: ca.inverse.sogomaint-2.0.2
parent
e2f1faa697
commit
f81f48cb90
15
ChangeLog
15
ChangeLog
|
@ -1,5 +1,20 @@
|
|||
2010-03-02 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* UI/PreferencesUI/UIxPreferences.m (-nameLabel, -colorLabel):
|
||||
removed useless methods.
|
||||
|
||||
* SoObjects/SOGo/SOGoUserDefaults.m (-sieveFilters)
|
||||
(-setSieveFilters:): new accessors.
|
||||
|
||||
* SoObjects/SOGo/SOGoDomainDefaults.m (-sieveScriptEnabled): new
|
||||
accessor.
|
||||
|
||||
* SoObjects/Mailer/SOGoSieveConverter.m: new class for producing
|
||||
sieve scripts from user defaults.
|
||||
|
||||
* UI/PreferencesUI/UIxFilterEditor.m: new template class for
|
||||
editing sieve scripts.
|
||||
|
||||
* UI/WebServerResources/generic.js (_): new gettextable equivalent
|
||||
of "getLabel".
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ Mailer_OBJC_FILES += \
|
|||
SOGoMailForward.m \
|
||||
SOGoMailReply.m \
|
||||
\
|
||||
SOGoSieveConverter.m \
|
||||
\
|
||||
EOQualifier+MailDAV.m \
|
||||
NSData+Mail.m \
|
||||
NSString+Mail.m
|
||||
|
|
|
@ -50,8 +50,10 @@
|
|||
#import "SOGoMailManager.h"
|
||||
#import "SOGoMailNamespace.h"
|
||||
#import "SOGoSentFolder.h"
|
||||
#import "SOGoSieveConverter.h"
|
||||
#import "SOGoTrashFolder.h"
|
||||
|
||||
|
||||
#import "SOGoMailAccount.h"
|
||||
|
||||
@implementation SOGoMailAccount
|
||||
|
@ -232,23 +234,25 @@ static NSString *sieveScriptName = @"sogo";
|
|||
|
||||
- (BOOL) updateFilters
|
||||
{
|
||||
NSMutableString *header, *script;
|
||||
NSMutableArray *requirements;
|
||||
NSMutableString *script, *header;
|
||||
NGInternetSocketAddress *address;
|
||||
NSDictionary *result, *values;
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoDomainDefaults *dd;
|
||||
NGSieveClient *client;
|
||||
NSString *v, *password;
|
||||
NSString *filterScript, *v, *password;
|
||||
SOGoSieveConverter *converter;
|
||||
BOOL b;
|
||||
|
||||
dd = [[context activeUser] domainDefaults];
|
||||
if (!([dd vacationEnabled] || [dd forwardEnabled]))
|
||||
if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
|
||||
return YES;
|
||||
|
||||
requirements = [NSMutableArray arrayWithCapacity: 15];
|
||||
ud = [[context activeUser] userDefaults];
|
||||
b = NO;
|
||||
|
||||
header = [NSMutableString stringWithString: @"require ["];
|
||||
script = [NSMutableString string];
|
||||
|
||||
// Right now, we handle Sieve filters here and only for vacation
|
||||
|
@ -274,8 +278,8 @@ static NSString *sieveScriptName = @"sogo";
|
|||
if (days == 0)
|
||||
days = 7;
|
||||
|
||||
[header appendString: @"\"vacation\""];
|
||||
|
||||
[requirements addObjectUniquely: @"vacation"];
|
||||
|
||||
// Skip mailing lists
|
||||
if (ignore)
|
||||
[script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) {"];
|
||||
|
@ -315,9 +319,27 @@ static NSString *sieveScriptName = @"sogo";
|
|||
[script appendString: @"keep;\r\n"];
|
||||
}
|
||||
|
||||
if ([header compare: @"require ["] != NSOrderedSame)
|
||||
converter = [SOGoSieveConverter sieveConverterForUser: [context activeUser]];
|
||||
filterScript = [converter sieveScriptWithRequirements: requirements];
|
||||
if (filterScript)
|
||||
{
|
||||
[header appendString: @"];\r\n"];
|
||||
if ([filterScript length])
|
||||
{
|
||||
b = YES;
|
||||
[script appendString: filterScript];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Sieve generation failure: %@",
|
||||
[converter lastScriptError]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([requirements count])
|
||||
{
|
||||
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
|
||||
[requirements componentsJoinedByString: @"\",\""]];
|
||||
[script insertString: header atIndex: 0];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/* SOGoSieveConverter.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Wolfgang Sourdeau
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef SOGOSIEVECONVERTER_H
|
||||
#define SOGOSIEVECONVERTER_H
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
@class NSDictionary;
|
||||
@class NSMutableArray;
|
||||
@class NSString;
|
||||
|
||||
@class SOGoUser;
|
||||
|
||||
@interface SOGoSieveConverter : NSObject
|
||||
{
|
||||
SOGoUser *user;
|
||||
NSMutableArray *requirements;
|
||||
NSString *scriptError;
|
||||
}
|
||||
|
||||
+ (id) sieveConverterForUser: (SOGoUser *) user;
|
||||
- (id) initForUser: (SOGoUser *) newUser;
|
||||
|
||||
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements;
|
||||
- (NSString *) lastScriptError;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGOSIEVECONVERTER_H */
|
|
@ -0,0 +1,610 @@
|
|||
/* SOGoSieveConverter.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Wolfgang Sourdeau
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+BSJSONAdditions.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
|
||||
#import "SOGoMailAccounts.h"
|
||||
|
||||
#import "SOGoSieveConverter.h"
|
||||
|
||||
typedef enum {
|
||||
UIxFilterFieldTypeAddress,
|
||||
UIxFilterFieldTypeHeader,
|
||||
UIxFilterFieldTypeSize,
|
||||
} UIxFilterFieldType;
|
||||
|
||||
static NSArray *sieveOperators = nil;
|
||||
static NSArray *sieveSizeOperators = nil;
|
||||
static NSMutableDictionary *fieldTypes = nil;
|
||||
static NSDictionary *sieveFields = nil;
|
||||
static NSDictionary *sieveFlags = nil;
|
||||
static NSDictionary *operatorRequirements = nil;
|
||||
static NSDictionary *methodRequirements = nil;
|
||||
|
||||
@interface NSString (SOGoSieveExtension)
|
||||
|
||||
- (NSString *) asSieveQuotedString;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (SOGoSieveExtension)
|
||||
|
||||
- (NSString *) _asSingleLineSieveQuotedString
|
||||
{
|
||||
NSString *escapedString;
|
||||
|
||||
escapedString = [[self stringByReplacingString: @"\\"
|
||||
withString: @"\\\\"]
|
||||
stringByReplacingString: @"\""
|
||||
withString: @"\\\""];
|
||||
|
||||
return [NSString stringWithFormat: @"\"%@\"", escapedString];
|
||||
}
|
||||
|
||||
- (NSString *) _asMultiLineSieveQuotedString
|
||||
{
|
||||
NSArray *lines;
|
||||
NSMutableArray *newLines;
|
||||
NSString *line, *newText;
|
||||
int count, max;
|
||||
|
||||
lines = [self componentsSeparatedByString: @"\n"];
|
||||
max = [lines count];
|
||||
newLines = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
line = [lines objectAtIndex: count];
|
||||
if ([line length] > 0 && [line characterAtIndex: 0] == '.')
|
||||
[newLines addObject: [NSString stringWithFormat: @".%@", line]];
|
||||
else
|
||||
[newLines addObject: line];
|
||||
}
|
||||
|
||||
newText = [NSString stringWithFormat: @"\r\n%@\r\n.\r\n",
|
||||
[newLines componentsJoinedByString: @"\n"]];
|
||||
|
||||
return newText;
|
||||
}
|
||||
|
||||
- (NSString *) asSieveQuotedString
|
||||
{
|
||||
NSRange nlRange;
|
||||
|
||||
nlRange = [self rangeOfString: @"\n"];
|
||||
|
||||
return ((nlRange.length > 0)
|
||||
? [self _asMultiLineSieveQuotedString]
|
||||
: [self _asSingleLineSieveQuotedString]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SOGoSieveConverter
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSArray *fields;
|
||||
|
||||
if (!sieveOperators)
|
||||
{
|
||||
sieveOperators = [NSArray arrayWithObjects: @"is", @"contains",
|
||||
@"matches", @"regex",
|
||||
@"over", @"under", nil];
|
||||
[sieveOperators retain];
|
||||
}
|
||||
if (!sieveSizeOperators)
|
||||
{
|
||||
sieveSizeOperators = [NSArray arrayWithObjects: @"over", @"under", nil];
|
||||
[sieveSizeOperators retain];
|
||||
}
|
||||
if (!fieldTypes)
|
||||
{
|
||||
fieldTypes = [NSMutableDictionary new];
|
||||
fields = [NSArray arrayWithObjects: @"to", @"cc", @"to_or_cc", @"from",
|
||||
nil];
|
||||
[fieldTypes setObject: [NSNumber
|
||||
numberWithInt: UIxFilterFieldTypeAddress]
|
||||
forKeys: fields];
|
||||
fields = [NSArray arrayWithObjects: @"header", @"subject", nil];
|
||||
[fieldTypes setObject: [NSNumber
|
||||
numberWithInt: UIxFilterFieldTypeHeader]
|
||||
forKeys: fields];
|
||||
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeSize]
|
||||
forKey: @"size"];
|
||||
}
|
||||
if (!sieveFields)
|
||||
{
|
||||
sieveFields
|
||||
= [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"\"to\"", @"to",
|
||||
@"\"cc\"", @"cc",
|
||||
@"[\"to\", \"cc\"]", @"to_or_cc",
|
||||
@"\"from\"", @"from",
|
||||
@"\"subject\"", @"subject",
|
||||
nil];
|
||||
[sieveFields retain];
|
||||
}
|
||||
if (!sieveFlags)
|
||||
{
|
||||
sieveFlags
|
||||
= [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"\\Answered", @"answered",
|
||||
@"\\Deleted", @"deleted",
|
||||
@"\\Draft", @"draft",
|
||||
@"\\Flagged", @"flagged",
|
||||
@"Junk", @"junk",
|
||||
@"NotJunk", @"not_junk",
|
||||
@"\\Seen", @"seen",
|
||||
@"$Label1", @"label1",
|
||||
@"$Label2", @"label2",
|
||||
@"$Label3", @"label3",
|
||||
@"$Label4", @"label4",
|
||||
@"$Label5", @"label5",
|
||||
nil];
|
||||
[sieveFlags retain];
|
||||
}
|
||||
if (!operatorRequirements)
|
||||
{
|
||||
operatorRequirements
|
||||
= [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"regex", @"regex",
|
||||
nil];
|
||||
[operatorRequirements retain];
|
||||
}
|
||||
if (!methodRequirements)
|
||||
{
|
||||
methodRequirements
|
||||
= [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"imapflags", @"addflag",
|
||||
@"imapflags", @"removeflag",
|
||||
@"imapflags", @"flag",
|
||||
@"vacation", @"vacation",
|
||||
@"notify", @"notify",
|
||||
@"fileinto", @"fileinto",
|
||||
@"reject", @"reject",
|
||||
@"regex", @"regex",
|
||||
nil];
|
||||
[methodRequirements retain];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id) sieveConverterForUser: (SOGoUser *) newUser
|
||||
{
|
||||
SOGoSieveConverter *newConverter;
|
||||
|
||||
newConverter = [[self alloc] initForUser: newUser];
|
||||
[newConverter autorelease];
|
||||
|
||||
return newConverter;
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
user = nil;
|
||||
requirements = nil;
|
||||
scriptError = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initForUser: (SOGoUser *) newUser
|
||||
{
|
||||
if ((self = [self init]))
|
||||
{
|
||||
ASSIGN (user, newUser);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[user release];
|
||||
[requirements release];
|
||||
[scriptError release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL) _saveFilters
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) _extractRuleField: (NSString **) field
|
||||
fromRule: (NSDictionary *) rule
|
||||
andType: (UIxFilterFieldType *) type
|
||||
{
|
||||
NSNumber *fieldType;
|
||||
NSString *jsonField, *customHeader;
|
||||
|
||||
jsonField = [rule objectForKey: @"field"];
|
||||
if (jsonField)
|
||||
{
|
||||
fieldType = [fieldTypes objectForKey: jsonField];
|
||||
if (fieldType)
|
||||
{
|
||||
*type = [fieldType intValue];
|
||||
if ([jsonField isEqualToString: @"header"])
|
||||
{
|
||||
customHeader = [rule objectForKey: @"custom_header"];
|
||||
if ([customHeader length])
|
||||
*field = [customHeader asSieveQuotedString];
|
||||
else
|
||||
scriptError = (@"Pseudo-header field 'header' without"
|
||||
@" 'custom_header' parameter.");
|
||||
}
|
||||
else if ([jsonField isEqualToString: @"size"])
|
||||
*field = nil;
|
||||
else
|
||||
*field = [sieveFields objectForKey: jsonField];
|
||||
}
|
||||
else
|
||||
scriptError
|
||||
= [NSString stringWithFormat: @"Rule based on unknown field '%@'",
|
||||
*field];
|
||||
}
|
||||
else
|
||||
scriptError = @"Rule without any specified field.";
|
||||
|
||||
return (scriptError == nil);
|
||||
}
|
||||
|
||||
- (BOOL) _extractRuleOperator: (NSString **) operator
|
||||
fromRule: (NSDictionary *) rule
|
||||
isNot: (BOOL *) isNot
|
||||
{
|
||||
NSString *jsonOperator, *baseOperator, *requirement;
|
||||
int baseLength;
|
||||
|
||||
jsonOperator = [rule objectForKey: @"operator"];
|
||||
if (jsonOperator)
|
||||
{
|
||||
*isNot = [jsonOperator hasSuffix: @"_not"];
|
||||
if (*isNot)
|
||||
{
|
||||
baseLength = [jsonOperator length] - 4;
|
||||
baseOperator
|
||||
= [jsonOperator substringWithRange: NSMakeRange (0, baseLength)];
|
||||
}
|
||||
else
|
||||
baseOperator = jsonOperator;
|
||||
|
||||
if ([sieveOperators containsObject: baseOperator])
|
||||
{
|
||||
requirement = [operatorRequirements objectForKey: baseOperator];
|
||||
if (requirement)
|
||||
[requirements addObjectUniquely: requirement];
|
||||
*operator = baseOperator;
|
||||
}
|
||||
else
|
||||
scriptError = [NSString stringWithFormat:
|
||||
@"Rule has unknown operator '%@'",
|
||||
baseOperator];
|
||||
}
|
||||
else
|
||||
scriptError = @"Rule without any specified operator";
|
||||
|
||||
return (scriptError == nil);
|
||||
}
|
||||
|
||||
- (BOOL) _validateRuleOperator: (NSString *) operator
|
||||
withFieldType: (UIxFilterFieldType) type
|
||||
{
|
||||
BOOL rc;
|
||||
|
||||
if (type == UIxFilterFieldTypeSize)
|
||||
rc = [sieveSizeOperators containsObject: operator];
|
||||
else
|
||||
rc = (![sieveSizeOperators containsObject: operator]
|
||||
&& [sieveOperators containsObject: operator]);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) _extractRuleValue: (NSString **) value
|
||||
fromRule: (NSDictionary *) rule
|
||||
withFieldType: (UIxFilterFieldType) type
|
||||
{
|
||||
NSString *extractedValue;
|
||||
|
||||
extractedValue = [rule objectForKey: @"value"];
|
||||
if (extractedValue)
|
||||
{
|
||||
if (type == UIxFilterFieldTypeSize)
|
||||
*value = [NSString stringWithFormat: @"%d",
|
||||
[extractedValue intValue]];
|
||||
else
|
||||
*value = [extractedValue asSieveQuotedString];
|
||||
}
|
||||
else
|
||||
scriptError = @"Rule lacks a 'value' parameter";
|
||||
|
||||
return (scriptError == nil);
|
||||
}
|
||||
|
||||
- (NSString *) _composeSieveRuleOnField: (NSString *) field
|
||||
withType: (UIxFilterFieldType) type
|
||||
operator: (NSString *) operator
|
||||
revert: (BOOL) revert
|
||||
andValue: (NSString *) value
|
||||
{
|
||||
NSMutableString *sieveRule;
|
||||
|
||||
sieveRule = [NSMutableString stringWithCapacity: 100];
|
||||
if (revert)
|
||||
[sieveRule appendString: @"not "];
|
||||
if (type == UIxFilterFieldTypeAddress)
|
||||
[sieveRule appendString: @"address "];
|
||||
else if (type == UIxFilterFieldTypeHeader)
|
||||
[sieveRule appendString: @"header "];
|
||||
else if (type == UIxFilterFieldTypeSize)
|
||||
[sieveRule appendString: @"size "];
|
||||
[sieveRule appendFormat: @":%@ ", operator];
|
||||
if (type == UIxFilterFieldTypeSize)
|
||||
[sieveRule appendFormat: @"%@K", value];
|
||||
else
|
||||
[sieveRule appendFormat: @"%@ %@", field, value];
|
||||
|
||||
return sieveRule;
|
||||
}
|
||||
|
||||
- (NSString *) _extractSieveRule: (NSDictionary *) rule
|
||||
{
|
||||
NSString *field, *operator, *value;
|
||||
UIxFilterFieldType type;
|
||||
BOOL isNot;
|
||||
|
||||
return (([self _extractRuleField: &field fromRule: rule andType: &type]
|
||||
&& [self _extractRuleOperator: &operator fromRule: rule
|
||||
isNot: &isNot]
|
||||
&& [self _validateRuleOperator: operator
|
||||
withFieldType: type]
|
||||
&& [self _extractRuleValue: &value fromRule: rule
|
||||
withFieldType: type])
|
||||
? [self _composeSieveRuleOnField: field
|
||||
withType: type
|
||||
operator: operator
|
||||
revert: isNot
|
||||
andValue: value]
|
||||
: nil);
|
||||
}
|
||||
|
||||
- (NSArray *) _extractSieveRules: (NSArray *) rules
|
||||
{
|
||||
NSMutableArray *sieveRules;
|
||||
NSString *sieveRule;
|
||||
int count, max;
|
||||
|
||||
max = [rules count];
|
||||
if (max)
|
||||
{
|
||||
sieveRules = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; !scriptError && count < max; count++)
|
||||
{
|
||||
sieveRule = [self _extractSieveRule: [rules objectAtIndex: count]];
|
||||
if (sieveRule)
|
||||
[sieveRules addObject: sieveRule];
|
||||
}
|
||||
}
|
||||
else
|
||||
sieveRules = nil;
|
||||
|
||||
return sieveRules;
|
||||
}
|
||||
|
||||
- (NSString *) _extractSieveAction: (NSDictionary *) action
|
||||
{
|
||||
NSString *sieveAction, *method, *requirement, *argument,
|
||||
*flag, *mailbox;
|
||||
SOGoDomainDefaults *dd;
|
||||
|
||||
method = [action objectForKey: @"method"];
|
||||
if (method)
|
||||
{
|
||||
argument = [action objectForKey: @"argument"];
|
||||
if ([method isEqualToString: @"discard"]
|
||||
|| [method isEqualToString: @"keep"]
|
||||
|| [method isEqualToString: @"stop"])
|
||||
sieveAction = method;
|
||||
else
|
||||
{
|
||||
if (argument)
|
||||
{
|
||||
if ([method isEqualToString: @"addflag"])
|
||||
{
|
||||
flag = [sieveFlags objectForKey: argument];
|
||||
if (flag)
|
||||
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
||||
method, [flag asSieveQuotedString]];
|
||||
else
|
||||
scriptError
|
||||
= [NSString stringWithFormat:
|
||||
@"Action with invalid flag argument '%@'",
|
||||
argument];
|
||||
}
|
||||
else if ([method isEqualToString: @"fileinto"])
|
||||
{
|
||||
dd = [user domainDefaults];
|
||||
mailbox
|
||||
= [[argument componentsSeparatedByString: @"/"]
|
||||
componentsJoinedByString: [dd imapFolderSeparator]];
|
||||
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
||||
method, [mailbox asSieveQuotedString]];
|
||||
}
|
||||
else if ([method isEqualToString: @"redirect"])
|
||||
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
||||
method, [argument asSieveQuotedString]];
|
||||
else if ([method isEqualToString: @"reject"])
|
||||
sieveAction = [NSString stringWithFormat: @"%@ text: %@",
|
||||
method, [argument asSieveQuotedString]];
|
||||
else
|
||||
scriptError
|
||||
= [NSString stringWithFormat: @"Action has unknown method '%@'",
|
||||
method];
|
||||
}
|
||||
else
|
||||
scriptError = @"Action missing 'argument' parameter";
|
||||
}
|
||||
if (method)
|
||||
{
|
||||
requirement = [methodRequirements objectForKey: method];
|
||||
if (requirement)
|
||||
[requirements addObjectUniquely: requirement];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptError = @"Action missing 'method' parameter";
|
||||
sieveAction = nil;
|
||||
}
|
||||
|
||||
return sieveAction;
|
||||
}
|
||||
|
||||
- (NSArray *) _extractSieveActions: (NSArray *) actions
|
||||
{
|
||||
NSMutableArray *sieveActions;
|
||||
NSString *sieveAction;
|
||||
int count, max;
|
||||
|
||||
max = [actions count];
|
||||
sieveActions = [NSMutableArray arrayWithCapacity: max];
|
||||
for (count = 0; !scriptError && count < max; count++)
|
||||
{
|
||||
sieveAction = [self _extractSieveAction: [actions objectAtIndex: count]];
|
||||
if (!scriptError)
|
||||
[sieveActions addObject: sieveAction];
|
||||
}
|
||||
|
||||
return sieveActions;
|
||||
}
|
||||
|
||||
- (NSString *) _convertScriptToSieve: (NSDictionary *) newScript
|
||||
{
|
||||
NSMutableString *sieveText;
|
||||
NSString *match;
|
||||
NSArray *sieveRules, *sieveActions;
|
||||
|
||||
sieveText = [NSMutableString stringWithCapacity: 1024];
|
||||
match = [newScript objectForKey: @"match"];
|
||||
if ([match isEqualToString: @"allmessages"])
|
||||
match = nil;
|
||||
if (match)
|
||||
{
|
||||
if ([match isEqualToString: @"all"] || [match isEqualToString: @"any"])
|
||||
{
|
||||
sieveRules
|
||||
= [self _extractSieveRules: [newScript objectForKey: @"rules"]];
|
||||
if (sieveRules)
|
||||
[sieveText appendFormat: @"if %@of (%@) {\r\n",
|
||||
match,
|
||||
[sieveRules componentsJoinedByString: @", "]];
|
||||
else
|
||||
scriptError = [NSString stringWithFormat:
|
||||
@"Test '%@' used without any"
|
||||
@" specified rule",
|
||||
match];
|
||||
}
|
||||
else
|
||||
scriptError = [NSString stringWithFormat: @"Bad test: %@",
|
||||
match];
|
||||
}
|
||||
sieveActions = [self _extractSieveActions:
|
||||
[newScript objectForKey: @"actions"]];
|
||||
if ([sieveActions count])
|
||||
[sieveText appendFormat: @" %@;\r\n",
|
||||
[sieveActions componentsJoinedByString: @";\r\n "]];
|
||||
|
||||
if (match)
|
||||
[sieveText appendFormat: @"}\r\n"];
|
||||
|
||||
return sieveText;
|
||||
}
|
||||
|
||||
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements
|
||||
{
|
||||
NSMutableString *sieveScript;
|
||||
NSString *sieveText;
|
||||
NSArray *scripts;
|
||||
int count, max;
|
||||
BOOL previousWasConditional;
|
||||
NSDictionary *currentScript;
|
||||
|
||||
sieveScript = [NSMutableString stringWithCapacity: 8192];
|
||||
|
||||
ASSIGN (requirements, newRequirements);
|
||||
[scriptError release];
|
||||
scriptError = nil;
|
||||
|
||||
scripts = [[user userDefaults] sieveFilters];
|
||||
max = [scripts count];
|
||||
if (max)
|
||||
{
|
||||
previousWasConditional = NO;
|
||||
for (count = 0; !scriptError && count < max; count++)
|
||||
{
|
||||
currentScript = [scripts objectAtIndex: count];
|
||||
if ([[currentScript objectForKey: @"active"] boolValue])
|
||||
{
|
||||
sieveText = [self _convertScriptToSieve: currentScript];
|
||||
if ([sieveText hasPrefix: @"if"])
|
||||
{
|
||||
if (previousWasConditional)
|
||||
[sieveScript appendFormat: @"els"];
|
||||
else
|
||||
previousWasConditional = YES;
|
||||
}
|
||||
else
|
||||
previousWasConditional = NO;
|
||||
[sieveScript appendString: sieveText];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[scriptError retain];
|
||||
[requirements release];
|
||||
requirements = nil;
|
||||
|
||||
if (scriptError)
|
||||
sieveScript = nil;
|
||||
|
||||
return sieveScript;
|
||||
}
|
||||
|
||||
- (NSString *) lastScriptError
|
||||
{
|
||||
return scriptError;
|
||||
}
|
||||
|
||||
@end
|
|
@ -42,6 +42,8 @@
|
|||
|
||||
@interface NSMutableDictionary (SOGoDictionaryUtilities)
|
||||
|
||||
- (void) setObject: (id) object
|
||||
forKeys: (NSArray *) keys;
|
||||
- (void) setObjects: (NSArray *) objects
|
||||
forKeys: (NSArray *) keys;
|
||||
|
||||
|
|
|
@ -184,6 +184,17 @@
|
|||
|
||||
@implementation NSMutableDictionary (SOGoDictionaryUtilities)
|
||||
|
||||
- (void) setObject: (id) object
|
||||
forKeys: (NSArray *) keys
|
||||
{
|
||||
unsigned int count, max;
|
||||
|
||||
max = [keys count];
|
||||
for (count = 0; count < max; count++)
|
||||
[self setObject: object
|
||||
forKey: [keys objectAtIndex: count]];
|
||||
}
|
||||
|
||||
- (void) setObjects: (NSArray *) objects
|
||||
forKeys: (NSArray *) keys
|
||||
{
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
WOLogFile = "/var/log/sogo/sogo.log";
|
||||
WOPidFile = "/var/run/sogo/sogo.pid";
|
||||
|
||||
NGImap4ConnectionStringSeparator = ".";
|
||||
|
||||
SOGoZipPath = "/usr/bin/zip";
|
||||
|
||||
WOUseRelativeURLs = YES;
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
- (NSString *) imapFolderSeparator;
|
||||
- (BOOL) imapAclConformsToIMAPExt;
|
||||
- (BOOL) forceIMAPLoginWithEmail;
|
||||
- (BOOL) sieveScriptsEnabled;
|
||||
- (BOOL) forwardEnabled;
|
||||
- (BOOL) vacationEnabled;
|
||||
- (NSString *) mailingMechanism;
|
||||
|
|
|
@ -155,6 +155,11 @@
|
|||
return [self boolForKey: @"SOGoForceIMAPLoginWithEmail"];
|
||||
}
|
||||
|
||||
- (BOOL) sieveScriptsEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoSieveScriptsEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) forwardEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoForwardEnabled"];
|
||||
|
|
|
@ -140,6 +140,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
|
|||
- (void) setRemindWithASound: (BOOL) newValue;
|
||||
- (BOOL) remindWithASound;
|
||||
|
||||
- (void) setSieveFilters: (NSArray *) newValue;
|
||||
- (NSArray *) sieveFilters;
|
||||
|
||||
- (void) setVacationOptions: (NSDictionary *) newValue;
|
||||
- (NSDictionary *) vacationOptions;
|
||||
|
||||
|
|
|
@ -565,6 +565,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
|
|||
return [self boolForKey: @"SOGoRemindWithASound"];
|
||||
}
|
||||
|
||||
- (void) setSieveFilters: (NSArray *) newValue
|
||||
{
|
||||
[self setObject: newValue forKey: @"SOGoSieveFilters"];
|
||||
}
|
||||
|
||||
- (NSArray *) sieveFilters
|
||||
{
|
||||
return [self arrayForKey: @"SOGoSieveFilters"];
|
||||
}
|
||||
|
||||
- (void) setVacationOptions: (NSDictionary *) newValue
|
||||
{
|
||||
[self setObject: newValue forKey: @"Vacation"];
|
||||
|
|
|
@ -13,6 +13,8 @@ PreferencesUI_OBJC_FILES = \
|
|||
\
|
||||
UIxJSONPreferences.m \
|
||||
UIxPreferences.m \
|
||||
UIxFilterEditor.m \
|
||||
\
|
||||
UIxAdditionalPreferences.m
|
||||
|
||||
PreferencesUI_RESOURCE_FILES += \
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# compile settings
|
||||
|
||||
SUBMINOR_VERSION:=1
|
||||
|
||||
ADDITIONAL_CPPFLAGS += \
|
||||
-DSOGO_MAJOR_VERSION=$(MAJOR_VERSION) \
|
||||
-DSOGO_MINOR_VERSION=$(MINOR_VERSION) \
|
||||
-DSOGO_SUBMINOR_VERSION=$(SUBMINOR_VERSION)
|
|
@ -0,0 +1,104 @@
|
|||
/* UIxFilterEditor.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSCharacterSet.h>
|
||||
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@interface UIxFilterEditor : UIxComponent
|
||||
{
|
||||
NSString *filterId;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIxFilterEditor
|
||||
|
||||
/* convert and save */
|
||||
- (BOOL) _validateFilterId
|
||||
{
|
||||
NSCharacterSet *digits;
|
||||
BOOL rc;
|
||||
|
||||
digits = [NSCharacterSet decimalDigitCharacterSet];
|
||||
rc = ([filterId isEqualToString: @"new"]
|
||||
|| [[filterId stringByTrimmingCharactersInSet: digits] length] == 0);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (id <WOActionResults>) defaultAction
|
||||
{
|
||||
id <WOActionResults> result;
|
||||
WORequest *request;
|
||||
|
||||
request = [context request];
|
||||
ASSIGN (filterId, [request formValueForKey: @"filter"]);
|
||||
if (filterId)
|
||||
{
|
||||
if ([self _validateFilterId])
|
||||
result = self;
|
||||
else
|
||||
result = [self responseWithStatus: 403
|
||||
andString: @"Bad value for 'filter'"];
|
||||
}
|
||||
else
|
||||
result = [self responseWithStatus: 403
|
||||
andString: @"Missing 'filter' parameter"];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *) filterId
|
||||
{
|
||||
return filterId;
|
||||
}
|
||||
|
||||
- (NSString *) firstMailAccount
|
||||
{
|
||||
NSArray *accounts;
|
||||
NSDictionary *account;
|
||||
NSString *login, *accountName;
|
||||
SOGoUser *ownerUser;
|
||||
|
||||
login = [[self clientObject] nameInContainer];
|
||||
ownerUser = [SOGoUser userWithLogin: login];
|
||||
|
||||
accounts = [ownerUser mailAccounts];
|
||||
if ([accounts count] > 0)
|
||||
{
|
||||
account = [accounts objectAtIndex: 0];
|
||||
accountName = [[account objectForKey: @"name"] asCSSIdentifier];
|
||||
}
|
||||
else
|
||||
accountName = @"";
|
||||
|
||||
return accountName;
|
||||
}
|
||||
|
||||
@end
|
|
@ -42,6 +42,7 @@
|
|||
SOGoUserDefaults *userDefaults;
|
||||
NSCalendarDate *today;
|
||||
NSArray *daysOfWeek, *daysBetweenResponsesList;
|
||||
NSArray *sieveFilters;
|
||||
NSMutableDictionary *vacationOptions, *forwardOptions;
|
||||
BOOL hasChanged, composeMessageTypeHasChanged;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@
|
|||
*/
|
||||
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSPropertyList.h>
|
||||
#import <Foundation/NSScanner.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSUserDefaults.h> /* for locale strings */
|
||||
#import <Foundation/NSPropertyList.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
|
@ -35,6 +36,7 @@
|
|||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+BSJSONAdditions.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSScanner+BSJSONAdditions.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
|
@ -83,6 +85,13 @@
|
|||
composeMessageTypeHasChanged = NO;
|
||||
|
||||
dd = [user domainDefaults];
|
||||
if ([dd sieveScriptsEnabled])
|
||||
{
|
||||
sieveFilters = [[userDefaults sieveFilters] copy];
|
||||
if (!sieveFilters)
|
||||
sieveFilters = [NSArray new];
|
||||
}
|
||||
|
||||
if ([dd vacationEnabled])
|
||||
{
|
||||
vacationOptions = [[userDefaults vacationOptions] mutableCopy];
|
||||
|
@ -107,6 +116,7 @@
|
|||
[item release];
|
||||
[user release];
|
||||
[userDefaults release];
|
||||
[sieveFilters release];
|
||||
[vacationOptions release];
|
||||
[calendarCategories release];
|
||||
[calendarCategoriesColors release];
|
||||
|
@ -661,11 +671,54 @@
|
|||
|
||||
/* mail autoreply (vacation) */
|
||||
|
||||
- (BOOL) isSieveScriptsEnabled
|
||||
{
|
||||
return [[user domainDefaults] sieveScriptsEnabled];
|
||||
}
|
||||
|
||||
- (NSString *) sieveCapabilities
|
||||
{
|
||||
#warning this should be deduced from the server
|
||||
static NSArray *capabilities = nil;
|
||||
|
||||
if (!capabilities)
|
||||
{
|
||||
capabilities = [NSArray arrayWithObjects: @"fileinto", @"reject",
|
||||
@"envelope", @"vacation", @"imapflags",
|
||||
@"notify", @"subaddress", @"relational",
|
||||
@"comparator-i;ascii-numeric", @"regex", nil];
|
||||
[capabilities retain];
|
||||
}
|
||||
|
||||
return [[NSDictionary dictionary]
|
||||
jsonStringForArray: capabilities
|
||||
withIndentLevel: 0];
|
||||
}
|
||||
|
||||
- (BOOL) isVacationEnabled
|
||||
{
|
||||
return [[user domainDefaults] vacationEnabled];
|
||||
}
|
||||
|
||||
- (void) setSieveFiltersValue: (NSString *) newValue
|
||||
{
|
||||
NSScanner *jsonScanner;
|
||||
|
||||
if ([newValue hasPrefix: @"["])
|
||||
{
|
||||
jsonScanner = [NSScanner scannerWithString: newValue];
|
||||
[jsonScanner scanJSONArray: &sieveFilters];
|
||||
[sieveFilters retain];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *) sieveFiltersValue
|
||||
{
|
||||
return [[NSDictionary dictionary]
|
||||
jsonStringForArray: sieveFilters
|
||||
withIndentLevel: 0];
|
||||
}
|
||||
|
||||
- (void) setEnableVacation: (BOOL) enableVacation
|
||||
{
|
||||
[vacationOptions setObject: [NSNumber numberWithBool: enableVacation]
|
||||
|
@ -884,6 +937,8 @@
|
|||
id folder;
|
||||
|
||||
dd = [[context activeUser] domainDefaults];
|
||||
if ([dd sieveScriptsEnabled])
|
||||
[userDefaults setSieveFilters: sieveFilters];
|
||||
if ([dd vacationEnabled])
|
||||
[userDefaults setVacationOptions: vacationOptions];
|
||||
if ([dd forwardEnabled])
|
||||
|
@ -897,7 +952,6 @@
|
|||
account = [folder lookupName: [[mailAccounts objectForKey: @"name"] asCSSIdentifier]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
[account updateFilters];
|
||||
|
||||
if (composeMessageTypeHasChanged)
|
||||
|
@ -947,16 +1001,6 @@
|
|||
userCanChangePassword];
|
||||
}
|
||||
|
||||
- (NSString *) nameLabel
|
||||
{
|
||||
return [self labelForKey: @"Name"];
|
||||
}
|
||||
|
||||
- (NSString *) colorLabel
|
||||
{
|
||||
return [self labelForKey: @"Color"];
|
||||
}
|
||||
|
||||
- (NSArray *) languageCategories
|
||||
{
|
||||
NSArray *categoryLabels;
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
protectedBy = "View";
|
||||
pageName = "UIxPreferences";
|
||||
};
|
||||
editFilter = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxFilterEditor";
|
||||
};
|
||||
identities = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxIdentities";
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE var:component>
|
||||
<var:component
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:uix="OGo:uix"
|
||||
xmlns:rsrc="OGo:url"
|
||||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:popup="YES"
|
||||
>
|
||||
<script type="text/javascript">
|
||||
var filterId = '<var:string value="filterId"/>';
|
||||
var firstMailAccount = '<var:string value="firstMailAccount"/>';
|
||||
</script>
|
||||
<form id="mainForm" var:href="ownPath">
|
||||
<div id="filterNameContainer" class="container">
|
||||
<label><var:string label:value="Filter name:"
|
||||
/><input const:name="filterName" class="textField" type="text"
|
||||
var:value="filterName"/></label><br/>
|
||||
</div>
|
||||
<div id="filterMatchContainer" class="container">
|
||||
<var:string label:value="For incoming messages that"/>
|
||||
<select const:name="matchType" const:id="matchType"
|
||||
><option const:value="all"
|
||||
><var:string label:value="match all of the following rules:"
|
||||
/></option
|
||||
><option const:value="any"
|
||||
><var:string label:value="match any of the following rules:"
|
||||
/></option
|
||||
><option const:value="allmessages"
|
||||
><var:string label:value="match all messages"
|
||||
/></option
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div id="filterRulesContainer" class="container">
|
||||
<div id="filterRules"><!-- empty --></div>
|
||||
<div class="bottomToolbar"
|
||||
><a const:id="ruleAdd" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="add-icon.png" label:title="Add" />
|
||||
</span></a
|
||||
><a const:id="ruleDelete" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="remove-icon.png" label:title="Delete" />
|
||||
</span></a
|
||||
></div>
|
||||
</div>
|
||||
<!-- <div id="splitter" class="dragHandle"></div> -->
|
||||
<div id="filterActionsContainer" class="container">
|
||||
<var:string label:value="Perform these actions:"/><br/>
|
||||
<div id="filterActions"><!-- empty --></div>
|
||||
<div class="bottomToolbar"
|
||||
><a const:id="actionAdd" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="add-icon.png" label:title="Add" />
|
||||
</span></a
|
||||
><a const:id="actionDelete" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="remove-icon.png" label:title="Delete" />
|
||||
</span></a
|
||||
></div>
|
||||
</div>
|
||||
</form>
|
||||
</var:component>
|
|
@ -101,11 +101,11 @@
|
|||
><table id="categoriesList" cellspacing="0">
|
||||
<thead>
|
||||
<tr class="tableview"
|
||||
><td const:class="tbtv_headercell" const:id="nameTableHeader"
|
||||
><var:string var:value="nameLabel" /></td
|
||||
><td const:class="tbtv_headercell" const:id="colorTableHeader"
|
||||
><var:string var:value="colorLabel" /></td
|
||||
></tr
|
||||
><th const:class="tbtv_headercell" const:id="nameTableHeader"
|
||||
><var:string label:value="Name"/></th
|
||||
><th const:class="tbtv_headercell" const:id="colorTableHeader"
|
||||
><var:string label:value="Color"/></th
|
||||
></tr
|
||||
></thead>
|
||||
<tbody>
|
||||
<var:foreach list="categoryList" item="category">
|
||||
|
@ -173,15 +173,45 @@
|
|||
const:id="composeMessagesType"
|
||||
string="itemComposeMessagesText"
|
||||
selection="userComposeMessagesType"/>
|
||||
<var:if condition="isSieveScriptsEnabled"
|
||||
><br/><label><var:string label:value="Filters"/></label>
|
||||
<script type="text/javascript">
|
||||
var sieveCapabilities = <var:string value="sieveCapabilities" const:escapeHTML="NO"/>;
|
||||
</script>
|
||||
<div id="filtersListWrapper"
|
||||
><table id="filtersList" cellspacing="0">
|
||||
<thead>
|
||||
<tr class="tableview"
|
||||
><th const:class="tbtv_headercell" const:id="nameTableHeader"
|
||||
><var:string label:value="Name" /></th
|
||||
><th const:class="tbtv_headercell" const:id="activeTableHeader"
|
||||
><var:string label:value="Active" /></th
|
||||
></tr
|
||||
></thead>
|
||||
<tbody><!--space --></tbody>
|
||||
</table>
|
||||
<input type="hidden" const:name="sieveFilters" const:id="sieveFilters"
|
||||
var:value="sieveFiltersValue"/></div>
|
||||
<div class="bottomToolbar">
|
||||
<a const:id="filterAdd" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="add-icon.png" label:title="Add" />
|
||||
</span></a>
|
||||
<a const:id="filterDelete" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="remove-icon.png" label:title="Delete" />
|
||||
</span></a>
|
||||
<a const:id="filterMoveUp" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="up-icon.png" label:title="Move Up" />
|
||||
</span></a>
|
||||
<a const:id="filterMoveDown" class="bottomButton" href="#">
|
||||
<span><img rsrc:src="down-icon.png" label:title="Move Down" />
|
||||
</span></a>
|
||||
</div>
|
||||
</var:if>
|
||||
</div>
|
||||
<div id="identitiesView" class="tab">
|
||||
<!--<var:multiselection const:id="identitiesList" item="item"
|
||||
list="identitiesList" displayString="itemIdentityText">
|
||||
</var:multiselection>
|
||||
<br/>-->
|
||||
<textarea const:id="signature" const:name="signature"
|
||||
var:value="signature" />
|
||||
</div>
|
||||
<div id="identitiesView" class="tab"
|
||||
><textarea const:id="signature" const:name="signature"
|
||||
var:value="signature"
|
||||
/></div>
|
||||
<var:if condition="isVacationEnabled">
|
||||
<div id="vacationView" class="tab">
|
||||
<label><input type="checkbox"
|
||||
|
|
|
@ -101,14 +101,6 @@ function validateContactEditor() {
|
|||
return rc;
|
||||
}
|
||||
|
||||
function showCoords(node) {
|
||||
node = $("givenName");
|
||||
window.alert("x: " + node.cascadeLeftOffset()
|
||||
+ ";y: " + node.cascadeTopOffset()
|
||||
+ ";width: " + window.width()
|
||||
+ ";height: " + window.height());
|
||||
}
|
||||
|
||||
function onFnKeyDown() {
|
||||
var fn = $("fn");
|
||||
fn.onkeydown = null;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
DIV#pageContent
|
||||
{ padding: 10px;
|
||||
padding-top: 15px; }
|
||||
|
||||
#splitter
|
||||
{ cursor: n-resize;
|
||||
top: 230px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 5px; }
|
||||
|
||||
LABEL
|
||||
{ margin: 0px;
|
||||
padding: 0px; }
|
||||
|
||||
#pageContent INPUT,
|
||||
#pageContent SELECT
|
||||
{ background-color: #ccddec;
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
border-width: 1px; }
|
||||
|
||||
#pageContent INPUT
|
||||
{ padding-left: 2px; }
|
||||
|
||||
DIV.container
|
||||
{ margin-bottom: 14px; }
|
||||
|
||||
DIV#filterRulesContainer,
|
||||
DIV#filterActionsContainer
|
||||
{ position: absolute;
|
||||
margin: 0px;
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
left: 10px;
|
||||
right: 10px; }
|
||||
|
||||
DIV#filterRulesContainer
|
||||
{ top: 128px;
|
||||
height: 102px; }
|
||||
|
||||
DIV#filterActionsContainer
|
||||
{ top: 235px;
|
||||
bottom: 0px; }
|
||||
|
||||
DIV#filterRules, DIV#filterActions
|
||||
{ position: absolute;
|
||||
cursor: default;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 21px;
|
||||
border-top: 1px solid #909090;
|
||||
border-left: 1px solid #909090;
|
||||
border-bottom: 1px solid #ffffff;
|
||||
border-right: 1px solid #ffffff;
|
||||
background: #ccddec;
|
||||
overflow: hidden;
|
||||
overflow-y: auto; }
|
||||
|
||||
DIV#filterRules
|
||||
{ top: 0px; }
|
||||
|
||||
DIV#filterActions
|
||||
{ top: 20px; }
|
||||
|
||||
DIV.bottomToolbar
|
||||
{ position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
bottom: 0px; }
|
||||
|
||||
DIV.rule,
|
||||
DIV.action
|
||||
{ overflow: hidden;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #9b9b9b; }
|
||||
|
||||
DIV.rule._selected,
|
||||
DIV.action._selected
|
||||
{ background-color: #9abcd8;
|
||||
color: #fff; }
|
||||
|
||||
SPAN.fieldContainer
|
||||
{ width: 100px; }
|
||||
|
||||
SPAN.operatorContainer
|
||||
{ width: 300px; }
|
||||
|
||||
SPAN.valueContainer INPUT
|
||||
{ width: 160px; }
|
||||
|
||||
SPAN.fileintoArgument SELECT
|
||||
{ max-width: 250px; }
|
||||
|
||||
SPAN.rejectArgument TEXTAREA
|
||||
{ height: 54px;
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
padding-right: -50px; }
|
|
@ -0,0 +1,794 @@
|
|||
/* -*- Mode: java; tab-width: 2; c-label-minimum-indentation: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
/* Cyrus: comparator-i;ascii-numeric fileinto reject vacation imapflags
|
||||
notify envelope relational regex subaddress copy */
|
||||
var sieveCapabilities = [];
|
||||
|
||||
var filter;
|
||||
|
||||
var selectedRuleDiv = null;
|
||||
var selectedActionDiv = null;
|
||||
|
||||
var fieldLabels;
|
||||
var methodLabels;
|
||||
var operatorLabels;
|
||||
var operatorRequirements;
|
||||
var methodRequirements;
|
||||
var flagLabels;
|
||||
|
||||
var mailboxes = [];
|
||||
|
||||
function onLoadHandler() {
|
||||
setupConstants();
|
||||
setupEventHandlers();
|
||||
|
||||
if (window.opener)
|
||||
sieveCapabilities = window.opener.getSieveCapabilitiesFromEditor();
|
||||
if (!window.opener || filterId == "new") {
|
||||
setupNewFilterData();
|
||||
} else {
|
||||
filter = window.opener.getFilterFromEditor(filterId);
|
||||
}
|
||||
|
||||
if (!window.opener || window.opener.userMailboxes) {
|
||||
setupFilterViews();
|
||||
} else {
|
||||
loadMailboxes();
|
||||
}
|
||||
}
|
||||
|
||||
function loadMailboxes() {
|
||||
var url = (ApplicationBaseURL + "Mail/"
|
||||
+ encodeURI(firstMailAccount) + "/mailboxes");
|
||||
triggerAjaxRequest(url, onLoadMailboxesCallback);
|
||||
}
|
||||
|
||||
function onLoadMailboxesCallback(http) {
|
||||
if (http.readyState == 4) {
|
||||
window.opener.userMailboxes = [];
|
||||
if (http.status == 200) {
|
||||
checkAjaxRequestsState();
|
||||
if (http.responseText.length > 0) {
|
||||
var jsonResponse = http.responseText.evalJSON(true);
|
||||
var responseMboxes = jsonResponse.mailboxes;
|
||||
for (var i = 0; i < responseMboxes.length; i++) {
|
||||
var name = responseMboxes[i].path.substr(1);
|
||||
window.opener.userMailboxes.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
setupFilterViews();
|
||||
}
|
||||
}
|
||||
|
||||
function setupConstants() {
|
||||
fieldLabels = { "subject": _("Subject"),
|
||||
"from": _("From"),
|
||||
"to": _("To"),
|
||||
"cc": _("Cc"),
|
||||
"to_or_cc": _("To or Cc"),
|
||||
"size": _("Size (Kb)"),
|
||||
"header": _("Header") };
|
||||
methodLabels = { "addflag": _("Flag the message with:"),
|
||||
"discard": _("Discard the message"),
|
||||
"fileinto": _("File the message in:"),
|
||||
"keep": _("Keep the message"),
|
||||
"redirect": _("Forward the message to:"),
|
||||
"reject": _("Send a reject message:"),
|
||||
"vacation": _("Send a vacation message"),
|
||||
"stop": _("Stop processing filter rules") };
|
||||
|
||||
operatorLabels = { "under": _("is under"),
|
||||
"over": _("is over"),
|
||||
"is": _("is"),
|
||||
"is_not": _("is not"),
|
||||
"contains": _("contains"),
|
||||
"contains_not": _("does not contain"),
|
||||
"matches": _("matches"),
|
||||
"matches_not": _("does not match"),
|
||||
"regex": _("matches regex"),
|
||||
"regex_not": _("does not match regex") };
|
||||
|
||||
flagLabels = { "seen": _("Seen"),
|
||||
"deleted": _("Deleted"),
|
||||
"answered": _("Answered"),
|
||||
"flagged": _("Flagged"),
|
||||
"junk": _("Junk"),
|
||||
"not_junk": _("Not Junk") };
|
||||
for (var i = 1; i < 6; i++) {
|
||||
var key = "label" + i;
|
||||
flagLabels[key] = _("Label " + i);
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventHandlers() {
|
||||
var filterName = $("mainForm").filterName;
|
||||
if (filterName) {
|
||||
var boundCB = onFilterNameChange
|
||||
.bindAsEventListener(filterName);
|
||||
filterName.observe("change", boundCB);
|
||||
}
|
||||
var matchTypeSelect = $("matchType");
|
||||
if (matchTypeSelect) {
|
||||
var boundCB = onMatchTypeChange
|
||||
.bindAsEventListener(matchTypeSelect);
|
||||
matchTypeSelect.observe("change", boundCB);
|
||||
}
|
||||
|
||||
var filterRules = $("filterRules");
|
||||
var boundCB = onFilterRulesDivClick
|
||||
.bindAsEventListener(filterRules);
|
||||
filterRules.observe("click", boundCB);
|
||||
var ruleAdd = $("ruleAdd");
|
||||
if (ruleAdd) {
|
||||
var boundCB = onRuleAddClick.bindAsEventListener(ruleAdd);
|
||||
ruleAdd.observe("click", boundCB);
|
||||
}
|
||||
var ruleDelete = $("ruleDelete");
|
||||
if (ruleDelete) {
|
||||
var boundCB = onRuleDeleteClick.bindAsEventListener(ruleDelete);
|
||||
ruleDelete.observe("click", boundCB);
|
||||
}
|
||||
|
||||
var filterActions = $("filterActions");
|
||||
var boundCB = onFilterActionsDivClick
|
||||
.bindAsEventListener(filterActions);
|
||||
filterActions.observe("click", boundCB);
|
||||
var actionAdd = $("actionAdd");
|
||||
if (actionAdd) {
|
||||
var boundCB = onActionAddClick.bindAsEventListener(actionAdd);
|
||||
actionAdd.observe("click", boundCB);
|
||||
}
|
||||
var actionDelete = $("actionDelete");
|
||||
if (actionDelete) {
|
||||
var boundCB = onActionDeleteClick
|
||||
.bindAsEventListener(actionDelete);
|
||||
actionDelete.observe("click", boundCB);
|
||||
}
|
||||
}
|
||||
|
||||
function onFilterNameChange(event) {
|
||||
filter.name = this.value;
|
||||
}
|
||||
|
||||
function onMatchTypeChange() {
|
||||
var matchType = this.value;
|
||||
filter.match = matchType;
|
||||
var container = $("filterRulesContainer");
|
||||
var otherContainer = $("filterActionsContainer");
|
||||
var otherContainerTop;
|
||||
if (matchType == "allmessages") {
|
||||
container.hide();
|
||||
otherContainerTop = 130;
|
||||
} else {
|
||||
container.show();
|
||||
otherContainerTop = 240;
|
||||
}
|
||||
otherContainer.setStyle({ top: otherContainerTop + "px" });
|
||||
}
|
||||
|
||||
function onFilterRulesDivClick(event) {
|
||||
setSelectedRuleDiv(null);
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onFilterActionsDivClick(event) {
|
||||
setSelectedActionDiv(null);
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function createFilterRule() {
|
||||
return { field: "subject", operator: "contains", value: "" };
|
||||
}
|
||||
|
||||
function createFilterAction() {
|
||||
return { method: "fileinto", argument: "INBOX" };
|
||||
}
|
||||
|
||||
function setupNewFilterData() {
|
||||
var newFilterTemplate = $({ name: _("Untitled Filter"),
|
||||
match: "any" });
|
||||
newFilterTemplate.rules = $([ createFilterRule() ]);
|
||||
newFilterTemplate.actions = $([ createFilterAction() ]);
|
||||
|
||||
filter = newFilterTemplate;
|
||||
}
|
||||
|
||||
function setupFilterViews() {
|
||||
var filterName = $("mainForm").filterName;
|
||||
if (filterName) {
|
||||
filterName.value = filter.name;
|
||||
if (filterId == "new") {
|
||||
filterName.focus();
|
||||
$(filterName).selectText(0, filterName.value.length);
|
||||
}
|
||||
}
|
||||
|
||||
var matchTypeSelect = $("matchType");
|
||||
if (matchTypeSelect) {
|
||||
matchTypeSelect.value = filter.match;
|
||||
}
|
||||
if (filter.match != "allmessages") {
|
||||
var filterRules = $("filterRules");
|
||||
if (filterRules && filter.rules) {
|
||||
for (var i = 0; i < filter.rules.length; i++) {
|
||||
appendRule(filterRules, filter.rules[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var filterActions = $("filterActions");
|
||||
if (filterActions && filter.actions) {
|
||||
for (var i = 0; i < filter.actions.length; i++) {
|
||||
appendAction(filterActions, filter.actions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function appendRule(container, rule) {
|
||||
var ruleDiv = createElement("div", null, "rule",
|
||||
{ rule: rule }, null,
|
||||
container);
|
||||
var boundCB = onRuleDivClick.bindAsEventListener(ruleDiv);
|
||||
ruleDiv.observe("click", boundCB);
|
||||
ensureRuleRepresentation(ruleDiv);
|
||||
|
||||
return ruleDiv;
|
||||
}
|
||||
|
||||
function onRuleDivClick(event) {
|
||||
setSelectedRuleDiv(this);
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function setSelectedRuleDiv(newDiv) {
|
||||
if (selectedRuleDiv) {
|
||||
selectedRuleDiv.removeClassName("_selected");
|
||||
}
|
||||
selectedRuleDiv = newDiv;
|
||||
if (selectedRuleDiv) {
|
||||
selectedRuleDiv.addClassName("_selected");
|
||||
}
|
||||
}
|
||||
|
||||
function ensureRuleRepresentation(container) {
|
||||
ensureFieldRepresentation(container);
|
||||
ensureOperatorRepresentation(container);
|
||||
ensureValueRepresentation(container);
|
||||
}
|
||||
|
||||
function ensureFieldRepresentation(container) {
|
||||
var fieldSpans = container.select("SPAN.fieldContainer");
|
||||
var fieldSpan;
|
||||
if (fieldSpans.length)
|
||||
fieldSpan = fieldSpans[0];
|
||||
else {
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
fieldSpan = createElement("span", null, "fieldContainer",
|
||||
null, null, container);
|
||||
}
|
||||
ensureFieldSelectRepresentation(container, fieldSpan);
|
||||
ensureFieldCustomHeaderRepresentation(container, fieldSpan);
|
||||
}
|
||||
|
||||
function ensureFieldSelectRepresentation(container, fieldSpan) {
|
||||
var fields
|
||||
= [ "subject", "from", "to", "cc", "to_or_cc", "size", "header" ];
|
||||
var selects = fieldSpan.select("SELECT");
|
||||
var select;
|
||||
if (selects.length)
|
||||
select = selects[0];
|
||||
else {
|
||||
select = createElement("select");
|
||||
select.rule = container.rule;
|
||||
var boundCB = onFieldSelectChange.bindAsEventListener(select);
|
||||
select.observe("change", boundCB);
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
var field = fields[i];
|
||||
var fieldOption = createElement("option", null, null,
|
||||
{ value: field }, null, select);
|
||||
fieldOption.appendChild(document
|
||||
.createTextNode(fieldLabels[field]));
|
||||
}
|
||||
fieldSpan.appendChild(select);
|
||||
}
|
||||
select.value = container.rule.field;
|
||||
container.rule.field = select.value;
|
||||
}
|
||||
|
||||
function onFieldSelectChange(event) {
|
||||
this.rule.field = this.value;
|
||||
var fieldSpan = this.parentNode;
|
||||
var container = fieldSpan.parentNode;
|
||||
ensureFieldCustomHeaderRepresentation(container, fieldSpan);
|
||||
ensureOperatorRepresentation(container);
|
||||
ensureValueRepresentation(container);
|
||||
}
|
||||
|
||||
function ensureFieldCustomHeaderRepresentation(container, fieldSpan) {
|
||||
var headerInputs = fieldSpan.select("INPUT");
|
||||
var headerInput = null;
|
||||
if (headerInputs.length) {
|
||||
headerInput = headerInputs[0];
|
||||
}
|
||||
if (container.rule.field == "header") {
|
||||
if (!headerInput) {
|
||||
headerInput = createElement("input", null, "textField",
|
||||
{ type: "text" }, null, fieldSpan);
|
||||
headerInput.rule = container.rule;
|
||||
if (!container.rule.custom_header)
|
||||
container.rule.custom_header = "";
|
||||
headerInput.value = container.rule.custom_header;
|
||||
var boundCB
|
||||
= onFieldCustomHeaderChange.bindAsEventListener(headerInput);
|
||||
headerInput.observe("change", boundCB);
|
||||
headerInput.focus();
|
||||
}
|
||||
} else {
|
||||
if (headerInput) {
|
||||
if (container.rule.custom_header)
|
||||
container.rule.custom_header = null;
|
||||
fieldSpan.removeChild(headerInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onFieldCustomHeaderChange(event) {
|
||||
this.rule.custom_header = this.value;
|
||||
}
|
||||
|
||||
function ensureOperatorRepresentation(container) {
|
||||
var operatorSpans = container.select("SPAN.operatorContainer");
|
||||
var operatorSpan;
|
||||
if (operatorSpans.length)
|
||||
operatorSpan = operatorSpans[0];
|
||||
else
|
||||
operatorSpan = createElement("span", null, "operatorContainer",
|
||||
null, null, container);
|
||||
ensureOperatorSelectRepresentation(container, operatorSpan);
|
||||
}
|
||||
|
||||
function ensureOperatorSelectRepresentation(container, operatorSpan) {
|
||||
var operators = determineOperators(container.rule.field);
|
||||
|
||||
var ruleField = container.rule.field;
|
||||
var selects = operatorSpan.select("SELECT");
|
||||
var select = null;
|
||||
if (selects.length) {
|
||||
select = selects[0];
|
||||
if ((ruleField == "size" && !select.sizeOperator)
|
||||
|| (ruleField != "size" && select.sizeOperator)) {
|
||||
operatorSpan.removeChild(select);
|
||||
select = null;
|
||||
}
|
||||
}
|
||||
if (!select) {
|
||||
select = createElement("select");
|
||||
select.rule = container.rule;
|
||||
select.sizeOperator = (ruleField == "size");
|
||||
var boundCB = onOperatorSelectChange.bindAsEventListener(select);
|
||||
select.observe("change", boundCB);
|
||||
for (var i = 0; i < operators.length; i++) {
|
||||
var operator = operators[i];
|
||||
var operatorOption = createElement("option", null, null,
|
||||
{ value: operator }, null,
|
||||
select);
|
||||
operatorOption.appendChild(document
|
||||
.createTextNode(operatorLabels[operator]));
|
||||
}
|
||||
operatorSpan.appendChild(select);
|
||||
}
|
||||
if (container.rule.operator
|
||||
&& operators.indexOf(container.rule.operator) == -1) {
|
||||
container.rule.operator = operators[0];
|
||||
}
|
||||
select.value = container.rule.operator;
|
||||
container.rule.operator = select.value;
|
||||
}
|
||||
|
||||
function onOperatorSelectChange(event) {
|
||||
this.rule.operator = this.value;
|
||||
var valueSpans = this.parentNode.parentNode.select("SPAN.valueContainer");
|
||||
if (valueSpans.length) {
|
||||
var valueInputs = valueSpans[0].select("INPUT");
|
||||
if (valueInputs.length) {
|
||||
valueInputs[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function determineOperators(field) {
|
||||
var operators;
|
||||
if (field == "size") {
|
||||
operators = [ "under", "over" ];
|
||||
} else {
|
||||
var baseOperators = [ "is", "contains", "matches" ];
|
||||
if (sieveCapabilities.indexOf("regex") > -1) {
|
||||
baseOperators.push("regex");
|
||||
}
|
||||
operators = [];
|
||||
for (var i = 0; i < baseOperators.length; i++) {
|
||||
operators.push(baseOperators[i]);
|
||||
operators.push(baseOperators[i] + "_not");
|
||||
}
|
||||
}
|
||||
|
||||
return operators;
|
||||
}
|
||||
|
||||
function ensureValueRepresentation(container) {
|
||||
var valueSpans = container.select("SPAN.valueContainer");
|
||||
var valueSpan;
|
||||
if (valueSpans.length)
|
||||
valueSpan = valueSpans[0];
|
||||
else
|
||||
valueSpan = createElement("span", null, "valueContainer",
|
||||
null, null, container);
|
||||
ensureValueInputRepresentation(container, valueSpan);
|
||||
}
|
||||
|
||||
function ensureValueInputRepresentation(container, valueSpan) {
|
||||
var inputs = valueSpan.select("INPUT");
|
||||
var input;
|
||||
if (inputs.length) {
|
||||
input = inputs[0];
|
||||
}
|
||||
else {
|
||||
input = createElement("input", null, "textField");
|
||||
input.rule = container.rule;
|
||||
var boundCB = onValueInputChange.bindAsEventListener(input);
|
||||
input.observe("change", boundCB);
|
||||
valueSpan.appendChild(input);
|
||||
}
|
||||
input.value = container.rule.value;
|
||||
ensureFieldValidity(input);
|
||||
}
|
||||
|
||||
function ensureFieldValidity(input) {
|
||||
var valid = true;
|
||||
if (input.rule.field == "size") {
|
||||
valid = ensureFieldIsNumerical(input);
|
||||
} else
|
||||
input.removeClassName("_invalid");
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
function onValueInputChange(event) {
|
||||
if (ensureFieldValidity(this))
|
||||
this.rule.value = this.value;
|
||||
else
|
||||
this.rule.value = "0";
|
||||
}
|
||||
|
||||
function ensureFieldIsNumerical(input) {
|
||||
var valid = !isNaN(input.value);
|
||||
if (valid) {
|
||||
input.removeClassName("_invalid");
|
||||
} else {
|
||||
input.addClassName("_invalid");
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
function appendAction(container, action) {
|
||||
var actionDiv = createElement("div", null, "action",
|
||||
{ action: action }, null,
|
||||
container);
|
||||
var boundCB = onActionDivClick.bindAsEventListener(actionDiv);
|
||||
actionDiv.observe("click", boundCB);
|
||||
ensureActionRepresentation(actionDiv);
|
||||
|
||||
return actionDiv;
|
||||
}
|
||||
|
||||
function onActionDivClick(event) {
|
||||
setSelectedActionDiv(this);
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function setSelectedActionDiv(newSpan) {
|
||||
if (selectedActionDiv) {
|
||||
selectedActionDiv.removeClassName("_selected");
|
||||
}
|
||||
selectedActionDiv = newSpan;
|
||||
if (selectedActionDiv) {
|
||||
selectedActionDiv.addClassName("_selected");
|
||||
}
|
||||
}
|
||||
|
||||
function ensureActionRepresentation(container) {
|
||||
ensureMethodRepresentation(container);
|
||||
ensureArgumentRepresentation(container);
|
||||
}
|
||||
|
||||
function ensureMethodRepresentation(container) {
|
||||
var methodSpans = container.select("SPAN.methodContainer");
|
||||
var methodSpan;
|
||||
if (methodSpans.length)
|
||||
methodSpan = methodSpans[0];
|
||||
else {
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
methodSpan = createElement("span", null, "methodContainer",
|
||||
null, null, container);
|
||||
}
|
||||
ensureMethodSelectRepresentation(container, methodSpan);
|
||||
}
|
||||
|
||||
function ensureMethodSelectRepresentation(container, methodSpan) {
|
||||
var methods = [ "redirect", "discard", "keep" ];
|
||||
if (sieveCapabilities.indexOf("reject") > -1) {
|
||||
methods.push("reject");
|
||||
}
|
||||
if (sieveCapabilities.indexOf("fileinto") > -1) {
|
||||
methods.push("fileinto");
|
||||
}
|
||||
if (sieveCapabilities.indexOf("imapflags") > -1) {
|
||||
methods.push("addflag");
|
||||
}
|
||||
methods.push("stop");
|
||||
/* TODO: those are currently unimplemented */
|
||||
// if (sieveCapabilities.indexOf("notify") > -1) {
|
||||
// methods.push("notify");
|
||||
// }
|
||||
// if (sieveCapabilities.indexOf("vacation") > -1) {
|
||||
// methods.push("vacation");
|
||||
// }
|
||||
|
||||
var selects = methodSpan.select("SELECT");
|
||||
var select;
|
||||
if (selects.length)
|
||||
select = selects[0];
|
||||
else {
|
||||
select = createElement("select");
|
||||
select.action = container.action;
|
||||
var boundCB = onMethodSelectChange.bindAsEventListener(select);
|
||||
select.observe("change", boundCB);
|
||||
for (var i = 0; i < methods.length; i++) {
|
||||
var method = methods[i];
|
||||
var methodOption = createElement("option", null, null,
|
||||
{ value: method }, null, select);
|
||||
methodOption.appendChild(document
|
||||
.createTextNode(methodLabels[method]));
|
||||
}
|
||||
methodSpan.appendChild(select);
|
||||
}
|
||||
select.value = container.action.method;
|
||||
}
|
||||
|
||||
function onMethodSelectChange(event) {
|
||||
this.action.method = this.value;
|
||||
var methodSpan = this.parentNode;
|
||||
var container = methodSpan.parentNode;
|
||||
ensureArgumentRepresentation(container);
|
||||
}
|
||||
|
||||
function ensureArgumentRepresentation(container) {
|
||||
var argumentWidgetMethods
|
||||
= { "addflag": ensureFlagArgRepresentation,
|
||||
"fileinto": ensureMailboxArgRepresentation,
|
||||
"redirect": ensureRedirectArgRepresentation,
|
||||
"reject": ensureRejectArgRepresentation,
|
||||
"vacation": ensureVacationArgRepresentation };
|
||||
|
||||
var widgetMethod = argumentWidgetMethods[container.action.method];
|
||||
var spanClass = container.action.method + "Argument";
|
||||
|
||||
var argumentSpans = container.select("SPAN.argumentContainer");
|
||||
var argumentSpan;
|
||||
if (argumentSpans.length) {
|
||||
argumentSpan = argumentSpans[0];
|
||||
if (argumentSpan
|
||||
&& (!widgetMethod || !argumentSpan.hasClassName(spanClass))) {
|
||||
container.removeChild(argumentSpan);
|
||||
container.action.argument = null;
|
||||
argumentSpan = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
argumentSpan = null;
|
||||
|
||||
if (!argumentSpan && widgetMethod) {
|
||||
argumentSpan = createElement("span", null,
|
||||
["argumentContainer", spanClass],
|
||||
null, null, container);
|
||||
widgetMethod(container, argumentSpan);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureFlagArgRepresentation(container, argumentSpan) {
|
||||
var flags = [ "seen", "deleted", "answered", "flagged", "junk",
|
||||
"not_junk" ];
|
||||
for (var i = 1; i < 6; i++) {
|
||||
flags.push("label" + i);
|
||||
}
|
||||
|
||||
var selects = argumentSpan.select("SELECT");
|
||||
var select;
|
||||
if (selects.length)
|
||||
select = selects[0];
|
||||
else {
|
||||
select = createElement("select");
|
||||
select.action = container.action;
|
||||
var boundCB = onFlagArgumentSelectChange.bindAsEventListener(select);
|
||||
select.observe("change", boundCB);
|
||||
for (var i = 0; i < flags.length; i++) {
|
||||
var flag = flags[i];
|
||||
var flagOption = createElement("option", null, null,
|
||||
{ value: flag }, null, select);
|
||||
var label = flagLabels[flag];
|
||||
flagOption.appendChild(document.createTextNode(label));
|
||||
}
|
||||
argumentSpan.appendChild(select);
|
||||
}
|
||||
/* 1) initialize the value if null
|
||||
2) set the SELECT to the corresponding value
|
||||
3) if value was not null in 1, we must ensure the SELECT contains it */
|
||||
if (!container.action.argument)
|
||||
container.action.argument = "seen";
|
||||
select.value = container.action.argument;
|
||||
container.action.argument = select.value;
|
||||
}
|
||||
|
||||
function onFlagArgumentSelectChange(event) {
|
||||
this.action.argument = this.value;
|
||||
}
|
||||
|
||||
function ensureMailboxArgRepresentation(container, argumentSpan) {
|
||||
var selects = argumentSpan.select("SELECT");
|
||||
var select;
|
||||
if (selects.length)
|
||||
select = selects[0];
|
||||
else {
|
||||
select = createElement("select");
|
||||
select.action = container.action;
|
||||
if (!container.action.argument)
|
||||
container.action.argument = "INBOX";
|
||||
var boundCB = onMailboxArgumentSelectChange.bindAsEventListener(select);
|
||||
select.observe("change", boundCB);
|
||||
var mailboxes = (window.opener
|
||||
? window.opener.userMailboxes
|
||||
: ["INBOX" ]);
|
||||
for (var i = 0; i < mailboxes.length; i++) {
|
||||
var mailbox = mailboxes[i];
|
||||
var mboxOption = createElement("option", null, null,
|
||||
{ value: mailbox }, null, select);
|
||||
mboxOption.appendChild(document.createTextNode(mailbox));
|
||||
}
|
||||
argumentSpan.appendChild(select);
|
||||
}
|
||||
select.value = container.action.argument;
|
||||
container.action.argument = select.value;
|
||||
}
|
||||
|
||||
function onMailboxArgumentSelectChange(event) {
|
||||
this.action.argument = this.value;
|
||||
}
|
||||
|
||||
function ensureRedirectArgRepresentation(container, argumentSpan) {
|
||||
var emailInputs = argumentSpan.select("INPUT");
|
||||
var emailInput = null;
|
||||
if (emailInputs.length) {
|
||||
emailInput = emailInputs[0];
|
||||
}
|
||||
if (!emailInput) {
|
||||
emailInput = createElement("input", null, "textField",
|
||||
{ type: "text" }, null, argumentSpan);
|
||||
emailInput.action = container.action;
|
||||
if (!container.action.argument)
|
||||
container.action.argument = "";
|
||||
var boundCB
|
||||
= onEmailArgumentChange.bindAsEventListener(emailInput);
|
||||
emailInput.observe("change", boundCB);
|
||||
emailInput.focus();
|
||||
}
|
||||
emailInput.value = container.action.argument;
|
||||
}
|
||||
|
||||
function onEmailArgumentChange(event) {
|
||||
this.action.argument = this.value;
|
||||
}
|
||||
|
||||
function ensureRejectArgRepresentation(container, argumentSpan) {
|
||||
var msgAreas = argumentSpan.select("TEXTAREA");
|
||||
var msgArea = null;
|
||||
if (msgAreas.length) {
|
||||
msgArea = msgAreas[0];
|
||||
}
|
||||
if (!msgArea) {
|
||||
msgArea = createElement("textarea", null, null,
|
||||
{ action: container.action }, null,
|
||||
argumentSpan);
|
||||
if (!container.action.argument)
|
||||
container.action.argument = "";
|
||||
var boundCB
|
||||
= onMsgArgumentChange.bindAsEventListener(msgArea);
|
||||
msgArea.observe("change", boundCB);
|
||||
msgArea.focus();
|
||||
}
|
||||
msgArea.value = container.action.argument;
|
||||
}
|
||||
|
||||
function onMsgArgumentChange(event) {
|
||||
this.action.argument = this.value;
|
||||
}
|
||||
|
||||
function ensureVacationArgRepresentation(container, argumentSpan) {
|
||||
|
||||
}
|
||||
|
||||
function onRuleAddClick(event) {
|
||||
var filterRules = $("filterRules");
|
||||
if (filterRules) {
|
||||
var newRule = createFilterRule();
|
||||
filter.rules.push(newRule);
|
||||
var newRuleDiv = appendRule(filterRules, newRule);
|
||||
setSelectedRuleDiv(newRuleDiv);
|
||||
filterRules.scrollTop = newRuleDiv.offsetTop;
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onRuleDeleteClick(event) {
|
||||
if (selectedRuleDiv) {
|
||||
var ruleIndex = filter.rules.indexOf(selectedRuleDiv.rule);
|
||||
filter.rules.splice(ruleIndex, 1);
|
||||
var nextSelected = selectedRuleDiv.next();
|
||||
if (!nextSelected)
|
||||
nextSelected = selectedRuleDiv.previous();
|
||||
selectedRuleDiv.parentNode.removeChild(selectedRuleDiv);
|
||||
setSelectedRuleDiv(nextSelected);
|
||||
}
|
||||
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onActionAddClick(event) {
|
||||
var filterActions = $("filterActions");
|
||||
if (filterActions) {
|
||||
var newAction = createFilterAction();
|
||||
filter.actions.push(newAction);
|
||||
var newActionDiv = appendAction(filterActions, newAction);
|
||||
setSelectedActionDiv(newActionDiv);
|
||||
filterActions.scrollTop = newActionDiv.offsetTop;
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onActionDeleteClick(event) {
|
||||
if (selectedActionDiv) {
|
||||
var actionIndex = filter.actions.indexOf(selectedActionDiv.action);
|
||||
filter.actions.splice(actionIndex, 1);
|
||||
var nextSelected = selectedActionDiv.next();
|
||||
if (!nextSelected)
|
||||
nextSelected = selectedActionDiv.previous();
|
||||
selectedActionDiv.parentNode.removeChild(selectedActionDiv);
|
||||
setSelectedActionDiv(nextSelected);
|
||||
}
|
||||
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function savePreferences() {
|
||||
if (window.opener) {
|
||||
window.opener.updateFilterFromEditor(filterId, filter);
|
||||
}
|
||||
window.close();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// function configureDragHandles() {
|
||||
// var handle = $("splitter");
|
||||
// if (handle) {
|
||||
// handle.addInterface(SOGoDragHandlesInterface);
|
||||
// handle.upperBlock = $("filterRulesContainer");
|
||||
// handle.lowerBlock = $("filterActionsContainer");
|
||||
// }
|
||||
// }
|
||||
|
||||
document.observe("dom:loaded", onLoadHandler);
|
|
@ -76,3 +76,33 @@ DIV.bottomToolbar
|
|||
left: 2em;
|
||||
right: 2em;
|
||||
margin: 0px; }
|
||||
|
||||
/* mail filters */
|
||||
DIV#filtersListWrapper
|
||||
{ overflow: auto;
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 2em;
|
||||
top: 154px;
|
||||
left: 2em;
|
||||
padding: 0px;
|
||||
margin-top: 2px;
|
||||
border-left: 1px solid #9b9b9b;
|
||||
background: #ccddec; }
|
||||
|
||||
TABLE#filtersList
|
||||
{ width: 100%; }
|
||||
|
||||
TR.filterListRow
|
||||
{ background: #ccddec;
|
||||
cursor: pointer; }
|
||||
|
||||
TD.filterListCell
|
||||
{ -moz-user-select: none; }
|
||||
|
||||
TH#activeTableHeader
|
||||
{ width: 50px;
|
||||
text-align: center; }
|
||||
|
||||
TD.activeColumn
|
||||
{ text-align: center; }
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* -*- Mode: java; tab-width: 2; c-label-minimum-indentation: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
var isSieveScriptsEnabled = false;
|
||||
var filters = [];
|
||||
|
||||
function savePreferences(sender) {
|
||||
var sendForm = true;
|
||||
|
@ -37,12 +40,41 @@ function savePreferences(sender) {
|
|||
}
|
||||
}
|
||||
|
||||
if (isSieveScriptsEnabled) {
|
||||
var jsonFilters = prototypeIfyFilters();
|
||||
$("sieveFilters").setValue(jsonFilters.toJSON());
|
||||
}
|
||||
|
||||
if (sendForm)
|
||||
$("mainForm").submit();
|
||||
$("mainForm").submit();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function prototypeIfyFilters() {
|
||||
var newFilters = $([]);
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
var filter = filters[i];
|
||||
var newFilter = $({});
|
||||
newFilter.name = filter.name;
|
||||
newFilter.match = filter.match;
|
||||
newFilter.active = filter.active;
|
||||
|
||||
newFilter.rules = $([]);
|
||||
for (var j = 0; j < filter.rules.length; j++) {
|
||||
newFilter.rules.push($(filter.rules[j]));
|
||||
}
|
||||
|
||||
newFilter.actions = $([]);
|
||||
for (var j = 0; j < filter.actions.length; j++) {
|
||||
newFilter.actions.push($(filter.actions[j]));
|
||||
}
|
||||
newFilters.push(newFilter);
|
||||
}
|
||||
|
||||
return newFilters;
|
||||
}
|
||||
|
||||
function _setupEvents(enable) {
|
||||
var widgets = [ "timezone", "shortDateFormat", "longDateFormat",
|
||||
"timeFormat", "weekStartDay", "dayStartTime", "dayEndTime",
|
||||
|
@ -92,6 +124,10 @@ function addDefaultEmailAddresses() {
|
|||
}
|
||||
|
||||
function initPreferences() {
|
||||
var filtersListWrapper = $("filtersListWrapper");
|
||||
if (filtersListWrapper) {
|
||||
isSieveScriptsEnabled = true;
|
||||
}
|
||||
_setupEvents(true);
|
||||
if (typeof (initAdditionalPreferences) != "undefined")
|
||||
initAdditionalPreferences();
|
||||
|
@ -122,7 +158,205 @@ function initPreferences() {
|
|||
}
|
||||
|
||||
if ($("addDefaultEmailAddresses"))
|
||||
$("addDefaultEmailAddresses").observe("click", addDefaultEmailAddresses);
|
||||
$("addDefaultEmailAddresses").observe("click",
|
||||
addDefaultEmailAddresses);
|
||||
|
||||
initSieveFilters();
|
||||
}
|
||||
|
||||
function initSieveFilters() {
|
||||
var table = $("filtersList");
|
||||
if (table) {
|
||||
var filtersValue = $("sieveFilters").getValue();
|
||||
if (filtersValue && filtersValue.length) {
|
||||
filters = $(filtersValue.evalJSON(false));
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
appendSieveFilterRow(table, i, filters[i]);
|
||||
}
|
||||
}
|
||||
$("filterAdd").observe("click", onFilterAdd);
|
||||
$("filterDelete").observe("click", onFilterDelete);
|
||||
$("filterMoveUp").observe("click", onFilterMoveUp);
|
||||
$("filterMoveDown").observe("click", onFilterMoveDown);
|
||||
}
|
||||
}
|
||||
|
||||
function appendSieveFilterRow(filterTable, number, filter) {
|
||||
var row = createElement("tr");
|
||||
row.observe("mousedown", onRowClick);
|
||||
row.observe("dblclick", onFilterEdit.bindAsEventListener(row));
|
||||
|
||||
var nameColumn = createElement("td");
|
||||
nameColumn.appendChild(document.createTextNode(filter["name"]));
|
||||
row.appendChild(nameColumn);
|
||||
|
||||
var activeColumn = createElement("td", null, "activeColumn");
|
||||
var cb = createElement("input", null, "checkBox",
|
||||
{ checked: filter.active,
|
||||
type: "checkbox" },
|
||||
null, activeColumn);
|
||||
var bound = onScriptActiveCheck.bindAsEventListener(cb);
|
||||
cb.observe("change", bound);
|
||||
row.appendChild(activeColumn);
|
||||
|
||||
filterTable.tBodies[0].appendChild(row);
|
||||
}
|
||||
|
||||
function onScriptActiveCheck(event) {
|
||||
var index = this.parentNode.parentNode.rowIndex - 1;
|
||||
filters[index].active = this.checked;
|
||||
}
|
||||
|
||||
function updateSieveFilterRow(filterTable, number, filter) {
|
||||
var row = $(filterTable.tBodies[0].rows[number]);
|
||||
var columns = row.childNodesWithTag("td");
|
||||
var nameColumn = columns[0];
|
||||
while (nameColumn.firstChild) {
|
||||
nameColumn.removeChild(nameColumn.firstChild);
|
||||
}
|
||||
nameColumn.appendChild(document.createTextNode(filter.name));
|
||||
|
||||
var activeColumn = columns[1];
|
||||
while (activeColumn.firstChild) {
|
||||
activeColumn.removeChild(activeColumn.firstChild);
|
||||
}
|
||||
createElement("input", null, "checkBox",
|
||||
{ checked: filter.active,
|
||||
type: "checkbox" },
|
||||
null, activeColumn);
|
||||
}
|
||||
|
||||
function _editFilter(filterId) {
|
||||
var urlstr = ApplicationBaseURL + "editFilter?filter=" + filterId;
|
||||
var win = window.open(urlstr, "sieve_filter_" + filterId,
|
||||
"width=450,height=380,resizable=0");
|
||||
if (win)
|
||||
win.focus();
|
||||
}
|
||||
|
||||
function onFilterAdd(event) {
|
||||
log("onFilterAdd");
|
||||
_editFilter("new");
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onFilterDelete(event) {
|
||||
var filtersList = $("filtersList").tBodies[0];
|
||||
var nodes = filtersList.getSelectedNodes();
|
||||
if (nodes.length > 0) {
|
||||
var deletedFilters = [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
deletedFilters.push(node.rowIndex - 1);
|
||||
}
|
||||
deletedFilters = deletedFilters.sort(function (x,y) { return x-y; });
|
||||
var rows = filtersList.rows;
|
||||
for (var i = 0; i < deletedFilters.length; i++) {
|
||||
var filterNbr = deletedFilters[i];
|
||||
filters.splice(filterNbr, 1);
|
||||
var row = rows[filterNbr];
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onFilterMoveUp(event) {
|
||||
var filtersList = $("filtersList").tBodies[0];
|
||||
var nodes = filtersList.getSelectedNodes();
|
||||
if (nodes.length > 0) {
|
||||
var node = nodes[0];
|
||||
var previous = node.previous();
|
||||
if (previous) {
|
||||
var count = node.rowIndex - 1;
|
||||
node.parentNode.removeChild(node);
|
||||
filtersList.insertBefore(node, previous);
|
||||
var swapFilter = filters[count];
|
||||
filters[count] = filters[count - 1];
|
||||
filters[count - 1] = swapFilter;
|
||||
}
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onFilterMoveDown(event) {
|
||||
var filtersList = $("filtersList").tBodies[0];
|
||||
var nodes = filtersList.getSelectedNodes();
|
||||
if (nodes.length > 0) {
|
||||
var node = nodes[0];
|
||||
var next = node.next();
|
||||
if (next) {
|
||||
var count = node.rowIndex - 1;
|
||||
filtersList.removeChild(next);
|
||||
filtersList.insertBefore(next, node);
|
||||
var swapFilter = filters[count];
|
||||
filters[count] = filters[count + 1];
|
||||
filters[count + 1] = swapFilter;
|
||||
}
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function onFilterEdit(event) {
|
||||
_editFilter(this.rowIndex - 1);
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function copyFilter(originalFilter) {
|
||||
var newFilter = {};
|
||||
|
||||
newFilter.name = originalFilter.name;
|
||||
newFilter.match = originalFilter.match;
|
||||
newFilter.active = originalFilter.active;
|
||||
newFilter.rules = [];
|
||||
for (var i = 0; i < originalFilter.rules.length; i++) {
|
||||
newFilter.rules.push(_copyFilterElement(originalFilter.rules[i]));
|
||||
}
|
||||
newFilter.actions = [];
|
||||
for (var i = 0; i < originalFilter.actions.length; i++) {
|
||||
newFilter.actions.push(_copyFilterElement(originalFilter.actions[i]));
|
||||
}
|
||||
|
||||
return newFilter;
|
||||
}
|
||||
|
||||
function _copyFilterElement(filterElement) { /* element = rule or action */
|
||||
var newElement = {};
|
||||
for (var k in filterElement) {
|
||||
var value = filterElement[k];
|
||||
if (typeof(value) == "string" || typeof(value) == "number") {
|
||||
newElement[k] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
function getSieveCapabilitiesFromEditor() {
|
||||
return sieveCapabilities;
|
||||
}
|
||||
|
||||
function getFilterFromEditor(filterId) {
|
||||
return copyFilter(filters[filterId]);
|
||||
}
|
||||
|
||||
function updateFilterFromEditor(filterId, filter) {
|
||||
var sanitized = {};
|
||||
for (var k in filter) {
|
||||
if (!(k == "rules" && filter.match == "allmessages")) {
|
||||
sanitized[k] = filter[k];
|
||||
}
|
||||
}
|
||||
|
||||
var table = $("filtersList");
|
||||
if (filterId == "new") {
|
||||
var newNumber = filters.length;
|
||||
filters.push(sanitized);
|
||||
appendSieveFilterRow(table, newNumber, sanitized);
|
||||
} else {
|
||||
filters[filterId] = sanitized;
|
||||
updateSieveFilterRow(table, filterId, sanitized);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTableActions() {
|
||||
|
@ -292,7 +526,7 @@ function onComposeMessagesTypeChange(event) {
|
|||
return savePreferences();
|
||||
else {
|
||||
// Restore previous value of composeMessagesType
|
||||
$("composeMessagesType").stopObserving("change", onComposeMessagesTypeChange);
|
||||
$("composeMessagesType").stopObserving("change", onComposeMessagesTypeChange);
|
||||
$("composeMessagesType").value = ((Event.element(event).value == 1)?"0":"1");
|
||||
Event.element(event).blur();
|
||||
$("composeMessagesType").observe("change", onComposeMessagesTypeChange);
|
||||
|
@ -304,13 +538,13 @@ function onComposeMessagesTypeChange(event) {
|
|||
// HTML mode
|
||||
CKEDITOR.replace('signature',
|
||||
{
|
||||
height: "290px",
|
||||
toolbar :
|
||||
height: "290px",
|
||||
toolbar :
|
||||
[['Bold', 'Italic', '-', 'Link',
|
||||
'Font','FontSize','-','TextColor',
|
||||
'BGColor']
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 335 B |
|
@ -90,7 +90,7 @@ a:hover
|
|||
div#header
|
||||
{ margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 0;
|
||||
padding: 0px;
|
||||
border-bottom: 1px solid #000; }
|
||||
|
||||
div#header img.headerlogo
|
||||
|
@ -540,8 +540,8 @@ DIV.resize-handle
|
|||
width: 2px;
|
||||
border-right: 1px solid #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
max-height: 2em; } /* will be set in JavaScript when setting drag handles */
|
||||
|
||||
@media print
|
||||
|
@ -584,7 +584,7 @@ DIV.dialog.left
|
|||
background-position: top left; }
|
||||
|
||||
DIV.dialog.left DIV
|
||||
{ border-left: 0;
|
||||
{ border-left: 0px;
|
||||
margin-left: 19px;
|
||||
text-align: left; }
|
||||
|
||||
|
@ -594,7 +594,7 @@ DIV.dialog.right
|
|||
background-position: top right; }
|
||||
|
||||
DIV.dialog.right DIV
|
||||
{ border-right: 0;
|
||||
{ border-right: 0px;
|
||||
margin-right: 19px;
|
||||
text-align: right; }
|
||||
|
||||
|
@ -609,7 +609,7 @@ DIV#uploadResults DIV
|
|||
|
||||
DIV#uploadDialog H3,
|
||||
DIV#uploadResults H3
|
||||
{ margin-top: 0;
|
||||
{ margin-top: 0px;
|
||||
font-size: 1.2em; }
|
||||
|
||||
/* INPUT styles */
|
||||
|
@ -650,6 +650,9 @@ INPUT.textField
|
|||
vertical-align: middle;
|
||||
width: 250px; }
|
||||
|
||||
INPUT.textField._invalid
|
||||
{ background-color: #f00 !important; }
|
||||
|
||||
INPUT#searchValue
|
||||
{ margin-right: 1em;
|
||||
width: 20em;
|
||||
|
@ -722,7 +725,7 @@ TABLE.framenocaption
|
|||
{ margin-top: 14px; }
|
||||
|
||||
TABLE.frame TD
|
||||
{ padding: 0; }
|
||||
{ padding: 0px; }
|
||||
|
||||
SPAN.caption
|
||||
{ display: inline-block;
|
||||
|
@ -890,8 +893,8 @@ P#mailerSelectionContainer
|
|||
{ line-height: 25px;
|
||||
margin-top: 2px; }
|
||||
|
||||
DIV.bottomToolbar {
|
||||
vertical-align: middle;
|
||||
DIV.bottomToolbar
|
||||
{ vertical-align: middle;
|
||||
background-image: url("thead_bg.png");
|
||||
border: 1px solid #9B9B9B;
|
||||
background-color: #E6E7E6;
|
||||
|
@ -901,9 +904,10 @@ DIV.bottomToolbar {
|
|||
margin-left: 2px;
|
||||
margin-right: 2px; }
|
||||
|
||||
DIV.bottomToolbar A.bottomButton SPAN, DIV.bottomToolbar A.bottomButton {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
DIV.bottomToolbar A.bottomButton SPAN,
|
||||
DIV.bottomToolbar A.bottomButton
|
||||
{ margin: 0px;
|
||||
padding: 0px;
|
||||
width: 23px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
@ -912,8 +916,7 @@ DIV.bottomToolbar A.bottomButton SPAN, DIV.bottomToolbar A.bottomButton {
|
|||
border-bottom: 1px solid #9B9B9B;
|
||||
border-right: 1px solid #9B9B9B;
|
||||
float: left;
|
||||
height: 20px;
|
||||
}
|
||||
height: 20px; }
|
||||
|
||||
DIV.bottomToolbar A.bottomButton SPAN
|
||||
{ background: transparent url('thead_bg.png') repeat-x top right; }
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 306 B |
Loading…
Reference in New Issue