mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-09 21:45:26 +00:00
feat(mail): Improve mail search (advanced search)
This commit is contained in:
@@ -143,6 +143,23 @@
|
||||
"This mail is being sent from an unsecure network!" = "This mail is being sent from an unsecure network!";
|
||||
"Address Book" = "Address Book";
|
||||
"Search For" = "Search For";
|
||||
"Contains" = "Contains";
|
||||
"Does not contains" = "Does not contains";
|
||||
"Advanced search" = "Advanced search";
|
||||
"Anytime" = "Anytime";
|
||||
"Last 7 days" = "Last 7 days";
|
||||
"Last 30 days" = "Last 30 days";
|
||||
"Last 6 month" = "Last 6 month";
|
||||
"Before" = "Before";
|
||||
"After" = "After";
|
||||
"Between" = "Between";
|
||||
"and" = "and";
|
||||
"With attachments" = "With attachments";
|
||||
"In favorites" = "In favorites";
|
||||
"Unseen only" = "Unseen only";
|
||||
"Show more" = "Show more";
|
||||
"Reset" = "Reset";
|
||||
"Message size" = "Message size";
|
||||
|
||||
/* Popup "show" */
|
||||
"all" = "all";
|
||||
|
||||
@@ -143,6 +143,23 @@
|
||||
"This mail is being sent from an unsecure network!" = "Ce mail est envoyé depuis un réseau non sécurisé !";
|
||||
"Address Book" = "Carnet d'adresses";
|
||||
"Search For" = "Rechercher";
|
||||
"Contains" = "Contient";
|
||||
"Does not contains" = "Ne contient pas";
|
||||
"Advanced search" = "Recherche avancée";
|
||||
"Anytime" = "N'importe quand";
|
||||
"Last 7 days" = "Les 7 derniers jours";
|
||||
"Last 30 days" = "Les 30 derniers jours";
|
||||
"Last 6 month" = "Les 6 derniers mois";
|
||||
"Before" = "Avant";
|
||||
"After" = "Après";
|
||||
"Between" = "Entre";
|
||||
"and" = "et";
|
||||
"With attachments" = "Avec des pièces jointes";
|
||||
"In favorites" = "En favori";
|
||||
"Unseen only" = "Non lu uniquement";
|
||||
"Show more" = "Plus de paramètres";
|
||||
"Reset" = "Réinitialiser";
|
||||
"Message size" = "Taille du message";
|
||||
|
||||
/* Popup "show" */
|
||||
"all" = "Tous";
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSUserDefaults.h> /* for locale string constants */
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSNumberFormatter.h>
|
||||
|
||||
#import <NGObjWeb/WOApplication.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
@@ -340,7 +341,6 @@
|
||||
&& [[currentPart objectForKey:@"disposition"] objectForKey:@"type"]
|
||||
&& [[[[currentPart objectForKey:@"disposition"] objectForKey:@"type"] uppercaseString] isEqualToString:@"INLINE"];
|
||||
isImage = [SOGoMailBodyPart bodyPartClassForMimeType: [contentType lowercaseString] inContext: [self context]] == [SOGoImageMailBodyPart class];
|
||||
id foo = [SOGoMailBodyPart bodyPartClassForMimeType: [contentType lowercaseString] inContext: [self context]];
|
||||
|
||||
|
||||
if (![ud hideInlineAttachments] || ([ud hideInlineAttachments] && !(isInline && isImage))) {
|
||||
@@ -471,17 +471,19 @@
|
||||
EOQualifier *qualifier, *notDeleted, *searchQualifier;
|
||||
WORequest *request;
|
||||
NSDictionary *sortingAttributes, *content, *filter;
|
||||
NSArray *filters, *labels;
|
||||
NSString *searchBy, *searchInput, *searchString, *match, *label;
|
||||
NSArray *filters, *labels, *searchInputSplit, *flags;
|
||||
NSString *searchBy, *searchInput, *searchString, *match, *label, *operator, *sizeUnit, *dateFrom, *dateTo;
|
||||
NSMutableArray *qualifiers, *searchArray, *labelQualifiers;
|
||||
NSNumberFormatter *formatter;
|
||||
NSNumber *size;
|
||||
BOOL unseenOnly, flaggedOnly;
|
||||
int max, i;
|
||||
int max, i, j;
|
||||
|
||||
request = [context request];
|
||||
content = [[request contentAsString] objectFromJSONString];
|
||||
notDeleted = [EOQualifier qualifierWithQualifierFormat: @"(not (flags = %@))", @"deleted"];
|
||||
qualifiers = [NSMutableArray arrayWithObject: notDeleted];
|
||||
searchString = nil;
|
||||
searchString = @"";
|
||||
match = nil;
|
||||
filters = [content objectForKey: @"filters"];
|
||||
labels = [content objectForKey: @"labels"];
|
||||
@@ -494,35 +496,145 @@
|
||||
if (max > 0) {
|
||||
searchArray = [NSMutableArray arrayWithCapacity: max];
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
filter = [filters objectAtIndex:i];
|
||||
searchBy = [filter objectForKey: @"searchBy"];
|
||||
searchInput = [filter objectForKey: @"searchInput"];
|
||||
if (searchBy && searchInput)
|
||||
{
|
||||
if ([[filter objectForKey: @"negative"] boolValue])
|
||||
searchString = [NSString stringWithFormat: @"(not (%@ doesContain: '%@'))", searchBy, searchInput];
|
||||
else
|
||||
searchString = [NSString stringWithFormat: @"(%@ doesContain: '%@')", searchBy, searchInput];
|
||||
{
|
||||
filter = [filters objectAtIndex:i];
|
||||
searchBy = [filter objectForKey: @"searchBy"];
|
||||
searchInput = [filter objectForKey: @"searchInput"];
|
||||
|
||||
if (searchBy && searchInput)
|
||||
{
|
||||
// Size
|
||||
if ([searchBy isEqualToString: @"size"]) {
|
||||
operator = [filter objectForKey: @"operator"];
|
||||
sizeUnit = [filter objectForKey: @"sizeUnit"];
|
||||
formatter = [[NSNumberFormatter alloc] init];
|
||||
formatter.numberStyle = NSNumberFormatterDecimalStyle;
|
||||
size = [formatter numberFromString: searchInput];
|
||||
[formatter release];
|
||||
if ([[sizeUnit lowercaseString] isEqualToString: @"kb"]) {
|
||||
size = [NSNumber numberWithLongLong: [size longLongValue] * 1024];
|
||||
} else if ([[sizeUnit lowercaseString] isEqualToString: @"mb"]) {
|
||||
size = [NSNumber numberWithLongLong: [size longLongValue] * 1024 * 1024];
|
||||
} else if ([[sizeUnit lowercaseString] isEqualToString: @"gb"]) {
|
||||
size = [NSNumber numberWithLongLong: [size longLongValue] * 1024 * 1024 * 1024];
|
||||
}
|
||||
|
||||
searchString = [NSString stringWithFormat: @"(%@ %@ %@)", searchBy, operator, [size stringValue]];
|
||||
} else if ([searchBy isEqualToString: @"date"]) {
|
||||
// Date
|
||||
operator = [filter objectForKey: @"operator"];
|
||||
searchString = [NSString stringWithFormat: @"(%@ %@ (NSCalendarDate)\"%@\")", searchBy, operator, searchInput];
|
||||
} else if ([searchBy isEqualToString: @"date_between"]) {
|
||||
// Date between
|
||||
dateFrom = [filter objectForKey: @"dateFrom"];
|
||||
dateTo = [filter objectForKey: @"dateTo"];
|
||||
searchString = [NSString stringWithFormat: @"(date >= (NSCalendarDate)\"%@\" AND date <= (NSCalendarDate)\"%@\")", dateFrom, dateTo];
|
||||
} else if ([searchBy isEqualToString: @"attachment"]) {
|
||||
// Attachment
|
||||
// Not possible with imap search, check in getUIDsInFolder method
|
||||
// The attachments must be checked in headers
|
||||
} else if ([searchBy isEqualToString: @"favorite"]) {
|
||||
// Favorite
|
||||
flaggedOnly = YES;
|
||||
} else if ([searchBy isEqualToString: @"unseen"]) {
|
||||
// Unseen
|
||||
unseenOnly = YES;
|
||||
} else if ([searchBy isEqualToString: @"contains"]) {
|
||||
// Contains
|
||||
// Split on space to check each word
|
||||
searchInput = [searchInput stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
||||
searchInputSplit = [searchInput componentsSeparatedByString:@" "];
|
||||
if ([searchInputSplit count] > 1) {
|
||||
searchString = @"(";
|
||||
j = 0;
|
||||
for (searchInput in searchInputSplit) {
|
||||
if (j > 0) {
|
||||
searchString = [NSString stringWithFormat: @"%@ OR", searchString];
|
||||
}
|
||||
|
||||
searchString = [NSString stringWithFormat: @"%@ (subject doesContain: '%@' OR body doesContain: '%@')",
|
||||
searchString, searchInput, searchInput];
|
||||
j++;
|
||||
}
|
||||
searchString = [NSString stringWithFormat: @"%@)", searchString];
|
||||
} else {
|
||||
searchString = [NSString stringWithFormat: @"(subject doesContain: '%@' OR body doesContain: '%@')",
|
||||
searchInput, searchInput];
|
||||
}
|
||||
|
||||
} else if ([searchBy isEqualToString: @"not_contains"]) {
|
||||
// Not contains
|
||||
searchInput = [searchInput stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
||||
searchString = [NSString stringWithFormat: @"(NOT (subject doesContain: '%@') AND NOT (body doesContain: '%@'))",
|
||||
searchInput, searchInput];
|
||||
|
||||
// Split on space to check each word
|
||||
searchInput = [searchInput stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
||||
searchInputSplit = [searchInput componentsSeparatedByString:@" "];
|
||||
if ([searchInputSplit count] > 1) {
|
||||
searchString = @"(";
|
||||
j = 0;
|
||||
for (searchInput in searchInputSplit) {
|
||||
if (j > 0) {
|
||||
searchString = [NSString stringWithFormat: @"%@ AND", searchString];
|
||||
}
|
||||
|
||||
searchString = [NSString stringWithFormat: @"%@ (NOT (subject doesContain: '%@') AND NOT (body doesContain: '%@'))",
|
||||
searchString, searchInput, searchInput];
|
||||
j++;
|
||||
}
|
||||
searchString = [NSString stringWithFormat: @"%@)", searchString];
|
||||
} else {
|
||||
searchString = [NSString stringWithFormat: @"(NOT (subject doesContain: '%@') AND NOT (body doesContain: '%@'))",
|
||||
searchInput, searchInput];
|
||||
}
|
||||
} else if ([searchBy isEqualToString: @"flags"]) {
|
||||
// Flags
|
||||
flags = [filter objectForKey: @"flags"];
|
||||
if (flags && [flags count] > 0) {
|
||||
searchString = @"(";
|
||||
|
||||
for (j = 0 ; j < [flags count] ; j++) {
|
||||
if (j > 0)
|
||||
searchString = [NSString stringWithFormat: @"%@ AND", searchString];
|
||||
searchString = [NSString stringWithFormat: @"%@ (flags = '%@')",
|
||||
searchString,
|
||||
[[flags objectAtIndex: j] stringByReplacingOccurrencesOfString: @"_$" withString:@"$"]];
|
||||
}
|
||||
searchString = [NSString stringWithFormat: @"%@)", searchString];
|
||||
}
|
||||
} else {
|
||||
// Others
|
||||
searchString = [NSString stringWithFormat: @"(%@ doesContain: '%@')", searchBy, searchInput];
|
||||
}
|
||||
|
||||
if ([[filter objectForKey: @"negative"] boolValue])
|
||||
searchString = [NSString stringWithFormat: @"(not %@)", searchString];
|
||||
|
||||
|
||||
if (searchString && [searchString length] > 0) {
|
||||
searchQualifier = [EOQualifier qualifierWithQualifierFormat: searchString];
|
||||
if (searchQualifier)
|
||||
[searchArray addObject: searchQualifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Missing parameters in search filter: %@", filter];
|
||||
}
|
||||
}
|
||||
sortingAttributes = [content objectForKey: @"sortingAttributes"];
|
||||
if (sortingAttributes)
|
||||
match = [sortingAttributes objectForKey: @"match"]; // AND, OR
|
||||
if ([match isEqualToString: @"OR"])
|
||||
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: searchArray];
|
||||
else
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifierArray: searchArray];
|
||||
[qualifier autorelease];
|
||||
[qualifiers addObject: qualifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Missing parameters in search filter: %@", filter];
|
||||
}
|
||||
}
|
||||
|
||||
if ([searchArray count] > 0) {
|
||||
sortingAttributes = [content objectForKey: @"sortingAttributes"];
|
||||
if (sortingAttributes)
|
||||
match = [sortingAttributes objectForKey: @"match"]; // AND, OR
|
||||
if ([match isEqualToString: @"OR"])
|
||||
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: searchArray];
|
||||
else
|
||||
qualifier = [[EOAndQualifier alloc] initWithQualifierArray: searchArray];
|
||||
[qualifier autorelease];
|
||||
[qualifiers addObject: qualifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,14 +831,25 @@
|
||||
|
||||
- (NSDictionary *) getUIDsInFolder: (SOGoMailFolder *) folder
|
||||
withHeaders: (BOOL) includeHeaders
|
||||
{
|
||||
return [self getUIDsInFolder: folder
|
||||
withHeaders: includeHeaders
|
||||
onlyAttachments: NO];
|
||||
}
|
||||
|
||||
- (NSDictionary *) getUIDsInFolder: (SOGoMailFolder *) folder
|
||||
withHeaders: (BOOL) includeHeaders
|
||||
onlyAttachments: (BOOL) onlyAttachments
|
||||
{
|
||||
NSArray *uids, *threadedUids, *headers;
|
||||
NSMutableDictionary *data;
|
||||
NSMutableArray *tmpHeaders, *tmpUids;
|
||||
NSNumber *uid;
|
||||
SOGoMailAccount *account;
|
||||
id quota;
|
||||
|
||||
NSRange r;
|
||||
int count;
|
||||
int count, i, j;
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
|
||||
@@ -762,11 +885,35 @@
|
||||
|
||||
a = [uids flattenedArray];
|
||||
count = [a count];
|
||||
if (count > headersPrefetchMaxSize)
|
||||
if (count > headersPrefetchMaxSize && !onlyAttachments) // Only attachment to get all messages
|
||||
count = headersPrefetchMaxSize;
|
||||
r = NSMakeRange(0, count);
|
||||
headers = [self getHeadersForUIDs: [a subarrayWithRange: r]
|
||||
inFolder: folder];
|
||||
|
||||
// This part is used to filter attachements when searching
|
||||
// There is no way to filter only attachments when using imap SEARCH
|
||||
if (onlyAttachments) {
|
||||
tmpHeaders = [NSMutableArray arrayWithArray: headers];
|
||||
tmpUids = [NSMutableArray arrayWithArray: uids];
|
||||
|
||||
for (i = ([tmpHeaders count] - 1); i > 0; i--) {
|
||||
// Search for no attachment
|
||||
if (0 == [[[tmpHeaders objectAtIndex: i] objectAtIndex: 1] intValue]) {
|
||||
uid = [[tmpHeaders objectAtIndex: i] objectAtIndex: 10]; // Uid
|
||||
[tmpHeaders removeObjectAtIndex: i];
|
||||
|
||||
for (j = ([tmpUids count] - 1); j >= 0; j--) {
|
||||
if ([uid isEqual: [tmpUids objectAtIndex: j]]) {
|
||||
[tmpUids removeObjectAtIndex: j]; // -1 for header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
headers = [tmpHeaders copy];
|
||||
uids = [tmpUids copy];
|
||||
}
|
||||
|
||||
[data setObject: headers forKey: @"headers"];
|
||||
}
|
||||
|
||||
@@ -779,6 +926,7 @@
|
||||
else
|
||||
sortByThread = NO;
|
||||
}
|
||||
|
||||
if (uids != nil)
|
||||
[data setObject: uids forKey: @"uids"];
|
||||
[data setObject: [NSNumber numberWithBool: sortByThread] forKey: @"threaded"];
|
||||
@@ -846,8 +994,8 @@
|
||||
*/
|
||||
- (id <WOActionResults>) getUIDsAction
|
||||
{
|
||||
BOOL noHeaders;
|
||||
NSDictionary *data, *requestContent;
|
||||
BOOL noHeaders, onlyAttachments;
|
||||
NSDictionary *data, *requestContent, *filter;
|
||||
SOGoMailFolder *folder;
|
||||
WORequest *request;
|
||||
WOResponse *response;
|
||||
@@ -858,8 +1006,26 @@
|
||||
folder = [self clientObject];
|
||||
|
||||
noHeaders = [[[requestContent objectForKey: @"sortingAttributes"] objectForKey: @"noHeaders"] boolValue];
|
||||
|
||||
if ([[folder nameInContainer] isEqualToString: @"folderOther_SP_Users"]) {
|
||||
// When the folder is folderOther_SP_Users (shared main folder), return no mailboxes
|
||||
return response = [self responseWithStatus: 200
|
||||
andJSONRepresentation: [NSDictionary dictionaryWithObject: [NSArray array] forKey:@"mailboxes"]];
|
||||
}
|
||||
|
||||
onlyAttachments = NO;
|
||||
if (requestContent
|
||||
&& [requestContent objectForKey: @"filters"]
|
||||
&& [[requestContent objectForKey: @"filters"] count] > 0) {
|
||||
for (filter in [requestContent objectForKey: @"filters"]) {
|
||||
if ([filter objectForKey: @"searchBy"]
|
||||
&& [[filter objectForKey: @"searchBy"] isEqualToString: @"attachment"])
|
||||
onlyAttachments = YES;
|
||||
}
|
||||
}
|
||||
data = [self getUIDsInFolder: folder
|
||||
withHeaders: !noHeaders];
|
||||
withHeaders: !noHeaders
|
||||
onlyAttachments: onlyAttachments];
|
||||
|
||||
if (data != nil)
|
||||
response = [self responseWithStatus: 200 andJSONRepresentation: data];
|
||||
@@ -1060,13 +1226,14 @@
|
||||
|
||||
// UID
|
||||
[msg addObject: [message objectForKey: @"uid"]];
|
||||
[headers addObject: msg];
|
||||
|
||||
// isAnswered
|
||||
[msg addObject: [NSNumber numberWithBool: [self isMessageAnswered]]];
|
||||
|
||||
// isForwarded
|
||||
[msg addObject: [NSNumber numberWithBool: [self isMessageForwarded]]];
|
||||
|
||||
[headers addObject: msg];
|
||||
|
||||
[self setMessage: [msgsList nextObject]];
|
||||
|
||||
|
||||
@@ -3,68 +3,34 @@
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:label="OGo:label">
|
||||
|
||||
<div class="view-list hide-print" layout="column" ng-class="{'view-list--close': mailbox.centerIsClose(centerIsClose)}">
|
||||
|
||||
<!-- in virtual mailbox mode -->
|
||||
<md-toolbar class="md-whiteframe-z1 md-hue-3"
|
||||
ng-hide="mailbox.service.$virtualPath === false || mailbox.mode.multiple">
|
||||
<div class="md-toolbar-tools"
|
||||
layout="row" layout-align="start start">
|
||||
<div class="pseudo-input-container pseudo-input-container--compact md-flex sg-no-wrap">
|
||||
<label class="pseudo-input-label pseudo-input-label">
|
||||
<var:string label:value="Search messages in"/>
|
||||
</label>
|
||||
<md-select class="pseudo-input-field" label:aria-label="Search messages in" ng-model="mailbox.service.$virtualPath">
|
||||
<md-option ng-value="''">
|
||||
<span ng-bind="app.accounts[0].name"><!-- main account 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"
|
||||
ng-bind="folder.$displayName"><!-- mailbox name --></span>
|
||||
</md-option>
|
||||
</md-select>
|
||||
</div>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More search options" ng-click="$mdMenu.open()">
|
||||
<md-icon>settings</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<sg-checkmark ng-model="app.search.subfolders"
|
||||
sg-true-value="1"
|
||||
sg-false-value="0">
|
||||
<var:string label:value="Search subfolders"/>
|
||||
</sg-checkmark>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.search.match='AND'">
|
||||
<md-icon label:aria-label="Match all of the following">{{ app.search.match == 'AND' ? 'check' : null }}</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 label:aria-label="Match any of the following">{{ app.search.match == 'OR' ? 'check' : null }}</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"
|
||||
label:aria-label="Search"
|
||||
ng-click="app.toggleAdvancedSearch()"
|
||||
ng-disabled="app.search.params.length == 0">
|
||||
<md-icon>{{ app.service.selectedFolder.$isLoading ? 'stop' : 'search' }}</md-icon>
|
||||
</md-button>
|
||||
<!-- in virtual mailbox mode -->
|
||||
<md-toolbar class="md-whiteframe-z1 md-hue-2 _md _md-toolbar-transitions advanced-search-clickable-toolbar"
|
||||
ng-hide="app.service.$virtualPath === false || mailbox.mode.multiple"
|
||||
ng-click="app.showAdvancedSearch()">
|
||||
<md-icon class="material-icons sg-icon-toolbar-bg">search</md-icon>
|
||||
<div class="sg-toolbar-auto">
|
||||
<form class="md-toolbar-tools sg-toolbar-secondary" layout="row">
|
||||
<div ng-click="app.hideAdvancedSearch($event)">
|
||||
<a class="sg-icon-button" ng-click="app.hideAdvancedSearch($event)" aria-label="Back">
|
||||
<md-icon>arrow_back</md-icon>
|
||||
</a>
|
||||
</div>
|
||||
<md-input-container class="md-flex" md-no-float="md-no-float">
|
||||
<var:string label:value="Advanced search"/>
|
||||
</md-input-container>
|
||||
|
||||
<a class="sg-icon-button show-advanced-search" ng-click="app.showAdvancedSearch()" aria-label="Advanced search">
|
||||
<md-icon>more_horiz</md-icon>
|
||||
<md-tooltip><var:string label:value="Advanced search"/></md-tooltip>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<!-- single-selection toolbars -->
|
||||
<md-toolbar class="md-accent md-hue-1"
|
||||
ng-hide="mailbox.service.$virtualPath !== false || mailbox.mode.multiple">
|
||||
ng-hide="mailbox.service.$virtualPath !== false || mailbox.mode.multiple">
|
||||
<!-- sort mode (default) -->
|
||||
<div class="md-toolbar-tools" ng-hide="mailbox.mode.search">
|
||||
<md-button class="sg-icon-button" label:aria-label="Search"
|
||||
@@ -184,6 +150,10 @@
|
||||
<md-option value="body"><var:string label:value="Entire Message"/></md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<a class="sg-icon-button show-advanced-search" ng-click="app.showAdvancedSearch()" aria-label="Advanced search">
|
||||
<md-icon>more_horiz</md-icon>
|
||||
<md-tooltip><var:string label:value="Advanced search"/></md-tooltip>
|
||||
</a>
|
||||
</form>
|
||||
</md-toolbar>
|
||||
|
||||
@@ -418,4 +388,248 @@
|
||||
md-colors="::{color: 'default-background-500'}"><var:string label:value="No message selected"/></div>
|
||||
</md-content>
|
||||
</div>
|
||||
<script type="text/ng-template" id="advancedSearch">
|
||||
<md-dialog flex="50" flex-sm="80" flex-xs="100">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<md-icon class="material-icons sg-icon-toolbar-bg">search</md-icon>
|
||||
<div class="pseudo-input-container md-flex">
|
||||
<var:string label:value="Advanced search"/>
|
||||
</div>
|
||||
<md-button class="md-icon-button" ng-click="dialogCtrl.closeDialog()">
|
||||
<md-icon aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-dialog-content class="md-dialog-content">
|
||||
|
||||
<div class="md-toolbar-tools sg-toolbar-auto advanced-search">
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="From"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.from" ng-model="dialogCtrl.mainController.searchForm.from" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)" />
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="To"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.to" ng-model="dialogCtrl.mainController.searchForm.to" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="Contains"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.contains" ng-model="dialogCtrl.mainController.searchForm.contains" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)" />
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="Does not contains"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.doesnotcontains" ng-model="dialogCtrl.mainController.searchForm.doesnotcontains" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="Subject"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.subject" ng-model="dialogCtrl.mainController.searchForm.subject" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="Body"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.body" ng-model="dialogCtrl.mainController.searchForm.body" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<div class="search-inline">
|
||||
<md-select ng-model="dialogCtrl.mainController.searchForm.date" class="search-right-space">
|
||||
<md-option value="anytime">
|
||||
<var:string label:value="Anytime"/>
|
||||
</md-option>
|
||||
<md-option value="last7days">
|
||||
<var:string label:value="Last 7 days"/>
|
||||
</md-option>
|
||||
<md-option value="last30days">
|
||||
<var:string label:value="Last 30 days"/>
|
||||
</md-option>
|
||||
<md-option value="last6month">
|
||||
<var:string label:value="Last 6 month"/>
|
||||
</md-option>
|
||||
<md-option value="before">
|
||||
<var:string label:value="Before"/>
|
||||
</md-option>
|
||||
<md-option value="after">
|
||||
<var:string label:value="After"/>
|
||||
</md-option>
|
||||
<md-option value="between">
|
||||
<var:string label:value="Between"/>
|
||||
</md-option>
|
||||
</md-select>
|
||||
|
||||
<md-datepicker class="md-text search-right-space"
|
||||
ng-model="dialogCtrl.mainController.searchForm.dateStart"
|
||||
md-hide-icons="calendar"
|
||||
ng-change="dialogCtrl.mainController.changeDate()"
|
||||
ng-show="['before', 'after', 'between'].indexOf(dialogCtrl.mainController.searchForm.date) != -1">
|
||||
</md-datepicker>
|
||||
|
||||
<div class="search-right-space search-and" ng-show="['between'].indexOf(dialogCtrl.mainController.searchForm.date) != -1">
|
||||
<var:string label:value="and"/>
|
||||
</div>
|
||||
|
||||
<md-datepicker class="md-text search-right-space "
|
||||
ng-model="dialogCtrl.mainController.searchForm.dateEnd"
|
||||
md-hide-icons="calendar"
|
||||
ng-change="dialogCtrl.mainController.changeDate()"
|
||||
ng-show="['between'].indexOf(dialogCtrl.mainController.searchForm.date) != -1">
|
||||
</md-datepicker>
|
||||
</div>
|
||||
|
||||
<md-input-container class="advanced-search-full">
|
||||
<label><var:string label:value="Bcc"/></label>
|
||||
<input type="text" var:value="dialogCtrl.mainController.searchForm.bcc" ng-model="dialogCtrl.mainController.searchForm.bcc" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<div class="search-inline">
|
||||
<md-input-container class="md-text search-right-space">
|
||||
<label><var:string label:value="Message size"/></label>
|
||||
<input type="number" var:value="dialogCtrl.mainController.searchForm.size" ng-model="dialogCtrl.mainController.searchForm.size" ng-keyup="dialogCtrl.mainController.searchFieldChange($event)"/>
|
||||
</md-input-container>
|
||||
|
||||
<md-select ng-model="dialogCtrl.mainController.searchForm.sizeOperator" class="search-right-space">
|
||||
<md-option value=">">
|
||||
>
|
||||
</md-option>
|
||||
<md-option value="<">
|
||||
<
|
||||
</md-option>
|
||||
</md-select>
|
||||
<md-select ng-model="dialogCtrl.mainController.searchForm.sizeUnit" class="search-right-space">
|
||||
<md-option value="kb">
|
||||
<var:string label:value="KiB"/>
|
||||
</md-option>
|
||||
<md-option value="mb">
|
||||
<var:string label:value="MiB"/>
|
||||
</md-option>
|
||||
<md-option value="gb">
|
||||
<var:string label:value="GiB"/>
|
||||
</md-option>
|
||||
</md-select>
|
||||
</div>
|
||||
|
||||
|
||||
<md-checkbox
|
||||
ng-model="dialogCtrl.mainController.searchForm.attachements"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0"
|
||||
label:aria-label="With attachments">
|
||||
<var:string label:value="With attachments"/>
|
||||
</md-checkbox>
|
||||
|
||||
<md-checkbox
|
||||
ng-model="dialogCtrl.mainController.searchForm.favorite"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0"
|
||||
label:aria-label="In favorites">
|
||||
<var:string label:value="In favorites"/>
|
||||
</md-checkbox>
|
||||
|
||||
<md-checkbox
|
||||
ng-model="dialogCtrl.mainController.searchForm.unseen"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0"
|
||||
label:aria-label="Unseen only">
|
||||
<var:string label:value="Unseen only"/>
|
||||
</md-checkbox>
|
||||
|
||||
<md-chips class="sg-readonly advanced-search-full"
|
||||
sg-focus-on="flags"
|
||||
ng-model="dialogCtrl.mainController.searchForm.flags"
|
||||
md-transform-chip="$chip.name">
|
||||
<md-chip-template>
|
||||
<span class="sg-chip-color">
|
||||
<span ng-style="{ 'background-color': dialogCtrl.message.$tags[$chip][1] }"><!-- color --></span>
|
||||
</span>
|
||||
<span ng-bind="dialogCtrl.message.$tags[$chip][0] || $chip"><!-- tag --></span>
|
||||
</md-chip-template>
|
||||
<md-autocomplete
|
||||
md-search-text="dialogCtrl.mainController.searchForm.tags.searchText"
|
||||
md-items="tag in dialogCtrl.message.filterTags(dialogCtrl.mainController.searchForm.tags.searchText, dialogCtrl.mainController.message.flags)"
|
||||
md-no-cache="true"
|
||||
label:placeholder="Add a tag">
|
||||
<md-item-template>
|
||||
<div layout="row" layout-align="start center">
|
||||
<div class="sg-color-chip"
|
||||
ng-style="{ 'background-color': tag.color }"><!-- color --></div>
|
||||
<div md-highlight-text="dialogCtrl.mainController.searchForm.tags.searchText"
|
||||
md-highlight-flags="^i">{{ tag.description }}</div>
|
||||
</div>
|
||||
</md-item-template>
|
||||
</md-autocomplete>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<div class="search-inline advanced-search-full advanced-search-in">
|
||||
<div class="pseudo-input-container pseudo-input-container--compact md-flex sg-no-wrap">
|
||||
<label class="pseudo-input-label pseudo-input-label">
|
||||
<var:string label:value="Search messages in"/>
|
||||
</label>
|
||||
<md-select class="pseudo-input-field" label:aria-label="Search messages in" ng-model="dialogCtrl.mailbox.$virtualPath">
|
||||
<md-option ng-value="''">
|
||||
<span ng-bind="dialogCtrl.mainController.accounts[0].name"><!-- main account name --></span>
|
||||
</md-option>
|
||||
<md-option ng-repeat="folder in
|
||||
dialogCtrl.mainController.accounts[0].$flattenMailboxes()
|
||||
track by folder.path"
|
||||
ng-value="folder.path">
|
||||
<span ng-class="'sg-child-level-' + folder.level"
|
||||
ng-bind="folder.$displayName"><!-- mailbox name --></span>
|
||||
</md-option>
|
||||
</md-select>
|
||||
</div>
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="More search options" ng-click="$mdMenu.open()">
|
||||
<md-icon>settings</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<sg-checkmark ng-model="dialogCtrl.mainController.search.subfolders"
|
||||
sg-true-value="1"
|
||||
sg-false-value="0">
|
||||
<var:string label:value="Search subfolders"/>
|
||||
</sg-checkmark>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="dialogCtrl.mainController.search.match='AND'">
|
||||
<md-icon label:aria-label="Match all of the following">{{ dialogCtrl.mainController.search.match == 'AND' ? 'check' : null }}</md-icon>
|
||||
<var:string label:value="Match all of the following"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="dialogCtrl.mainController.search.match='OR'">
|
||||
<md-icon label:aria-label="Match any of the following">{{ dialogCtrl.mainController.search.match == 'OR' ? 'check' : null }}</md-icon>
|
||||
<var:string label:value="Match any of the following"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions>
|
||||
<md-button
|
||||
class="md-warn"
|
||||
label:aria-label="Cancel"
|
||||
ng-click="dialogCtrl.closeDialog()">
|
||||
<var:string label:value="Cancel"/>
|
||||
</md-button>
|
||||
<md-button
|
||||
label:aria-label="Reset"
|
||||
ng-click="dialogCtrl.mainController.reset()">
|
||||
<var:string label:value="Reset"/>
|
||||
</md-button>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button
|
||||
label:aria-label="Search"
|
||||
ng-click="dialogCtrl.search()">
|
||||
<var:string label:value="Search"/>
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
</script>
|
||||
</container>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="moduleName"
|
||||
const:jsFiles="vendor/ckeditor/build/ckeditor.js, Common/sgCkeditor.component.js, Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.js, Mailer.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js, vendor/punycode.js">
|
||||
const:jsFiles="vendor/ckeditor/build/ckeditor.js, Common/sgCkeditor.component.js, Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.js, Mailer.services.js, vendor/angular-file-upload.min.js, vendor/FileSaver.min.js, vendor/punycode.js, vendor/mark.min.js">
|
||||
<script type="text/javascript">
|
||||
var mailAccounts = <var:string value="mailAccounts" const:escapeHTML="NO" />;
|
||||
var userNames = <var:string value="userNames" const:escapeHTML="NO" />;
|
||||
@@ -122,60 +122,11 @@
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="UIxMailFolderTemplate">
|
||||
<md-toolbar layout="row" layout-align="space-between center" class="toolbar-main"
|
||||
ng-hide="mailbox.service.$virtualPath !== false">
|
||||
|
||||
<md-toolbar layout="row" layout-align="space-between center" class="toolbar-main">
|
||||
<var:component className="UIxTopnavToolbar"/>
|
||||
</md-toolbar>
|
||||
|
||||
<!-- Advanced search toolbar -->
|
||||
<md-toolbar layout="column" class="toolbar-main md-hue-3"
|
||||
ng-show="mailbox.service.$virtualPath !== false">
|
||||
<div class="md-toolbar-tools">
|
||||
<div layout="column" class="md-flex">
|
||||
<div class="pseudo-input-container pseudo-input-container--compact">
|
||||
<label class="pseudo-input-label"><var:string label:value="Add a Criteria"/></label>
|
||||
</div>
|
||||
<div layout="row">
|
||||
<span class="md-button sg-outline-button" ng-click="app.addSearchParam('subject')">
|
||||
<var:string label:value="Subject"/>
|
||||
</span>
|
||||
<span class="md-button sg-outline-button" ng-click="app.addSearchParam('from')">
|
||||
<var:string label:value="From"/>
|
||||
</span>
|
||||
<span class="md-button sg-outline-button" ng-click="app.addSearchParam('to')">
|
||||
<var:string label:value="To"/>
|
||||
</span>
|
||||
<span class="md-button sg-outline-button" ng-click="app.addSearchParam('cc')">
|
||||
<var:string label:value="Cc"/>
|
||||
</span>
|
||||
<span class="md-button sg-outline-button" ng-click="app.addSearchParam('body')">
|
||||
<var:string label:value="Body"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<md-button type="button" class="sg-icon-button" ng-click="app.hideAdvancedSearch()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="md-toolbar-tools sg-toolbar-auto">
|
||||
<md-chips class="md-flex"
|
||||
ng-show="app.search.params.length != 0 || app.currentSearchParam"
|
||||
ng-model="app.search.params"
|
||||
md-transform-chip="app.newSearchParam($chip)"
|
||||
md-add-on-blur="true">
|
||||
<input type="text" sg-focus-on="advancedSearch"
|
||||
ng-disabled="app.currentSearchParam.length == 0"
|
||||
sg-placeholder="app.search.options[app.currentSearchParam]"/>
|
||||
<md-chip-template>
|
||||
<span class="md-caption" ng-show="$chip.negative == 0"><var:string label:value="match"/></span>
|
||||
<span class="md-caption" ng-show="$chip.negative == 1"><var:string label:value="does not match"/></span>
|
||||
<span class="md-caption" ng-bind="$chip.searchBy.capitalize() | loc"><!-- search by --></span>
|
||||
<span ng-bind="$chip.searchInput"><!-- search input --></span>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<div layout="row" class="md-flex sg-block-print">
|
||||
<var:component className="UIxMailFolderTemplate" />
|
||||
</div>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</md-card-actions>
|
||||
<md-card-content>
|
||||
<div class="sg-padded">
|
||||
<h5 class="sg-md-headline" ng-bind="viewer.message.subject"><!-- subject --></h5>
|
||||
<h5 class="sg-md-headline" ng-bind-html="viewer.message.subject"><!-- subject --></h5>
|
||||
<time class="msg-date" datetime="viewer.message.date" ng-bind="::viewer.message.date"><!-- date --></time>
|
||||
</div>
|
||||
<div layout="row" layout-wrap="layout-wrap">
|
||||
@@ -151,7 +151,7 @@
|
||||
sg-email="::viewer.message.from[0].email"
|
||||
size="40">person</sg-avatar-image>
|
||||
<div>
|
||||
<span ng-bind="::viewer.message.from[0].name"><!-- from --></span>
|
||||
<span ng-bind-html="::viewer.message.from[0].name"><!-- from --></span>
|
||||
<a href="#" class="md-caption"
|
||||
ng-bind="::viewer.message.from[0].email"
|
||||
ng-click="viewer.selectRecipient(viewer.message.from[0], $event)"><!-- from --></a>
|
||||
|
||||
@@ -4,7 +4,7 @@ include ../common.make
|
||||
|
||||
WEBSERVER_RESOURCE_DIRS = css fonts img js
|
||||
JS_FILES = js/Administration.* js/Common.* js/Contacts.* js/Mailer.* js/Main.* js/Preferences.* js/Scheduler.*
|
||||
JS_LIB_FILES = js/vendor/angular-animate.* js/vendor/angular-aria.* js/vendor/angular-cookies.* js/vendor/angular-messages.* js/vendor/angular-file-upload.* js/vendor/FileSaver.* js/vendor/ng-sortable.* js/vendor/angular-material.* js/vendor/angular-sanitize.* js/vendor/angular-ui-router.* js/vendor/angular.* js/vendor/lodash.* js/vendor/qrcode.* js/vendor/punycode.js
|
||||
JS_LIB_FILES = js/vendor/angular-animate.* js/vendor/angular-aria.* js/vendor/angular-cookies.* js/vendor/angular-messages.* js/vendor/angular-file-upload.* js/vendor/FileSaver.* js/vendor/ng-sortable.* js/vendor/angular-material.* js/vendor/angular-sanitize.* js/vendor/angular-ui-router.* js/vendor/angular.* js/vendor/lodash.* js/vendor/qrcode.* js/vendor/punycode.js js/vendor/mark.min.js
|
||||
CSS_FILES = css/styles.css css/styles.css.map css/no-animation.css css/no-animation.css.map
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
@@ -141,7 +141,8 @@ module.exports = function(grunt) {
|
||||
'<%= src %>/ng-sortable/dist/ng-sortable.min.js{,map}',
|
||||
'<%= src %>/lodash/lodash{,.min}.js',
|
||||
'<%= src %>/qrcodejs/qrcode{,.min}.js',
|
||||
'<%= src %>/punycode/punycode.js'
|
||||
'<%= src %>/punycode/punycode.js',
|
||||
'<%= src %>/mark.js/dist/mark.min.js'
|
||||
];
|
||||
for (var j = 0; j < js.length; j++) {
|
||||
var files = grunt.file.expand(grunt.template.process(js[j], {data: options}));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
!function(){"use strict";function e(e,t){if(e.state("mail",{url:"/Mail",views:{mailboxes:{templateUrl:"UIxMailMainFrame",controller:"MailboxesController",controllerAs:"app"}},resolve:{stateAccounts:a}}).state("mail.account",{url:"/:accountId",abstract:!0,views:{mailbox:{template:"<ui-view/>"}},resolve:{stateAccount:o}}).state("mail.account.virtualMailbox",{url:"/virtual",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:l}}).state("mail.account.virtualMailbox.message",{url:"/:mailboxId/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},resolve:{stateMailbox:n,stateMessages:i,stateMessage:c},onEnter:u,onExit:d}).state("mail.account.inbox",{url:"/inbox",onEnter:r}).state("mail.account.mailbox",{url:"/:mailboxId",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:s,stateMessages:i}}).state("mail.account.mailbox.message",{url:"/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},params:{reload:{value:!1}},onEnter:u,onExit:d,resolve:{stateMessage:c}}),t.rules.otherwise("/Mail/0/inbox"),navigator&&navigator.registerProtocolHandler){e=window.location.origin+window.ApplicationBaseURL+"UIxMailPopupView#!/Mail/0/new?%s";try{navigator.registerProtocolHandler("mailto",e,"SOGo")}catch(e){}}}function a(e,t,a){var a=a.$findAll(e.mailAccounts),o=[];return angular.forEach(a,function(t,e){var a=t.$getMailboxes();o.push(0===e?a.then(function(e){return t}):t)}),t.all(o)}function o(t,e){return _.find(e,function(e){return e.id==t.accountId})}function s(e,t,a,o,r){var i,l=o(o(t.mailboxId)),n=function(e){var t=_.find(e,function(e){return e.path==l});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t};return r.selectedFolder&&!r.$virtualMode&&(r.selectedFolder.$isLoading=!0),a.$getMailboxes().then(function(e){if(i=n(e))return i.$topIndex=0,i.selectFolder(),i;$state.go("mail.account",{accountId:s.$account.id})})}function r(t,a,o){return t.injector().getAsync("stateAccount").then(function(e){return 0<e.$mailboxes.length?t.router.stateService.target("mail.account.mailbox",{accountId:e.id,mailboxId:a(e.$mailboxes[0].path)}):(o.selectedFolder=!1,t.router.stateService.target("mail"))})}function i(e,t,a,o){return a.$virtualMode?[]:o?(o.$unselectMessages(),o.$filter().catch(function(){return e.reject("Mailbox not found")})):e.reject("Mailbox doesn't exist")}function l(e,t){return t.$virtualMode?t.selectedFolder:e.reject("No virtual mailbox defined")}function n(e,t,a,o){var r=a(a(o.mailboxId));return t.$virtualMode?(t.selectedFolder.resetSelectedMessage(),_.find(t.selectedFolder.$mailboxes,function(e){return e.path==r})):e.reject("No virtual mailbox defined for message")}function c(e,t,a,o,r,i){var l=_.find(r.$messages,function(e){return e.uid==parseInt(a.messageId)});if(l&&l.$reload)return l.$reload({useCache:!a.reload,nocache:a.reload});o.go("mail.account.mailbox",{accountId:r.$account.id,mailboxId:t(r.path)})}function u(e,t){t.$selectedMessage=parseInt(e.messageId)}function d(e){delete e.$selectedMessage}function t(e,t,a,o,r){e.DebugEnabled||o.defaultErrorHandler(function(){}),t.onError({to:"mail.**"},function(e){"mail"!=e.to().name&&!e.ignored()&&e.error().message.indexOf("superseded")<0&&(a.error("transition error to "+e.to().name+": "+e.error().detail),r.selectedFolder=!1,o.go("mail"))})}angular.module("SOGo.MailerUI",["ngCookies","ui.router","sgCkeditor","angularFileUpload","SOGo.Common","SOGo.ContactsUI","SOGo.SchedulerUI","ngAnimate","SOGo.PreferencesUI"]).config(e).run(t),e.$inject=["$stateProvider","$urlServiceProvider"],a.$inject=["$window","$q","Account"],o.$inject=["$stateParams","stateAccounts"],s.$inject=["$q","$stateParams","stateAccount","decodeUriFilter","Mailbox"],r.$inject=["$transition$","encodeUriFilter","Mailbox"],i.$inject=["$q","$state","Mailbox","stateMailbox"],l.$inject=["$q","Mailbox"],n.$inject=["$q","Mailbox","decodeUriFilter","$stateParams"],c.$inject=["Mailbox","encodeUriFilter","$stateParams","$state","stateMailbox","stateMessages"],u.$inject=["$stateParams","stateMailbox"],d.$inject=["stateMailbox"],t.$inject=["$window","$transitions","$log","$state","Mailbox"]}();
|
||||
!function(){"use strict";function e(e,t){if(e.state("mail",{url:"/Mail",views:{mailboxes:{templateUrl:"UIxMailMainFrame",controller:"MailboxesController",controllerAs:"app"}},resolve:{stateAccounts:a}}).state("mail.account",{url:"/:accountId",abstract:!0,views:{mailbox:{template:"<ui-view/>"}},resolve:{stateAccount:o}}).state("mail.account.virtualMailbox",{url:"/virtual",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:l}}).state("mail.account.virtualMailbox.message",{url:"/:mailboxId/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},resolve:{stateMailbox:n,stateMessages:i,stateMessage:c},onEnter:u,onExit:d}).state("mail.account.inbox",{url:"/inbox",onEnter:r}).state("mail.account.mailbox",{url:"/:mailboxId",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:s,stateMessages:i}}).state("mail.account.mailbox.message",{url:"/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},params:{reload:{value:!1}},onEnter:u,onExit:d,resolve:{stateMessage:c}}),t.rules.otherwise("/Mail/0/inbox"),navigator&&navigator.registerProtocolHandler){e=window.location.origin+window.ApplicationBaseURL+"UIxMailPopupView#!/Mail/0/new?%s";try{navigator.registerProtocolHandler("mailto",e,"SOGo")}catch(e){}}}function a(e,t,a){var a=a.$findAll(e.mailAccounts),o=[];return angular.forEach(a,function(t,e){var a=t.$getMailboxes();o.push(0===e?a.then(function(e){return t}):t)}),t.all(o)}function o(t,e){return _.find(e,function(e){return e.id==t.accountId})}function s(e,t,a,o,r){var i,l=o(o(t.mailboxId)),n=function(e){var t=_.find(e,function(e){return e.path==l});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t};return r.selectedFolder&&!r.$virtualMode&&(r.selectedFolder.$isLoading=!0),a.$getMailboxes().then(function(e){if(i=n(e))return i.$topIndex=0,i.selectFolder(),i;$state.go("mail.account",{accountId:s.$account.id})})}function r(t,a,o){return t.injector().getAsync("stateAccount").then(function(e){return 0<e.$mailboxes.length?t.router.stateService.target("mail.account.mailbox",{accountId:e.id,mailboxId:a(e.$mailboxes[0].path)}):(o.selectedFolder=!1,t.router.stateService.target("mail"))})}function i(e,t,a,o){return a.$virtualMode?[]:o?(o.$unselectMessages(),o.$filter().catch(function(){return e.reject("Mailbox not found")})):e.reject("Mailbox doesn't exist")}function l(e,t){return t.$virtualMode?t.selectedFolder:e.reject("No virtual mailbox defined")}function n(e,t,a,o){var r=a(a(o.mailboxId));return t.$virtualMode?(t.selectedFolder.resetSelectedMessage(),_.find(t.selectedFolder.$mailboxes,function(e){return e.path==r})):e.reject("No virtual mailbox defined for message")}function c(e,t,a,o,r,i){var l=_.find(r.$messages,function(e){return e.uid==parseInt(a.messageId)});if(l&&l.$reload)return l.$reload({useCache:!a.reload,nocache:a.reload});o.go("mail.account.mailbox",{accountId:r.$account.id,mailboxId:t(r.path)})}function u(e,t){t.$selectedMessage=parseInt(e.messageId)}function d(e){delete e.$selectedMessage}function t(e,t,a,o,r){e.DebugEnabled||o.defaultErrorHandler(function(){}),t.onError({to:"mail.**"},function(e){("mail"!=e.to().name&&!e.ignored()&&e.error().message.indexOf("superseded")<0||"mail.account.virtualMailbox"===e.to().name||"mail.account.virtualMailbox.message"===e.to().name)&&(a.error("transition error to "+e.to().name+": "+e.error().detail),r.selectedFolder=!1,o.go("mail"))})}angular.module("SOGo.MailerUI",["ngCookies","ui.router","sgCkeditor","angularFileUpload","SOGo.Common","SOGo.ContactsUI","SOGo.SchedulerUI","ngAnimate","SOGo.PreferencesUI"]).config(e).run(t),e.$inject=["$stateProvider","$urlServiceProvider"],a.$inject=["$window","$q","Account"],o.$inject=["$stateParams","stateAccounts"],s.$inject=["$q","$stateParams","stateAccount","decodeUriFilter","Mailbox"],r.$inject=["$transition$","encodeUriFilter","Mailbox"],i.$inject=["$q","$state","Mailbox","stateMailbox"],l.$inject=["$q","Mailbox"],n.$inject=["$q","Mailbox","decodeUriFilter","$stateParams"],c.$inject=["Mailbox","encodeUriFilter","$stateParams","$state","stateMailbox","stateMessages"],u.$inject=["$stateParams","stateMailbox"],d.$inject=["stateMailbox"],t.$inject=["$window","$transitions","$log","$state","Mailbox"]}();
|
||||
//# sourceMappingURL=Mailer.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -45,6 +45,7 @@
|
||||
$refreshTimeout: null,
|
||||
$virtualMode: false,
|
||||
$virtualPath: false,
|
||||
$searchMode: false,
|
||||
PRELOAD: PRELOAD,
|
||||
BATCH_DELETE_LIMIT: BATCH_DELETE_LIMIT
|
||||
});
|
||||
@@ -169,6 +170,9 @@
|
||||
this.$visibleMessages = this.$messages;
|
||||
this.$selectedMessages = [];
|
||||
}
|
||||
if (angular.isUndefined(this.$highlightWords)) {
|
||||
this.$highlightWords = [];
|
||||
}
|
||||
angular.extend(this, data);
|
||||
if (this.path) {
|
||||
this.id = this.$id();
|
||||
@@ -242,6 +246,16 @@
|
||||
Mailbox.selectedFolder = this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function setSearchMode
|
||||
* @memberof Mailbox.prototype
|
||||
* @desc Set search mode for controller
|
||||
* @param {array} searchMode - a boolean
|
||||
*/
|
||||
Mailbox.prototype.setSearchMode = function (searchMode) {
|
||||
Mailbox.$searchMode = searchMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function getLength
|
||||
* @memberof Mailbox.prototype
|
||||
@@ -385,6 +399,27 @@
|
||||
// Sorting preferences are common to all mailboxes
|
||||
angular.extend(Mailbox.$query, sortingAttributes);
|
||||
|
||||
if (filters && filters.length > 0) {
|
||||
// Remove highlight words
|
||||
this.$highlightWords = [];
|
||||
filters.forEach(filter => {
|
||||
if ("subject_or_from" == filter.searchBy
|
||||
|| "contains" == filter.searchBy
|
||||
|| "body" == filter.searchBy
|
||||
|| "from" == filter.searchBy
|
||||
|| "to" == filter.searchBy
|
||||
|| "subject" == filter.searchBy) {
|
||||
var words = filter.searchInput.split(" ");
|
||||
words.forEach(word => {
|
||||
var cleanedWord = word.trim().toLowerCase();
|
||||
if (!this.$highlightWords.includes(cleanedWord)) {
|
||||
this.$highlightWords.push(cleanedWord);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
angular.extend(options, { sortingAttributes: Mailbox.$query });
|
||||
if (angular.isDefined(filters)) {
|
||||
options.filters = _.reject(angular.copy(filters), function(filter) {
|
||||
@@ -1249,4 +1284,23 @@
|
||||
Mailbox.$$resource.post(this.id, action);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function setHighlightWords
|
||||
* @memberof Mailbox.prototype
|
||||
* @desc Set highlight words when searching
|
||||
* @param {array} highlightWords - a list of words
|
||||
*/
|
||||
Mailbox.prototype.setHighlightWords = function (highlightWords) {
|
||||
this.$highlightWords = highlightWords;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function getHighlightWords
|
||||
* @memberof Mailbox.prototype
|
||||
* @desc Get highlight words when searching
|
||||
* @returns a list of words
|
||||
*/
|
||||
Mailbox.prototype.getHighlightWords = function () {
|
||||
return this.$highlightWords;
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
_.forEach(hotkeys, function(key) {
|
||||
sgHotkeys.deregisterHotkey(key);
|
||||
});
|
||||
if (vm.mode.search) {
|
||||
vm.mode.search = false;
|
||||
vm.selectedFolder.$reset({ filter: true });
|
||||
}
|
||||
// if (vm.mode.search) {
|
||||
// vm.mode.search = false;
|
||||
// vm.selectedFolder.$reset({ filter: true });
|
||||
// }
|
||||
});
|
||||
|
||||
// Update window's title with unseen messages count of selected mailbox
|
||||
@@ -175,6 +175,12 @@
|
||||
};
|
||||
|
||||
this.cancelSearch = function() {
|
||||
// Clean highlights
|
||||
if (vm.account) {
|
||||
vm.account.$getMailboxes().$$state.value.forEach((mailbox) => {
|
||||
mailbox.setHighlightWords([]);
|
||||
});
|
||||
}
|
||||
vm.mode.search = false;
|
||||
vm.selectedFolder.$filter(vm.service.$query).then(function() {
|
||||
if (vm.selectedFolder.$selectedMessage) {
|
||||
|
||||
@@ -2,35 +2,36 @@
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
MailboxesController.$inject = ['$scope', '$state', '$transitions', '$timeout', '$window', '$mdUtil', '$mdMedia', '$mdSidenav', '$mdDialog', '$mdToast', 'sgConstant', 'sgFocus', 'encodeUriFilter', 'Dialog', 'sgSettings', 'sgHotkeys', 'Account', 'Mailbox', 'VirtualMailbox', 'User', 'Preferences', 'stateAccounts'];
|
||||
function MailboxesController($scope, $state, $transitions, $timeout, $window, $mdUtil, $mdMedia, $mdSidenav, $mdDialog, $mdToast, sgConstant, focus, encodeUriFilter, Dialog, Settings, sgHotkeys, Account, Mailbox, VirtualMailbox, User, Preferences, stateAccounts) {
|
||||
MailboxesController.$inject = ['$scope', '$rootScope', '$state', '$transitions', '$timeout', '$window', '$mdUtil', '$mdMedia', '$mdSidenav', '$mdDialog', '$mdToast', 'sgConstant', 'sgFocus', 'encodeUriFilter', 'Dialog', 'sgSettings', 'sgHotkeys', 'Account', 'Mailbox', 'VirtualMailbox', 'User', 'Preferences', 'stateAccounts', 'Message'];
|
||||
function MailboxesController($scope, $rootScope, $state, $transitions, $timeout, $window, $mdUtil, $mdMedia, $mdSidenav, $mdDialog, $mdToast, sgConstant, focus, encodeUriFilter, Dialog, Settings, sgHotkeys, Account, Mailbox, VirtualMailbox, User, Preferences, stateAccounts, Message) {
|
||||
var vm = this,
|
||||
account,
|
||||
mailbox,
|
||||
hotkeys = [];
|
||||
|
||||
$scope.closeDialog = function () {
|
||||
$mdDialog.hide();
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.service = Mailbox;
|
||||
this.accounts = stateAccounts;
|
||||
this.message = Message;
|
||||
this.advancedSearchPanelVisible = false;
|
||||
|
||||
// Advanced search options
|
||||
this.currentSearchParam = '';
|
||||
this.reset();
|
||||
|
||||
this.search = {
|
||||
options: {'': '', // no placeholder when no criteria is active
|
||||
subject: l('Enter Subject'),
|
||||
from: l('Enter From'),
|
||||
to: l('Enter To'),
|
||||
cc: l('Enter Cc'),
|
||||
body: l('Enter Body')
|
||||
},
|
||||
subfolders: 1,
|
||||
match: 'AND',
|
||||
params: []
|
||||
};
|
||||
this.highlightWords = [];
|
||||
|
||||
this.showSubscribedOnly = Preferences.defaults.SOGoMailShowSubscribedFoldersOnly;
|
||||
|
||||
@@ -44,6 +45,16 @@
|
||||
sgHotkeys.deregisterHotkey(key);
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.$on('showMailAdvancedSearchPanel', function () {
|
||||
vm.showAdvancedSearch();
|
||||
});
|
||||
|
||||
$rootScope.$on('resetMailAdvancedSearchPanel', function () {
|
||||
vm.service.$virtualPath = false;
|
||||
vm.service.$virtualMode = false;
|
||||
vm.reset();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +72,13 @@
|
||||
Mailbox.selectedFolderController.confirmDelete(Mailbox.selectedFolder);
|
||||
}
|
||||
}));
|
||||
keys.push(sgHotkeys.createHotkey({
|
||||
key: 'shift+s',
|
||||
description: l('Advanced search'),
|
||||
callback: function () {
|
||||
vm.showAdvancedSearch();
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// Register the hotkeys
|
||||
@@ -68,14 +86,168 @@
|
||||
sgHotkeys.registerHotkey(key);
|
||||
});
|
||||
}
|
||||
|
||||
this.hideAdvancedSearch = function() {
|
||||
this.hideAdvancedSearch = function(e) {
|
||||
vm.service.$virtualPath = false;
|
||||
vm.service.$virtualMode = false;
|
||||
|
||||
account = vm.accounts[0];
|
||||
mailbox = vm.searchPreviousMailbox;
|
||||
$state.go('mail.account.mailbox', { accountId: account.id, mailboxId: encodeUriFilter(mailbox.path) });
|
||||
vm.search.params = [];
|
||||
vm.highlightWords = [];
|
||||
if (mailbox && mailbox.path) {
|
||||
// Reset
|
||||
mailbox.setHighlightWords([]);
|
||||
mailbox.$filter({
|
||||
"sort": "date",
|
||||
"asc": false,
|
||||
"match": "OR"
|
||||
}).then(function () {
|
||||
$state.go('mail.account.mailbox', { accountId: account.id, mailboxId: encodeUriFilter(mailbox.path) });
|
||||
vm.$onInit(); // Reinit search fields
|
||||
});
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
this.addHighlightWords = function(sentence) {
|
||||
var words = sentence.split(" ");
|
||||
|
||||
words.forEach(word => {
|
||||
var cleanedWord = word.trim().toLowerCase();
|
||||
if (!this.highlightWords.includes(cleanedWord)) {
|
||||
this.highlightWords.push(cleanedWord);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.reset = function() {
|
||||
this.highlightWords = [];
|
||||
this.searchForm = {
|
||||
from: '',
|
||||
to: '',
|
||||
contains: '',
|
||||
notContains: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
date: 'anytime',
|
||||
dateStart: new Date(),
|
||||
dateEnd: new Date(),
|
||||
bcc: '',
|
||||
size: '',
|
||||
sizeOperator: '>',
|
||||
sizeUnit: 'mb',
|
||||
attachements: 0,
|
||||
favorite: 0,
|
||||
unseen: 0,
|
||||
tags: { searchText: '', selected: '' },
|
||||
flags: [],
|
||||
};
|
||||
}
|
||||
|
||||
this.addSearchParameters = function() {
|
||||
this.search.params = [];
|
||||
this.highlightWords = [];
|
||||
// From
|
||||
if (this.searchForm.from && this.searchForm.from.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('from', this.searchForm.from));
|
||||
this.addHighlightWords(this.searchForm.from);
|
||||
}
|
||||
// To
|
||||
if (this.searchForm.to && this.searchForm.to.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('to', this.searchForm.to));
|
||||
}
|
||||
// Bcc
|
||||
if (this.searchForm.bcc && this.searchForm.bcc.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('bcc', this.searchForm.bcc));
|
||||
}
|
||||
// Contains
|
||||
if (this.searchForm.contains && this.searchForm.contains.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('contains', this.searchForm.contains));
|
||||
this.addHighlightWords(this.searchForm.contains);
|
||||
}
|
||||
// Does not contains
|
||||
if (this.searchForm.doesnotcontains && this.searchForm.doesnotcontains.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('not_contains', this.searchForm.doesnotcontains));
|
||||
}
|
||||
// Subject
|
||||
if (this.searchForm.subject && this.searchForm.subject.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('subject', this.searchForm.subject));
|
||||
this.addHighlightWords(this.searchForm.subject);
|
||||
}
|
||||
// Body
|
||||
if (this.searchForm.body && this.searchForm.body.length > 0) {
|
||||
this.search.params.push(this.newSearchParam('body', this.searchForm.body));
|
||||
this.addHighlightWords(this.searchForm.body);
|
||||
}
|
||||
// Date
|
||||
if (this.searchForm.date && this.searchForm.date.length > 0) {
|
||||
var date = null;
|
||||
var dateTo = null;
|
||||
var today = new Date();
|
||||
var tmp = new Date(today);
|
||||
switch (this.searchForm.date) {
|
||||
case 'anytime':
|
||||
break;
|
||||
case 'last7days':
|
||||
tmp.setDate(tmp.getDate() - 7);
|
||||
date = this.formatDate(tmp);
|
||||
this.search.params.push(this.newSearchParam('date', date, '>='));
|
||||
break;
|
||||
case 'last30days':
|
||||
tmp.setDate(tmp.getDate() - 30);
|
||||
date = this.formatDate(tmp);
|
||||
this.search.params.push(this.newSearchParam('date', date, '>='));
|
||||
break;
|
||||
case 'last6month':
|
||||
tmp.setMonth(tmp.getMonth() - 6);
|
||||
date = this.formatDate(tmp);
|
||||
this.search.params.push(this.newSearchParam('date', date, '>='));
|
||||
break;
|
||||
case 'before':
|
||||
date = this.formatDate(this.searchForm.dateStart);
|
||||
this.search.params.push(this.newSearchParam('date', date, '<'));
|
||||
break;
|
||||
case 'after':
|
||||
date = this.formatDate(this.searchForm.dateStart);
|
||||
this.search.params.push(this.newSearchParam('date', date, '>='));
|
||||
break;
|
||||
case 'between':
|
||||
date = this.formatDate(this.searchForm.dateStart);
|
||||
dateTo = this.formatDate(this.searchForm.dateEnd);
|
||||
this.search.params.push(this.newSearchDateBetweenParam(date, dateTo));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Size
|
||||
if (this.searchForm.size && this.searchForm.size > 0) {
|
||||
this.search.params.push(this.newSearchParam('size', this.searchForm.size.toString(), this.searchForm.sizeOperator));
|
||||
}
|
||||
// Attachment
|
||||
if (this.searchForm.attachements) {
|
||||
this.search.params.push(this.newSearchParam('attachment', '1', '='));
|
||||
}
|
||||
// Favorite
|
||||
if (this.searchForm.favorite) {
|
||||
this.search.params.push(this.newSearchParam('favorite', '1', '='));
|
||||
}
|
||||
// Unseen
|
||||
if (this.searchForm.unseen) {
|
||||
this.search.params.push(this.newSearchParam('unseen', '1', '='));
|
||||
}
|
||||
// Flags
|
||||
if (this.searchForm.flags && this.searchForm.flags.length > 0) {
|
||||
this.search.params.push(this.newSearchFlagsParam());
|
||||
}
|
||||
|
||||
this.toggleAdvancedSearch();
|
||||
}
|
||||
|
||||
this.searchFieldChange = function (event) {
|
||||
if (13 == event.keyCode) {
|
||||
this.addSearchParameters();
|
||||
$mdDialog.hide();
|
||||
vm.advancedSearchPanelVisible = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.toggleAdvancedSearch = function() {
|
||||
@@ -109,6 +281,7 @@
|
||||
|
||||
if (Mailbox.$virtualPath.length) {
|
||||
root = vm.accounts[0].$getMailboxByPath(Mailbox.$virtualPath);
|
||||
root.setHighlightWords(vm.highlightWords);
|
||||
mailboxes.push(root);
|
||||
if (vm.search.subfolders && root.children.length)
|
||||
_visit(root.children);
|
||||
@@ -119,6 +292,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
mailboxes.forEach((mailbox) => {
|
||||
mailbox
|
||||
});
|
||||
vm.virtualMailbox.setMailboxes(mailboxes);
|
||||
vm.virtualMailbox.startSearch(vm.search.match, vm.search.params);
|
||||
if ($state.$current.name != 'mail.account.virtualMailbox')
|
||||
@@ -126,24 +302,49 @@
|
||||
}
|
||||
};
|
||||
|
||||
this.addSearchParam = function(v) {
|
||||
this.currentSearchParam = v;
|
||||
focus('advancedSearch');
|
||||
return false;
|
||||
|
||||
this.formatDate = function(date) {
|
||||
var year = date.getFullYear();
|
||||
var month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
var day = date.getDate().toString().padStart(2, '0');
|
||||
return year + '-' + month + '-' + day;
|
||||
};
|
||||
|
||||
this.newSearchParam = function(pattern) {
|
||||
if (pattern.length && this.currentSearchParam.length) {
|
||||
var n = 0, searchParam = this.currentSearchParam;
|
||||
this.changeDate = function() {
|
||||
if ('between' == this.searchForm.date) {
|
||||
if (this.searchForm.dateStart > this.searchForm.dateEnd) {
|
||||
this.searchForm.dateEnd = this.searchForm.dateStart;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.newSearchParam = function (searchParam, pattern, operator = '>') {
|
||||
if (pattern.length && searchParam.length) {
|
||||
var n = 0;
|
||||
if (pattern.startsWith("!")) {
|
||||
n = 1;
|
||||
pattern = pattern.substring(1).trim();
|
||||
}
|
||||
this.currentSearchParam = '';
|
||||
return { searchBy: searchParam, searchInput: pattern, negative: n };
|
||||
|
||||
switch (searchParam) {
|
||||
case 'size':
|
||||
return { searchBy: searchParam, searchInput: pattern, negative: n, operator: operator, sizeUnit: this.searchForm.sizeUnit };
|
||||
case 'date':
|
||||
return { searchBy: searchParam, searchInput: pattern, negative: n, operator: operator };
|
||||
default:
|
||||
return { searchBy: searchParam, searchInput: pattern, negative: n };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.newSearchDateBetweenParam = function (dateFrom, dateTo) {
|
||||
return { searchBy: 'date_between', searchInput: "*", dateFrom: dateFrom, dateTo: dateTo, negative: 0 };
|
||||
};
|
||||
|
||||
this.newSearchFlagsParam = function () {
|
||||
return { searchBy: 'flags', searchInput: "*", flags: vm.searchForm.flags, negative: 0 };
|
||||
};
|
||||
|
||||
this.toggleAccountState = function (account) {
|
||||
account.$expanded = !account.$expanded;
|
||||
if (!this.debounceSaveState) {
|
||||
@@ -192,12 +393,46 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.showAdvancedSearch = function() {
|
||||
Mailbox.$virtualPath = '';
|
||||
// Close sidenav on small devices
|
||||
if (!$mdMedia(sgConstant['gt-md']))
|
||||
$mdSidenav('left').close();
|
||||
if (!vm.advancedSearchPanelVisible) {
|
||||
vm.advancedSearchPanelVisible = true;
|
||||
if (Mailbox.selectedFolder.path)
|
||||
Mailbox.$virtualPath = Mailbox.selectedFolder.path;
|
||||
|
||||
// Close sidenav on small devices
|
||||
if (!$mdMedia(sgConstant['gt-md']))
|
||||
$mdSidenav('left').close();
|
||||
|
||||
$mdDialog.show({
|
||||
template: document.getElementById('advancedSearch').innerHTML,
|
||||
parent: angular.element(document.body),
|
||||
controller: function () {
|
||||
var dialogCtrl = this;
|
||||
|
||||
this.$onInit = function () {
|
||||
// Pass main controller
|
||||
this.mainController = vm;
|
||||
this.mailbox = Mailbox;
|
||||
this.message = Message;
|
||||
};
|
||||
|
||||
dialogCtrl.closeDialog = function () {
|
||||
$mdDialog.hide();
|
||||
vm.advancedSearchPanelVisible = false;
|
||||
};
|
||||
|
||||
dialogCtrl.search = function () {
|
||||
this.mainController.addSearchParameters();
|
||||
$mdDialog.hide();
|
||||
vm.advancedSearchPanelVisible = false;
|
||||
};
|
||||
},
|
||||
controllerAs: 'dialogCtrl',
|
||||
clickOutsideToClose: false,
|
||||
escapeToClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.newFolder = function(parentFolder) {
|
||||
@@ -309,5 +544,7 @@
|
||||
angular
|
||||
.module('SOGo.MailerUI')
|
||||
.controller('MailboxesController', MailboxesController);
|
||||
|
||||
|
||||
})();
|
||||
|
||||
|
||||
@@ -357,9 +357,11 @@
|
||||
// Don't report any state error
|
||||
});
|
||||
$transitions.onError({ to: 'mail.**' }, function(transition) {
|
||||
if (transition.to().name != 'mail' &&
|
||||
if ((transition.to().name != 'mail' &&
|
||||
!transition.ignored() &&
|
||||
transition.error().message.indexOf('superseded') < 0) {
|
||||
transition.error().message.indexOf('superseded') < 0)
|
||||
|| transition.to().name === "mail.account.virtualMailbox"
|
||||
|| transition.to().name === "mail.account.virtualMailbox.message") {
|
||||
$log.error('transition error to ' + transition.to().name + ': ' + transition.error().detail);
|
||||
// Unselect everything
|
||||
Mailbox.selectedFolder = false;
|
||||
|
||||
@@ -473,6 +473,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (this.$parts)
|
||||
// Use the cache
|
||||
return this.$parts;
|
||||
@@ -480,12 +482,80 @@
|
||||
else if (this.parts)
|
||||
_visit(this.parts);
|
||||
|
||||
|
||||
// Highlight words
|
||||
if (parts && this.$mailbox && this.$mailbox.getHighlightWords().length > 0) {
|
||||
var i = 0, j = 0;
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
if (parts[i]
|
||||
&& parts[i].type
|
||||
&& ("UIxMailPartHTMLViewer" == parts[i].type
|
||||
|| "UIxMailPartTextViewer" == parts[i].type)) {
|
||||
// Content
|
||||
parts[i].content = this.highlightSearchTerms(parts[i].content);
|
||||
// Title
|
||||
this.subject = this.getHighlightSubject();
|
||||
// From
|
||||
this.from = this.getHighlightFrom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache result
|
||||
this.$parts = parts;
|
||||
|
||||
return parts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function highlightSearchTerms
|
||||
* @memberof Message.prototype
|
||||
* @desc Returns the data with highlight search
|
||||
* @returns the data with highlighted search terms
|
||||
*/
|
||||
Message.prototype.highlightSearchTerms = function (data) {
|
||||
var i = 0;
|
||||
if (this.$mailbox.getHighlightWords()
|
||||
&& this.$mailbox.getHighlightWords().length > 0
|
||||
&& data
|
||||
&& -1 === data.indexOf("data-markjs")) {
|
||||
var dom = document.createElement("DIV");
|
||||
dom.innerHTML = data;
|
||||
var markInstance = new Mark(dom);
|
||||
markInstance.mark(this.$mailbox.getHighlightWords());
|
||||
data = dom.innerHTML;
|
||||
dom.remove();
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function getHighlightSubject
|
||||
* @memberof Message.prototype
|
||||
* @desc Returns the subject with highlight search
|
||||
* @returns the subject with highlighted search terms
|
||||
*/
|
||||
Message.prototype.getHighlightSubject = function () {
|
||||
return this.highlightSearchTerms(this.subject);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function getHighlightFrom
|
||||
* @memberof Message.prototype
|
||||
* @desc Returns the from with highlight search
|
||||
* @returns the from with highlighted search terms
|
||||
*/
|
||||
Message.prototype.getHighlightFrom = function () {
|
||||
var i = 0;
|
||||
for (i = 0; i < this.from.length; i++) {
|
||||
this.from[i].full = this.highlightSearchTerms(this.from[i].full);
|
||||
this.from[i].name = this.highlightSearchTerms(this.from[i].name);
|
||||
}
|
||||
|
||||
return this.from;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $editableContent
|
||||
* @memberof Message.prototype
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
sgMailboxListItemController.$inject = ['$scope', '$element', '$state', '$timeout', '$mdToast', '$mdPanel', '$mdMedia', '$mdSidenav', 'sgConstant', 'Dialog', 'Mailbox', 'encodeUriFilter'];
|
||||
function sgMailboxListItemController($scope, $element, $state, $timeout, $mdToast, $mdPanel, $mdMedia, $mdSidenav, sgConstant, Dialog, Mailbox, encodeUriFilter) {
|
||||
sgMailboxListItemController.$inject = ['$scope', '$rootScope', '$element', '$state', '$timeout', '$mdToast', '$mdPanel', '$mdMedia', '$mdSidenav', 'sgConstant', 'Dialog', 'Mailbox', 'encodeUriFilter'];
|
||||
function sgMailboxListItemController($scope, $rootScope, $element, $state, $timeout, $mdToast, $mdPanel, $mdMedia, $mdSidenav, sgConstant, Dialog, Mailbox, encodeUriFilter) {
|
||||
var $ctrl = this;
|
||||
|
||||
|
||||
@@ -84,10 +84,12 @@
|
||||
|
||||
|
||||
this.selectFolder = function($event) {
|
||||
$rootScope.$broadcast('resetMailAdvancedSearchPanel'); // Reset advanced search panel (broadcast event to MailboxesController)
|
||||
if (this.editMode || this.mailbox == Mailbox.selectedFolder || this.mailbox.isNoSelect())
|
||||
return;
|
||||
Mailbox.$virtualPath = false;
|
||||
if (Mailbox.$virtualMode) {
|
||||
|
||||
this.mailbox.setHighlightWords([]);
|
||||
if (Mailbox.selectedFolder) {
|
||||
Mailbox.$virtualMode = false;
|
||||
Mailbox.selectedFolder.$reset({ filter: true });
|
||||
}
|
||||
@@ -283,6 +285,8 @@
|
||||
// Close sidenav on small devices
|
||||
if (!$mdMedia(sgConstant['gt-md']))
|
||||
$mdSidenav('left').close();
|
||||
|
||||
$rootScope.$broadcast('showMailAdvancedSearchPanel'); // Show advanced search panel (broadcast event to MailboxesController)
|
||||
};
|
||||
|
||||
this.share = function() {
|
||||
|
||||
@@ -141,11 +141,8 @@
|
||||
if ($ctrl.mailboxNameElement)
|
||||
$ctrl.mailboxNameElement.innerHTML = $ctrl.message.$mailbox.$displayName;
|
||||
|
||||
// Sender or recipient when in Sent or Draft mailbox
|
||||
if ($ctrl.MailboxService.selectedFolder.isSentFolder || $ctrl.MailboxService.selectedFolder.isDraftsFolder)
|
||||
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('to', Preferences.defaults.SOGoMailDisplayFullEmail).encodeEntities();
|
||||
else
|
||||
$ctrl.senderElement.innerHTML = $ctrl.message.$shortAddress('from', Preferences.defaults.SOGoMailDisplayFullEmail).encodeEntities();
|
||||
// Subject and sender or recipient when in Sent or Draft mailbox
|
||||
$ctrl.defineSubjectAndSenderElements();
|
||||
|
||||
// Priority icon
|
||||
if ($ctrl.message.priority && $ctrl.message.priority.level < 3) {
|
||||
@@ -169,9 +166,6 @@
|
||||
$ctrl.threadButton.classList.add('ng-hide');
|
||||
}
|
||||
|
||||
// Subject
|
||||
$ctrl.subjectElement.innerHTML = $ctrl.message.subject.encodeEntities();
|
||||
|
||||
// Message size
|
||||
$ctrl.sizeElement.innerHTML = $ctrl.message.size;
|
||||
|
||||
@@ -196,6 +190,23 @@
|
||||
this.MailboxService = Mailbox;
|
||||
};
|
||||
|
||||
this.defineSubjectAndSenderElements = function() {
|
||||
if ($ctrl && $ctrl.message) {
|
||||
// Subject
|
||||
$ctrl.subjectElement.innerHTML = $ctrl.message.getHighlightSubject();
|
||||
|
||||
// Sender or recipient when in Sent or Draft mailbox
|
||||
if ($ctrl.MailboxService.selectedFolder.isSentFolder || $ctrl.MailboxService.selectedFolder.isDraftsFolder)
|
||||
$ctrl.senderElement.innerHTML = $ctrl.message.highlightSearchTerms($ctrl.message.$shortAddress('to', Preferences.defaults.SOGoMailDisplayFullEmail).encodeEntities());
|
||||
else
|
||||
$ctrl.senderElement.innerHTML = $ctrl.message.highlightSearchTerms($ctrl.message.$shortAddress('from', Preferences.defaults.SOGoMailDisplayFullEmail).encodeEntities());
|
||||
}
|
||||
};
|
||||
|
||||
this.$doCheck = function () {
|
||||
$ctrl.defineSubjectAndSenderElements();
|
||||
};
|
||||
|
||||
this.toggleThread = function() {
|
||||
if (this.message.collapsed)
|
||||
this.threadIconElement.classList.add('md-rotate-180-ccw');
|
||||
|
||||
+7
File diff suppressed because one or more lines are too long
@@ -16,7 +16,8 @@
|
||||
"lodash": "^4.17.21",
|
||||
"ng-sortable": "1.3.7",
|
||||
"qrcodejs": "^1.0.0",
|
||||
"punycode": "^2.3.0"
|
||||
"punycode": "^2.3.0",
|
||||
"mark.js": "^8.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.8.5",
|
||||
|
||||
@@ -18,10 +18,37 @@ md-toolbar {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.md-toolbar-tools {
|
||||
.md-toolbar-tools, .md-toolbar-tools .md-datepicker-input, .md-chips {
|
||||
font-size: $subhead-font-size-base;
|
||||
}
|
||||
|
||||
.md-toolbar-tools md-select:not([disabled]):focus .md-select-value {
|
||||
border-bottom-color: unset;
|
||||
color: unset;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.search-right-space {
|
||||
margin-right: 10px!important;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.search-and {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.search-inline {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.md-toolbar-tools .md-datepicker-input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
md-toolbar,
|
||||
.md-toolbar-tools {
|
||||
// Animate the first icon button of a "secondary" toolbar
|
||||
@@ -144,7 +171,3 @@ hgroup {
|
||||
min-height: $bl * 6;
|
||||
height: $bl * 6;
|
||||
}
|
||||
|
||||
//.sg-toolbar-search {
|
||||
// padding: $toolbar-padding 0;
|
||||
//}
|
||||
|
||||
@@ -452,4 +452,79 @@ md-dialog md-dialog-actions.sg-mail-editor-attachments {
|
||||
.sogo-raw-html-embed .raw-html-embed__preview-placeholder {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Search highlight
|
||||
mark {
|
||||
background: yellow;
|
||||
color: black;
|
||||
}
|
||||
|
||||
// Advanced search
|
||||
.advanced-search {
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.advanced-search-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.advanced-search md-input-container,
|
||||
.advanced-search md-select,
|
||||
.advanced-search md-datepicker,
|
||||
.advanced-search .search-and,
|
||||
.advanced-search a {
|
||||
margin: 10px 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.advanced-search-in {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-inline md-menu,
|
||||
.search-inline button {
|
||||
height: auto !important;
|
||||
margin: auto !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.advanced-search-more {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.advanced-search-more-no-bottom {
|
||||
margin-bottom: 0!important;
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
|
||||
.advanced-search-more > :first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.advanced-search-clickable-toolbar {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.advanced-search-clickable-toolbar form {
|
||||
padding-left: 0!important;
|
||||
}
|
||||
|
||||
.advanced-search-clickable-toolbar form >div:first-child {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.advanced-search-more button {
|
||||
margin-right: 0!important;
|
||||
}
|
||||
|
||||
.show-advanced-search {
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
Reference in New Issue
Block a user