Compare commits
263
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f7b69b037 | ||
|
|
c6cd3b0e1d | ||
|
|
31026a5bed | ||
|
|
08fbc00d7a | ||
|
|
957336b24c | ||
|
|
2a925de52c | ||
|
|
cf8dd02411 | ||
|
|
da37b3f868 | ||
|
|
24450a9c20 | ||
|
|
964f79db35 | ||
|
|
c0f51cf731 | ||
|
|
ae3ce7186d | ||
|
|
5034a72ed1 | ||
|
|
fdad1e061a | ||
|
|
d5d54822b0 | ||
|
|
81cdf5be7b | ||
|
|
959ea0f62f | ||
|
|
1c666e8ead | ||
|
|
7692832672 | ||
|
|
2bfd9f983d | ||
|
|
2a5e19a762 | ||
|
|
258f5e0b25 | ||
|
|
3609e41205 | ||
|
|
2ea2edbccd | ||
|
|
fab9e938fc | ||
|
|
9a2f3914e2 | ||
|
|
f13b3dd201 | ||
|
|
413550a025 | ||
|
|
06ac255f1a | ||
|
|
0682b24438 | ||
|
|
a01926b878 | ||
|
|
58ff8139e9 | ||
|
|
70bb512bb8 | ||
|
|
7b580f8af6 | ||
|
|
11d9765a69 | ||
|
|
24a3ecd2d6 | ||
|
|
139ed39663 | ||
|
|
875d23aea0 | ||
|
|
c4ad8a64fa | ||
|
|
c464791d92 | ||
|
|
92bf494973 | ||
|
|
47ab4b837c | ||
|
|
e30fb0d1fb | ||
|
|
3efbf65f80 | ||
|
|
0982d50643 | ||
|
|
532105f1fa | ||
|
|
7cdfaf4d9c | ||
|
|
8cb19546df | ||
|
|
6dc02abe80 | ||
|
|
f70876aa05 | ||
|
|
3efdbf0166 | ||
|
|
348c81bf6c | ||
|
|
44158af057 | ||
|
|
4bb1fa0995 | ||
|
|
05882362f0 | ||
|
|
90a3fcacaf | ||
|
|
45ac6845a5 | ||
|
|
b4af6f5033 | ||
|
|
6bbd182559 | ||
|
|
7290fa2ea2 | ||
|
|
788f36b96e | ||
|
|
cb0d327f90 | ||
|
|
b646e35f5d | ||
|
|
f3a5df37ae | ||
|
|
7a7547c7c3 | ||
|
|
11e7af3450 | ||
|
|
16d312eb98 | ||
|
|
295b53df66 | ||
|
|
cab6a8c1e2 | ||
|
|
59ad8a2e5a | ||
|
|
ab8feff67d | ||
|
|
b9294da996 | ||
|
|
18a300ca5b | ||
|
|
b8b45eda75 | ||
|
|
370be4f835 | ||
|
|
2720aebf04 | ||
|
|
bf02b3046f | ||
|
|
3d4a965412 | ||
|
|
372206e69e | ||
|
|
9dc00acd53 | ||
|
|
9d5f42eefa | ||
|
|
f4dcd37570 | ||
|
|
c4858ffb2b | ||
|
|
d72188e2da | ||
|
|
3647f066da | ||
|
|
e207cc79a7 | ||
|
|
eb84e618e6 | ||
|
|
669bd99a2d | ||
|
|
3b0ead7bbd | ||
|
|
4483d47e89 | ||
|
|
a5213b3321 | ||
|
|
e6e34ae053 | ||
|
|
815b3b4b03 | ||
|
|
b08d98e2e1 | ||
|
|
1d2c0ed350 | ||
|
|
0bdbe5bbac | ||
|
|
acfa96263b | ||
|
|
99b1353faf | ||
|
|
27e57e397e | ||
|
|
778612bf41 | ||
|
|
9d92318524 | ||
|
|
cf9b7fab36 | ||
|
|
f1037e7d48 | ||
|
|
233b9c0613 | ||
|
|
89206f3705 | ||
|
|
a87daf282c | ||
|
|
fb4889a15e | ||
|
|
3fdabf8671 | ||
|
|
66c52f22dd | ||
|
|
8f7334f11d | ||
|
|
624833eca9 | ||
|
|
431c53b84d | ||
|
|
c9d9f7b47a | ||
|
|
1667f8879d | ||
|
|
3611c4021d | ||
|
|
bba0d6f44d | ||
|
|
bf390f17dd | ||
|
|
834aba85a6 | ||
|
|
d878c69c15 | ||
|
|
d9d59c6b52 | ||
|
|
69000a0929 | ||
|
|
876d9cfa21 | ||
|
|
117e6b6bc1 | ||
|
|
01e2256483 | ||
|
|
49e7cf0994 | ||
|
|
b3d6973fb1 | ||
|
|
aae522259f | ||
|
|
68ef8784a0 | ||
|
|
da821ea6c7 | ||
|
|
4dee99e0ba | ||
|
|
eea08d083d | ||
|
|
8055acfd4a | ||
|
|
1fc947d8c8 | ||
|
|
7b3a238fb9 | ||
|
|
99d121fd3e | ||
|
|
eab3ee111a | ||
|
|
eeb6be1f08 | ||
|
|
29108bbb62 | ||
|
|
9c6884bd7d | ||
|
|
0aac4897fa | ||
|
|
2a99d1fd80 | ||
|
|
ba148eec5b | ||
|
|
49c4884cc9 | ||
|
|
e5d6ee93c3 | ||
|
|
40eff05d4e | ||
|
|
be031fe2c9 | ||
|
|
0fe1419289 | ||
|
|
9db633cada | ||
|
|
8a2d7d56e0 | ||
|
|
0c17e745f1 | ||
|
|
18bc75e50b | ||
|
|
68ad3c1045 | ||
|
|
85c8aebbd4 | ||
|
|
0a8834193f | ||
|
|
91e4c2cefc | ||
|
|
b80d8dbb06 | ||
|
|
525d1ad80f | ||
|
|
332dd0d96d | ||
|
|
a45000d15d | ||
|
|
3301bdb055 | ||
|
|
2f82b5cfd3 | ||
|
|
e3b64e035d | ||
|
|
1470258b5c | ||
|
|
83fee27ca8 | ||
|
|
e5b0596bc9 | ||
|
|
26e8276b08 | ||
|
|
078ba95f8e | ||
|
|
65525dd33b | ||
|
|
da81f7bc58 | ||
|
|
8fcd594be6 | ||
|
|
548f056b9c | ||
|
|
dffdbf318c | ||
|
|
161bcfac87 | ||
|
|
c6f17cd45c | ||
|
|
38713a6c67 | ||
|
|
8cef98eba1 | ||
|
|
2144d3122a | ||
|
|
9d272ae063 | ||
|
|
fcd0140ffd | ||
|
|
d783782af7 | ||
|
|
869b3c4cd1 | ||
|
|
2da2937391 | ||
|
|
5034d6655a | ||
|
|
d1446f7dc1 | ||
|
|
c5a219add8 | ||
|
|
bc2c839fbc | ||
|
|
2e736490d5 | ||
|
|
20bf66d931 | ||
|
|
864a7f8028 | ||
|
|
7b5541b5d1 | ||
|
|
744cba4422 | ||
|
|
ded467e0a2 | ||
|
|
a718b87d91 | ||
|
|
6e2e71b202 | ||
|
|
04e63e5edf | ||
|
|
f1a50bcb74 | ||
|
|
0672b96b8b | ||
|
|
93fa74d279 | ||
|
|
ffa4079fa8 | ||
|
|
8f75bdce2d | ||
|
|
4f53ea70e1 | ||
|
|
b72a9383a4 | ||
|
|
d3d9a720dc | ||
|
|
02daebe36b | ||
|
|
336e4c93e9 | ||
|
|
47252affba | ||
|
|
8fb5c1d09f | ||
|
|
54334b294e | ||
|
|
c51bed2580 | ||
|
|
935df2bd00 | ||
|
|
85a843bc1e | ||
|
|
723372dddb | ||
|
|
9362513fee | ||
|
|
2e3057c2ce | ||
|
|
456649a38a | ||
|
|
86818d4f64 | ||
|
|
b59287d6df | ||
|
|
bdc8dfb2b4 | ||
|
|
2825285b72 | ||
|
|
314dcfd30e | ||
|
|
d28fccf71e | ||
|
|
ff632ec3e8 | ||
|
|
b7af83f7e3 | ||
|
|
d2aeee39ef | ||
|
|
ecc0c453d7 | ||
|
|
420dece1e7 | ||
|
|
c738a2dc70 | ||
|
|
e41e17c5d9 | ||
|
|
5741d7f95c | ||
|
|
e13f7a267b | ||
|
|
dd7b929f17 | ||
|
|
ad9b969d6d | ||
|
|
704307e6d6 | ||
|
|
ce8a4b6d8a | ||
|
|
8ab591a1b6 | ||
|
|
4434547a18 | ||
|
|
29d86ad478 | ||
|
|
15a5ab0b12 | ||
|
|
3b5013555d | ||
|
|
655a499cd4 | ||
|
|
52559087b3 | ||
|
|
fb7d5b628f | ||
|
|
6e82a7ba7b | ||
|
|
fbcaf25428 | ||
|
|
a17e59af58 | ||
|
|
b966fd3e30 | ||
|
|
caa50d25cb | ||
|
|
636e5c375e | ||
|
|
f6ea2229f8 | ||
|
|
7f2ef967b4 | ||
|
|
063e973ef3 | ||
|
|
eb543ce14f | ||
|
|
9465fbad4b | ||
|
|
560feb45d9 | ||
|
|
b5ca697b43 | ||
|
|
33d0f30d97 | ||
|
|
0d537ab27b | ||
|
|
2239286697 | ||
|
|
d0d79728d2 | ||
|
|
e6c9289784 | ||
|
|
ae017b50bc | ||
|
|
42eb9e51b4 | ||
|
|
8690cee221 |
No files matched your search
+3
-5
@@ -9,6 +9,7 @@
|
||||
*/*/obj/
|
||||
*/obj/
|
||||
.scss-lint-config.yml_
|
||||
ActiveSync/ActiveSync.SOGo
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
SoObjects/SOGo/SOGo.framework/
|
||||
@@ -20,11 +21,8 @@ UI/WebServerResources/bower_components/
|
||||
UI/WebServerResources/css/
|
||||
UI/WebServerResources/css/styles.css
|
||||
UI/WebServerResources/css/styles.css.map
|
||||
UI/WebServerResources/js/Common.js*
|
||||
UI/WebServerResources/js/Contacts.js*
|
||||
UI/WebServerResources/js/Mailer.js*
|
||||
UI/WebServerResources/js/Preferences.js*
|
||||
UI/WebServerResources/js/Scheduler.js*
|
||||
UI/WebServerResources/js/*.js
|
||||
UI/WebServerResources/js/*.js.map
|
||||
UI/WebServerResources/js/vendor/
|
||||
UI/WebServerResources/node_modules/
|
||||
UI/WebServerResources/scss/.sass-cache/
|
||||
|
||||
@@ -133,6 +133,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: values];
|
||||
[o save];
|
||||
@@ -159,7 +160,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *nameInCache;
|
||||
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
||||
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||
else
|
||||
{
|
||||
NSString *component_name;
|
||||
@@ -170,7 +171,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
}
|
||||
|
||||
return nameInCache;
|
||||
@@ -673,7 +674,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSDictionary *component;
|
||||
NSArray *allComponents;
|
||||
|
||||
BOOL updated;
|
||||
BOOL updated, initialLoadInProgress;
|
||||
int deleted, return_count;
|
||||
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
@@ -683,8 +684,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
||||
matchingSyncToken: theSyncKey
|
||||
fromDate: theFilterType
|
||||
initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
||||
|
||||
|
||||
// Check for the WindowSize
|
||||
@@ -847,12 +864,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages;
|
||||
NSArray *allMessages, *a;
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
highestmodseq = 0;
|
||||
|
||||
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||
}
|
||||
else
|
||||
{
|
||||
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
max = [allMessages count];
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
@@ -911,7 +951,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
DESTROY(pool);
|
||||
|
||||
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
NSDictionary *imapFolderGUIDS;
|
||||
id context;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,12 +151,14 @@ static BOOL debugOn = NO;
|
||||
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -229,23 +231,27 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
if (!imapFolderGUIDS)
|
||||
{
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||
[imapFolderGUIDS retain];
|
||||
|
||||
}
|
||||
|
||||
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -938,6 +944,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
[o save];
|
||||
|
||||
command_count++;
|
||||
@@ -1023,6 +1030,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1046,6 +1054,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1204,7 +1213,7 @@ static BOOL debugOn = NO;
|
||||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||
|
||||
count = [allMessages count];
|
||||
|
||||
@@ -2619,6 +2628,11 @@ static BOOL debugOn = NO;
|
||||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
||||
|
||||
// If there is no References: header, initialize it with In-Reply-To.
|
||||
if ([mailObject inReplyTo] && ![references count])
|
||||
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
||||
|
||||
if ([references count] > 0)
|
||||
{
|
||||
// If there are more than ten identifiers listed, we eliminate the second one.
|
||||
|
||||
@@ -233,6 +233,7 @@ Installation
|
||||
This section will guide you through the installation of the native
|
||||
Microsoft Outlook compatibility layer SOGo offers.
|
||||
|
||||
////
|
||||
Red Hat Enterprise Linux v6 x86_64
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -316,7 +317,7 @@ from this guide.
|
||||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||
disable the upstart check. For more details, refer to:
|
||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||
|
||||
////
|
||||
|
||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
||||
----
|
||||
|
||||
You might consider changing the realm and domain used, to suit your
|
||||
enviroment.
|
||||
environment.
|
||||
|
||||
You might also have to
|
||||
remove `/etc/samba/smb.conf` prior running this command.
|
||||
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
||||
OpenChange Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
||||
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||
|
||||
First, create the OpenChange MySQL user:
|
||||
|
||||
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
||||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||
----
|
||||
|
||||
////
|
||||
On RHEL, make sure SELinux is disabled:
|
||||
|
||||
setenforce 0
|
||||
////
|
||||
|
||||
Next, you can start Samba using the usual command:
|
||||
|
||||
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
||||
While HTTPS is not required to access this service, it is strongly
|
||||
recommended.
|
||||
|
||||
////
|
||||
On RHEL-based distributions, the apache configuration required by these
|
||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||
////
|
||||
|
||||
For Debian-based distributions, these files can be found
|
||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
||||
a2enconf ocsmanager
|
||||
a2enconf rpcproxy
|
||||
|
||||
////
|
||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||
////
|
||||
|
||||
The _reqtimeout_ apache module is known to cause problems when using the
|
||||
default configuration shipped with Debian-based systems. On such
|
||||
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
||||
a2dismod reqtimeout
|
||||
|
||||
You should now restart the Apache service and make sure it will start on
|
||||
boot. On RHEL-based distributions, do:
|
||||
boot.
|
||||
////
|
||||
On RHEL-based distributions, do:
|
||||
|
||||
chkconfig httpd on && /etc/init.d/httpd restart
|
||||
|
||||
////
|
||||
On Debian-based distributions, do:
|
||||
|
||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||
@@ -813,7 +822,7 @@ the `openchange_user_cleanup` script distributed with SOGo. The script
|
||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
||||
RHEL).
|
||||
To reset a user, run the script as root:
|
||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
has not been implemented.
|
||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
2.3.x (2015-MM-DD)
|
||||
2.3.2 (2015-09-16)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||
- updated CKEditor to version 4.5.3
|
||||
|
||||
Bug fixes
|
||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- less sync issues when setting read flag (Zentyal)
|
||||
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||
- support attachments from more mail clients (Zentyal)
|
||||
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||
- fixed localization of calendar categories with empty profile (#3295)
|
||||
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||
|
||||
2.3.1 (2015-07-23)
|
||||
------------------
|
||||
@@ -19,6 +33,7 @@ Enhancements
|
||||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||
- group mail addresses can be used as recipient in Outlook
|
||||
- added 'ActiveSync' module constraints
|
||||
- updated CKEditor to version 4.5.1
|
||||
- added Slovenian translation - thanks to Jens Riecken
|
||||
- added Chinese (Taiwan) translation
|
||||
|
||||
|
||||
@@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||
return newAttachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -104,6 +106,105 @@
|
||||
return objectVersion;
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList
|
||||
{
|
||||
BOOL updated;
|
||||
enum mapistore_error rc;
|
||||
NSData *currentChangeList, *changeKey;
|
||||
NSMutableArray *changeKeys;
|
||||
NSMutableData *newChangeList;
|
||||
NSUInteger count, len;
|
||||
struct SizedXid *changes;
|
||||
struct SPropValue property;
|
||||
struct SRow aRow;
|
||||
struct XID *currentChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t nChanges;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||
|
||||
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
if (!currentChangeList)
|
||||
{
|
||||
/* Create a new PredecessorChangeList */
|
||||
len = [changeKey length];
|
||||
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||
[newChangeList appendUInt8: len];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update current predecessor change list with new change key */
|
||||
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||
with: &nChanges];
|
||||
|
||||
updated = NO;
|
||||
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||
for (count = 0; count < nChanges && !updated; count++)
|
||||
{
|
||||
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||
{
|
||||
if ([globCnt length] != [oldGlobCnt length])
|
||||
{
|
||||
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||
abort();
|
||||
}
|
||||
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialise it */
|
||||
changeKeys = [NSMutableArray array];
|
||||
|
||||
if (!updated)
|
||||
[changeKeys addObject: changeKey];
|
||||
|
||||
for (count = 0; count < nChanges; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||
[changeKeys addObject: changeKey];
|
||||
}
|
||||
|
||||
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||
|
||||
newChangeList = [NSMutableData data];
|
||||
len = [changeKeys count];
|
||||
for (count = 0; count < len; count++)
|
||||
{
|
||||
changeKey = [changeKeys objectAtIndex: count];
|
||||
[newChangeList appendUInt8: [changeKey length]];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
}
|
||||
|
||||
if ([newChangeList length] > 0)
|
||||
{
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [self addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: how this can happen?
|
||||
//
|
||||
@@ -166,6 +267,9 @@
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
@@ -209,4 +313,36 @@
|
||||
return [sogoObject lastModified];
|
||||
}
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||
SyncImportReadStateChanges ROPs */
|
||||
NSNumber *flags;
|
||||
uint32_t newFlag;
|
||||
|
||||
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
if (flags)
|
||||
{
|
||||
newFlag = [flags unsignedLongValue];
|
||||
if (flag & SUPPRESS_RECEIPT)
|
||||
newFlag |= MSGFLAG_READ;
|
||||
if (flag & CLEAR_RN_PENDING)
|
||||
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag &= ~MSGFLAG_READ;
|
||||
if (flag & CLEAR_NRN_PENDING)
|
||||
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlag = MSGFLAG_READ;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag = 0x0;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -121,6 +121,7 @@
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) want_copy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
|
||||
@@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMID: (uint64_t) targetMid
|
||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
|
||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||
|
||||
if (targetChangeKey)
|
||||
if (targetPredecessorChangeList)
|
||||
{
|
||||
property.ulPropTag = PidTagChangeKey;
|
||||
property.value.bin = *targetChangeKey;
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *targetPredecessorChangeList;
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
goto end;
|
||||
{
|
||||
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
[destMsg save: memCtx];
|
||||
if (!wantCopy)
|
||||
@@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
NSString *oldMessageURL;
|
||||
MAPIStoreMapping *mapping;
|
||||
SOGoUser *ownerUser;
|
||||
struct Binary_r *targetChangeKey;
|
||||
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||
//TALLOC_CTX *memCtx;
|
||||
|
||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||
@@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
if (oldMessageURL)
|
||||
{
|
||||
[oldMessageURLs addObject: oldMessageURL];
|
||||
if (targetChangeKeys)
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
if (targetChangeKeys && targetPredecessorChangeList)
|
||||
{
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||
}
|
||||
else
|
||||
targetChangeKey = NULL;
|
||||
{
|
||||
targetChangeKey = NULL;
|
||||
targetPredecessorChangeList = NULL;
|
||||
}
|
||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||
fromFolder: sourceFolder
|
||||
withMID: targetMids[count]
|
||||
andChangeKey: targetChangeKey
|
||||
andPredecessorChangeList: targetPredecessorChangeList
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey;
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
@@ -75,6 +75,7 @@ static Class NSNumberK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
@@ -261,7 +262,6 @@ static Class NSNumberK;
|
||||
*/
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
inChangeListOnly: (BOOL) inChangeListOnly
|
||||
{
|
||||
struct XID *xid;
|
||||
NSString *guid;
|
||||
@@ -270,19 +270,15 @@ static Class NSNumberK;
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
if (!inChangeListOnly)
|
||||
{
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
}
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
|
||||
/* 2. append/update predecessor change list */
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
@@ -296,6 +292,77 @@ static Class NSNumberK;
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
withOldChangeKey: (NSData *) oldChangeKey
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *changeKeyDict;
|
||||
NSString *guid;
|
||||
NSMutableDictionary *changeList;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid, *givenChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t i, length;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (predecessorChangeList)
|
||||
{
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldChangeKey)
|
||||
{
|
||||
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||
if (givenChangeKey) {
|
||||
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||
|
||||
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||
if (!changeKeyDict ||
|
||||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||
{
|
||||
/* The given change key is greater than current one stored in
|
||||
metadata or it does not exist */
|
||||
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil]
|
||||
forKey: @"ChangeKey"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
- (EOQualifier *) componentQualifier
|
||||
{
|
||||
if (!componentQualifier)
|
||||
@@ -465,8 +532,7 @@ static Class NSNumberK;
|
||||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: NO];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
}
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
@@ -483,12 +549,13 @@ static Class NSNumberK;
|
||||
}
|
||||
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
|
||||
[self synchroniseCache];
|
||||
if (newChangeKey)
|
||||
if (oldChangeKey || pcl)
|
||||
{
|
||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||
messageEntry = [messages objectForKey: messageKey];
|
||||
@@ -496,8 +563,8 @@ static Class NSNumberK;
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"no version record found for message '%@'",
|
||||
messageKey];
|
||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: YES];
|
||||
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||
withOldChangeKey: oldChangeKey];
|
||||
[versionsMessage save];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +209,16 @@
|
||||
|
||||
- (void) updateVersions
|
||||
{
|
||||
NSData *newChangeKey;
|
||||
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||
NSData *newChangeKey, *predecessorChangeList;
|
||||
|
||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||
|
||||
[(MAPIStoreGCSFolder *) container
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey];
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey
|
||||
andPredecessorChangeList: predecessorChangeList];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreMailMessage.h"
|
||||
@@ -108,7 +109,7 @@
|
||||
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
||||
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
||||
0x68};
|
||||
|
||||
|
||||
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
||||
asBinaryInMemCtx: memCtx];
|
||||
|
||||
@@ -117,19 +118,7 @@
|
||||
|
||||
- (NSString *) _fileName
|
||||
{
|
||||
NSString *fileName;
|
||||
NSDictionary *parameters;
|
||||
|
||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
||||
objectForKey: @"name"];
|
||||
if (!fileName)
|
||||
{
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
fileName = [parameters objectForKey: @"filename"];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return [bodyInfo filename];
|
||||
}
|
||||
|
||||
- (int) getPidTagAttachLongFilename: (void **) data
|
||||
@@ -178,7 +167,7 @@
|
||||
|
||||
- (int) getPidTagAttachContentId: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
{
|
||||
*data = [[bodyInfo objectForKey: @"bodyId"]
|
||||
asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
#undef DEBUG
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
@@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
@@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid;
|
||||
uint32_t i, length;
|
||||
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
talloc_free (sizedXIDList);
|
||||
}
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
@@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
/* 1. set change key association */
|
||||
@@ -924,8 +965,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return changeNumber;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
NSString *messageUid;
|
||||
@@ -936,7 +976,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
messageEntry = [messages objectForKey: messageUid];
|
||||
if (!messageEntry)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
||||
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||
@"this message %@", messageKey];
|
||||
synced = [self synchroniseCacheForUID: messageUid];
|
||||
if (synced)
|
||||
@@ -947,11 +987,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
return messageEntry;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
[self _setChangeKey: changeKey
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
/* Update predecessor change list property given the change key. It
|
||||
returns if the change key has been added to the list or not */
|
||||
BOOL added = NO;
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *messageEntry;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct XID *xid;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||
if (messageEntry)
|
||||
{
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (changeList)
|
||||
{
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
{
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
[versionsMessage save];
|
||||
added = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
NSDictionary *messages, *changeKeyDict;
|
||||
@@ -1217,6 +1303,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
|
||||
@@ -1231,12 +1318,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
NSDictionary *result;
|
||||
NSUInteger count;
|
||||
NSArray *a;
|
||||
NSData *changeKey;
|
||||
NSData *changeList;
|
||||
|
||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||
fromFolder: sourceFolder withMIDs: targetMids
|
||||
andChangeKeys: targetChangeKeys
|
||||
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
|
||||
@@ -1325,11 +1413,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
[self synchroniseCache];
|
||||
for (count = 0; count < midCount; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
||||
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
[destUIDs objectAtIndex: count]];
|
||||
[self setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
[self _updatePredecessorChangeListWith: changeList
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#import <Mailer/NSData+Mail.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "Codepages.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
@@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
count1 = [keys indexOfObject: data1];
|
||||
data2 = [entry2 objectForKey: @"mimeType"];
|
||||
count2 = [keys indexOfObject: data2];
|
||||
|
||||
|
||||
if (count1 == count2)
|
||||
{
|
||||
data1 = [entry1 objectForKey: @"key"];
|
||||
@@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
else
|
||||
stringValue = @"";
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v = 0;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
|
||||
@@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([[self attachmentKeys]
|
||||
count] > 0)
|
||||
v |= MSGFLAG_HASATTACH;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
v = 2;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
|
||||
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
if ([flags containsObject: @"flagged"])
|
||||
v = 6;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
||||
{
|
||||
cn = [ngAddress displayName];
|
||||
|
||||
|
||||
// If we don't have a displayName, we use the email address instead. This
|
||||
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
||||
// nor in the recipient field if we reply to the email.
|
||||
@@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
|
||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||
|
||||
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
@@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
{
|
||||
uint32_t v;
|
||||
NSString *s;
|
||||
|
||||
|
||||
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
||||
v = 0x1;
|
||||
|
||||
|
||||
if ([s hasPrefix: @"1"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"2"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"4"]) v = 0x0;
|
||||
else if ([s hasPrefix: @"5"]) v = 0x0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||
format = EDITOR_FORMAT_PLAINTEXT;
|
||||
else if ([headerMimeType isEqualToString: @"text/html"])
|
||||
format = EDITOR_FORMAT_HTML;
|
||||
else
|
||||
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, format);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
p = 0;
|
||||
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
||||
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
||||
|
||||
|
||||
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
||||
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
||||
p++;
|
||||
@@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0);
|
||||
p++;
|
||||
|
||||
|
||||
// PR_7BIT_DISPLAY_NAME_UNICODE
|
||||
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
@@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_SMTP_ADDRESS_UNICODE
|
||||
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
|
||||
|
||||
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
||||
p++;
|
||||
@@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
withPrefix: (NSString *) keyPrefix
|
||||
{
|
||||
NSArray *parts;
|
||||
NSDictionary *parameters;
|
||||
NSUInteger count, max;
|
||||
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
||||
if ([[bodyInfo filename] length] > 0)
|
||||
{
|
||||
if ([keyPrefix length] == 0)
|
||||
keyPrefix = @"0";
|
||||
@@ -1645,15 +1643,29 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL alreadyRead = NO;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
alreadyRead = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"]
|
||||
containsObject: @"seen"];
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
{
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
modified = alreadyRead;
|
||||
}
|
||||
else
|
||||
[sogoObject addFlags: imapFlag];
|
||||
{
|
||||
[sogoObject addFlags: imapFlag];
|
||||
modified = !alreadyRead;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -1694,7 +1706,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSNumber *value;
|
||||
|
||||
|
||||
@@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||
if (modseq)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
{
|
||||
if (res->relop == RELOP_GT)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
|
||||
}
|
||||
else
|
||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
|
||||
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||
{
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
version = [properties objectForKey: @"version"];
|
||||
|
||||
return (version
|
||||
? exchange_globcnt ([version unsignedLongLongValue])
|
||||
? [version unsignedLongLongValue]
|
||||
: ULLONG_MAX);
|
||||
}
|
||||
|
||||
@@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
||||
BOOL updatedMetadata;
|
||||
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||
NSData *changeKey, *messageData;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
@@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
[sogoObject setNameInContainer: messageKey];
|
||||
[mapping registerURL: [self url] withID: mid];
|
||||
|
||||
/* synchronise the cache and update the change key with the one provided
|
||||
by the client. Before doing this, lets issue a unselect/select combo
|
||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
||||
/* synchronise the cache and update the predecessor change list
|
||||
with the change key provided by the client. Before doing
|
||||
this, lets issue a unselect/select combo because of timing
|
||||
issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||
IMAP APPEND */
|
||||
[client unselect];
|
||||
[client select: folderName];
|
||||
|
||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
if (changeKey)
|
||||
[(MAPIStoreMailFolder *) container
|
||||
setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
{
|
||||
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
if (!updatedMetadata)
|
||||
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||
}
|
||||
|
||||
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||
requested once it is saved */
|
||||
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||
if (changeNumber)
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||
forKey: @"version"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
withAID: (uint32_t) aid;
|
||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||
andRowCount: (uint32_t *) countPtr;
|
||||
- (int) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||
|
||||
- (NSArray *) activeContainerMessageTables;
|
||||
|
||||
@@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
||||
}
|
||||
|
||||
[self save: memCtx];
|
||||
/* We make sure that any change-related properties are removes from the
|
||||
/* We make sure that any change-related properties are removed from the
|
||||
properties dictionary, to make sure that related methods will be
|
||||
invoked the next time they are requested. */
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
|
||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||
{
|
||||
@@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
||||
return [self getNo: data inMemCtx: memCtx];;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
// [self subclassResponsibility: _cmd];
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
uint32_t mid_count,
|
||||
uint64_t *src_mids, uint64_t *t_mids,
|
||||
struct Binary_r **target_change_keys,
|
||||
struct Binary_r **target_predecessor_change_lists,
|
||||
uint8_t want_copy)
|
||||
{
|
||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||
@@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
fromFolder: sourceFolder
|
||||
withMIDs: t_mids
|
||||
andChangeKeys: target_change_keys
|
||||
andPredecessorChangeLists: target_predecessor_change_lists
|
||||
wantCopy: want_copy
|
||||
inMemCtx: mem_ctx];
|
||||
TRYCATCH_END(pool)
|
||||
@@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||
struct MAPIStoreTallocWrapper *wrapper;
|
||||
NSAutoreleasePool *pool;
|
||||
MAPIStoreMessage *message;
|
||||
int rc;
|
||||
enum mapistore_error rc;
|
||||
|
||||
if (message_object)
|
||||
{
|
||||
|
||||
@@ -531,7 +531,7 @@
|
||||
}
|
||||
[vToDo setTimeStampAsDate: now];
|
||||
|
||||
[sogoObject saveComponent: vCalendar];
|
||||
[sogoObject saveCalendar: vCalendar];
|
||||
|
||||
[self updateVersions];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
+ (id) dataWithXID: (const struct XID *) xid;
|
||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length;
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#undef DEBUG
|
||||
#include <stdbool.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <talloc.h>
|
||||
#include <util/time.h>
|
||||
#include <gen_ndr/exchange.h>
|
||||
@@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
NSMutableData *xidData;
|
||||
struct FlatUID_r flatUID;
|
||||
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||
[xidData appendBytes: flatUID.ab length: 16];
|
||||
[xidData appendBytes: xid->Data length: xid->Size];
|
||||
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
|
||||
return xidData;
|
||||
}
|
||||
@@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
{
|
||||
xid = talloc_zero (memCtx, struct XID);
|
||||
|
||||
[self _extractGUID: &xid->GUID];
|
||||
[self _extractGUID: &xid->NameSpaceGuid];
|
||||
|
||||
xid->Size = max - 16;
|
||||
xid->LocalId.length = max - 16;
|
||||
|
||||
bytes = (uint8_t *) [self bytes];
|
||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
||||
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
return xid;
|
||||
}
|
||||
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length
|
||||
{
|
||||
struct Binary_r bin;
|
||||
struct SizedXid *sizedXIDArray;
|
||||
|
||||
bin.cb = [self length];
|
||||
bin.lpb = (uint8_t *)[self bytes];
|
||||
|
||||
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||
if (!sizedXIDArray)
|
||||
{
|
||||
NSLog (@"Impossible to parse SizedXID array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sizedXIDArray;
|
||||
}
|
||||
|
||||
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||
{
|
||||
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||
|
||||
if ([self length] > 0)
|
||||
globCnt = *(uint64_t *) [self bytes];
|
||||
|
||||
if ([otherGlobCnt length] > 0)
|
||||
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||
|
||||
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||
}
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
{
|
||||
|
||||
@@ -161,8 +161,11 @@
|
||||
minute: [tzStart minuteOfHour] second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||
|
||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
||||
day: 0 hour: 0 minute: 0
|
||||
tmpDate = [tmpDate addYear: 0
|
||||
month: ((pos > 0) ? 0 : 1)
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: 0];
|
||||
|
||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||
@@ -197,12 +200,41 @@
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
|
||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||
effectively shift the time change by a whole week.
|
||||
|
||||
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
*/
|
||||
if (dayOfWeek == dateDayOfWeek)
|
||||
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||
pos--;
|
||||
|
||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||
|
||||
@@ -2988,7 +2988,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
theUser = [SOGoUser userWithLogin: theUID];
|
||||
aParent = [theUser calendarsFolderInContext: context];
|
||||
|
||||
|
||||
if ([aParent isKindOfClass: [NSException class]])
|
||||
return nil;
|
||||
|
||||
aFolders = [aParent subFolders];
|
||||
e = [aFolders objectEnumerator];
|
||||
while ((aFolder = [e nextObject]))
|
||||
|
||||
@@ -1493,8 +1493,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
// the database. We do this ONLY when using SOGo from the
|
||||
// Web interface or over ActiveSync.
|
||||
// Over DAV, it'll be handled directly in PUTAction:
|
||||
if (![context request] || [[context request] handledByDefaultHandler]
|
||||
|| [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"])
|
||||
if (![context request] ||
|
||||
[[context request] handledByDefaultHandler] ||
|
||||
[[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"])
|
||||
{
|
||||
// If an alarm was specified, let's use it. This would happen if an attendee accepts/declines/etc. an
|
||||
// event invitation and also sets an alarm along the way. This would happen ONLY from the web interface.
|
||||
|
||||
@@ -84,17 +84,30 @@
|
||||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *rules;
|
||||
NSArray *allEvents, *rules;
|
||||
NSCalendarDate *untilDate;
|
||||
NSMutableDictionary *data, *repeat;
|
||||
NSString *frequency;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
iCalEvent *masterComponent;
|
||||
iCalRecurrenceRule *rule;
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
|
||||
|
||||
rules = [self recurrenceRules];
|
||||
if ([self recurrenceId])
|
||||
{
|
||||
// If the component is an occurrence of a recurrent component,
|
||||
// consider the recurrence rules of the master component.
|
||||
allEvents = [[self parent] events];
|
||||
masterComponent = [allEvents objectAtIndex: 0];
|
||||
rules = [masterComponent recurrenceRules];
|
||||
}
|
||||
else
|
||||
{
|
||||
rules = [self recurrenceRules];
|
||||
}
|
||||
|
||||
if ([rules count] > 0)
|
||||
{
|
||||
rule = [rules objectAtIndex: 0];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* NGVCard+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2014 Inverse inc.
|
||||
* Copyright (C) 2009-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
@@ -31,6 +31,10 @@
|
||||
- (CardElement *) elementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type;
|
||||
|
||||
- (void) addElementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value;
|
||||
|
||||
- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord;
|
||||
- (NSMutableDictionary *) asLDIFRecord;
|
||||
|
||||
|
||||
@@ -188,8 +188,8 @@ convention:
|
||||
}
|
||||
|
||||
- (void) addElementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value
|
||||
{
|
||||
NSArray *allValues;
|
||||
NSEnumerator *list;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2006-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -32,9 +31,13 @@
|
||||
@class NSString;
|
||||
|
||||
@interface SOGoContactGCSFolder : SOGoGCSFolder <SOGoContactFolder>
|
||||
|
||||
{
|
||||
NSString *baseCardDAVURL, *basePublicCardDAVURL;
|
||||
}
|
||||
- (void) fixupContactRecord: (NSMutableDictionary *) contactRecord;
|
||||
- (NSDictionary *) lookupContactWithName: (NSString *) aName;
|
||||
- (NSString *) cardDavURL;
|
||||
- (NSString *) publicCardDavURL;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2013 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2006-2013-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -67,6 +66,24 @@ static NSArray *folderListingFields = nil;
|
||||
@"c_component", nil];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
baseCardDAVURL = nil;
|
||||
basePublicCardDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCardDAVURL release];
|
||||
[basePublicCardDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (Class) objectClassForContent: (NSString *) content
|
||||
{
|
||||
CardGroup *cardEntry;
|
||||
@@ -436,4 +453,48 @@ static NSArray *folderListingFields = nil;
|
||||
return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCardDAVURL)
|
||||
{
|
||||
davURL = [[self realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCardDAVURL = davURL;
|
||||
[baseCardDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) cardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCardDAVURL)
|
||||
{
|
||||
davURL = [[self publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCardDAVURL = davURL;
|
||||
[basePublicCardDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]];
|
||||
}
|
||||
|
||||
@end /* SOGoContactGCSFolder */
|
||||
@@ -97,10 +97,10 @@
|
||||
|
||||
- (NSString *) davCollectionTag;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
/* flags */
|
||||
|
||||
- (NSException *) addFlagsToAllMessages: (id) _f;
|
||||
|
||||
@@ -2119,6 +2119,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
EOQualifier *searchQualifier;
|
||||
NSMutableArray *allTokens;
|
||||
@@ -2195,6 +2196,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
|
||||
for (i = 0; i < [fetchResults count]; i++)
|
||||
{
|
||||
if ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"] && initialLoadInProgress)
|
||||
continue;
|
||||
|
||||
d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||
[allTokens addObject: d];
|
||||
@@ -2205,7 +2209,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
if (highestmodseq == 0)
|
||||
highestmodseq = 1;
|
||||
|
||||
if (highestmodseq > 0)
|
||||
if (highestmodseq > 0 && !initialLoadInProgress)
|
||||
{
|
||||
id uid;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSArray+Utilities.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSArray+Utilities.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2011 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSObject+Utilities.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSObject+Utilities.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -373,6 +373,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) asCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *cssIdentifier;
|
||||
unichar currentChar;
|
||||
int count, max, idx;
|
||||
@@ -381,10 +382,12 @@ static int cssEscapingCount;
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
cssIdentifier = [NSMutableString string];
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
max = [self length];
|
||||
|
||||
if (max > 0)
|
||||
{
|
||||
if (isdigit([self characterAtIndex: 0]))
|
||||
if ([numericSet characterIsMember: [self characterAtIndex: 0]])
|
||||
// A CSS identifier can't start with a digit; we add an underscore
|
||||
[cssIdentifier appendString: @"_"];
|
||||
for (count = 0; count < max; count++)
|
||||
@@ -415,6 +418,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) fromCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *newString;
|
||||
NSString *currentString;
|
||||
int count, length, max, idx;
|
||||
@@ -423,12 +427,14 @@ static int cssEscapingCount;
|
||||
if (!cssEscapingStrings)
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
newString = [NSMutableString string];
|
||||
max = [self length];
|
||||
count = 0;
|
||||
|
||||
if (max > 0
|
||||
&& [self characterAtIndex: 0] == '_'
|
||||
&& isdigit([self characterAtIndex: 1]))
|
||||
&& [numericSet characterIsMember: [self characterAtIndex: 1]])
|
||||
{
|
||||
/* If the identifier starts with an underscore followed by a digit,
|
||||
we remove the underscore */
|
||||
|
||||
@@ -128,8 +128,8 @@
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
|
||||
/* multiget helper */
|
||||
- (WOResponse *) performMultigetInContext: (WOContext *) queryContext
|
||||
|
||||
@@ -1151,6 +1151,7 @@ static NSArray *childRecordFields = nil;
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
/* TODO:
|
||||
- validation:
|
||||
@@ -1200,13 +1201,17 @@ static NSArray *childRecordFields = nil;
|
||||
mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: YES]];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
if (!initialLoadInProgress)
|
||||
{
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_lastmodified", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
}
|
||||
|
||||
records = mRecords;
|
||||
}
|
||||
else
|
||||
@@ -1499,7 +1504,9 @@ static NSArray *childRecordFields = nil;
|
||||
properties = [self parseDAVRequestedProperties: propElement];
|
||||
records = [self syncTokenFieldsWithProperties: properties
|
||||
matchingSyncToken: syncToken
|
||||
fromDate: nil];
|
||||
fromDate: nil
|
||||
initialLoad: NO];
|
||||
|
||||
[self _appendComponentProperties: [properties allKeys]
|
||||
fromRecords: records
|
||||
matchingSyncToken: [syncToken intValue]
|
||||
|
||||
@@ -778,7 +778,10 @@ static Class NSNullK;
|
||||
NSRange r;
|
||||
|
||||
r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]];
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
|
||||
// We check if the range is ok here since we could be using DomainLessLogin
|
||||
if (r.location != NSNotFound)
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
}
|
||||
|
||||
sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator];
|
||||
|
||||
@@ -70,7 +70,11 @@ class Carddav:
|
||||
if not self.cards:
|
||||
url = "/SOGo/so/%s/Contacts/personal/view" % (self.login)
|
||||
content = self._get(url)
|
||||
self.cards = content['cards']
|
||||
#print "\nCONTENT:", content
|
||||
if 'cards' in content:
|
||||
self.cards = content['cards']
|
||||
else:
|
||||
self.cards = []
|
||||
return self.cards
|
||||
|
||||
def get_cards(self, pattern):
|
||||
|
||||
@@ -89,9 +89,9 @@ class preferences:
|
||||
get = HTTPPreferencesGET(url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute(get)
|
||||
if DEBUG: print "LUC (url):", url
|
||||
if DEBUG: print "LUC (status):", get.response["status"]
|
||||
if DEBUG: print "LUC (body):", get.response['body']
|
||||
if DEBUG: print "DEBUG (url):", url
|
||||
if DEBUG: print "DEBUG (status):", get.response["status"]
|
||||
if DEBUG: print "DEBUG (body):", get.response['body']
|
||||
content = simplejson.loads(get.response['body'])
|
||||
result = None
|
||||
try:
|
||||
@@ -117,7 +117,7 @@ class preferences:
|
||||
# Get the right sub-dict and change the key/value
|
||||
subdict = self.find_key(self.preferences, preference)
|
||||
if not subdict:
|
||||
print "LUC(nosubdict):", preference, self.preferences
|
||||
raise AttributeError("ERROR(nosubdict): looking for %s in: %s" %(preference, str(self.preferences)))
|
||||
subdict[preference] = value
|
||||
|
||||
def set(self, preference, value=None):
|
||||
|
||||
@@ -85,6 +85,13 @@ class JsonDavPhoneTests(unittest.TestCase):
|
||||
self.allphones = list(self.newphone)
|
||||
self.allphones.extend(self.newphones_difftype)
|
||||
self.allphones.extend(self.newphones_sametype)
|
||||
#- In case there are no cards for this user
|
||||
try:
|
||||
self._get_card()
|
||||
except IndexError:
|
||||
path = 'Contacts/personal'
|
||||
(card, path, gid) = self._create_new_card(path)
|
||||
self._save_card(card)
|
||||
|
||||
def tearDown(self):
|
||||
self._connect_as_user()
|
||||
@@ -102,6 +109,23 @@ class JsonDavPhoneTests(unittest.TestCase):
|
||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
||||
self.dv = carddav.Carddav(newuser, newpassword)
|
||||
|
||||
def _create_new_card(self, path):
|
||||
gid = self.dv.newguid(path)
|
||||
card = {'c_categories': None,
|
||||
'c_cn': 'John Doe',
|
||||
'c_component': 'vcard',
|
||||
'c_givenname': 'John Doe',
|
||||
'c_mail': 'johndoe@nothere.com',
|
||||
'c_name': gid,
|
||||
'c_o': '',
|
||||
'c_screenname': '',
|
||||
'c_sn': '',
|
||||
'c_telephonenumber': '123.456.7890',
|
||||
'emails': [{'type': 'pref', 'value': 'johndoe@nothere.com'}],
|
||||
'phones': [{'type': 'home', 'value': '111.222.3333'}],
|
||||
'id': gid}
|
||||
return (card, path, gid)
|
||||
|
||||
def _get_card(self, name="John Doe"):
|
||||
tmp_card = self.dv.get_cards(name)[0]
|
||||
self.card = self.dv.get_card(tmp_card['c_name'])
|
||||
|
||||
@@ -28,12 +28,14 @@ class preventInvitationsTest(unittest.TestCase):
|
||||
self.caldav = CalDAVSchedulingTest(self)
|
||||
cal = self.prefs.get("Calendar")
|
||||
if "PreventInvitationsWhitelist" not in cal:
|
||||
#print "LUC (cal):", cal
|
||||
cal["PreventInvitationsWhitelist"] = None
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
if "PreventInvitations" not in cal:
|
||||
cal["PreventInvitations"] = 0
|
||||
self.prefs.set("PreventInvitations", 0)
|
||||
|
||||
def tearDown(self):
|
||||
self.prefs.set("autoReplyText", "")
|
||||
#self.prefs.set("autoReplyText", "")
|
||||
self.prefs.set('PreventInvitations', 0)
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
#- Manual Cleanup, not called because classs is not derived from unittest
|
||||
|
||||
@@ -21,8 +21,8 @@ class UIPostsTests(unittest.TestCase):
|
||||
self.cookie = sogoLogin.getAuthCookie(hostname, port, username, password)
|
||||
|
||||
def _urlPostData(self, client, url, data, exp_status=200):
|
||||
post = webdavlib.HTTPPOST(url, data)
|
||||
post.content_type = "application/x-www-form-urlencoded"
|
||||
post = webdavlib.HTTPPOST(url, simplejson.dumps(data))
|
||||
post.content_type = "application/json"
|
||||
post.cookie = self.cookie
|
||||
|
||||
client.execute(post)
|
||||
@@ -43,16 +43,14 @@ class UIPostsTests(unittest.TestCase):
|
||||
""" Add Web Calendar """
|
||||
|
||||
ret=True
|
||||
data = "url=%s" % webCalendarURL
|
||||
data = {"url":"%s" % webCalendarURL}
|
||||
calendarBaseURL="/SOGo/so/%s/Calendar" % username
|
||||
addWebCalendarURL = "%s/addWebCalendar" % calendarBaseURL
|
||||
response = self._urlPostData(self.client, addWebCalendarURL, data)
|
||||
|
||||
respJSON = simplejson.loads(response['body'])
|
||||
folderID = respJSON['folderID']
|
||||
calID = respJSON['id']
|
||||
|
||||
#sogo1:Calendar/C07-5006F300-1-370E2480
|
||||
(_, calID) = folderID.split('/', 1)
|
||||
self.assertNotEqual(calID, None)
|
||||
|
||||
# reload the cal
|
||||
|
||||
@@ -22,6 +22,8 @@ $(TEST_TOOL)_OBJC_FILES += \
|
||||
TestSBJsonParser.m \
|
||||
\
|
||||
TestNGMimeAddressHeaderFieldGenerator.m \
|
||||
TestNGMimeMessageGenerator.m \
|
||||
\
|
||||
TestNSData+Crypto.m \
|
||||
TestNSString+Crypto.m \
|
||||
TestNSString+URLEscaping.m \
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@class NSArray;
|
||||
@@ -52,6 +54,8 @@
|
||||
|
||||
- (BOOL) run;
|
||||
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2;
|
||||
@end
|
||||
|
||||
#define test(c) { \
|
||||
|
||||
@@ -185,4 +185,79 @@ static NSString *SOGoTestAssertException = @"SOGoTestAssertException";
|
||||
return YES;
|
||||
}
|
||||
|
||||
/* Helper function for diffForString:andString */
|
||||
NSString *_stringForCharacterAtIndex(NSUInteger index, NSString *str, NSUInteger length)
|
||||
{
|
||||
NSString *chrStr;
|
||||
unichar chr;
|
||||
if (index < length)
|
||||
{
|
||||
chr = [str characterAtIndex: index];
|
||||
if (isprint(chr))
|
||||
{
|
||||
chrStr = [NSString stringWithFormat: @"%c", chr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chr == 10)
|
||||
chrStr = @"[NL]";
|
||||
else if (chr == 0)
|
||||
chrStr = @"[\0]";
|
||||
else
|
||||
chrStr = [NSString stringWithFormat: @"[NP: %u]", chr];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
chrStr = @"[none]";
|
||||
}
|
||||
|
||||
return chrStr;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a string with a very verbose diff of the two strings.
|
||||
In case the strings are equal it returns an empty string.
|
||||
Example output for the strings 'flower' and 'flotera':
|
||||
<begin of example>
|
||||
0 |f|
|
||||
1 |l|
|
||||
2 |o|
|
||||
3 |w|t|<--
|
||||
4 |e|
|
||||
5 |r|
|
||||
6 |[none]|a|<--
|
||||
<end of example>
|
||||
*/
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2
|
||||
{
|
||||
BOOL differencesFound = NO;
|
||||
NSString *finalSTR = @"";
|
||||
NSUInteger i, length1, length2;
|
||||
NSString *sc1, *sc2;
|
||||
|
||||
length1 = [str1 length];
|
||||
length2 = [str2 length];
|
||||
for (i = 0; i < length1 || i < length2; i++)
|
||||
{
|
||||
sc1 = _stringForCharacterAtIndex(i, str1, length1);
|
||||
sc2 = _stringForCharacterAtIndex(i, str2, length2);
|
||||
|
||||
if ([sc1 isEqualToString: sc2])
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|\n", i, sc1];
|
||||
else
|
||||
{
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|%@|<--\n", i, sc1, sc2];
|
||||
differencesFound = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!differencesFound)
|
||||
return @"";
|
||||
|
||||
return finalSTR;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,134 @@
|
||||
/* TestNGMimeMessageGenerator.m - this file is part of SOGo
|
||||
*
|
||||
* 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 "SOGoTest.h"
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
@interface TestNGMimeMessageGenerator : SOGoTest
|
||||
@end
|
||||
|
||||
@implementation TestNGMimeMessageGenerator
|
||||
|
||||
- (void) test_generateDataForHeaderField_value
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
NSArray *cases = [NSArray arrayWithObjects:
|
||||
[NSArray arrayWithObjects: @"Message-ID", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", nil],
|
||||
[NSArray arrayWithObjects: @"Content-Type",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneHebrewOneLatin",
|
||||
@"עs",
|
||||
@"=?utf-8?q?=D7=A2s?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneLatineOneHebrew",
|
||||
@"sע",
|
||||
@"=?utf-8?q?s=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterHebrew",
|
||||
@"ע",
|
||||
@"=?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterRussian",
|
||||
@"Б",
|
||||
@"=?utf-8?q?=D0=91?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderParameter",
|
||||
@"parameter=ע",
|
||||
@"parameter==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderParameters",
|
||||
@"plain; parameter=ע; parameter-plain; parameter2=ea",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=;\n parameter-plain; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndNoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n =?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndTwoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; z=ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n z==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderExtrablanks",
|
||||
@"plain; parameter=ע; parameter 2spaces; parameter2=ea",
|
||||
@"plain;\n \\ parameter==?utf-8?q?=D7=A2?=;\n parameter 2spaces; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-Encoded-Unbalanced-Paramter-Quote",
|
||||
@"text/plain; name=\"ע",
|
||||
@"text/plain;\n name==?utf-8?q?=22=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-type",
|
||||
@"text/plain; name=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"text/plain;\n name=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-disposition",
|
||||
@"attachment; filename=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"attachment;\n filename=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-length", @"2912", @"2912", nil],
|
||||
[NSArray arrayWithObjects: @"content-transfer-encoding", @"quoted-printable", @"quoted-printable", nil],
|
||||
nil
|
||||
];
|
||||
NSEnumerator *enumerator;
|
||||
NSArray *testCase;
|
||||
|
||||
[NGMimeMessageGenerator initialize];
|
||||
generator = [[NGMimeMessageGenerator alloc] init];
|
||||
[generator autorelease];
|
||||
|
||||
enumerator = [cases objectEnumerator];
|
||||
while ((testCase = [enumerator nextObject]) != nil)
|
||||
{
|
||||
NSData *result;
|
||||
NSMutableData *resultWithNulByte;
|
||||
NSString *header = [testCase objectAtIndex: 0];
|
||||
NSData *headerData = [testCase objectAtIndex: 1];
|
||||
NSString *expected = [testCase objectAtIndex: 2];
|
||||
result = [generator generateDataForHeaderField: header
|
||||
value: headerData];
|
||||
if (result == nil)
|
||||
result = [@"[nil]" dataUsingEncoding: NSUTF8StringEncoding];
|
||||
|
||||
resultWithNulByte = [result mutableCopy];
|
||||
[resultWithNulByte appendBytes: "\0" length: 1];
|
||||
NSString *resultString = [NSString stringWithCString:[resultWithNulByte bytes]];
|
||||
|
||||
|
||||
BOOL testResult = [resultString isEqualToString: expected];
|
||||
|
||||
NSString *diff = [self stringFromDiffBetween: [NSString stringWithString: resultString]
|
||||
and: [NSString stringWithString: expected]];
|
||||
NSString *testErrorMsg = [NSString
|
||||
stringWithFormat: @">> For %@ header received:\n%@[END]\n>> instead of:\n%@[END]\n>> for:\n%@\n>> diff:\n%@\n>> lengthReceived: %u lengthExpected: %u",
|
||||
header,
|
||||
resultString,
|
||||
expected,
|
||||
headerData,
|
||||
diff,
|
||||
[resultString length],
|
||||
[expected length]
|
||||
];
|
||||
|
||||
testWithMessage(testResult, testErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -35,7 +35,7 @@
|
||||
{
|
||||
const char *inStrings[] = { "éléphant", "2š", "sogo+test", NULL };
|
||||
const char **inString;
|
||||
NSString *outStrings[] = { @"%C3%A9l%C3%A9phant", @"2%C5%A1", @"sogo+test" };
|
||||
NSString *outStrings[] = { @"%C3%A9l%C3%A9phant", @"2%C5%A1", @"sogo%2Btest" };
|
||||
NSString **outString;
|
||||
NSString *result, *error;
|
||||
|
||||
|
||||
@@ -116,13 +116,19 @@ static NSURL *tableURL = nil;
|
||||
- (void) _removeUserFolders: (NSString *) userId
|
||||
{
|
||||
NSArray *folderPaths;
|
||||
NSString *path;
|
||||
int count, max;
|
||||
|
||||
folderPaths = [self _userFolderPaths: userId];
|
||||
max = [folderPaths count];
|
||||
if (max > 0)
|
||||
for (count = 0; count < max; count++)
|
||||
[fm deleteFolderAtPath: [folderPaths objectAtIndex: count]];
|
||||
{
|
||||
path = [folderPaths objectAtIndex: count];
|
||||
[fm deleteFolderAtPath: path];
|
||||
if (verbose)
|
||||
NSLog (@"Deleting %@", path);
|
||||
}
|
||||
else
|
||||
NSLog (@"No folder returned for user '%@'", userId);
|
||||
}
|
||||
@@ -144,6 +150,8 @@ static NSURL *tableURL = nil;
|
||||
userId];
|
||||
if ([fc evaluateExpressionX: sql])
|
||||
NSLog (@"Unable to delete the preference record for '%@'", userId);
|
||||
else if (verbose)
|
||||
NSLog (@"Removed preference record for '%@'", userId);
|
||||
[cm releaseChannel: fc];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Administration";
|
||||
}
|
||||
|
||||
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
|
||||
inContext: (WOContext*) context
|
||||
{
|
||||
|
||||
@@ -408,16 +408,17 @@
|
||||
|
||||
- (id <WOActionResults>) copyAction
|
||||
{
|
||||
WORequest *request;
|
||||
id <WOActionResults> response;
|
||||
NSString *destinationFolderId;
|
||||
NSArray *contactsId;
|
||||
NSDictionary *data;
|
||||
NSException *ex;
|
||||
|
||||
request = [context request];
|
||||
data = [[[context request] contentAsString] objectFromJSONString];
|
||||
contactsId = [data objectForKey: @"uids"];
|
||||
destinationFolderId = [data objectForKey: @"folder"];
|
||||
|
||||
if ((destinationFolderId = [request formValueForKey: @"folder"]) &&
|
||||
(contactsId = [request formValuesForKey: @"uid"]))
|
||||
if (destinationFolderId && [contactsId count])
|
||||
ex = [self _moveContacts: contactsId
|
||||
toFolder: destinationFolderId
|
||||
andKeepCopy: YES];
|
||||
@@ -436,16 +437,17 @@
|
||||
|
||||
- (id <WOActionResults>) moveAction
|
||||
{
|
||||
WORequest *request;
|
||||
id <WOActionResults> response;
|
||||
NSString *destinationFolderId;
|
||||
NSDictionary *data;
|
||||
NSArray *contactsId;
|
||||
NSException *ex;
|
||||
|
||||
request = [context request];
|
||||
data = [[[context request] contentAsString] objectFromJSONString];
|
||||
contactsId = [data objectForKey: @"uids"];
|
||||
destinationFolderId = [data objectForKey: @"folder"];
|
||||
|
||||
if ((destinationFolderId = [request formValueForKey: @"folder"])
|
||||
&& (contactsId = [request formValuesForKey: @"uid"]))
|
||||
if (destinationFolderId && [contactsId count])
|
||||
ex = [self _moveContacts: contactsId
|
||||
toFolder: destinationFolderId
|
||||
andKeepCopy: NO];
|
||||
|
||||
@@ -249,14 +249,7 @@
|
||||
|
||||
- (NSString *) commonLocalizableStrings
|
||||
{
|
||||
NSString *rc;
|
||||
|
||||
if (isPopup)
|
||||
rc = @"";
|
||||
else
|
||||
rc = [NSString stringWithFormat: @"var clabels = %@;", [self _stringsForFramework: nil]];
|
||||
|
||||
return rc;
|
||||
return [NSString stringWithFormat: @"var clabels = %@;", [self _stringsForFramework: nil]];
|
||||
}
|
||||
|
||||
- (NSString *) productLocalizableStrings
|
||||
@@ -440,40 +433,6 @@
|
||||
return ([[self productCSSURL] length] > 0);
|
||||
}
|
||||
|
||||
- (BOOL) _moduleIs: (NSString *) moduleName
|
||||
{
|
||||
NSString *frameworkName;
|
||||
|
||||
frameworkName = [[context page] frameworkName];
|
||||
|
||||
return [frameworkName isEqualToString: moduleName];
|
||||
}
|
||||
|
||||
- (BOOL) isCalendar
|
||||
{
|
||||
return [self _moduleIs: @"SchedulerUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isContacts
|
||||
{
|
||||
return [self _moduleIs: @"ContactsUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isMail
|
||||
{
|
||||
return [self _moduleIs: @"MailerUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isPreferences
|
||||
{
|
||||
return [self _moduleIs: @"PreferencesUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isAdministration
|
||||
{
|
||||
return [self _moduleIs: @"AdministrationUI"];
|
||||
}
|
||||
|
||||
- (void) setToolbar: (NSString *) newToolbar
|
||||
{
|
||||
ASSIGN (toolbar, newToolbar);
|
||||
@@ -509,6 +468,12 @@
|
||||
return [[context activeUser] cn];
|
||||
}
|
||||
|
||||
- (NSString *) userEmail
|
||||
{
|
||||
return [[context activeUser] systemEmail];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) canLogoff
|
||||
{
|
||||
BOOL canLogoff;
|
||||
|
||||
@@ -43,6 +43,24 @@
|
||||
"Copy To" = "Copy To";
|
||||
"Add to:" = "Add to:";
|
||||
|
||||
/* Subheader of empty addressbook */
|
||||
"No contact" = "No contact";
|
||||
|
||||
/* Number of contacts in addressbook; string is prefixed by number */
|
||||
"contacts" = "contacts";
|
||||
|
||||
/* No contact matching search criteria */
|
||||
"No matching contact" = "No matching contact";
|
||||
|
||||
/* Number of contacts matching search criteria; string is prefixed by number */
|
||||
"matching contacts" = "matching contacts";
|
||||
|
||||
/* Number of selected contacts in list */
|
||||
"selected" = "selected";
|
||||
|
||||
/* Empty right pane */
|
||||
"No contact selected" = "No contact selected";
|
||||
|
||||
/* Tooltips */
|
||||
|
||||
"Create a new address book card" = "Create a new address book card";
|
||||
|
||||
@@ -367,6 +367,8 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
day = [date dayOfMonth];
|
||||
[card setBday: [NSString stringWithFormat: @"%.4d%.2d%.2d", year, month, day]];
|
||||
}
|
||||
else
|
||||
[card setBday: nil];
|
||||
|
||||
if ([[attributes objectForKey: @"addresses"] isKindOfClass: [NSArray class]])
|
||||
{
|
||||
@@ -430,8 +432,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([attrs isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"tel" ofType: [attrs objectForKey: @"type"]];
|
||||
[element setSingleValue: [attrs objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"tel"
|
||||
ofType: [attrs objectForKey: @"type"]
|
||||
withValue: [attrs objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,8 +451,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([o isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"email" ofType: [o objectForKey: @"type"]];
|
||||
[element setSingleValue: [o objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"email"
|
||||
ofType: [o objectForKey: @"type"]
|
||||
withValue: [o objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,8 +470,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([attrs isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"url" ofType: [attrs objectForKey: @"type"]];
|
||||
[element setSingleValue: [attrs objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"url"
|
||||
ofType: [attrs objectForKey: @"type"]
|
||||
withValue: [attrs objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +112,11 @@
|
||||
|
||||
request = [context request];
|
||||
rc = [NSMutableDictionary dictionary];
|
||||
data = [request formValueForKey: @"contactsFile"];
|
||||
if ([data respondsToSelector: @selector(isEqualToString:)])
|
||||
fileContent = (NSString *) data;
|
||||
else
|
||||
{
|
||||
fileContent = [[NSString alloc] initWithData: (NSData *) data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
[fileContent autorelease];
|
||||
}
|
||||
data = [[[[[request httpRequest] body] parts] lastObject] body];
|
||||
|
||||
fileContent = [[NSString alloc] initWithData: (NSData *) data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
[fileContent autorelease];
|
||||
|
||||
if (fileContent && [fileContent length])
|
||||
{
|
||||
@@ -132,12 +128,10 @@
|
||||
imported = 0;
|
||||
}
|
||||
|
||||
[rc setObject: [NSNumber numberWithInt: imported]
|
||||
forKey: @"imported"];
|
||||
[rc setObject: [NSNumber numberWithInt: imported] forKey: @"imported"];
|
||||
|
||||
response = [self responseWithStatus: 200];
|
||||
[response setHeader: @"text/html"
|
||||
forKey: @"content-type"];
|
||||
[response setHeader: @"text/html" forKey: @"content-type"];
|
||||
[(WOResponse*)response appendContentString: [rc jsonRepresentation]];
|
||||
|
||||
return response;
|
||||
|
||||
@@ -20,14 +20,6 @@
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@class NSString;
|
||||
|
||||
@class SOGoContactGCSFolder;
|
||||
|
||||
@interface UIxContactFolderLinksTemplate : UIxComponent
|
||||
{
|
||||
SOGoContactGCSFolder *addressBook;
|
||||
NSString *baseCardDAVURL, *basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,69 +24,6 @@
|
||||
|
||||
@implementation UIxContactFolderLinksTemplate
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
addressBook = [self clientObject];
|
||||
baseCardDAVURL = nil;
|
||||
basePublicCardDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCardDAVURL release];
|
||||
[basePublicCardDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCardDAVURL)
|
||||
{
|
||||
davURL = [[addressBook realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCardDAVURL = davURL;
|
||||
[baseCardDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) cardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCardDAVURL)
|
||||
{
|
||||
davURL = [[addressBook publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCardDAVURL = davURL;
|
||||
[basePublicCardDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
|
||||
@@ -71,6 +71,11 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Contacts";
|
||||
}
|
||||
|
||||
- (void) _setupContext
|
||||
{
|
||||
SOGoUser *activeUser;
|
||||
@@ -275,32 +280,70 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return [[self queryParameterForKey: @"popup"] boolValue];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] enablePublicAccess];
|
||||
}
|
||||
|
||||
- (NSString *) contactFolders
|
||||
{
|
||||
SOGoContactFolders *folderContainer;
|
||||
NSArray *folders;
|
||||
NSMutableDictionary *urls, *acls;
|
||||
NSMutableArray *foldersAttrs;
|
||||
NSString *userLogin, *owner;
|
||||
NSArray *folders, *allACLs;
|
||||
NSDictionary *folderAttrs;
|
||||
id currentFolder;
|
||||
|
||||
BOOL objectCreator, objectEditor, objectEraser;
|
||||
int max, i;
|
||||
|
||||
userLogin = [[context activeUser] login];
|
||||
folderContainer = [self clientObject];
|
||||
|
||||
// return [folderContainer subFolders];
|
||||
|
||||
folders = [folderContainer subFolders];
|
||||
|
||||
max = [folders count];
|
||||
foldersAttrs = [NSMutableArray arrayWithCapacity: max];
|
||||
urls = nil;
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
currentFolder = [folders objectAtIndex: i];
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
|
||||
// We extract URLs for this address book
|
||||
if ([currentFolder respondsToSelector: @selector(cardDavURL)])
|
||||
{
|
||||
urls = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[currentFolder cardDavURL], @"cardDavURL",
|
||||
nil];
|
||||
if ([self isPublicAccessEnabled])
|
||||
{
|
||||
[urls setObject: [currentFolder publicCardDavURL] forKey: @"publicCardDavURL"];
|
||||
}
|
||||
}
|
||||
|
||||
// We extract ACLs for this address book
|
||||
allACLs = ([owner isEqualToString: userLogin] ? nil : [currentFolder aclsForUser: userLogin]);
|
||||
objectCreator = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectCreator]);
|
||||
objectEditor = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectEditor]);
|
||||
objectEraser = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectEraser]);
|
||||
acls = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: objectCreator], @"objectCreator",
|
||||
[NSNumber numberWithBool: objectEditor], @"objectEditor",
|
||||
[NSNumber numberWithBool: objectEraser], @"objectEraser", nil];
|
||||
|
||||
// NOTE: keep urls as the last key/value here, to avoid chopping the dictionary
|
||||
// if it is not a GCS folder
|
||||
folderAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%@", [currentFolder nameInContainer]], @"id",
|
||||
[currentFolder displayName], @"name",
|
||||
[currentFolder ownerInContext: context], @"owner",
|
||||
owner, @"owner",
|
||||
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoGCSFolderK]], @"isEditable",
|
||||
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoContactSourceFolderK]
|
||||
&& ![currentFolder isPersonalSource]], @"isRemote",
|
||||
acls, @"acls",
|
||||
urls, @"urls",
|
||||
nil];
|
||||
[foldersAttrs addObject: folderAttrs];
|
||||
}
|
||||
@@ -419,12 +462,6 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return [super defaultAction];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] enablePublicAccess];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface UIxContactViewTemplate : UIxComponent
|
||||
|
||||
@@ -838,11 +838,10 @@
|
||||
o = [card birthday];
|
||||
if (o)
|
||||
{
|
||||
NSNumber *time = [NSNumber numberWithInt: [o timeIntervalSince1970]];
|
||||
[data setObject: time forKey: @"birthday"];
|
||||
[data setObject: [NSNumber numberWithInt: [o timeIntervalSince1970]]
|
||||
forKey: @"birthday"];
|
||||
}
|
||||
// o = [card source];
|
||||
// if (o) [data setObject: o forKey: @"source"];
|
||||
|
||||
o = [card tz];
|
||||
if (o) [data setObject: o forKey: @"tz"];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2014 Inverse inc.
|
||||
Copyright (C) 2006-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#import <Contacts/SOGoContactFolder.h>
|
||||
#import <Contacts/SOGoContactFolders.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
|
||||
#import <NGCards/NGVCard.h>
|
||||
#import <NGCards/NGVList.h>
|
||||
#import <SoObjects/Contacts/SOGoContactGCSEntry.h>
|
||||
@@ -85,6 +88,31 @@
|
||||
return s;
|
||||
}
|
||||
|
||||
- (void) saveSortValue
|
||||
{
|
||||
NSMutableDictionary *contactSettings;
|
||||
NSString *ascending, *sort;
|
||||
SOGoUserSettings *us;
|
||||
|
||||
sort = [[context request] formValueForKey: @"sort"];
|
||||
ascending = [[context request] formValueForKey: @"asc"];
|
||||
|
||||
if ([sort length])
|
||||
{
|
||||
us = [[context activeUser] userSettings];
|
||||
contactSettings = [us objectForKey: @"Contact"];
|
||||
// Must create if it doesn't exist
|
||||
if (!contactSettings)
|
||||
{
|
||||
contactSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: contactSettings forKey: @"Contact"];
|
||||
}
|
||||
[contactSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", [ascending intValue]], nil]
|
||||
forKey: @"SortingState"];
|
||||
[us synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *) contactInfos
|
||||
{
|
||||
id <SOGoContactFolder> folder;
|
||||
@@ -97,6 +125,8 @@
|
||||
WORequest *rq;
|
||||
unsigned int i;
|
||||
|
||||
[self saveSortValue];
|
||||
|
||||
if (!contactInfos)
|
||||
{
|
||||
folder = [self clientObject];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* UIxMailPartTextViewer.h - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* Copyright (C) 2006 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
/*
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of OpenGroupware.org.
|
||||
|
||||
OGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
OGo 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with OGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
/* UIxMailPartTextViewer.m - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
UIxMailPartTextViewer
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
"Account: " = "Account: ";
|
||||
"Shared Account: " = "Shared Account: ";
|
||||
|
||||
/* Empty right pane */
|
||||
"No message selected" = "No message selected";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Access rights to";
|
||||
"For user" = "For user";
|
||||
@@ -228,6 +231,9 @@
|
||||
"Delete Message" = "Delete Message";
|
||||
"Delete Selected Messages" = "Delete Selected Messages";
|
||||
|
||||
/* Number of selected messages in list */
|
||||
"selected" = "selected";
|
||||
|
||||
"This Folder" = "This Folder";
|
||||
|
||||
/* Label popup menu */
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
errorFormat = [self labelForKey: @"The folder with name \"%@\" could not be created."];
|
||||
errorFormat = [self labelForKey: @"The folder with name \"%@\" could not be created." inContext: context];
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [NSString stringWithFormat: errorFormat, folderName]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing 'name' parameter."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing 'name' parameter." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andJSONRepresentation: jsonResponse];
|
||||
@@ -149,7 +149,7 @@
|
||||
|
||||
if (!newFolderName || [newFolderName length] == 0)
|
||||
{
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing name parameter"]
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing name parameter" inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [message jsonRepresentation]];
|
||||
@@ -160,7 +160,7 @@
|
||||
error = [co renameTo: newFolderName];
|
||||
if (error)
|
||||
{
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to rename folder."]
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to rename folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [message jsonRepresentation]];
|
||||
@@ -251,7 +251,7 @@
|
||||
error = [connection moveMailboxAtURL: srcURL toURL: destURL];
|
||||
if (error)
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [jsonResponse jsonRepresentation]];
|
||||
@@ -283,7 +283,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [jsonResponse jsonRepresentation]];
|
||||
@@ -374,7 +374,7 @@
|
||||
{
|
||||
uids = [value componentsSeparatedByString: @","];
|
||||
response = [co archiveUIDs: uids
|
||||
inArchiveNamed: [self labelForKey: @"Saved Messages.zip"]
|
||||
inArchiveNamed: [self labelForKey: @"Saved Messages.zip" inContext: context]
|
||||
inContext: context];
|
||||
if (!response)
|
||||
response = [self responseWith204];
|
||||
@@ -739,6 +739,7 @@
|
||||
NSDictionary *content, *result;
|
||||
BOOL addOrRemove;
|
||||
NGImap4Client *client;
|
||||
id flag;
|
||||
|
||||
int i;
|
||||
|
||||
@@ -751,7 +752,13 @@
|
||||
|
||||
// We unescape our flags
|
||||
for (i = [flags count]-1; i >= 0; i--)
|
||||
[flags replaceObjectAtIndex: i withObject: [[flags objectAtIndex: i] fromCSSIdentifier]];
|
||||
{
|
||||
flag = [flags objectAtIndex: i];
|
||||
if ([flag isKindOfClass: [NSString class]])
|
||||
[flags replaceObjectAtIndex: i withObject: [flag fromCSSIdentifier]];
|
||||
else
|
||||
[flags removeObjectAtIndex: i];
|
||||
}
|
||||
|
||||
co = [self clientObject];
|
||||
client = [[co imap4Connection] client];
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#import <Mailer/SOGoSentFolder.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSObject+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoDateFormatter.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
@@ -131,7 +132,7 @@
|
||||
else if ([now dayOfCommonEra] - [messageDate dayOfCommonEra] == 1)
|
||||
{
|
||||
// Yesterday
|
||||
return [self labelForKey: @"Yesterday"];
|
||||
return [self labelForKey: @"Yesterday" inContext: context];
|
||||
}
|
||||
else if ([now dayOfCommonEra] - [messageDate dayOfCommonEra] < 7)
|
||||
{
|
||||
@@ -190,10 +191,10 @@
|
||||
s = [[s substringFromIndex: r.location+1]
|
||||
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
if ([s hasPrefix: @"1"]) result = [self labelForKey: @"highest"];
|
||||
else if ([s hasPrefix: @"2"]) result = [self labelForKey: @"high"];
|
||||
else if ([s hasPrefix: @"4"]) result = [self labelForKey: @"low"];
|
||||
else if ([s hasPrefix: @"5"]) result = [self labelForKey: @"lowest"];
|
||||
if ([s hasPrefix: @"1"]) result = [self labelForKey: @"highest" inContext: context];
|
||||
else if ([s hasPrefix: @"2"]) result = [self labelForKey: @"high" inContext: context];
|
||||
else if ([s hasPrefix: @"4"]) result = [self labelForKey: @"low" inContext: context];
|
||||
else if ([s hasPrefix: @"5"]) result = [self labelForKey: @"lowest" inContext: context];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +243,7 @@
|
||||
{
|
||||
NSString *s;
|
||||
|
||||
s = [self labelForKey:@"View Mail Folder"];
|
||||
s = [self labelForKey:@"View Mail Folder" inContext: context];
|
||||
s = [s stringByAppendingString:@": "];
|
||||
s = [s stringByAppendingString:[self objectTitle]];
|
||||
return s;
|
||||
@@ -622,11 +623,12 @@
|
||||
- (NSDictionary *) getUIDsInFolder: (SOGoMailFolder *) folder
|
||||
withHeaders: (BOOL) includeHeaders
|
||||
{
|
||||
NSMutableDictionary *data;
|
||||
NSArray *uids, *threadedUids, *headers;
|
||||
NSRange r;
|
||||
NSMutableDictionary *data;
|
||||
SOGoMailAccount *account;
|
||||
id quota;
|
||||
|
||||
NSRange r;
|
||||
int count;
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
@@ -644,10 +646,14 @@
|
||||
if (includeHeaders)
|
||||
{
|
||||
// Also retrieve the first headers, up to 'headersPrefetchMaxSize'
|
||||
count = [[uids flattenedArray] count];
|
||||
if (count > headersPrefetchMaxSize) count = headersPrefetchMaxSize;
|
||||
NSArray *a;
|
||||
|
||||
a = [uids flattenedArray];
|
||||
count = [a count];
|
||||
if (count > headersPrefetchMaxSize)
|
||||
count = headersPrefetchMaxSize;
|
||||
r = NSMakeRange(0, count);
|
||||
headers = [self getHeadersForUIDs: [[uids flattenedArray] subarrayWithRange: r]
|
||||
headers = [self getHeadersForUIDs: [a subarrayWithRange: r]
|
||||
inFolder: folder];
|
||||
|
||||
[data setObject: headers forKey: @"headers"];
|
||||
@@ -744,7 +750,6 @@
|
||||
noHeaders = [[[requestContent objectForKey: @"sortingAttributes"] objectForKey:@"noHeaders"] boolValue];
|
||||
data = [self getUIDsInFolder: folder
|
||||
withHeaders: !noHeaders];
|
||||
|
||||
[response appendContentString: [data jsonRepresentation]];
|
||||
|
||||
return response;
|
||||
@@ -878,7 +883,7 @@
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"No UID specified", @"error", nil];
|
||||
return [self responseWithStatus: 404 /* Not Found */
|
||||
andString: [data jsonRepresentation]];
|
||||
andString: [data jsonRepresentation]];
|
||||
}
|
||||
|
||||
uids = [data objectForKey: @"uids"];
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Mail";
|
||||
}
|
||||
|
||||
- (void) _setupContext
|
||||
{
|
||||
SOGoUser *activeUser;
|
||||
@@ -118,6 +123,18 @@
|
||||
return [accounts jsonRepresentation];
|
||||
}
|
||||
|
||||
- (WOResponse *) mailAccountsAction
|
||||
{
|
||||
WOResponse *response;
|
||||
NSString *s;
|
||||
|
||||
s = [self mailAccounts];
|
||||
response = [self responseWithStatus: 200
|
||||
andString: s];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
- (NSString *) userNames
|
||||
{
|
||||
NSArray *accounts, *userNames;
|
||||
|
||||
@@ -342,10 +342,19 @@
|
||||
actionClass = "UIxMailMainFrame";
|
||||
actionName = "unseenCount";
|
||||
};
|
||||
mailAccounts = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailMainFrame";
|
||||
actionName = "mailAccounts";
|
||||
};
|
||||
// viewerTemplate = {
|
||||
// protectedBy = "View";
|
||||
// pageName = "UIxMailViewTemplate";
|
||||
// };
|
||||
UIxMailPopupView = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailPopupView";
|
||||
};
|
||||
UIxMailEditor = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailEditor";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
"Connect" = "Connect";
|
||||
|
||||
"Authentication Failed" = "Authentication Failed";
|
||||
"Wrong username or password." = "Wrong username or password.";
|
||||
"cookiesNotEnabled" = "You cannot login because your browser's cookies are disabled. Please enable cookies in your browser's settings and try again.";
|
||||
|
||||
|
||||
@@ -183,7 +183,6 @@
|
||||
SOGoWebAuthenticator *auth;
|
||||
SOGoAppointmentFolders *calendars;
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoSystemDefaults *sd;
|
||||
SOGoUser *loggedInUser;
|
||||
NSDictionary *params;
|
||||
NSString *username, *password, *language, *domain, *remoteHost;
|
||||
@@ -226,13 +225,15 @@
|
||||
|
||||
response = [self responseWithStatus: 200
|
||||
andJSONRepresentation: json];
|
||||
|
||||
|
||||
// We get the proper username for cookie creation. If we are using a multidomain
|
||||
// environment with SOGoEnableDomainBasedUID, we could have to append the domain
|
||||
// to the username. Also when SOGoEnableDomainBasedUID is enabled, we could be in
|
||||
// the DomainLessLogin situation, so we would NOT add the domain. -getUIDForEmail
|
||||
// has all the logic for this, so lets use it.
|
||||
if ([domain isNotNull])
|
||||
{
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([sd enableDomainBasedUID] &&
|
||||
[username rangeOfString: @"@"].location == NSNotFound)
|
||||
username = [NSString stringWithFormat: @"%@@%@", username, domain];
|
||||
username = [[SOGoUserManager sharedUserManager] getUIDForEmail: username];
|
||||
}
|
||||
|
||||
authCookie = [auth cookieWithUsername: username
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* toolbar */
|
||||
"Save and Close" = "Save and Close";
|
||||
"Close" = "Close";
|
||||
"Preferences saved" = "Preferences saved";
|
||||
|
||||
/* tabs */
|
||||
"General" = "General";
|
||||
|
||||
@@ -61,7 +61,8 @@ static SoProduct *preferencesProduct = nil;
|
||||
SOGoUserDefaults *defaults;
|
||||
NSMutableArray *accounts;
|
||||
NSArray *categoryLabels;
|
||||
|
||||
NSDictionary *locale;
|
||||
|
||||
if (!preferencesProduct)
|
||||
{
|
||||
preferencesProduct = [[SoProduct alloc] initWithBundle:
|
||||
@@ -78,7 +79,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoTimeZone"])
|
||||
[[defaults source] setObject: [defaults timeZoneName] forKey: @"SOGoTimeZone"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoShortDateFormat"])
|
||||
[[defaults source] setObject: [defaults shortDateFormat] forKey: @"SOGoShortDateFormat"];
|
||||
|
||||
@@ -90,7 +91,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoLoginModule"])
|
||||
[[defaults source] setObject: [defaults loginModule] forKey: @"SOGoLoginModule"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoRefreshViewCheck"])
|
||||
[[defaults source] setObject: [defaults refreshViewCheck] forKey: @"SOGoRefreshViewCheck"];
|
||||
|
||||
@@ -99,13 +100,13 @@ static SoProduct *preferencesProduct = nil;
|
||||
//
|
||||
if (![[defaults source] objectForKey: @"SOGoFirstDayOfWeek"])
|
||||
[[defaults source] setObject: [NSNumber numberWithInt: [defaults firstDayOfWeek]] forKey: @"SOGoFirstDayOfWeek"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoDayStartTime"])
|
||||
[[defaults source] setObject: [NSString stringWithFormat: @"%02d:00", [defaults dayStartHour]] forKey: @"SOGoDayStartTime"];
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoDayEndTime"])
|
||||
[[defaults source] setObject: [NSString stringWithFormat: @"%02d:00", [defaults dayEndHour]] forKey: @"SOGoDayEndTime"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoFirstWeekOfYear"])
|
||||
[[defaults source] setObject: [defaults firstWeekOfYear] forKey: @"SOGoFirstWeekOfYear"];
|
||||
|
||||
@@ -114,20 +115,20 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoCalendarEventsDefaultClassification"])
|
||||
[[defaults source] setObject: [defaults calendarEventsDefaultClassification] forKey: @"SOGoCalendarEventsDefaultClassification"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoCalendarTasksDefaultClassification"])
|
||||
[[defaults source] setObject: [defaults calendarTasksDefaultClassification] forKey: @"SOGoCalendarTasksDefaultClassification"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoCalendarDefaultReminder"])
|
||||
[[defaults source] setObject: [defaults calendarDefaultReminder] forKey: @"SOGoCalendarDefaultReminder"];
|
||||
|
||||
|
||||
// Populate default calendar categories, based on the user's preferred language
|
||||
if (![defaults calendarCategories])
|
||||
{
|
||||
categoryLabels = [[[self labelForKey: @"calendar_category_labels" withResourceManager: [preferencesProduct resourceManager]]
|
||||
componentsSeparatedByString: @","]
|
||||
sortedArrayUsingSelector: @selector (localizedCaseInsensitiveCompare:)];
|
||||
|
||||
|
||||
[defaults setCalendarCategories: categoryLabels];
|
||||
}
|
||||
if (![defaults calendarCategoriesColors])
|
||||
@@ -143,7 +144,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
[defaults setCalendarCategoriesColors: colors];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Default Contacts preferences
|
||||
//
|
||||
@@ -154,10 +155,10 @@ static SoProduct *preferencesProduct = nil;
|
||||
categoryLabels = [[[self labelForKey: @"contacts_category_labels" withResourceManager: [preferencesProduct resourceManager]]
|
||||
componentsSeparatedByString: @","]
|
||||
sortedArrayUsingSelector: @selector (localizedCaseInsensitiveCompare:)];
|
||||
|
||||
|
||||
if (!categoryLabels)
|
||||
categoryLabels = [NSArray array];
|
||||
|
||||
|
||||
[defaults setContactsCategories: categoryLabels];
|
||||
}
|
||||
|
||||
@@ -172,27 +173,27 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoMailReplyPlacement"])
|
||||
[[defaults source] setObject: [defaults mailReplyPlacement] forKey: @"SOGoMailReplyPlacement"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoMailSignaturePlacement"])
|
||||
[[defaults source] setObject: [defaults mailSignaturePlacement] forKey: @"SOGoMailSignaturePlacement"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoMailComposeMessageType"])
|
||||
[[defaults source] setObject: [defaults mailComposeMessageType] forKey: @"SOGoMailComposeMessageType"];
|
||||
|
||||
|
||||
if (![[defaults source] objectForKey: @"SOGoMailDisplayRemoteInlineImages"])
|
||||
[[defaults source] setObject: [defaults mailDisplayRemoteInlineImages] forKey: @"SOGoMailDisplayRemoteInlineImages"];
|
||||
|
||||
|
||||
// Populate default mail lablels, based on the user's preferred language
|
||||
if (![[defaults source] objectForKey: @"SOGoMailLabelsColors"])
|
||||
{
|
||||
SOGoMailLabel *label;
|
||||
NSArray *labels;
|
||||
id v;
|
||||
|
||||
|
||||
int i;
|
||||
|
||||
|
||||
v = [defaults mailLabelsColors];
|
||||
|
||||
|
||||
labels = [SOGoMailLabel labelsFromDefaults: v component: self];
|
||||
v = [NSMutableDictionary dictionary];
|
||||
for (i = 0; i < [labels count]; i++)
|
||||
@@ -201,7 +202,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
[v setObject: [NSArray arrayWithObjects: [label label], [label color], nil]
|
||||
forKey: [label name]];
|
||||
}
|
||||
|
||||
|
||||
[defaults setMailLabelsColors: v];
|
||||
}
|
||||
|
||||
@@ -214,6 +215,10 @@ static SoProduct *preferencesProduct = nil;
|
||||
//
|
||||
values = [[[[defaults source] values] mutableCopy] autorelease];
|
||||
|
||||
// Add locale code (used by CK Editor)
|
||||
locale = [[preferencesProduct resourceManager] localeForLanguageNamed: [defaults language]];
|
||||
[values setObject: [locale objectForKey: @"NSLocaleCode"] forKey: @"LocaleCode"];
|
||||
|
||||
accounts = [NSMutableArray arrayWithArray: [values objectForKey: @"AuxiliaryMailAccounts"]];
|
||||
account = [[[context activeUser] mailAccounts] objectAtIndex: 0];
|
||||
if (![account objectForKey: @"receipts"])
|
||||
@@ -250,6 +255,16 @@ static SoProduct *preferencesProduct = nil;
|
||||
forKey: @"PreventInvitationsWhitelist"];
|
||||
}
|
||||
|
||||
// Initialize some default values
|
||||
if (![settings objectForKey: @"Calendar"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Calendar"];
|
||||
|
||||
if (![settings objectForKey: @"Contact"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Contact"];
|
||||
|
||||
if (![settings objectForKey: @"Mail"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Mail"];
|
||||
|
||||
return [self _makeResponse: [[settings source] values]];
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,11 @@ static NSArray *reminderValues = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Preferences";
|
||||
}
|
||||
|
||||
// - (void) setHasChanged: (BOOL) newHasChanged
|
||||
// {
|
||||
// hasChanged = newHasChanged;
|
||||
@@ -1064,7 +1069,7 @@ static NSArray *reminderValues = nil;
|
||||
|
||||
if (!capabilities)
|
||||
{
|
||||
if ([self _sieveClient])
|
||||
if ([self isSieveScriptsEnabled] && [self _sieveClient])
|
||||
capabilities = [[self _sieveClient] capabilities];
|
||||
else
|
||||
capabilities = [NSArray array];
|
||||
@@ -2086,6 +2091,10 @@ static NSArray *reminderValues = nil;
|
||||
// We convert our object into a mutable one
|
||||
v = [[v mutableCopy] autorelease];
|
||||
|
||||
if ([[v objectForKey: @"SOGoLoginModule"] isEqualToString: @"Last"])
|
||||
[v setObject: [NSNumber numberWithBool: YES] forKey: @"SOGoRememberLastModule"];
|
||||
else
|
||||
[v setObject: [NSNumber numberWithBool: NO] forKey: @"SOGoRememberLastModule"];
|
||||
|
||||
// We remove short/long date formats if they are default ones
|
||||
if ([[v objectForKey: @"SOGoShortDateFormat"] isEqualToString: @"default"])
|
||||
|
||||
@@ -360,40 +360,19 @@ static SoProduct *commonProduct = nil;
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
SOGoObject *currentClient, *parent;
|
||||
BOOL found;
|
||||
NSString *hostLessURL;
|
||||
Class objectClass, userFolderClass;
|
||||
// , groupFolderClass
|
||||
|
||||
currentClient = [self clientObject];
|
||||
if (currentClient
|
||||
&& [currentClient isKindOfClass: [SOGoObject class]])
|
||||
if ([[self parent] respondsToSelector: @selector(modulePath)])
|
||||
{
|
||||
// groupFolderClass = [SOGoCustomGroupFolder class];
|
||||
userFolderClass = [SOGoUserFolder class];
|
||||
NSString *baseURL;
|
||||
|
||||
objectClass = [currentClient class];
|
||||
// found = (objectClass == groupFolderClass || objectClass == userFolderClass);
|
||||
found = (objectClass == userFolderClass);
|
||||
while (!found && currentClient)
|
||||
{
|
||||
parent = [currentClient container];
|
||||
objectClass = [parent class];
|
||||
if (// objectClass == groupFolderClass
|
||||
// ||
|
||||
objectClass == userFolderClass)
|
||||
found = YES;
|
||||
else
|
||||
currentClient = parent;
|
||||
}
|
||||
baseURL = [[self clientObject] baseURLInContext: context];
|
||||
|
||||
if ([baseURL hasSuffix: [NSString stringWithFormat: @"%@/", [[self parent] modulePath]]])
|
||||
return baseURL;
|
||||
|
||||
return [NSString stringWithFormat: @"%@%@", baseURL, [[self parent] modulePath]];
|
||||
}
|
||||
else
|
||||
currentClient = [WOApplication application];
|
||||
|
||||
hostLessURL = [[currentClient baseURLInContext: context] hostlessURL];
|
||||
|
||||
return [hostLessURL substringToIndex: [hostLessURL length] - 1];
|
||||
return @"SOGo";
|
||||
}
|
||||
|
||||
- (NSString *) ownPath
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
NSArray *weekDays;
|
||||
NSString *currentView, *timeFormat, *currentTableHour;
|
||||
NSCalendarDate *startDate, *currentTableDay;
|
||||
NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay;
|
||||
NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay;
|
||||
unsigned int numberOfDays;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/WOResourceManager+SOGo.h>
|
||||
|
||||
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
|
||||
#import <SoObjects/Appointments/SOGoAppointmentFolders.h>
|
||||
|
||||
#import "UIxCalDayTable.h"
|
||||
|
||||
@class SOGoAppointment;
|
||||
@@ -292,6 +295,28 @@
|
||||
return [weekDays objectAtIndex: [currentTableDay dayOfWeek]];
|
||||
}
|
||||
|
||||
- (NSString *) labelForMonth
|
||||
{
|
||||
NSCalendarDate *nextDay;
|
||||
BOOL isLastOrFirstDay;
|
||||
|
||||
isLastOrFirstDay = NO;
|
||||
if ([currentTableDay dayOfMonth] == 1)
|
||||
{
|
||||
isLastOrFirstDay = YES;
|
||||
}
|
||||
else if ([currentTableDay dayOfMonth] > 27)
|
||||
{
|
||||
nextDay = [currentTableDay dateByAddingYears: 0
|
||||
months: 0
|
||||
days: 1];
|
||||
if ([nextDay dayOfMonth] == 1)
|
||||
isLastOrFirstDay = YES;
|
||||
}
|
||||
|
||||
return isLastOrFirstDay? [currentTableDay descriptionWithCalendarFormat: @"%b" locale: locale] : nil;
|
||||
}
|
||||
|
||||
- (NSString *) labelForDate
|
||||
{
|
||||
return [dateFormatter shortFormattedDate: currentTableDay];
|
||||
@@ -410,6 +435,11 @@
|
||||
return daysView;
|
||||
}
|
||||
|
||||
- (NSString *) daysViewHeaderClasses
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@ daysHeader", [self daysViewClasses]];
|
||||
}
|
||||
|
||||
- (NSString *) dayClasses
|
||||
{
|
||||
NSMutableString *classes;
|
||||
@@ -468,4 +498,9 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) isWeekView
|
||||
{
|
||||
return [currentView isEqualToString:@"weekview"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -94,7 +94,7 @@ static NSArray *tasksFields = nil;
|
||||
@"c_classification", @"c_category",
|
||||
@"c_partmails", @"c_partstates", @"c_owner",
|
||||
@"c_iscycle", @"c_nextalarm",
|
||||
@"c_recurrence_id", @"isException", @"editable",
|
||||
@"c_recurrence_id", @"isException", @"viewable", @"editable",
|
||||
@"erasable", @"ownerIsOrganizer", @"c_description", nil];
|
||||
[eventsFields retain];
|
||||
}
|
||||
@@ -104,7 +104,7 @@ static NSArray *tasksFields = nil;
|
||||
@"calendarName",
|
||||
@"c_status", @"c_title", @"c_enddate",
|
||||
@"c_classification", @"c_location", @"c_category",
|
||||
@"editable", @"erasable",
|
||||
@"viewable", @"editable", @"erasable",
|
||||
@"c_priority", @"c_owner",
|
||||
@"c_iscycle", @"c_nextalarm",
|
||||
@"c_recurrence_id", @"isException", @"c_description", nil];
|
||||
@@ -258,7 +258,7 @@ static NSArray *tasksFields = nil;
|
||||
|
||||
labelKey = [NSString stringWithFormat: @"%@_class%@",
|
||||
type, [component objectForKey: @"c_classification"]];
|
||||
[component setObject: [self labelForKey: labelKey]
|
||||
[component setObject: [self labelForKey: labelKey inContext: context]
|
||||
forKey: @"c_title"];
|
||||
}
|
||||
|
||||
@@ -441,21 +441,35 @@ static NSArray *tasksFields = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
id foo;
|
||||
|
||||
foo = [currentFolder fetchCoreInfosFrom: startDate
|
||||
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component];
|
||||
currentInfos = [foo objectEnumerator];
|
||||
component: component]
|
||||
objectEnumerator];
|
||||
}
|
||||
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]);
|
||||
|
||||
|
||||
while ((newInfo = [currentInfos nextObject]))
|
||||
{
|
||||
if ([fields containsObject: @"viewable"])
|
||||
{
|
||||
if ([owner isEqualToString: userLogin])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
|
||||
else
|
||||
{
|
||||
role = [currentFolder roleForComponentsWithAccessClass: [[newInfo objectForKey: @"c_classification"] intValue]
|
||||
forUser : userLogin];
|
||||
|
||||
if ([role isEqualToString: @"ComponentViewer"])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0] forKey: @"viewable"];
|
||||
}
|
||||
}
|
||||
|
||||
if ([fields containsObject: @"editable"])
|
||||
{
|
||||
if (folderIsRemote)
|
||||
@@ -548,19 +562,19 @@ static NSArray *tasksFields = nil;
|
||||
{
|
||||
// Same day
|
||||
if (forAllDay)
|
||||
return [self labelForKey: @"Today"];
|
||||
return [self labelForKey: @"Today" inContext: context];
|
||||
else
|
||||
return [dateFormatter formattedTime: date];
|
||||
}
|
||||
else if ([now dayOfCommonEra] - [date dayOfCommonEra] == 1)
|
||||
{
|
||||
// Yesterday
|
||||
return [self labelForKey: @"Yesterday"];
|
||||
return [self labelForKey: @"Yesterday" inContext: context];
|
||||
}
|
||||
else if ([date dayOfCommonEra] - [now dayOfCommonEra] == 1)
|
||||
{
|
||||
// Tomorrow
|
||||
return [self labelForKey: @"Tomorrow"];
|
||||
return [self labelForKey: @"Tomorrow" inContext: context];
|
||||
}
|
||||
else if (abs(delta = [date dayOfCommonEra] - [now dayOfCommonEra]) < 7)
|
||||
{
|
||||
@@ -573,7 +587,7 @@ static NSArray *tasksFields = nil;
|
||||
return dayOfWeek;
|
||||
else
|
||||
// With the past 7 days
|
||||
return [NSString stringWithFormat: [self labelForKey: @"last %@"], dayOfWeek];
|
||||
return [NSString stringWithFormat: [self labelForKey: @"last %@" inContext: context], dayOfWeek];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -652,10 +666,11 @@ static NSArray *tasksFields = nil;
|
||||
return [self _responseWithData: data];
|
||||
}
|
||||
|
||||
- (void) saveEventFilterValue
|
||||
- (void) saveFilterValue: (NSString *) submodule
|
||||
{
|
||||
NSString *filter;
|
||||
SOGoUserSettings *us;
|
||||
NSMutableDictionary *calendarSettings;
|
||||
|
||||
filter = [[context request] formValueForKey: @"filterpopup"];
|
||||
if ([filter length]
|
||||
@@ -663,7 +678,15 @@ static NSArray *tasksFields = nil;
|
||||
&& ![filter isEqualToString: @"view_future"])
|
||||
{
|
||||
us = [[context activeUser] userSettings];
|
||||
[us setObject: filter forKey: @"CalendarDefaultFilter"];
|
||||
calendarSettings = [us objectForKey: @"Calendar"];
|
||||
// Must create if it doesn't exist
|
||||
if (!calendarSettings)
|
||||
{
|
||||
calendarSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: calendarSettings forKey: @"Calendar"];
|
||||
}
|
||||
//[us setObject: filter forKey: submodule];
|
||||
[calendarSettings setObject: filter forKey: submodule];
|
||||
[us synchronize];
|
||||
}
|
||||
}
|
||||
@@ -686,7 +709,8 @@ static NSArray *tasksFields = nil;
|
||||
calendarSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: calendarSettings forKey: @"Calendar"];
|
||||
}
|
||||
[calendarSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", (ascending?1:0)], nil] forKey: submodule];
|
||||
[calendarSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", [ascending intValue]], nil]
|
||||
forKey: submodule];
|
||||
[us synchronize];
|
||||
}
|
||||
}
|
||||
@@ -722,10 +746,11 @@ static NSArray *tasksFields = nil;
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partmails Participants email addresses
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partstates Participants states
|
||||
* @apiSuccess (Success 200) {String} events.c_owner Event's owner
|
||||
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic/recurring
|
||||
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
@@ -743,7 +768,7 @@ static NSArray *tasksFields = nil;
|
||||
NSString *sort, *ascending;
|
||||
|
||||
[self _setupContext];
|
||||
[self saveEventFilterValue];
|
||||
[self saveFilterValue: @"EventsFilterState"];
|
||||
[self saveSortValue: @"EventsSortingState"];
|
||||
|
||||
newEvents = [NSMutableArray array];
|
||||
@@ -1244,6 +1269,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
@@ -1408,6 +1434,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
* @apiSuccess (Success 200) {Number} tasks.c_classification 0: Public, 1: Private, 2: Confidential
|
||||
* @apiSuccess (Success 200) {String} tasks.c_location Location
|
||||
* @apiSuccess (Success 200) {String} tasks.c_category Category
|
||||
* @apiSuccess (Success 200) {Number} tasks.viewable 1 if task is viewable by the active user
|
||||
* @apiSuccess (Success 200) {Number} tasks.editable 1 if task is editable by the active user
|
||||
* @apiSuccess (Success 200) {Number} tasks.erasable 1 if task is erasable by the active user
|
||||
* @apiSuccess (Success 200) {String} tasks.c_priority Priority (0-9)
|
||||
@@ -1436,6 +1463,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
filteredTasks = [NSMutableArray array];
|
||||
|
||||
[self _setupContext];
|
||||
[self saveFilterValue: @"TasksFilterState"];
|
||||
[self saveSortValue: @"TasksSortingState"];
|
||||
|
||||
startSecs = (unsigned int) [startDate timeIntervalSince1970];
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#import <NGCards/iCalPerson.h>
|
||||
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
@@ -45,6 +46,11 @@
|
||||
|
||||
@implementation UIxCalMainView
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Calendar";
|
||||
}
|
||||
|
||||
- (void) checkDefaultModulePreference
|
||||
{
|
||||
SOGoUserDefaults *ud;
|
||||
@@ -261,12 +267,14 @@
|
||||
- (WOResponse *) saveSelectedListAction
|
||||
{
|
||||
WORequest *request;
|
||||
NSDictionary *params;
|
||||
NSString *selectedList;
|
||||
|
||||
[self _setupContext];
|
||||
request = [context request];
|
||||
params = [[request contentAsString] objectFromJSONString];
|
||||
|
||||
selectedList = [request formValueForKey: @"list"];
|
||||
selectedList = [params objectForKey: @"list"];
|
||||
[moduleSettings setObject: selectedList
|
||||
forKey: @"SelectedList"];
|
||||
[us synchronize];
|
||||
|
||||
@@ -20,14 +20,6 @@
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@class NSString;
|
||||
|
||||
@class SOGoAppointmentFolder;
|
||||
|
||||
@interface UIxCalendarFolderLinksTemplate : UIxComponent
|
||||
{
|
||||
SOGoAppointmentFolder *calendar;
|
||||
NSString *baseCalDAVURL, *basePublicCalDAVURL;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,89 +28,6 @@
|
||||
|
||||
@implementation UIxCalendarFolderLinksTemplate
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
calendar = [self clientObject];
|
||||
baseCalDAVURL = nil;
|
||||
basePublicCalDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCalDAVURL release];
|
||||
[basePublicCalDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCalDAVURL)
|
||||
{
|
||||
davURL = [[calendar realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCalDAVURL = davURL;
|
||||
[baseCalDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) calDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) webDavICSURL
|
||||
{
|
||||
return [calendar webDavICSURL];
|
||||
}
|
||||
|
||||
- (NSString *) webDavXMLURL
|
||||
{
|
||||
return [calendar webDavXMLURL];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCalDAVURL)
|
||||
{
|
||||
davURL = [[calendar publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCalDAVURL = davURL;
|
||||
[basePublicCalDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCalDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavICSURL
|
||||
{
|
||||
return [calendar publicWebDavICSURL];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavXMLURL
|
||||
{
|
||||
return [calendar publicWebDavXMLURL];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
@@ -118,14 +35,4 @@
|
||||
enablePublicAccess];
|
||||
}
|
||||
|
||||
- (BOOL) isWebCalendar
|
||||
{
|
||||
return ([calendar isKindOfClass: [SOGoWebAppointmentFolder class]]);
|
||||
}
|
||||
|
||||
- (NSString *) webCalendarURL
|
||||
{
|
||||
return [calendar folderPropertyValueInCategory: @"WebCalendars"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -842,7 +842,7 @@ static NSArray *reminderValues = nil;
|
||||
|
||||
clientObject = [self clientObject];
|
||||
ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
|
||||
if ([clientObject isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||
if ([[clientObject container] isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||
rc = componentReadableOnly;
|
||||
else
|
||||
{
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
actionName = "saveUserRights";
|
||||
};
|
||||
links = {
|
||||
protectedBy = "View";
|
||||
protectedBy = "Access Contents Information";
|
||||
pageName = "UIxCalendarFolderLinksTemplate";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,34 +7,51 @@
|
||||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:toolbar="UIxAdministration.toolbar"
|
||||
const:jsFiles="dtree.js">
|
||||
const:jsFiles="Common.js, Administration.js, Administration.services.js, Contacts.services.js">
|
||||
|
||||
<div id="helpDialog" style="display: none" class="dialog left">
|
||||
<main class="view layout-fill" ui-view="administration" layout="row" ng-controller="navController">
|
||||
<!-- administration -->
|
||||
</main>
|
||||
|
||||
<script type="text/ng-template" id="administration.html">
|
||||
<md-sidenav id="left-sidenav" class="md-sidenav-left md-whiteframe-z1" md-component-id="left" md-is-locked-open="$mdMedia('gt-md')" layout="column">
|
||||
<var:component className="UIxSidenavToolbarTemplate" />
|
||||
<md-content md-scroll-y="md-scroll-y" class="md-flex md-hue-2">
|
||||
<md-list>
|
||||
<md-list-item ng-click="app.go('rights')"
|
||||
ui-sref="administration.rights"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>security</md-icon>
|
||||
<p class="sg-item-name"><var:string label:value="Rights"/></p>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</md-sidenav>
|
||||
|
||||
<section layout="column" class="layout-fill">
|
||||
|
||||
<!-- TOP RIGHT TOOLBAR -->
|
||||
<md-toolbar layout="row" layout-align="space-between start" class="md-tall">
|
||||
<div class="md-toolbar-tools md-toolbar-tools-top" layout="row" layout-align="space-between start">
|
||||
<var:component className="UIxTopnavToolbarTemplate" />
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-content>
|
||||
<div ui-view="module"><!-- view --></div>
|
||||
</md-content>
|
||||
</section>
|
||||
|
||||
</script>
|
||||
|
||||
<!--
|
||||
RIGHTS ADMINISTRATION PANEL
|
||||
-->
|
||||
<script type="text/ng-template" id="rights.html">
|
||||
<div>
|
||||
<h3><!-- module title --></h3>
|
||||
<div><!-- module description --></div>
|
||||
<a href="#" class="button" id="helpDialogClose"><span><var:string label:value="Close"/></span></a>
|
||||
<label>TODO!</label>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div id="leftPanel">
|
||||
<div class="titlediv"><var:string label:value="Modules" /></div>
|
||||
<div id="administrationModules">
|
||||
<ul>
|
||||
<li id="ACLs" class="_selected"><var:string label:value="ACLs"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dragHandle" id="verticalDragHandle"><!-- space --></div>
|
||||
|
||||
<div id="rightPanel">
|
||||
<span><h1 id="moduleTitle"><var:string label:value="ACLs_title"/></h1>
|
||||
<div id="moduleDescription" style="display: none"><var:string label:value="ACLs_description" const:escapeHTML="NO"/></div></span>
|
||||
<var:component className="UIxAdministrationFilterPanel" qualifier="qualifier" />
|
||||
<div id="administrationContent"><!-- space --></div>
|
||||
</div>
|
||||
|
||||
<var:string value="errorAlertJavaScript" const:escapeHTML="NO" />
|
||||
</var:component>
|
||||
|
||||
@@ -5,8 +5,29 @@
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label">
|
||||
|
||||
<md-content md-scroll-y="true" class="md-padding md-whiteframe-z1 bg-sogoPaper-50">
|
||||
<div class="editor md-padding">
|
||||
<div layout="column" class="layout-fill">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Cancel"
|
||||
ng-click="editor.cancel()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Reset"
|
||||
ng-click="editor.reset()">
|
||||
<md-icon>undo</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Save"
|
||||
ng-click="editor.save(editor.card.$isCard()?cardForm:listForm)">
|
||||
<md-icon>save</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-content class="md-flex md-static">
|
||||
<div layout="column" class="viewer md-padding">
|
||||
<hgroup class="header">
|
||||
<h1 class="sg-md-display-2--light" ng-bind-html="editor.card.$fullname()"><!-- fullname --></h1>
|
||||
<h6 class="sg-md-display-2-subheader">{{editor.card.$description()}}</h6>
|
||||
@@ -21,7 +42,7 @@
|
||||
</label>
|
||||
<input type="text" ng-maxlength="30" ng-model="editor.card.c_cn"/>
|
||||
</md-input-container>
|
||||
<!-- todo: look for better reset/normalization. Semanticaly this should be a fieldset, but content doesn't flex properly du to browser styles-->
|
||||
<!-- todo: look for better reset/normalization. Semanticaly this should be a fieldset, but content doesn't flex properly du to browser styles -->
|
||||
<div id="contact-identification" class="sg-fieldset" layout="row" layout-sm="column">
|
||||
<md-input-container flex="40">
|
||||
<label>
|
||||
@@ -101,9 +122,11 @@
|
||||
<div class="section">
|
||||
<div class="attr" ng-repeat="email in editor.card.emails">
|
||||
<div class="md-layout-margin" layout="row" layout-align="space-between end">
|
||||
<md-select ng-model="email.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allEmailTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
<md-input-container flex="20">
|
||||
<md-select ng-model="email.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allEmailTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Email Address"/>
|
||||
@@ -115,7 +138,6 @@
|
||||
<md-icon>remove_circle</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="md-layout-margin" layout="row" layout-align="start center">
|
||||
<md-button class="sg-icon-button" type="button" ng-click="editor.addEmail()">
|
||||
@@ -128,20 +150,38 @@
|
||||
</div>
|
||||
|
||||
<!-- birthday -->
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Birthday"/>
|
||||
</label>
|
||||
<input type="date" ng-model="editor.card.birthday"/>
|
||||
</md-input-container>
|
||||
<div class="section">
|
||||
<div layout="row" layout-align="space-between end" ng-show="editor.card.birthday">
|
||||
<div class="pseudo-input-container">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Birthday"/>
|
||||
</label>
|
||||
<md-datepicker class="pseudo-input-field"
|
||||
ng-model="editor.card.birthday"><!-- birthday --></md-datepicker>
|
||||
</div>
|
||||
<md-button class="sg-icon-button" type="button" ng-click="editor.card.$delete('birthday', -1)">
|
||||
<md-icon>remove_circle</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="md-layout-margin" layout="row" layout-align="start center" ng-hide="editor.card.birthday">
|
||||
<md-button class="sg-icon-button" type="button" ng-click="editor.addBirthday()">
|
||||
<md-icon>add_circle</md-icon>
|
||||
</md-button>
|
||||
<label class="button-label">
|
||||
<var:string label:value="Add Birthday"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- phones -->
|
||||
<div class="section">
|
||||
<div class="attr" ng-repeat="phone in editor.card.phones">
|
||||
<div class="md-layout-margin" layout="row" layout-align="space-between end">
|
||||
<md-select ng-model="phone.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allTelTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
<md-input-container flex="20">
|
||||
<md-select ng-model="phone.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allTelTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Phone Number"/>
|
||||
@@ -169,9 +209,11 @@
|
||||
<div class="section">
|
||||
<div class="attr" ng-repeat="url in editor.card.urls">
|
||||
<div class="md-layout-margin" layout="row" layout-align="space-between end">
|
||||
<md-select ng-model="url.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allUrlTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
<md-input-container flex="20">
|
||||
<md-select ng-model="url.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allUrlTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="URL"/>
|
||||
@@ -197,9 +239,11 @@
|
||||
<div class="section">
|
||||
<div class="attr" ng-repeat="address in editor.card.addresses">
|
||||
<div layout="row" layout-align="center end">
|
||||
<md-select ng-model="address.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allAddressTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
<md-input-container flex="20">
|
||||
<md-select ng-model="address.type" label:placeholder="Type">
|
||||
<md-option ng-repeat="type in ::editor.allAddressTypes" ng-value="type">{{ type }}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="street"/>
|
||||
@@ -275,22 +319,10 @@
|
||||
</label>
|
||||
<textarea ng-model="editor.card.note"><!-- note --></textarea>
|
||||
</md-input-container>
|
||||
|
||||
<div class="fieldset md-layout-margin" layout="row" layout-align="end center">
|
||||
<md-button type="button" ng-click="editor.cancel()">
|
||||
<var:string label:value="Cancel"/>
|
||||
</md-button>
|
||||
<md-button type="button" ng-click="editor.reset()">
|
||||
<var:string label:value="Reset"/>
|
||||
</md-button>
|
||||
<md-button class="md-primary md-hue-3" type="submit">
|
||||
<var:string label:value="Save"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- list editor -->
|
||||
<form name="listForm" ng-if="editor.card.$isList()" ng-submit="editor.save(listForm)">
|
||||
<form name="listForm" ng-show="editor.card.$isList()" ng-submit="editor.save(listForm)">
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Display"/>
|
||||
@@ -327,22 +359,8 @@
|
||||
filter-selected="false"
|
||||
label:placeholder="Add Member"><!-- members --></md-contact-chips>
|
||||
</div>
|
||||
|
||||
<div class="fieldset md-layout-margin" layout="row" layout-align="end center">
|
||||
<md-button type="button" ng-click="editor.cancel()">
|
||||
<var:string label:value="Cancel"/>
|
||||
</md-button>
|
||||
<md-button type="button" ng-click="editor.reset()">
|
||||
<var:string label:value="Reset"/>
|
||||
</md-button>
|
||||
<var:if condition="canCreateOrModify">
|
||||
<md-button class="md-primary md-hue-3" type="submit">
|
||||
<var:string label:value="Save"/>
|
||||
</md-button>
|
||||
</var:if>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</md-content>
|
||||
|
||||
</div>
|
||||
</container>
|
||||
@@ -6,14 +6,15 @@
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label"
|
||||
>
|
||||
<md-dialog flex="40">
|
||||
<md-toolbar class="sg-padded">
|
||||
<md-dialog flex="60" flex-sm="100">
|
||||
<md-toolbar>
|
||||
<md-icon class="material-icons sg-icon-toolbar-bg">link</md-icon>
|
||||
<div class="md-toolbar-tools">
|
||||
<div class="pseudo-input-container md-flex">
|
||||
<label class="pseudo-input-label"><var:string label:value="Links to this Address Book"/></label>
|
||||
<div class="pseudo-input-field sg-md-title"><var:string value="addressBook.displayName"/></div>
|
||||
<div class="pseudo-input-field sg-md-title">{{links.addressbook.name}}</div>
|
||||
</div>
|
||||
<md-button class="md-icon-button" ng-click="close()">
|
||||
<md-button class="md-icon-button" ng-click="links.close()">
|
||||
<md-icon aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
@@ -24,7 +25,7 @@
|
||||
<div class="sg-md-title"><var:string label:value="Authenticated User Access"/></div>
|
||||
<md-input-container>
|
||||
<label><var:string label:value="CardDAV URL"/></label>
|
||||
<input var:value="cardDavURL" ng-readonly="true"/>
|
||||
<input type="text" ng-model="links.addressbook.urls.cardDavURL" ng-readonly="true"/>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<var:if condition="isPublicAccessEnabled">
|
||||
@@ -32,7 +33,7 @@
|
||||
<div class="sg-md-title"><var:string label:value="Public Access"/></div>
|
||||
<md-input-container>
|
||||
<label><var:string label:value="CardDAV URL"/></label>
|
||||
<input var:value="publicCardDavURL" ng-readonly="true"/>
|
||||
<input type="text" ng-model="links.addressbook.urls.publicCardDavURL" ng-readonly="true"/>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
@@ -9,104 +9,26 @@
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:userDefaultsKeys="SOGoContactsCategories"
|
||||
const:jsFiles="Common.js, Preferences.services.js, Contacts.js, Contacts.services.js">
|
||||
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, Common.js, Preferences.services.js, Mailer.services.js, Contacts.js, Contacts.services.js, vendor/angular-file-upload.min.js">
|
||||
<script type="text/javascript">
|
||||
var contactFolders = <var:string value="contactFolders" const:escapeHTML="NO" />;
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<div id="uploadDialog" style="display: none" class="dialog left">
|
||||
<div>
|
||||
<h3><var:string label:value="Import Cards"/></h3>
|
||||
<p class="prompt"><var:string label:value="Select a vCard or LDIF file."/></p>
|
||||
<form const:href="" name="uploadForm" id="uploadForm" method="post"
|
||||
enctype="multipart/form-data"
|
||||
onsubmit="return AIM.submit(this, {'onStart' : validateUploadForm,
|
||||
'onComplete' : uploadCompleted})">
|
||||
<p class="prompt"><input type="file" name="contactsFile" id="contactsFile" class="textField" required="required" />
|
||||
<a href="#" class="button actionButton" id="uploadSubmit"
|
||||
onclick="if($('uploadForm').onsubmit()) $('uploadForm').submit()">
|
||||
<span><var:string label:value="Upload" /></span></a>
|
||||
<a href="#" class="button" const:id="uploadCancel">
|
||||
<span><var:string label:value="Cancel" /></span></a></p>
|
||||
</form>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploadResults" dropdown-content="dropdown-content" class="f-dropdown">
|
||||
<div>
|
||||
<h3><var:string label:value="Import Cards"/></h3>
|
||||
<p id="uploadResultsContent">!- empty -</p>
|
||||
<p><a href="#" class="button" const:id="uploadOK">
|
||||
<span><var:string label:value="Done" /></span></a></p>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="f-dropdown" dropdown-content="dropdown-content" id="contactMenu">
|
||||
<li><var:string label:value="Properties" /></li>
|
||||
<li><var:string label:value="Categories" /></li>
|
||||
<li>!- separator -</li>
|
||||
<li><var:string label:value="Write" /></li>
|
||||
<li><var:string label:value="Instant Message" /></li>
|
||||
<li>!- separator -</li>
|
||||
<li><var:string label:value="Delete" /></li>
|
||||
<li>!- separator -</li>
|
||||
<li><var:string label:value="Move To" /></li>
|
||||
<li><var:string label:value="Copy To" /></li>
|
||||
<li><var:string label:value="Export" /></li>
|
||||
<li><var:string label:value="View Raw Source" /></li>
|
||||
</ul>
|
||||
-->
|
||||
<!--
|
||||
MAIN CONTENT ROW
|
||||
Content of the application view injected injected in the element bellow
|
||||
MUST be the first html element after body
|
||||
SHOULD be a main tag (with role="main")
|
||||
|
||||
Templates script tag wrappers goes next
|
||||
-->
|
||||
<main class="view md-layout-fill" ui-view="addressbooks" layout="row" ng-controller="navController"><!-- addressbooks list --></main>
|
||||
<main class="view layout-fill" ui-view="addressbooks" layout="row" ng-controller="navController"><!-- addressbooks list --></main>
|
||||
|
||||
<!-- TEMPLATE SCRIPT WRAPPER -->
|
||||
<script type="text/ng-template" id="UIxContactFoldersView">
|
||||
|
||||
<!-- dropdown menu for addressbook options button -->
|
||||
<div id="addressbookProperties" class="f-dropdown icons-dropdown" ng-hide="true">
|
||||
<ul class="button-group">
|
||||
<li label:tooltip="Links to this Address Book"
|
||||
tooltip-popup-delay="500">
|
||||
<span class="button"
|
||||
sg-dropdown-content-toggle="#addressbookLinks">
|
||||
<i class="icon-hyperlink"><!-- links --></i>
|
||||
</span>
|
||||
</li>
|
||||
<li label:tooltip="Rename"
|
||||
tooltip-popup-delay="500">
|
||||
<span class="button" ng-click="edit()">
|
||||
<i class="icon-pencil"><!-- rename --></i>
|
||||
</span>
|
||||
</li>
|
||||
<li label:tooltip="Import Cards"
|
||||
tooltip-popup-delay="500">
|
||||
<span class="button" ng-click="importCards()">
|
||||
<i class="icon-file"><!-- import --></i>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Sidenav -->
|
||||
<md-sidenav id="left-sidenav" class="md-sidenav-left md-whiteframe-z1" md-component-id="left" md-is-locked-open="isGtMedium" layout="column">
|
||||
<var:component className="UIxSidenavToolbarTemplate" />
|
||||
<!-- <div style="position: relative; display: flex;"> -->
|
||||
<md-content md-scroll-y="md-scroll-y">
|
||||
<md-content md-scroll-y="md-scroll-y" class="md-flex md-hue-2">
|
||||
<!-- User's addressbooks -->
|
||||
<section>
|
||||
<md-subheader class="sg-md-subheader">
|
||||
<md-subheader class="sg-md-subheader md-hue-2">
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<span><var:string label:value="AddressBooks"/></span>
|
||||
<span><var:string label:value="Address Books"/></span>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="New Addressbook..."
|
||||
ng-click="app.newAddressbook()">
|
||||
@@ -119,13 +41,13 @@
|
||||
ng-click="app.select(folder)"
|
||||
ng-dblclick="app.edit(folder)"
|
||||
ui-sref="app.addressbook({addressbookId: folder.id})"
|
||||
ui-sref-active="sg-active">
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>contacts</md-icon>
|
||||
<p class="sg-item-name"
|
||||
ng-show="app.editMode!=folder.id">{{folder.name}}</p>
|
||||
<md-input-container class="md-flex md-tile-content"
|
||||
ng-show="app.editMode == folder.id">
|
||||
<input class="folder-name" type="text"
|
||||
<input class="sg-item-name" type="text"
|
||||
label:aria-label="Name of the Address Book"
|
||||
ng-model="folder.name"
|
||||
ng-cloak="ng-cloak"
|
||||
@@ -141,10 +63,21 @@
|
||||
md-menu-origin="md-menu-origin">more_vert</md-icon>
|
||||
<md-menu-content width="2">
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.edit(folder)">
|
||||
<var:string label:value="Rename"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-show="folder.id != 'personal'">
|
||||
<md-button type="button" ng-click="app.confirmDelete()">
|
||||
<var:string label:value="Delete"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.importCards($event, folder)">
|
||||
<var:string label:value="Import"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.exportCards()">
|
||||
<var:string label:value="Export"/>
|
||||
@@ -168,7 +101,7 @@
|
||||
</section>
|
||||
<!-- Subscriptions -->
|
||||
<section>
|
||||
<md-subheader class="sg-md-subheader">
|
||||
<md-subheader class="sg-md-subheader md-hue-2">
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<span><var:string label:value="Subscriptions"/></span>
|
||||
<md-button class="sg-icon-button"
|
||||
@@ -184,13 +117,13 @@
|
||||
ng-click="app.select(folder)"
|
||||
ng-dblclick="app.edit(folder)"
|
||||
ui-sref="app.addressbook({addressbookId: folder.id})"
|
||||
ui-sref-active="sg-active">
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>contacts</md-icon>
|
||||
<p class="sg-item-name"
|
||||
ng-show="editMode!=folder.id">{{folder.name}}</p>
|
||||
<md-input-container class="md-flex md-tile-content"
|
||||
ng-show="app.editMode==folder.id">
|
||||
<input class="folder-name" type="text"
|
||||
<input class="sg-item-name" type="text"
|
||||
label:aria-label="Name of the Address Book"
|
||||
ng-model="folder.name"
|
||||
ng-cloak="ng-cloak"
|
||||
@@ -205,6 +138,11 @@
|
||||
ng-click="$mdOpenMenu()"
|
||||
md-menu-origin="md-menu-origin">more_vert</md-icon>
|
||||
<md-menu-content width="2">
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.edit(folder)">
|
||||
<var:string label:value="Rename"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.confirmDelete(folder)">
|
||||
<var:string label:value="Delete"/>
|
||||
@@ -217,14 +155,14 @@
|
||||
</section>
|
||||
<!-- Remote/domain addressbooks -->
|
||||
<section>
|
||||
<md-subheader class="sg-md-subheader md-padding">
|
||||
<md-subheader class="sg-md-subheader md-hue-2">
|
||||
<var:string label:value="Global Addressbooks"/>
|
||||
</md-subheader>
|
||||
<md-list>
|
||||
<md-list-item ng-repeat="folder in app.service.$remotes track by folder.id"
|
||||
ng-click="app.select(folder)"
|
||||
ui-sref="app.addressbook({addressbookId: folder.id})"
|
||||
ui-sref-active="sg-active">
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>public</md-icon>
|
||||
<p class="sg-item-name">{{folder.name}}</p>
|
||||
</md-list-item>
|
||||
@@ -234,26 +172,27 @@
|
||||
<!-- </div> -->
|
||||
</md-sidenav>
|
||||
|
||||
<section layout="column" class="sg-app-content md-layout-fill" ui-view="addressbook"><!-- contacts list --></section>
|
||||
<section layout="column" class="sg-app-content layout-fill" ui-view="addressbook"><!-- contacts list --></section>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addressbook">
|
||||
<md-toolbar layout="column" layout-align="space-between start" class="md-tall toolbar-main">
|
||||
<div class="md-toolbar-tools md-toolbar-tools-top sg-padded" layout="row" layout-align="space-between start">
|
||||
<var:component className="UIxTopnavToolbarTemplate" />
|
||||
</div>
|
||||
<div class="md-toolbar-tools" layout="row" layout-align="space-between center"
|
||||
ng-show="addressbook.selectedFolder.$selectedCount() == 0">
|
||||
<md-toolbar layout="row" layout-align="space-between center" class="toolbar-main">
|
||||
<var:component className="UIxTopnavToolbarTemplate" />
|
||||
</md-toolbar>
|
||||
|
||||
<div class="view-list" layout="row" layout-align="space-between center"
|
||||
ng-hide="addressbook.mode.search">
|
||||
<div class="sg-toolbar-group">
|
||||
<div layout="row" class="md-flex">
|
||||
|
||||
<div class="view-list" layout="column">
|
||||
|
||||
<!-- single-selection toolbar -->
|
||||
<md-toolbar ng-show="addressbook.selectedFolder.$selectedCount() == 0">
|
||||
<!-- sort mode (default) -->
|
||||
<div class="md-toolbar-tools" ng-hide="addressbook.mode.search">
|
||||
<md-button class="sg-icon-button" label:aria-label="Search"
|
||||
ng-click="addressbook.mode.search = true">
|
||||
<md-icon>search</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="sg-toolbar-group-last">
|
||||
<div class="sg-folder-name">{{addressbook.selectedFolder.name}}</div>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Sort"
|
||||
ng-click="$mdOpenMenu()">
|
||||
@@ -295,9 +234,10 @@
|
||||
</md-menu-item>
|
||||
<md-menu-divider> <!-- divider --></md-menu-divider>
|
||||
<md-menu-item >
|
||||
<md-button ng-click="addressbook.sort()">
|
||||
<md-button ng-href="#">
|
||||
<md-checkbox
|
||||
ng-model="addressbook.selectedFolder.$query.asc"
|
||||
ng-change="addressbook.sort(addressbook.selectedFolder.constructor.$query.sort)"
|
||||
ng-model="addressbook.selectedFolder.constructor.$query.asc"
|
||||
ng-true-value="0"
|
||||
ng-false-value="1"><var:string label:value="Descending Order"/></md-checkbox>
|
||||
</md-button>
|
||||
@@ -308,100 +248,120 @@
|
||||
<md-icon>refresh</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- search mode -->
|
||||
<div class="view-list sg-padded--right sg-toolbar-search" layout="row" layout-align="space-between center"
|
||||
ng-show="addressbook.mode.search"
|
||||
sg-search="addressbook.selectedFolder.$filter(searchText, { search: searchField })">
|
||||
<md-button class="sg-icon-button"
|
||||
sg-search-cancel="addressbook.cancelSearch()"
|
||||
label:aria-label="Back">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<md-input-container>
|
||||
<input name="folderSearch" type="search" />
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<md-select>
|
||||
<md-option value="name_or_address" selected="selected"><var:string label:value="Name or Email"/></md-option>
|
||||
<md-option value="category"><var:string label:value="Category"/></md-option>
|
||||
<md-option value="organization"><var:string label:value="Organization"/></md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<!-- search mode -->
|
||||
<div class="md-toolbar-tools sg-toolbar-secondary"
|
||||
ng-show="addressbook.mode.search"
|
||||
sg-search="addressbook.selectedFolder.$filter(searchText, { search: searchField })">
|
||||
<md-button class="sg-icon-button"
|
||||
sg-search-cancel="addressbook.cancelSearch()"
|
||||
label:aria-label="Back">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<md-input-container>
|
||||
<input name="folderSearch" type="search" ng-minlength="3"/>
|
||||
</md-input-container>
|
||||
<md-input-container flex="25">
|
||||
<md-select>
|
||||
<md-option value="name_or_address" selected="selected"><var:string label:value="Name or Email"/></md-option>
|
||||
<md-option value="category"><var:string label:value="Category"/></md-option>
|
||||
<md-option value="organization"><var:string label:value="Organization"/></md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<div layout="row" layout-align="start center" ng-show="addressbook.selectedFolder.$selectedCount() > 0">
|
||||
<md-button class="sg-icon-button" ng-click="addressbook.unselectCards()">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<label>{{addressbook.selectedFolder.$selectedCount()}} selected</label>
|
||||
<md-button class="sg-icon-button" ng-click="addressbook.selectAll()">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Select All"/></md-tooltip>
|
||||
<md-icon>select_all</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button" ng-click="addressbook.confirmDeleteSelectedCards()">
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More messages options" ng-click="$mdOpenMenu()">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="2">
|
||||
<md-menu-item>
|
||||
<md-button>
|
||||
<var:string label:value="Write"/>
|
||||
<!-- multiple selection mode -->
|
||||
<md-toolbar class="md-hue-1 sg-toolbar-secondary"
|
||||
ng-show="addressbook.selectedFolder.$selectedCount() > 0">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="sg-icon-button" ng-click="addressbook.unselectCards()">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<label>{{addressbook.selectedFolder.$selectedCount()}} <var:string label:value="selected"/></label>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button class="sg-icon-button" ng-click="addressbook.selectAll()">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Select All"/></md-tooltip>
|
||||
<md-icon>select_all</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
ng-show="addressbook.selectedFolder.acls.objectEraser"
|
||||
ng-click="addressbook.confirmDeleteSelectedCards()">
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Copy contacts" ng-click="$mdOpenMenu()">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Copy To"/></md-tooltip>
|
||||
<md-icon>content_copy</md-icon>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="addressbook.saveSelectedCards()">
|
||||
<var:string label:value="Export"/>
|
||||
<md-menu-content width="4">
|
||||
<md-menu-item>
|
||||
<md-button class="md-primary" ng-disabled="true"><var:string label:value="Address Books"/></md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item ng-repeat="folder in app.service.$addressbooks track by folder.id"
|
||||
ng-hide="addressbook.selectedFolder.id == folder.id">
|
||||
<md-button ng-click="addressbook.copySelectedCards(folder.id)">
|
||||
<span ng-class="'sg-child-level-' + folder.level">{{folder.name}}</span>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More messages options" ng-click="$mdOpenMenu()">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<div layout="row" class="md-flex" layout-align="space-between">
|
||||
<div class="view-list" layout="column">
|
||||
<md-menu-content width="2">
|
||||
<md-menu-item>
|
||||
<md-button ng-click="addressbook.newMessageWithSelectedCards($event)">
|
||||
<var:string label:value="Write"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="addressbook.saveSelectedCards()">
|
||||
<var:string label:value="Export"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-content id="contactsList" layout="column" class="md-flex">
|
||||
<header class="sg-md-subheader sg-md-subheader--fixed">
|
||||
<h2 class="md-default-theme sg-md-subhead-solo fg-sogoBlue-700">
|
||||
<span ng-show="addressbook.selectedFolder.cards.length > 0">{{addressbook.selectedFolder.cards.length}} <var:string label:value="contacts"/></span>
|
||||
<span ng-show="addressbook.selectedFolder.cards.length == 0"><var:string label:value="No contact"/></span>
|
||||
</h2>
|
||||
</header>
|
||||
<!-- Search field & special results
|
||||
<input type="text" placeholder="Search" ng-model="search.filter" ng-keyup="doSearch($event)" />
|
||||
<div ng-switch="search.status">
|
||||
<div ng-switch-when="min-char" class="alert-bg">
|
||||
<i class="icon-warning"></i><var:string label:value="Please enter at least three characters"/>
|
||||
</div>
|
||||
<div ng-switch-when="no-result" class="alert-bg">
|
||||
<i class="icon-ion-search"></i><var:string label:value="No matching card"/>
|
||||
</div>
|
||||
<div ng-switch-when="remote-addressbook" ng-show="addressbook.cards.length == 0" class="alert-bg">
|
||||
<i class="icon-ion-search"></i><var:string label:value="Initiate a search"/>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<md-subheader ng-show="addressbook.service.$query.value">
|
||||
<span ng-switch="addressbook.selectedFolder.cards.length">
|
||||
<span ng-switch-when="0"><var:string label:value="No matching contact"/></span>
|
||||
<span ng-switch-default="true">{{addressbook.selectedFolder.cards.length}} <var:string label:value="matching contacts"/></span>
|
||||
</span>
|
||||
</md-subheader>
|
||||
<md-subheader ng-hide="addressbook.service.$query.value"
|
||||
ng-switch="addressbook.selectedFolder.isRemote">
|
||||
<span ng-switch-when="1">
|
||||
<var:string label:value="Start a search to browse this address book"/>
|
||||
</span>
|
||||
<span ng-switch-default="true">
|
||||
<span ng-switch="addressbook.selectedFolder.cards.length">
|
||||
<span ng-switch-when="0"><var:string label:value="No contact"/></span>
|
||||
<span ng-switch-default="true">{{addressbook.selectedFolder.cards.length}} <var:string label:value="contacts"/></span>
|
||||
</span>
|
||||
</span>
|
||||
</md-subheader>
|
||||
<md-virtual-repeat-container class="md-flex">
|
||||
<md-list>
|
||||
<div md-virtual-repeat="currentCard in addressbook.selectedFolder.cards">
|
||||
<md-list class="sg-section-list">
|
||||
<div md-virtual-repeat="currentCard in addressbook.selectedFolder.cards" md-item-size="72">
|
||||
<md-list-item
|
||||
ng-class="{'sg-active': currentCard.id == addressbook.selectedFolder.selectedCard}"
|
||||
ng-click="addressbook.selectCard(currentCard)"
|
||||
ui-sref="app.addressbook.card.view({addressbookId: addressbook.selectedFolder.id, cardId: currentCard.id})">
|
||||
<!-- ui-sref-active="sg-active" -->
|
||||
ui-sref="app.addressbook.card.view({addressbookId: addressbook.selectedFolder.id, cardId: currentCard.id})"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<div class="sg-selected-avatar" ng-show="currentCard.selected"
|
||||
ng-click="currentCard.selected = !currentCard.selected">
|
||||
ng-click="addressbook.toggleCardSelection($event, currentCard)">
|
||||
<!-- selected avatar -->
|
||||
</div>
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
ng-show="addressbook.notSelectedComponent(currentCard, 'vcard')"
|
||||
ng-click="currentCard.selected = !currentCard.selected"
|
||||
sg-email="currentCard.$preferredEmail(addressbook.selectedFolder.$query)"
|
||||
size="48">
|
||||
ng-click="addressbook.toggleCardSelection($event, currentCard)"
|
||||
sg-email="currentCard.$preferredEmail(addressbook.selectedFolder.constructor.$query)"
|
||||
size="40">
|
||||
<!-- contact avatar -->
|
||||
</sg-avatar-image>
|
||||
<div class="sg-list-avatar"
|
||||
@@ -411,23 +371,37 @@
|
||||
</div>
|
||||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead-multi" ng-bind-html="currentCard.$fullname()"><!-- cn --></div>
|
||||
<div class="sg-md-body-multi">{{currentCard.$preferredEmail(addressbook.selectedFolder.$query)}}</div>
|
||||
<div class="sg-md-body-multi">{{currentCard.$preferredEmail(addressbook.selectedFolder.constructor.$query)}}</div>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</div>
|
||||
</md-list>
|
||||
</md-virtual-repeat-container>
|
||||
<div class="sg-progress-circular-floating"
|
||||
ng-show="addressbook.selectedFolder.$isLoading">
|
||||
<md-progress-circular class="md-accent"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- progress --></md-progress-circular>
|
||||
</div>
|
||||
<md-button class="md-fab md-fab-bottom-right md-accent"
|
||||
label:aria-label="New Contact"
|
||||
ng-show="addressbook.selectedFolder.acls.objectCreator"
|
||||
ng-click="addressbook.newComponent($event)">
|
||||
<md-icon>add</md-icon>
|
||||
</md-button>
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
<div id="detailView" class="view-detail ng-cloak" layout="column" ui-view="card"
|
||||
ng-show="addressbook.selectedFolder.selectedCard"><!-- card view --></div>
|
||||
|
||||
<div id="detailView" class="view-detail" layout="column" layout-align="start center"
|
||||
ng-class="{ 'sg-close': !addressbook.selectedFolder.selectedCard }"
|
||||
ui-view="card">
|
||||
<md-toolbar class="hide-sm"><!-- empty toolbar --></md-toolbar>
|
||||
<md-content class="hide-sm md-flex layout-fill md-hue-1" layout="column">
|
||||
<md-input-container layout-align="center center">
|
||||
<label class="sg-md-title"><var:string label:value="No contact selected"/></label>
|
||||
</md-input-container>
|
||||
</md-content>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,167 +3,177 @@
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label"
|
||||
>
|
||||
<md-card class="viewer md-flex">
|
||||
<md-card-content>
|
||||
<header class="msg-header">
|
||||
<div ng-show="editor.card.tag == 'vcard'">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="editor.card.$preferredEmail()"
|
||||
sg-src="editor.card.photoURL"
|
||||
size="48"><!-- avatar --></sg-avatar-image>
|
||||
</div>
|
||||
<div class="sg-list-avatar" ng-show="editor.card.tag == 'vlist'">
|
||||
<!--list avatar-->
|
||||
</div>
|
||||
<div class="msg-header-content">
|
||||
<h1 class="sg-md-display-2--light" ng-bind-html="editor.card.$fullname()"><!-- fullname --></h1>
|
||||
<h6 class="sg-md-display-2-subheader">{{editor.card.$description()}}</h6>
|
||||
<md-chips ng-model="editor.card.categories"
|
||||
class="sg-readonly" readonly="true">
|
||||
<md-chip-template>
|
||||
{{$chip.value}}
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
<div class="sg-icon-bar--vertical">
|
||||
<md-button class="sg-icon-button show-sm"
|
||||
label:aria-label="Close"
|
||||
ng-click="toggleDetailView()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Edit"
|
||||
ng-show="editor.currentFolder.isEditable"
|
||||
ui-sref="app.addressbook.card.editor({addressbookId: editor.currentFolder.id, cardId: editor.card.id})">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Edit"/></md-tooltip>
|
||||
<md-icon>mode_edit</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Delete"
|
||||
ng-click="editor.confirmDelete(editor.card)">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Delete"/></md-tooltip>
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
xmlns:label="OGo:label">
|
||||
|
||||
<div layout="column" class="layout-fill">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="sg-icon-button hide show-sm"
|
||||
label:aria-label="Close"
|
||||
ng-click="editor.close()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Edit"
|
||||
ng-show="editor.currentFolder.acls.objectEditor"
|
||||
ui-sref="app.addressbook.card.editor({addressbookId: editor.currentFolder.id, cardId: editor.card.id})">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Edit"/></md-tooltip>
|
||||
<md-icon>mode_edit</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Delete"
|
||||
ng-show="editor.currentFolder.acls.objectEraser"
|
||||
ng-click="editor.confirmDelete(editor.card)">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Delete"/></md-tooltip>
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
<md-menu>
|
||||
<md-button label:aria-label="More contact options" class="sg-icon-button" ng-click="$mdOpenMenu($event)">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button aria-label="View Raw Source"
|
||||
<md-button label:aria-label="Export"
|
||||
ng-click="editor.exportCard()">
|
||||
<var:string label:value="Export"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button label:aria-label="View Raw Source"
|
||||
ng-click="editor.viewRawSource($event)">
|
||||
<var:string label:value="View Raw Source"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<section class="msg-body">
|
||||
|
||||
<div class="pseudo-input-container" ng-show="editor.card.birthday">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Birthday"/>
|
||||
</label>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-content class="md-flex md-static">
|
||||
<div layout="column" class="viewer md-padding">
|
||||
<header class="msg-header">
|
||||
<div ng-show="editor.card.c_component == 'vcard'">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="editor.card.$preferredEmail()"
|
||||
sg-src="editor.card.photoURL"
|
||||
size="40"><!-- avatar --></sg-avatar-image>
|
||||
</div>
|
||||
<div class="pseudo-input-field">
|
||||
<span>{{editor.card.$birthday() | date}}</span>
|
||||
<div class="sg-list-avatar" ng-show="editor.card.c_component == 'vlist'">
|
||||
<!--list avatar-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-header-content">
|
||||
<h1 class="sg-md-display-2--light" ng-bind-html="editor.card.$fullname()"><!-- fullname --></h1>
|
||||
<h6 class="sg-md-display-2-subheader">{{editor.card.$description()}}</h6>
|
||||
<md-chips ng-model="editor.card.categories"
|
||||
class="sg-readonly" readonly="true">
|
||||
<md-chip-template>
|
||||
{{$chip.value}}
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
</header>
|
||||
<section class="msg-body">
|
||||
|
||||
<!-- list members -->
|
||||
<div class="section" ng-show="editor.card.refs.length > 0">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Members"/>
|
||||
</label>
|
||||
<md-list>
|
||||
<md-list-item class="md-3-line" ng-repeat="ref in editor.card.refs track by ref.reference">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="ref.$preferredEmail()"
|
||||
size="48">
|
||||
<!-- contact avatar -->
|
||||
</sg-avatar-image>
|
||||
<div class="md-list-item-text">
|
||||
<h3>
|
||||
<a ui-sref="app.addressbook.card.view({addressbookId: editor.currentFolder.id, cardId: ref.reference})">
|
||||
{{ ref.$fullname() }}
|
||||
</a>
|
||||
</h3>
|
||||
<h4 ng-show="ref.email">
|
||||
<a ui-sref="mailto:{{ref.email}}" ng-class="ng-scope">
|
||||
{{ ref.email }}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-show="editor.card.emails.length > 0">
|
||||
<div class="pseudo-input-container" ng-repeat="email in editor.card.emails">
|
||||
<div class="pseudo-input-container" ng-show="editor.card.$birthday().length">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label"><var:entity const:name="nbsp"/>{{email.type}}</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="mailto:{{email.value}}">{{email.value}}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-show="editor.card.phones.length > 0">
|
||||
<div class="pseudo-input-container" ng-repeat="phone in editor.card.phones">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">{{phone.type}}</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="tel:{{phone.value}}">{{phone.value}}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" ng-show="editor.card.urls">
|
||||
<div class="pseudo-input-container" ng-repeat="url in editor.card.urls">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label"><var:entity const:name="nbsp"/>{{url.type}}
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Birthday"/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="#" ng-href="{{url.value}}">{{url.value}}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" ng-show="editor.card.addresses">
|
||||
<div class="pseudo-input-container" ng-repeat="address in editor.card.addresses">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">{{address.type}}</label>
|
||||
</div>
|
||||
<div class="pseudo-input-field">
|
||||
<div sg-address="address"><!-- address --></div>
|
||||
<span>{{editor.card.$birthday()}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pseudo-input-container" ng-show="editor.card.note">
|
||||
<div class="key">
|
||||
|
||||
<!-- list members -->
|
||||
<div class="section" ng-show="editor.card.refs.length > 0">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Note"/>
|
||||
<var:string label:value="Members"/>
|
||||
</label>
|
||||
<md-list>
|
||||
<md-list-item class="md-3-line" ng-repeat="ref in editor.card.refs track by ref.reference">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="ref.$preferredEmail()"
|
||||
size="48">
|
||||
<!-- contact avatar -->
|
||||
</sg-avatar-image>
|
||||
<div class="md-list-item-text">
|
||||
<h3>
|
||||
<a ui-sref="app.addressbook.card.view({addressbookId: editor.currentFolder.id, cardId: ref.reference})">
|
||||
{{ ref.$fullname() }}
|
||||
</a>
|
||||
</h3>
|
||||
<h4 ng-show="ref.email">
|
||||
<a ui-sref="mailto:{{ref.email}}" ng-class="ng-scope">
|
||||
{{ ref.email }}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</div>
|
||||
<div class="pseudo-input-field">
|
||||
<div ng-bind-html="editor.card.note"><!-- note --></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
|
||||
<div class="section" ng-show="editor.card.emails.length > 0">
|
||||
<div class="pseudo-input-container" ng-repeat="email in editor.card.emails">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label"><var:entity const:name="nbsp"/>{{email.type}}</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="#" ng-bind="email.value"
|
||||
ng-click="addressbook.newMessageWithRecipient($event, email.value, editor.card.$fullname())"><!-- recipient --></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-show="editor.card.phones.length > 0">
|
||||
<div class="pseudo-input-container" ng-repeat="phone in editor.card.phones">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">{{phone.type}}</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="tel:{{phone.value}}">{{phone.value}}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" ng-show="editor.card.urls">
|
||||
<div class="pseudo-input-container" ng-repeat="url in editor.card.urls">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label"><var:entity const:name="nbsp"/>{{url.type}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-field">
|
||||
<a href="#" ng-href="{{url.value}}">{{url.value}}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" ng-show="editor.card.addresses">
|
||||
<div class="pseudo-input-container" ng-repeat="address in editor.card.addresses">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">{{address.type}}</label>
|
||||
</div>
|
||||
<div class="pseudo-input-field">
|
||||
<div sg-address="address"><!-- address --></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pseudo-input-container" ng-show="editor.card.note">
|
||||
<div class="key">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Note"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pseudo-input-field">
|
||||
<div ng-bind-html="editor.card.note"><!-- note --></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</md-content>
|
||||
</div>
|
||||
</container>
|
||||
@@ -7,8 +7,15 @@
|
||||
xmlns:label="OGo:label"
|
||||
xmlns:uix="OGo:uix">
|
||||
<md-dialog flex="50" flex-sm="100">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<div class="sg-md-title md-flex"><var:string label:value="Subscribe to a shared folder"/></div>
|
||||
<md-button class="md-icon-button" ng-click="subscribe.close()">
|
||||
<md-icon label:aria-label="Close">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-dialog-content>
|
||||
<h2 class="md-title"><var:string label:value="Subscribe to a shared folder"/></h2>
|
||||
<md-input-container>
|
||||
<label><md-icon>search</md-icon><var:string label:value="Search User"/></label>
|
||||
<input type="input"
|
||||
@@ -33,8 +40,7 @@
|
||||
<div class="sg-md-subhead-multi">{{user.cn}} {{user.userClass}}</div>
|
||||
<div class="sg-md-body-multi">{{user.c_email}}</div>
|
||||
</div>
|
||||
<md-icon ng-class="{ 'icon-expand-more': user.uid != subscribe.selectedUser.uid,
|
||||
'icon-expand-less': user.uid == subscribe.selectedUser.uid }"><!--icon--></md-icon>
|
||||
<md-icon ng-class="{'md-rotate-180': user.uid == subscribe.selectedUser.uid}">expand_more</md-icon>
|
||||
</div>
|
||||
</md-button>
|
||||
<md-card-content ng-show="user == subscribe.selectedUser">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
xmlns:rsrc="OGo:url"
|
||||
xmlns:label="OGo:label"
|
||||
xmlns:uix="OGo:uix">
|
||||
<div class="addressbookUserRights" layout="column">
|
||||
<div layout="column">
|
||||
|
||||
<md-checkbox name="canViewObjects"
|
||||
ng-model="acl.selectedUser.rights.canViewObjects"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:rsrc="OGo:url"
|
||||
xmlns:label="OGo:label">
|
||||
<var:string value="cssContent" const:escapeHTML="NO" />
|
||||
<div class="SOGoHTMLMail-CSS-Delimiter mailer_htmlcontent"
|
||||
><var:string value="flatContentAsString" const:escapeHTML="NO" /></div>
|
||||
<div class="SOGoHTMLMail-CSS-Delimiter mailer_htmlcontent">
|
||||
<var:string value="flatContentAsString" const:escapeHTML="NO" />
|
||||
</div>
|
||||
</container>
|
||||
@@ -250,14 +250,16 @@
|
||||
<label class="pseudo-input-label"><var:string label:value="Attendees"/></label>
|
||||
<!-- md-contact-chips don't support "readonly", so we build them using md-chips
|
||||
in readonly mode and a template similar to the one of md-contact-chips -->
|
||||
<md-chips class="md-contact-chips"
|
||||
ng-model="part.participants"
|
||||
<md-chips class="md-contact-chips sg-readonly"
|
||||
ng-model="::part.participants"
|
||||
readonly="true">
|
||||
<md-chip-template>
|
||||
<div class="md-contact-avatar"><img src="#" ng-src="{{$chip.image}}" alt="{{$chip.name}}"/></div>
|
||||
<div class="md-contact-avatar">
|
||||
<sg-avatar-image sg-email="$chip.email" size="32"><!-- avatar --></sg-avatar-image>
|
||||
</div>
|
||||
<div class="md-contact-name">{{$chip.name}}</div>
|
||||
<md-icon ng-class="'icon-' + $chip.status"><!-- partstat --></md-icon>
|
||||
</md-chip-template>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
</md-list>
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
var:title="filenameForDisplay"
|
||||
class="md-card-image"></img>
|
||||
<md-card-content>
|
||||
<p>
|
||||
<p class="md-caption">
|
||||
<var:string value="filenameForDisplay" /><br/>
|
||||
<var:string value="bodyInfo.size" formatter="sizeFormatter"/>
|
||||
</p>
|
||||
</md-card-content>
|
||||
<div class="md-actions" layout="row" layout-align="end center">
|
||||
<md-button class="sg-icon-button" var:href="pathForDownload">
|
||||
<md-tooltip md-direction="left"><var:string label:value="Save Attachment"/></md-tooltip>
|
||||
<md-icon>save</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
@@ -4,24 +4,16 @@
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label">
|
||||
<md-whiteframe class="md-whiteframe-z1" layout="row" layout-align="space-between center">
|
||||
<div layout="row">
|
||||
<div>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<img var:src="mimeImageURL"/>
|
||||
</div>
|
||||
|
||||
<div layout="column" layout-align="start start" layout-padding="layout-padding">
|
||||
<div>
|
||||
<var:string value="filenameForDisplay"/>
|
||||
</div>
|
||||
<div class="md-caption">
|
||||
<p class="md-caption">
|
||||
<var:string value="filenameForDisplay"/><br/>
|
||||
<var:string value="bodyInfo.size" formatter="sizeFormatter"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="row" layout-align="end end">
|
||||
<md-button class="sg-icon-button" var:href="pathToAttachment">
|
||||
</p>
|
||||
</md-card-content>
|
||||
<div class="md-actions" layout="row" layout-align="end center">
|
||||
<md-button class="sg-icon-button" var:href="pathToAttachment" target="_blank">
|
||||
<md-tooltip md-direction="left"><var:string label:value="View Attachment"/></md-tooltip>
|
||||
<md-icon>open_in_browser</md-icon>
|
||||
</md-button>
|
||||
@@ -50,5 +42,5 @@
|
||||
PartPath: <var:string value="partPath" /><br />
|
||||
-->
|
||||
|
||||
</md-whiteframe>
|
||||
</md-card>
|
||||
</div>
|
||||
@@ -6,180 +6,245 @@
|
||||
xmlns:label="OGo:label">
|
||||
|
||||
<md-dialog flex="80" flex-sm="100">
|
||||
<md-toolbar class="md-padding">
|
||||
<!-- TOOLBAR TO SHOW CC/BCC/ATTACHMENT FIELDS -->
|
||||
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<md-icon class="material-icons sg-icon-toolbar-bg">edit</md-icon>
|
||||
<md-button ng-show="editor.hideCc" ng-click="editor.hideCc = false">Cc</md-button>
|
||||
<md-button ng-show="editor.hideBcc" ng-click="editor.hideBcc = false">Bcc</md-button>
|
||||
|
||||
<md-button>
|
||||
<label for="file-input">
|
||||
Add files
|
||||
</label>
|
||||
<!-- from -->
|
||||
<sg-avatar-image hide-sm="hide-sm"
|
||||
sg-email="editor.message.editable.from"
|
||||
size="32"><!-- avatar --></sg-avatar-image>
|
||||
<md-input-container flex="flex">
|
||||
<label><var:string label:value="From"/></label>
|
||||
<md-select name="from"
|
||||
ng-model="editor.message.editable.from">
|
||||
<md-option ng-value="identity" ng-repeat="identity in editor.identities">{{identity}}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<div flex-gt-sm="flex-gt-sm"><!-- spacer --></div>
|
||||
<md-button class="sg-icon-button" ng-click="editor.send()">
|
||||
<md-icon>send</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button" ng-click="editor.message.$save()">
|
||||
<md-icon>save</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button" ng-click="editor.cancel()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
<input id="file-input" type="file"
|
||||
nv-file-select="nv-file-select"
|
||||
multiple="multiple"
|
||||
uploader="editor.uploader"
|
||||
ng-show="false"/>
|
||||
|
||||
<md-menu>
|
||||
<md-button label:aria-label="More mail options" class="iconBtton" ng-click="$mdOpenMenu($event)">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<md-menu-item>
|
||||
<md-button>
|
||||
<md-checkbox ng-model="editor.message.editable.receipt">
|
||||
<var:string label:value="Receipt"/>
|
||||
</md-checkbox>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<div layout="row" layout-align="center center">
|
||||
<span>
|
||||
<var:string label:value="Priority"/>
|
||||
</span>
|
||||
<md-slider flex="flex"
|
||||
md-discrete="md-discrete"
|
||||
ng-model="editor.message.editable.priority"
|
||||
step="1"
|
||||
min="1"
|
||||
max="5"
|
||||
label:aria-label="Priority">
|
||||
</md-slider>
|
||||
</div>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<form>
|
||||
<header>
|
||||
<header>
|
||||
<div class="msg-header-content">
|
||||
|
||||
<!-- FROM / TO / CC/ BCC / SUBJECT / ATTACHMENTS -->
|
||||
<div class="msg-header-content">
|
||||
<md-input-container>
|
||||
<label> <var:string label:value="From"/></label>
|
||||
<md-select name="from"
|
||||
ng-model="editor.message.editable.from">
|
||||
<md-option ng-value="identity" ng-repeat="identity in editor.identities">{{identity}}</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
|
||||
<div class="pseudo-input-container">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="To"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.to">
|
||||
<md-autocomplete
|
||||
md-selected-item="editor.autocomplete.to.selected"
|
||||
md-search-text="editor.autocomplete.to.searchText"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.to.searchText)"
|
||||
label:placeholder="Add a recipient">
|
||||
<span md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user}}</span>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-container" ng-hide="editor.hideCc">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Cc"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.cc">
|
||||
<md-autocomplete
|
||||
md-selected-item="editor.autocomplete.cc.selected"
|
||||
md-search-text="editor.autocomplete.cc.searchText"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.cc.searchText)"
|
||||
label:placeholder="Add a recipient">
|
||||
<span md-highlight-text="editor.autocomplete.cc.searchText"
|
||||
md-highlight-flags="^i">{{user}}</span>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<div class="pseudo-input-container" ng-hide="editor.hideBcc">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Bcc"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.bcc">
|
||||
<md-autocomplete
|
||||
md-selected-item="editor.autocomplete.bcc.selected"
|
||||
md-search-text="editor.autocomplete.bcc.searchText"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.bcc.searchText)"
|
||||
label:placeholder="Add a recipient">
|
||||
<span md-highlight-text="editor.autocomplete.bcc.searchText"
|
||||
md-highlight-flags="^i">{{user}}</span>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Subject"/>
|
||||
</label>
|
||||
<input type="text" name="subject" ng-model="editor.message.editable.subject"/>
|
||||
</md-input-container>
|
||||
|
||||
<md-list>
|
||||
<!-- FILES ALREADY UPLOADED, FOR EXAMPLE WHEN WE FORWARD A
|
||||
MAIL WITH ATTACHMENTS -->
|
||||
<md-list-item ng-repeat="item in editor.message.editable.attachmentAttrs">
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<span ng-bind="item.filename"><!-- filename --></span>
|
||||
<md-button class="sg-icon-button"
|
||||
ng-click="editor.message.$deleteAttachment(item.filename);">
|
||||
<md-icon>remove_circle_outline</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-list-item>
|
||||
<!-- FILE BEING ATTACHED TO A MAIL -->
|
||||
<md-list-item ng-repeat="item in editor.uploader.queue">
|
||||
<div layout="row" layout-align="space-between center">
|
||||
<span ng-bind="item.file.name"><!-- filename --></span>
|
||||
<md-progress-linear md-mode="determinate"
|
||||
value="{{item.progress}}"
|
||||
ng-show="item.isUploading" ><!-- progress--></md-progress-linear>
|
||||
<md-button class="sg-icon-button"
|
||||
ng-click="editor.uploader.cancelItem(item)"
|
||||
ng-show="item.isUploading">
|
||||
<md-icon>cancel</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button"
|
||||
ng-click="editor.message.$deleteAttachment(item.file.name); item.remove();"
|
||||
ng-show="!item.isUploading">
|
||||
<md-icon>remove_circle_outline</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<!-- to -->
|
||||
<div class="pseudo-input-container">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="To"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.to"
|
||||
md-on-append="editor.addRecipient($chip)">
|
||||
<md-autocomplete
|
||||
class="sg-chips-autocomplete"
|
||||
md-autofocus="true"
|
||||
md-search-text="editor.autocomplete.to.searchText"
|
||||
md-selected-item="editor.autocomplete.to.selected"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.to.searchText)"
|
||||
md-min-length="3"
|
||||
md-delay="300"
|
||||
md-no-cache="true"
|
||||
label:placeholder="Add a recipient">
|
||||
<md-item-template>
|
||||
<span class="md-contact-suggestion">
|
||||
<span class="md-contact-name"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$fullname}}</span>
|
||||
<span class="md-contact-email"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$email}}</span>
|
||||
</span>
|
||||
</md-item-template>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- MESSAGE CONTENT -->
|
||||
<textarea name="content" var:class="editorClass"
|
||||
ng-model="editor.message.editable.text"/>
|
||||
|
||||
<!-- TOOLBAR BUTTONS -->
|
||||
<div class="buttonsToolbar">
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="editor.cancel()"><var:string label:value="Cancel"/></md-button>
|
||||
<md-button ng-click="editor.message.$save()"><var:string label:value="Save"/></md-button>
|
||||
<md-button class="fg-sogoBlue-700 md-primary md-hue-3"
|
||||
ng-click="editor.send()"><var:string label:value="Send"/></md-button>
|
||||
<!-- cc -->
|
||||
<div class="pseudo-input-container" ng-hide="editor.hideCc">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Cc"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.cc"
|
||||
md-on-append="editor.addRecipient($chip)">
|
||||
<md-autocomplete
|
||||
class="sg-chips-autocomplete"
|
||||
md-search-text="editor.autocomplete.cc.searchText"
|
||||
md-selected-item="editor.autocomplete.cc.selected"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.cc.searchText)"
|
||||
md-min-length="3"
|
||||
md-delay="300"
|
||||
md-no-cache="true"
|
||||
label:placeholder="Add a recipient">
|
||||
<md-item-template>
|
||||
<span class="md-contact-suggestion">
|
||||
<span class="md-contact-name"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$fullname}}</span>
|
||||
<span class="md-contact-email"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$email}}</span>
|
||||
</span>
|
||||
</md-item-template>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<!-- bcc -->
|
||||
<div class="pseudo-input-container" ng-hide="editor.hideBcc">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="Bcc"/>
|
||||
</label>
|
||||
<md-chips ng-model="editor.message.editable.bcc"
|
||||
md-on-append="editor.addRecipient($chip)">
|
||||
<md-autocomplete
|
||||
class="sg-chips-autocomplete"
|
||||
md-selected-item="editor.autocomplete.bcc.selected"
|
||||
md-search-text="editor.autocomplete.bcc.searchText"
|
||||
md-items="user in editor.contactFilter(editor.autocomplete.bcc.searchText)"
|
||||
md-min-length="3"
|
||||
md-delay="300"
|
||||
md-no-cache="true"
|
||||
label:placeholder="Add a recipient">
|
||||
<md-item-template>
|
||||
<span class="md-contact-suggestion">
|
||||
<span class="md-contact-name"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$fullname}}</span>
|
||||
<span class="md-contact-email"
|
||||
md-highlight-text="editor.autocomplete.to.searchText"
|
||||
md-highlight-flags="^i">{{user.$$email}}</span>
|
||||
</span>
|
||||
</md-item-template>
|
||||
</md-autocomplete>
|
||||
<md-chip-template>{{$chip}}</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<!-- subject -->
|
||||
<md-input-container>
|
||||
<label>
|
||||
<var:string label:value="Subject"/>
|
||||
</label>
|
||||
<input type="text" name="subject" ng-model="editor.message.editable.subject"/>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- toolbar to show cc/bcc/attachment fields -->
|
||||
<div layout="column" layout-align="end end">
|
||||
<md-button class="sg-icon-button " ng-show="editor.hideCc" ng-click="editor.hideCc = false">Cc</md-button>
|
||||
<md-button class="sg-icon-button " ng-show="editor.hideBcc" ng-click="editor.hideBcc = false">Bcc</md-button>
|
||||
<md-menu>
|
||||
<md-button label:aria-label="More mail options" class="sg-icon-button" ng-click="$mdOpenMenu($event)">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<md-menu-item>
|
||||
<md-button ng-href="#">
|
||||
<md-checkbox ng-model="editor.message.editable.receipt">
|
||||
<var:string label:value="Return Receipt"/>
|
||||
</md-checkbox>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button class="md-primary" ng-disabled="true"><var:string label:value="Priority"/></md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="editor.message.editable.priority = 1">
|
||||
<md-icon ng-class="{ 'icon-check': editor.message.editable.priority == 1}"><!-- highest --></md-icon>
|
||||
<var:string label:value="highest"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="editor.message.editable.priority = 2">
|
||||
<md-icon ng-class="{ 'icon-check': editor.message.editable.priority == 2}"><!-- high --></md-icon>
|
||||
<var:string label:value="high"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="editor.message.editable.priority = 3">
|
||||
<md-icon ng-class="{ 'icon-check':
|
||||
!editor.message.editable.priority
|
||||
|| editor.message.editable.priority == 3}"><!-- normal --></md-icon>
|
||||
<var:string label:value="normal"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="editor.message.editable.priority = 4">
|
||||
<md-icon ng-class="{ 'icon-check': editor.message.editable.priority == 4}"><!-- low --></md-icon>
|
||||
<var:string label:value="low"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="editor.message.editable.priority = 5">
|
||||
<md-icon ng-class="{ 'icon-check': editor.message.editable.priority == 5}"><!-- lowest --></md-icon>
|
||||
<var:string label:value="lowest"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- MESSAGE CONTENT -->
|
||||
<textarea name="content" var:class="editorClass"
|
||||
ck-locale="editor.localeCode"
|
||||
ng-model="editor.message.editable.text"/>
|
||||
</md-dialog-content>
|
||||
|
||||
<!-- TOOLBAR BUTTONS -->
|
||||
<div class="md-actions">
|
||||
<div layout="row" layout-align="space-between center" layout-fill="layout-fill">
|
||||
<div>
|
||||
<!-- md-icon does not play well with Firefox - makes the
|
||||
file input never appear. This should eventually be
|
||||
fixed in Angular Material -->
|
||||
<label class="md-button" for="file-input">
|
||||
<span><var:string label:value="Attach"/></span>
|
||||
</label>
|
||||
<input id="file-input" type="file"
|
||||
nv-file-select="nv-file-select"
|
||||
multiple="multiple"
|
||||
uploader="editor.uploader"
|
||||
ng-show="false"/>
|
||||
</div>
|
||||
<div>
|
||||
<!-- FILES ALREADY UPLOADED, FOR EXAMPLE WHEN WE FORWARD A
|
||||
MAIL WITH ATTACHMENTS -->
|
||||
<md-chips ng-model="editor.message.editable.attachmentAttrs"
|
||||
class="sg-readonly" readonly="true">
|
||||
<md-chip-template>
|
||||
<span>{{$chip.filename}}</span>
|
||||
<md-icon ng-click="editor.message.$deleteAttachment($chip.filename);">close</md-icon>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
<!-- FILE BEING ATTACHED TO A MAIL -->
|
||||
<md-chips ng-model="editor.uploader.queue"
|
||||
class="sg-readonly" readonly="true">
|
||||
<md-chip-template>
|
||||
<span>{{$chip.file.name}}</span>
|
||||
<md-icon ng-show="!$chip.isUploading"
|
||||
ng-click="editor.message.$deleteAttachment($chip.file.name);
|
||||
$chip.remove();">close</md-icon>
|
||||
<md-icon ng-show="$chip.isUploading"
|
||||
ng-click="editor.uploader.cancelItem($chip)"
|
||||
ng-style="{ 'color': '#F00' }">cancel</md-icon>
|
||||
<span ng-show="$chip.isUploading">{{$chip.progress}} %</span>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</md-dialog>
|
||||
</container>
|
||||
@@ -4,48 +4,250 @@
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:label="OGo:label">
|
||||
<div class="view-list" layout="column">
|
||||
|
||||
<!-- in virtual mailbox mode -->
|
||||
<md-toolbar ng-hide="!app.showingAdvancedSearch || mailbox.selectedFolder.$selectedCount() > 0">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-input-container>
|
||||
<label><var:string label:value="Search messages in"/></label>
|
||||
<md-select ng-model="app.search.mailbox">
|
||||
<md-option ng-value="">
|
||||
<span>{{app.accounts[0].name}}</span>
|
||||
</md-option>
|
||||
<md-option ng-repeat="folder in
|
||||
app.accounts[0].$flattenMailboxes()
|
||||
track by folder.path"
|
||||
ng-value="folder.path">
|
||||
<span ng-class="'sg-child-level-' + folder.level">{{folder.name}}</span>
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More search options" ng-click="$mdOpenMenu()">
|
||||
<md-icon>settings</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button ng-href="#">
|
||||
<md-checkbox ng-model="app.search.subfolders"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0">
|
||||
<var:string label:value="Search subfolders"/>
|
||||
</md-checkbox>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.search.match='AND'">
|
||||
<md-icon ng-class="{ 'icon-check': app.search.match == 'AND'}">
|
||||
<!-- all --></md-icon><var:string label:value="Match all of the following"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.search.match='OR'">
|
||||
<md-icon ng-class="{ 'icon-check': app.search.match == 'OR'}">
|
||||
<!-- any --></md-icon><var:string label:value="Match any of the following"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<md-button class="sg-icon-button"
|
||||
ng-click="app.toggleAdvancedSearch()"
|
||||
ng-disabled="app.search.params.length == 0">
|
||||
<md-icon ng-class="{'icon-stop': app.service.selectedFolder.$isLoading,
|
||||
'icon-search': !app.service.selectedFolder.$isLoading}"><!-- icon --></md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<!-- single-selection toolbars -->
|
||||
<md-toolbar ng-hide="app.showingAdvancedSearch || mailbox.selectedFolder.$selectedCount() > 0">
|
||||
<!-- sort mode (default) -->
|
||||
<div class="md-toolbar-tools" ng-hide="mailbox.mode.search">
|
||||
<md-button class="sg-icon-button" label:aria-label="Search"
|
||||
ng-click="mailbox.mode.search = true">
|
||||
<md-icon>search</md-icon>
|
||||
</md-button>
|
||||
<div class="sg-folder-name">{{mailbox.selectedFolder.name}}</div>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Sort"
|
||||
ng-click="$mdOpenMenu()">
|
||||
<md-icon>sort</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.sort('subject')">
|
||||
<md-icon ng-class="{ 'icon-check': mailbox.sortedBy('subject') }">
|
||||
<!-- subject --></md-icon> <var:string label:value="Subject"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.sort('from')">
|
||||
<md-icon ng-class="{ 'icon-check': mailbox.sortedBy('from') }">
|
||||
<!-- from --></md-icon> <var:string label:value="From"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.sort('date')">
|
||||
<md-icon ng-class="{ 'icon-check': mailbox.sortedBy('date') }">
|
||||
<!-- date --></md-icon> <var:string label:value="Date"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.sort('size')">
|
||||
<md-icon ng-class="{ 'icon-check': mailbox.sortedBy('size') }">
|
||||
<!-- size --></md-icon> <var:string label:value="Size"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider> <!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.selectedFolder.$filter()">
|
||||
<md-checkbox
|
||||
ng-model="mailbox.service.$query.asc"
|
||||
ng-true-value="0"
|
||||
ng-false-value="1"><var:string label:value="Descending Order"/></md-checkbox>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<md-button class="sg-icon-button" aria-label="Refresh" ng-click="mailbox.selectedFolder.$filter()" >
|
||||
<md-icon>refresh</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<!-- search mode -->
|
||||
<div class="md-toolbar-tools sg-toolbar-secondary"
|
||||
ng-show="mailbox.mode.search"
|
||||
sg-search="mailbox.selectedFolder.$filter({ sort: 'date', asc: false }, [{ searchBy: searchField, searchInput: searchText }])">
|
||||
<md-button class="sg-icon-button"
|
||||
sg-search-cancel="mailbox.cancelSearch()"
|
||||
label:aria-label="Back">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<md-input-container>
|
||||
<input name="folderSearch" type="search" ng-minlength="3"/>
|
||||
</md-input-container>
|
||||
<md-input-container flex="25">
|
||||
<md-select>
|
||||
<md-option value="subject" selected="selected"><var:string label:value="Subject"/></md-option>
|
||||
<md-option value="from"><var:string label:value="Sender"/></md-option>
|
||||
<md-option value="subject_or_from"><var:string label:value="Subject or Sender"/></md-option>
|
||||
<md-option value="to_or_cc"><var:string label:value="To or Cc"/></md-option>
|
||||
<md-option value="body"><var:string label:value="Entire Message"/></md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<!-- multiple-selection mode -->
|
||||
<md-toolbar class="md-hue-1 sg-toolbar-secondary" ng-show="mailbox.selectedFolder.$selectedCount() > 0">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="sg-icon-button" ng-click="mailbox.unselectMessages()">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</md-button>
|
||||
<label>{{mailbox.service.selectedFolder.$selectedCount()}} <var:string label:value="selected"/></label>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button class="sg-icon-button" ng-click="mailbox.selectAll()">
|
||||
<md-tooltip md-direction="bottom"><var:string label:value="Select All"/></md-tooltip>
|
||||
<md-icon>select_all</md-icon>
|
||||
</md-button>
|
||||
<md-button class="sg-icon-button" ng-click="mailbox.confirmDeleteSelectedMessages()">
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Copy messages" ng-click="$mdOpenMenu()">
|
||||
<md-tooltip md-direction="bottom"><var:string label:value="Copy To"/></md-tooltip>
|
||||
<md-icon>content_copy</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<div ng-repeat="account in mailbox.accounts track by account.id">
|
||||
<md-menu-item>
|
||||
<md-button class="md-primary" ng-disabled="true">{{account.name}}</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item ng-repeat="folder in
|
||||
account.$flattenMailboxes()
|
||||
track by folder.path"
|
||||
ng-hide="mailbox.id == folder.id">
|
||||
<md-button ng-click="mailbox.copySelectedMessages(folder.id)">
|
||||
<span ng-class="'sg-child-level-' + folder.level">{{folder.name}}</span>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</div>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More messages options" ng-click="$mdOpenMenu()">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="2">
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.markSelectedMessagesAsFlagged()">
|
||||
<var:string label:value="Flag"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.markSelectedMessagesAsUnread()">
|
||||
<var:string label:value="Mark as Unread"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.saveSelectedMessages()">
|
||||
<var:string label:value="Save As..."/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-content id="messagesList" layout="column" class="md-flex">
|
||||
<md-progress-linear ng-show="mailbox.selectedFolder.$isLoading" md-mode="indeterminate"><!-- progress --></md-progress-linear>
|
||||
<header class="sg-md-subheader sg-md-subheader--fixed">
|
||||
<h2 class="md-default-theme sg-md-subhead-solo fg-sogoBlue-700">
|
||||
<span ng-show="mailbox.selectedFolder.$messages.length > 0">{{mailbox.selectedFolder.$messages.length}} <var:string label:value="messages"/></span>
|
||||
<span ng-show="mailbox.selectedFolder.$messages.length == 0"><var:string label:value="No message"/></span>
|
||||
</h2>
|
||||
</header>
|
||||
<md-subheader>
|
||||
<span ng-switch="mailbox.service.selectedFolder.getLength()">
|
||||
<span ng-switch-when="0"><var:string label:value="No message"/></span>
|
||||
<span ng-switch-default="true">{{mailbox.service.selectedFolder.getLength()}} <var:string label:value="messages"/></span>
|
||||
</span>
|
||||
</md-subheader>
|
||||
<md-virtual-repeat-container class="md-flex">
|
||||
<md-list>
|
||||
<div md-virtual-repeat="currentMessage in mailbox.selectedFolder.$messages">
|
||||
<md-list-item
|
||||
ng-class="{'sg-active': currentMessage.uid == mailbox.selectedFolder.selectedMessage, unread: !currentMessage.isread}"
|
||||
ng-click="mailbox.selectMessage(currentMessage)"
|
||||
ui-sref="mail.account.mailbox.message({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri), messageId: currentMessage.uid})">
|
||||
<!-- ui-sref-active="sg-active"> -->
|
||||
<div class="sg-selected-avatar"
|
||||
ng-show="currentMessage.selected"
|
||||
ng-click="currentMessage.selected = !currentMessage.selected"><!-- selected avatar --></div>
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
ng-show="!currentMessage.selected"
|
||||
ng-click="currentMessage.selected = !currentMessage.selected"
|
||||
sg-email="currentMessage['from'][0].email"
|
||||
size="48"><!-- avatar --></sg-avatar-image>
|
||||
<div class="sg-tile-content">
|
||||
<span class="msg-date"
|
||||
ng-bind-html="currentMessage.relativedate"><!-- date --></span>
|
||||
<div class="sg-md-subhead-multi">{{currentMessage.$shortAddress('from')}}</div>
|
||||
<div class="sg-md-body-multi">{{currentMessage.subject}}</div>
|
||||
<i class="icon-ion-refresh"
|
||||
ng-hide="mailbox.selectedFolder.$loadMessage(currentMessage.uid)"><!-- loading --></i>
|
||||
<md-list class="sg-section-list">
|
||||
<md-list-item
|
||||
md-virtual-repeat="currentMessage in mailbox.service.selectedFolder"
|
||||
md-on-demand="md-on-demand"
|
||||
ng-class="{'sg-active': currentMessage.uid == mailbox.service.selectedFolder.selectedMessage, unread: !currentMessage.isread}"
|
||||
ng-click="mailbox.selectMessage(currentMessage)"
|
||||
ui-sref="mail.account.mailbox.message({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri), messageId: currentMessage.uid})"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<div class="sg-selected-avatar"
|
||||
ng-show="currentMessage.selected"
|
||||
ng-click="mailbox.toggleMessageSelection($event, currentMessage)"><!-- selected avatar --></div>
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
ng-show="!currentMessage.selected"
|
||||
ng-click="mailbox.toggleMessageSelection($event, currentMessage)"
|
||||
sg-email="::currentMessage.from[0].email"
|
||||
size="40"><!-- avatar --></sg-avatar-image>
|
||||
<div class="sg-tile-content">
|
||||
<span class="msg-date"
|
||||
ng-bind-html="currentMessage.relativedate"><!-- date --></span>
|
||||
<div class="sg-md-subhead-multi">
|
||||
<span class="sg-label-outline"
|
||||
ng-show="mailbox.service.$virtualMode">{{currentMessage.$mailbox.name}}</span>
|
||||
{{currentMessage.$shortAddress('from')}}
|
||||
</div>
|
||||
<div class="sg-tile-icons">
|
||||
<md-icon ng-show="currentMessage.isflagged">star</md-icon>
|
||||
<md-icon ng-show="currentMessage.isanswered">reply</md-icon>
|
||||
<md-icon ng-show="currentMessage.isforwarded">forward</md-icon>
|
||||
<md-icon ng-show="currentMessage.hasattachment">attach_file</md-icon>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</div>
|
||||
<div class="sg-md-body-multi">{{currentMessage.subject}}</div>
|
||||
</div>
|
||||
<div class="sg-tile-icons">
|
||||
<md-icon ng-show="currentMessage.priority == 'highest' || currentMessage.priority == 'high'">warning</md-icon>
|
||||
<md-icon ng-show="currentMessage.isflagged">star</md-icon>
|
||||
<md-icon ng-show="currentMessage.isanswered">reply</md-icon>
|
||||
<md-icon ng-show="currentMessage.isforwarded">forward</md-icon>
|
||||
<md-icon ng-show="currentMessage.hasattachment">attach_file</md-icon>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-virtual-repeat-container>
|
||||
<div class="sg-progress-circular-floating"
|
||||
ng-show="mailbox.service.selectedFolder.$isLoading">
|
||||
<md-progress-circular class="md-accent"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- progress --></md-progress-circular>
|
||||
</div>
|
||||
<md-button class="md-fab md-fab-bottom-right md-accent"
|
||||
label:aria-label="Write a new message"
|
||||
ng-click="mailbox.newMessage($event)">
|
||||
@@ -54,7 +256,15 @@
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
<div id="detailView" class="view-detail ng-cloak" layout="column" ui-view="message"
|
||||
ng-show="mailbox.selectedFolder.selectedMessage"><!-- message view --></div>
|
||||
<div id="detailView" class="view-detail" layout="column" layout-align="start center"
|
||||
ng-class="{ 'sg-close': !mailbox.service.selectedFolder.selectedMessage }"
|
||||
ui-view="message">
|
||||
<md-toolbar class="hide-sm"><!-- empty toolbar --></md-toolbar>
|
||||
<md-content class="hide-sm md-flex layout-fill md-hue-1" layout="column">
|
||||
<md-input-container layout-align="center center">
|
||||
<label class="sg-md-title"><var:string label:value="No message selected"/></label>
|
||||
</md-input-container>
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
</container>
|
||||
Loaded 100 of 280 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user