(feat) added IMAP folders subscriptions management (fixes #255)

pull/221/head
Ludovic Marcotte 2016-09-14 15:57:49 -04:00
parent 49d58b436e
commit 42127c51ab
17 changed files with 362 additions and 104 deletions

View File

@ -813,7 +813,7 @@ void handle_eas_terminate(int signum)
}
allFoldersMetadata = [NSMutableArray array];
[self _flattenFolders: [accountFolder allFoldersMetadata] into: allFoldersMetadata parent: nil parentType: nil];
[self _flattenFolders: [accountFolder allFoldersMetadata: SOGoMailStandardListing] into: allFoldersMetadata parent: nil parentType: nil];
// Get GUIDs of folder (IMAP)
// e.g. {folderINBOX = folder6b93c528176f1151c7260000aef6df92}

2
NEWS
View File

@ -2,7 +2,7 @@
------------------
New features
-
- [web] added IMAP folder subscriptions management (#255)
Enhancements
- [web] don't allow a recurrence rule to end before the first occurrence

View File

@ -1,6 +1,5 @@
/*
Copyright (C) 2009-2014 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2009-2016 Inverse inc.
This file is part of SOGo.
@ -35,6 +34,7 @@
*/
@class NSArray;
@class NSMutableDictionary;
@class NSMutableArray;
@class NSString;
@ -50,6 +50,11 @@ typedef enum {
rfc4314
} SOGoIMAPAclStyle;
typedef enum {
SOGoMailStandardListing = 0,
SOGoMailSubscriptionsManagementListing = 1
} SOGoMailListingMode;
@interface SOGoMailAccount : SOGoMailBaseObject
{
SOGoMailFolder *inboxFolder;
@ -61,6 +66,7 @@ typedef enum {
NSMutableArray *identities;
NSString *otherUsersFolderName;
NSString *sharedFoldersName;
NSMutableDictionary *subscribedFolders;
}
- (SOGoIMAPAclStyle) imapAclStyle;
@ -83,8 +89,8 @@ typedef enum {
/* folder pathes */
- (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs;
- (NSArray *) allFolderPaths;
- (NSArray *) allFoldersMetadata;
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode;
- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode;
- (NSDictionary *) imapFolderGUIDs;

View File

@ -1,5 +1,4 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2007-2016 Inverse inc.
This file is part of SOGo.
@ -25,7 +24,6 @@
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NSNull+misc.h>
@ -73,6 +71,7 @@ static NSString *inboxFolderName = @"INBOX";
identities = nil;
otherUsersFolderName = nil;
sharedFoldersName = nil;
subscribedFolders = nil;
}
return self;
@ -88,11 +87,10 @@ static NSString *inboxFolderName = @"INBOX";
[identities release];
[otherUsersFolderName release];
[sharedFoldersName release];
[subscribedFolders release];
[super dealloc];
}
/* listing the available folders */
- (BOOL) isInDraftsFolder
{
return NO;
@ -116,8 +114,6 @@ static NSString *inboxFolderName = @"INBOX";
}
}
/* namespaces */
- (void) _appendNamespaces: (NSMutableArray *) folders
{
NSDictionary *namespaceDict;
@ -342,30 +338,45 @@ static NSString *inboxFolderName = @"INBOX";
//
//
//
- (NSArray *) allFolderPaths
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode
{
NSMutableArray *folderPaths, *namespaces;
NSArray *folders, *mainFolders;
SOGoUserDefaults *ud;
NSString *namespace;
BOOL subscribedOnly;
int count, max;
ud = [[context activeUser] userDefaults];
subscribedOnly = [ud mailShowSubscribedFoldersOnly];
if (theListingMode == SOGoMailStandardListing)
subscribedOnly = [[[context activeUser] userDefaults] mailShowSubscribedFoldersOnly];
else
{
subscribedOnly = NO;
DESTROY(subscribedFolders);
subscribedFolders = [[NSMutableDictionary alloc] init];
folders = [[self imap4Connection] allFoldersForURL: [self imap4URL]
onlySubscribedFolders: YES];
max = [folders count];
for (count = 0; count < max; count++)
{
[subscribedFolders setObject: [NSNull null]
forKey: [folders objectAtIndex: count]];
}
[[self imap4Connection] flushFolderHierarchyCache];
}
mainFolders = [[NSArray arrayWithObjects:
[self inboxFolderNameInContext: context],
[self draftsFolderNameInContext: context],
[self sentFolderNameInContext: context],
[self trashFolderNameInContext: context],
[self junkFolderNameInContext: context],
nil] stringsWithFormat: @"/%@"];
[self junkFolderNameInContext: context],
nil] stringsWithFormat: @"/%@"];
folders = [[self imap4Connection] allFoldersForURL: [self imap4URL]
onlySubscribedFolders: subscribedOnly];
onlySubscribedFolders: subscribedOnly];
folderPaths = [folders mutableCopy];
[folderPaths autorelease];
[folderPaths removeObjectsInArray: mainFolders];
namespaces = [NSMutableArray arrayWithCapacity: 10];
[self _appendNamespaces: namespaces];
@ -441,6 +452,9 @@ static NSString *inboxFolderName = @"INBOX";
return folderType;
}
//
//
//
- (NSMutableDictionary *) _insertFolder: (NSString *) folderPath
foldersList: (NSMutableArray *) theFolders
{
@ -449,8 +463,9 @@ static NSString *inboxFolderName = @"INBOX";
NSMutableDictionary *currentFolder, *parentFolder, *folder;
NSString *currentFolderName, *currentPath, *fullName, *folderType;
SOGoUserManager *userManager;
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed;
int i, j, count;
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder;
parentFolder = nil;
parentIsOtherUsersFolder = NO;
@ -469,13 +484,9 @@ static NSString *inboxFolderName = @"INBOX";
// Search for the current path in the children of the parent folder.
// For the first iteration, take the parent folder passed as argument.
if (parentFolder)
{
folders = [parentFolder objectForKey: @"children"];
}
folders = [parentFolder objectForKey: @"children"];
else
{
folders = theFolders;
}
folders = theFolders;
for (j = 0; j < [folders count]; j++)
{
@ -485,10 +496,9 @@ static NSString *inboxFolderName = @"INBOX";
folder = currentFolder;
// Make sure all branches are ready to receive children
if (!last && ![folder objectForKey: @"children"])
{
[folder setObject: [NSMutableArray array] forKey: @"children"];
}
break;
break;
}
}
@ -496,18 +506,13 @@ static NSString *inboxFolderName = @"INBOX";
currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName];
if (otherUsersFolderName
&& [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame)
{
isOtherUsersFolder = YES;
}
isOtherUsersFolder = YES;
else
{
isOtherUsersFolder = NO;
}
isOtherUsersFolder = NO;
if (folder == nil)
{
// Folder was not found; create it and add it to the folders list
if (parentIsOtherUsersFolder)
{
// Parent folder is the "Other users" folder; translate the user's mailbox name
@ -519,14 +524,10 @@ static NSString *inboxFolderName = @"INBOX";
currentFolderName = fullName;
}
else if (isOtherUsersFolder)
{
currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
}
currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
else if (sharedFoldersName
&& [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame)
{
currentFolderName = [self labelForKey: @"SharedFoldersName"];
}
currentFolderName = [self labelForKey: @"SharedFoldersName"];
flags = [NSMutableArray array];;
@ -536,13 +537,19 @@ static NSString *inboxFolderName = @"INBOX";
else
folderType = @"additional";
if ([subscribedFolders objectForKey: folderPath])
isSubscribed = YES;
else
isSubscribed = NO;
folder = [NSMutableDictionary dictionaryWithObjectsAndKeys:
currentPath, @"path",
folderType, @"type",
currentFolderName, @"name",
[NSMutableArray array], @"children",
flags, @"flags",
nil];
folderType, @"type",
currentFolderName, @"name",
[NSMutableArray array], @"children",
flags, @"flags",
[NSNumber numberWithBool: isSubscribed], @"subscribed",
nil];
// Either add this new folder to its parent or the list of root folders
[folders addObject: folder];
}
@ -557,7 +564,7 @@ static NSString *inboxFolderName = @"INBOX";
//
// Return a tree representation of the mailboxes
//
- (NSArray *) allFoldersMetadata
- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode
{
NSString *currentFolder;
NSMutableArray *folders;
@ -565,7 +572,7 @@ static NSString *inboxFolderName = @"INBOX";
NSAutoreleasePool *pool;
NSArray *allFolderPaths;
allFolderPaths = [self allFolderPaths];
allFolderPaths = [self allFolderPaths: theListingMode];
rawFolders = [allFolderPaths objectEnumerator];
folders = [NSMutableArray array];
@ -587,8 +594,6 @@ static NSString *inboxFolderName = @"INBOX";
return folders;
}
/* IMAP4 */
- (NSDictionary *) _mailAccount
{
NSDictionary *mailAccount;
@ -761,7 +766,7 @@ static NSString *inboxFolderName = @"INBOX";
[self trashFolderNameInContext: context],
nil] stringsWithFormat: @"/%@"];
else
folderList = [self allFolderPaths];
folderList = [self allFolderPaths: SOGoMailStandardListing];
folders = [NSMutableDictionary dictionary];

View File

@ -227,7 +227,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
prefix = [self absoluteImap4Name];
result = [[self mailAccountFolder] allFolderPaths];
result = [[self mailAccountFolder] allFolderPaths: SOGoMailStandardListing];
folderNames = [result objectEnumerator];
while ((currentFolderName = [folderNames nextObject]))
if ([currentFolderName hasPrefix: prefix])

View File

@ -361,4 +361,7 @@
"Folder compacted" = "Folder compacted";
/* Aria label for scope of search on messages */
"Search scope" = "Search scope";
"Search scope" = "Search scope";
/* Subscriptions Dialog */
"Manage Subscriptions" = "Manage Subscriptions";

View File

@ -23,10 +23,11 @@ MailerUI_OBJC_FILES += \
UIxMailPopupView.m \
UIxMailMoveToPopUp.m \
UIxMailFilterPanel.m \
UIxMailSearch.m \
UIxMailSearch.m \
\
UIxMailAccountActions.m \
UIxMailFolderActions.m \
UIxMailFolderSubscriptions.m \
UIxMailActions.m \
UIxMailEditor.m \
UIxMailToSelection.m \

View File

@ -46,14 +46,33 @@
co = [self clientObject];
folders = [co allFoldersMetadata];
folders = [co allFoldersMetadata: SOGoMailStandardListing];
data = [NSDictionary dictionaryWithObjectsAndKeys:
folders, @"mailboxes",
[co getInboxQuota], @"quotas",
nil];
return [self responseWithStatus: 200 andJSONRepresentation: data];
return [self responseWithStatus: 200
andJSONRepresentation: data];
}
- (WOResponse *) listAllMailboxesAction
{
SOGoMailAccount *co;
NSArray *folders;
NSDictionary *data;
co = [self clientObject];
folders = [co allFoldersMetadata: SOGoMailSubscriptionsManagementListing];
data = [NSDictionary dictionaryWithObjectsAndKeys:
folders, @"mailboxes",
nil];
return [self responseWithStatus: 200
andJSONRepresentation: data];
}
/* compose */

View File

@ -743,42 +743,62 @@
return response;
}
#warning here should be done what should be done: IMAP subscription
- (WOResponse *) _subscriptionStubAction
// - (WOResponse *) _subscriptionStubAction
// {
// NSString *mailInvitationParam, *mailInvitationURL;
// WOResponse *response;
// SOGoMailFolder *clientObject;
// mailInvitationParam
// = [[context request] formValueForKey: @"mail-invitation"];
// if ([mailInvitationParam boolValue])
// {
// clientObject = [self clientObject];
// mailInvitationURL
// = [[clientObject soURLToBaseContainerForCurrentUser]
// absoluteString];
// response = [self responseWithStatus: 302];
// [response setHeader: mailInvitationURL
// forKey: @"location"];
// }
// else
// {
// response = [self responseWithStatus: 500];
// [response appendContentString: @"How did you end up here?"];
// }
// return response;
// }
- (WOResponse *) _subscribeOrUnsubscribeAction: (BOOL) subscribing
{
NSString *mailInvitationParam, *mailInvitationURL;
NGImap4Client *client;
WOResponse *response;
SOGoMailFolder *clientObject;
SOGoMailFolder *co;
NSDictionary *d;
mailInvitationParam
= [[context request] formValueForKey: @"mail-invitation"];
if ([mailInvitationParam boolValue])
{
clientObject = [self clientObject];
mailInvitationURL
= [[clientObject soURLToBaseContainerForCurrentUser]
absoluteString];
response = [self responseWithStatus: 302];
[response setHeader: mailInvitationURL
forKey: @"location"];
}
co = [self clientObject];
client = [[co imap4Connection] client];
if (subscribing)
d = [client subscribe: [[co imap4URL] path]];
else
{
response = [self responseWithStatus: 500];
[response appendContentString: @"How did you end up here?"];
}
d = [client unsubscribe: [[co imap4URL] path]];
return response;
if ([[[[d objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"result"] isEqualToString: @"ok"])
return [self responseWith204];
return [self responseWithStatus: 200];
}
- (WOResponse *) subscribeAction
{
return [self _subscriptionStubAction];
return [self _subscribeOrUnsubscribeAction: YES];
}
- (WOResponse *) unsubscribeAction
{
return [self _subscriptionStubAction];
return [self _subscribeOrUnsubscribeAction: NO];
}
- (WOResponse *) addOrRemoveLabelAction

View File

@ -0,0 +1,28 @@
/* UIxMailFolderSubscriptions.h - this file is part of SOGo
*
* Copyright (C) 2016 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.
*/
#import <SOGoUI/UIxComponent.h>
@interface UIxMailFolderSubscriptions : UIxComponent
{
}
@end

View File

@ -0,0 +1,42 @@
/* UIxMailFolderSubscriptions.m - this file is part of SOGo
*
* Copyright (C) 2016 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.
*/
#import <NGObjWeb/WORequest.h>
#import "UIxMailFolderSubscriptions.h"
@implementation UIxMailFolderSubscriptions
- (id) init
{
if ((self = [super init]))
{
}
return self;
}
- (void) dealloc
{
[super dealloc];
}
@end

View File

@ -400,6 +400,15 @@
actionClass = "UIxMailAccountActions";
actionName = "listMailboxes";
};
viewAll = {
protectedBy = "View";
actionClass = "UIxMailAccountActions";
actionName = "listAllMailboxes";
};
subscribe = {
protectedBy = "Access Contents Information";
pageName = "UIxMailFolderSubscriptions";
};
createFolder = {
protectedBy = "View";
actionClass = "UIxMailFolderActions";

View File

@ -0,0 +1,50 @@
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE container>
<container
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-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">folder</md-icon>
<div class="pseudo-input-container md-flex">
<label class="pseudo-input-label"><var:string label:value="Manage Subscriptions"/></label>
<div class="sg-md-title">{{subscriptions.account.name}}</div>
</div>
<md-button class="md-icon-button" ng-click="subscriptions.close()">
<md-icon aria-label="Close dialog">close</md-icon>
</md-button>
</div>
</md-toolbar>
<md-dialog-content class="md-dialog-content" layout="column">
<md-list>
<md-list-item ng-repeat="folder in subscriptions.account.$flattenMailboxes({all: true })"
md-item-size="48"
ng-hide="subscriptions.app.metadataForFolder(folder).special">
<div ng-class="'sg-child-level-' + folder.level">
<md-icon>{{subscriptions.app.metadataForFolder(folder).icon}}</md-icon>
</div>
<p class="sg-item-name">
{{subscriptions.app.metadataForFolder(folder).name}}
</p>
<md-checkbox class="md-secondary"
ng-model="folder.subscribed"
ng-click="folder.$toggleSubscribe()"
ng-true-value="1"
ng-false-value="0">
</md-checkbox>
</md-list-item>
</md-list>
</md-dialog-content>
<md-dialog-actions>
<md-button type="button" ng-click="subscriptions.close()"><var:string label:value="Close"/></md-button>
</md-dialog-actions>
</md-dialog>
</container>

View File

@ -40,21 +40,35 @@
<md-list>
<md-list-item ng-click="app.toggleAccountState(account)">
<div class="sg-no-wrap">{{account.name}}</div>
<div class="md-flex"><!-- spacer --></div>
<md-button class="md-icon-button md-secondary"
ng-show="account.id == 0"
label:aria-label="Delegation..."
ng-click="app.delegate(account)">
<md-tooltip md-delay="300"><var:string label:value="Delegation..."/></md-tooltip>
<md-icon>people</md-icon>
</md-button>
<md-button class="md-icon-button md-secondary"
label:aria-label="New Folder..."
ng-click="app.newFolder(account)">
<md-tooltip md-delay="300"><var:string label:value="New Folder..."/></md-tooltip>
<md-icon>add_circle_outline</md-icon>
</md-button>
</md-list-item>
<md-menu class="md-secondary">
<md-icon label:aria-label="Options"
ng-click="$mdOpenMenu($event)"
md-menu-origin="md-menu-origin">more_vert</md-icon>
<md-menu-content width="3">
<md-menu-item ng-show="account.id == 0">
<md-button
label:aria-label="Delegation..."
ng-click="app.delegate(account)">
<var:string label:value="Delegation..."/>
</md-button>
</md-menu-item>
<md-menu-item ng-show="app.showSubscribedOnly == 1">
<md-button
label:aria-label="Subscribe..."
ng-click="app.subscribe(account)">
<var:string label:value="Subscribe..."/>
</md-button>
</md-menu-item>
<md-menu-item>
<md-button
label:aria-label="New Folder..."
ng-click="app.newFolder(account)">
<var:string label:value="New Folder..."/>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</md-list-item>
</md-list>
<div class="sg-quota ng-hide" ng-show="account.$quota">
<md-progress-linear md-mode="determinate"

View File

@ -8,7 +8,7 @@
* @constructor
* @param {object} futureAccountData
*/
function Account(futureAccountData) {
function Account(futureAccountData, fetchAll) {
// Data is immediately available
if (typeof futureAccountData.then !== 'function') {
angular.extend(this, futureAccountData);
@ -24,6 +24,14 @@
// The promise will be unwrapped first
//this.$unwrap(futureAccountData);
}
this.fetchAll = false;
// Check if we're displaying the IMAP subscription management dialog
if (angular.isDefined(fetchAll) && fetchAll) {
this.fetchAll = true;
this.$getMailboxes();
}
}
/**

View File

@ -84,7 +84,10 @@
Mailbox.$find = function(account) {
var path, futureMailboxData;
futureMailboxData = this.$$resource.fetch(account.id.toString(), 'view');
if (account.fetchAll)
futureMailboxData = this.$$resource.fetch(account.id.toString(), 'viewAll');
else
futureMailboxData = this.$$resource.fetch(account.id.toString(), 'view');
return Mailbox.$unwrapCollection(account, futureMailboxData); // a collection of mailboxes
};
@ -890,4 +893,15 @@
});
};
/**
* @function $toggleSubscribe
* @memberof Mailbox.prototype
* @desc Subscribe or unsubscribe to a mailbox
*/
Mailbox.prototype.$toggleSubscribe = function() {
if (this.subscribed)
return Mailbox.$$resource.post(this.id, 'subscribe');
return Mailbox.$$resource.post(this.id, 'unsubscribe');
};
})();

View File

@ -15,6 +15,7 @@
vm.service = Mailbox;
vm.accounts = stateAccounts;
vm.toggleAccountState = toggleAccountState;
vm.subscribe = subscribe;
vm.newFolder = newFolder;
vm.delegate = delegate;
vm.editFolder = editFolder;
@ -54,6 +55,10 @@
params: []
};
Preferences.ready().then(function() {
vm.showSubscribedOnly = Preferences.defaults.SOGoMailShowSubscribedFoldersOnly;
});
vm.refreshUnseenCount();
function showAdvancedSearch(path) {
@ -146,6 +151,40 @@
}, 150);
}
function subscribe(account) {
$mdDialog.show({
templateUrl: account.id + '/subscribe',
controller: SubscriptionsDialogController,
controllerAs: 'subscriptions',
clickOutsideToClose: true,
escapeToClose: true,
locals: {
srcApp: vm,
srcAccount: account
}
}).finally(function() {
account.$getMailboxes({reload: true});
});
/**
* @ngInject
*/
SubscriptionsDialogController.$inject = ['$scope', '$mdDialog', 'srcApp', 'srcAccount'];
function SubscriptionsDialogController($scope, $mdDialog, srcApp, srcAccount) {
var vm = this;
vm.app = srcApp;
vm.account = new Account({id: srcAccount.id,
name: srcAccount.name},
true);
vm.close = close;
function close() {
$mdDialog.cancel();
}
}
}
function newFolder(parentFolder) {
Dialog.prompt(l('New folder'),
l('Enter the new name of your folder :'))
@ -305,19 +344,19 @@
function metadataForFolder(folder) {
if (folder.type == 'inbox')
return {name: folder.name, icon:'inbox'};
return {name: folder.name, icon:'inbox', special: true};
else if (folder.type == 'draft')
return {name: l('DraftsFolderName'), icon: 'drafts'};
return {name: l('DraftsFolderName'), icon: 'drafts', special: true};
else if (folder.type == 'sent')
return {name: l('SentFolderName'), icon: 'send'};
return {name: l('SentFolderName'), icon: 'send', special: true};
else if (folder.type == 'trash')
return {name: l('TrashFolderName'), icon: 'delete'};
return {name: l('TrashFolderName'), icon: 'delete', special: true};
else if (folder.type == 'junk')
return {name: l('JunkFolderName'), icon: 'thumb_down'};
return {name: l('JunkFolderName'), icon: 'thumb_down', special: true};
else if (folder.type == 'additional')
return {name: folder.name, icon: 'folder_shared'};
return {name: folder.name, icon: 'folder_shared', special: true};
return {name: folder.name, icon: 'folder_open'};
return {name: folder.name, icon: 'folder_open', special: false};
}
function setFolderAs(folder, type) {