diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 92814703a..89059e950 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -54,18 +54,18 @@ /* name lookup */ -- (id ) lookupContactWithId: (NSString *) recordId -{ - SOGoContactGCSEntry *contact; - - if ([recordId length] > 0) - contact = [SOGoContactGCSEntry objectWithName: recordId - inContainer: self]; - else - contact = nil; - - return contact; -} +//- (id ) lookupContactWithId: (NSString *) recordId +//{ +// SOGoContactGCSEntry *contact; +// +// if ([recordId length] > 0) +// contact = [SOGoContactGCSEntry objectWithName: recordId +// inContainer: self]; +// else +// contact = nil; +// +// return contact; +//} - (Class) objectClassForContent: (NSString *) content { diff --git a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar index 7822b1a0a..bcc6285e2 100644 --- a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar +++ b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar @@ -27,7 +27,7 @@ ( { link = "delete"; label="Delete"; - onclick = "return uixDeleteSelectedContacts(this);"; + onclick = "return onToolbarDeleteSelectedContacts(this);"; image="tb-mail-delete-flat-24x24.png"; tooltip = "Delete selected card or address book"; } ) diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index 0b7c42e99..92b60fbc0 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -99,8 +99,8 @@ } - (void) _fillResults: (NSMutableDictionary *) results - inFolder: (id ) folder - withSearchOn: (NSString *) contact +inFolder: (id ) folder +withSearchOn: (NSString *) contact { NSEnumerator *folderResults; NSDictionary *currentContact; @@ -188,40 +188,41 @@ NSMutableArray *allContacts; unsigned int i; NSSortDescriptor *displayNameDescriptor; - + searchText = [self queryParameterForKey: @"search"]; if ([searchText length] > 0) { folders = [[self clientObject] subFolders]; allContacts = [NSMutableArray new]; - for (i = 0; i < [folders count]; i++) { - folder = [folders objectAtIndex: i]; - //NSLog(@"Address book: %@ (%@)", [folder displayName], [folder class]); - contacts = [folder lookupContactsWithFilter: searchText - sortBy: @"displayName" - ordering: NSOrderedAscending]; - if ([contacts count] > 0) { - [allContacts addObjectsFromArray: contacts]; + for (i = 0; i < [folders count]; i++) + { + folder = [folders objectAtIndex: i]; + //NSLog(@"Address book: %@ (%@)", [folder displayName], [folder class]); + contacts = [folder lookupContactsWithFilter: searchText + sortBy: @"displayName" + ordering: NSOrderedAscending]; + if ([contacts count] > 0) + [allContacts addObjectsFromArray: contacts]; } - } - + result = [context response]; - if ([allContacts count] > 0) { - // Sort the contacts by display name - displayNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"displayName" - ascending:YES] autorelease]; - descriptors = [NSArray arrayWithObjects: displayNameDescriptor, nil]; - sortedContacts = [allContacts sortedArrayUsingDescriptors:descriptors]; - - [(WOResponse*)result appendContentString: [sortedContacts jsonRepresentation]]; - } + if ([allContacts count] > 0) + { + // Sort the contacts by display name + displayNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"displayName" + ascending:YES] autorelease]; + descriptors = [NSArray arrayWithObjects: displayNameDescriptor, nil]; + sortedContacts = [allContacts sortedArrayUsingDescriptors:descriptors]; + + [(WOResponse*)result appendContentString: [sortedContacts jsonRepresentation]]; + } else [(WOResponse*)result setStatus: 404]; } else result = [NSException exceptionWithHTTPStatus: 400 - reason: @"missing 'search' parameter"]; - + reason: @"missing 'search' parameter"]; + return result; } @@ -240,7 +241,7 @@ } else result = [NSException exceptionWithHTTPStatus: 400 - reason: @"missing 'search' parameter"]; + reason: @"missing 'search' parameter"]; return result; } @@ -256,9 +257,9 @@ securityManager = [SoSecurityManager sharedSecurityManager]; -// return (([securityManager validatePermission: SoPerm_AccessContentsInformation -// onObject: contactFolder -// inContext: context] == nil) + // return (([securityManager validatePermission: SoPerm_AccessContentsInformation + // onObject: contactFolder + // inContext: context] == nil) folders = [NSMutableArray new]; [folders autorelease]; diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index ec2c8f27a..9b901efff 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -320,7 +320,7 @@ else result = (([[card childrenWithTag: @"url" andAttribute: @"type" - havingValue: @"work"] count] > 0) + havingValue: @"work"] count] > 0) || [[card childrenWithTag: @"org"] count] > 0); return result; @@ -524,26 +524,24 @@ - (id) deleteAction { NSException *ex; - id url; if (![self isDeletableClientObject]) /* return 400 == Bad Request */ - return [NSException exceptionWithHTTPStatus:400 - reason:@"method cannot be invoked on " - @"the specified object"]; - + return [NSException exceptionWithHTTPStatus: 400 + reason:@"method cannot be invoked on " + @"the specified object"]; + ex = [[self clientObject] delete]; if (ex) { - // TODO: improve error handling - [self debugWithFormat:@"failed to delete: %@", ex]; - + // TODO: improve error handling + [self debugWithFormat: @"failed to delete: %@", ex]; + return ex; } - - url = [[[self clientObject] container] baseURLInContext:[self context]]; - - return [self redirectToLocation:url]; + + return [self responseWithStatus: 200 + andString: [[self clientObject] nameInContainer]]; } @end /* UIxContactView */ diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index c47f89180..af5fa3799 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -98,7 +98,7 @@ }; }; }; - + SOGoContactLDAPFolder = { slots = { toolbar = { @@ -108,24 +108,24 @@ }; methods = { view = { - protectedBy = ""; + protectedBy = ""; pageName = "UIxContactsListView"; }; new = { - protectedBy = ""; + protectedBy = ""; pageName = "UIxContactEditor"; actionName = "new"; }; mailer-contacts = { - protectedBy = ""; + protectedBy = ""; pageName = "UIxContactsListView"; actionName = "mailerContacts"; }; - canAccessContent = { - protectedBy = ""; - actionClass = "UIxFolderActions"; - actionName = "canAccessContent"; - }; + canAccessContent = { + protectedBy = ""; + actionClass = "UIxFolderActions"; + actionName = "canAccessContent"; + }; }; }; diff --git a/UI/MailerUI/UIxMailMainFrame.m b/UI/MailerUI/UIxMailMainFrame.m index 691a2cfd7..95d247f9a 100644 --- a/UI/MailerUI/UIxMailMainFrame.m +++ b/UI/MailerUI/UIxMailMainFrame.m @@ -23,18 +23,23 @@ #import #import +#import #import #import #import #import #import +#import +#import + #import #import #import #import #import #import +#import #import #import "UIxMailMainFrame.h" @@ -144,18 +149,84 @@ - (id ) composeAction { - NSArray *accounts; - NSString *firstAccount, *newLocation, *parameters; + id contact; + NSArray *accounts, *contactsId; + NSString *firstAccount, *newLocation, *parameters, *folderId, *uid, *email; + NSMutableString *fn; + NSEnumerator *uids; + NSMutableArray *addresses; + NGVCard *card; SOGoMailAccounts *co; - NSDictionary *formValues; + SOGoContactFolders *folders; + SOGoParentFolder *folder; + WORequest *request; + parameters = nil; co = [self clientObject]; + + // We use the first mail account accounts = [[context activeUser] mailAccounts]; firstAccount = [[accounts objectsForKey: @"name"] objectAtIndex: 0]; - formValues = [[context request] formValues]; - parameters = ([formValues count] > 0 - ? [formValues asURLParameters] - : @"?mailto="); + request = [context request]; + + if ((folderId = [request formValueForKey: @"folder"]) && + (contactsId = [request formValuesForKey: @"uid"])) + { + // Retrieve the email addresses from the specified address book + // and contact IDs + folders = [[[self clientObject] container] privateContacts: @"Contacts" + inContext: nil]; + folder = [folders lookupName: folderId + inContext: nil + acquire: NO]; + if (folder) + { + uids = [contactsId objectEnumerator]; + uid = [uids nextObject]; + + addresses = [NSMutableArray new]; + + while (uid) + { + contact = [folder lookupName: uid + inContext: [self context] + acquire: NO]; + if (![(NSObject*)contact isKindOfClass: [NSException class]]) + { + // We fetch the preferred email address of the contact or + // the first defined email address + card = [contact vCard]; + email = [card preferredEMail]; + if (email == nil) + email = (NSString*)[card firstChildWithTag: @"EMAIL"]; + if (email) + { + email = [NSString stringWithFormat: @"<%@>", email]; + fn = [NSMutableString stringWithString: [card fn]]; + if (fn) + { + [fn appendFormat: @" %@", email]; + [addresses addObject: fn]; + } + else + [addresses addObject: email]; + } + } + uid = [uids nextObject]; + } + + if ([addresses count] > 0) + parameters = [NSString stringWithFormat: @"?mailto=%@", [addresses componentsJoinedByString: @","]]; + } + } + else if ([[request formValues] objectForKey: @"mailto"]) + // We use the email addresses defined in the request + parameters = [[request formValues] asURLParameters]; + + if (!parameters) + // No parameter passed; simply open the compose window + parameters = @"?mailto="; + newLocation = [NSString stringWithFormat: @"%@/%@/compose%@", [co baseURLInContext: context], firstAccount, diff --git a/UI/Templates/ContactsUI/UIxContactsListViewContainer.wox b/UI/Templates/ContactsUI/UIxContactsListViewContainer.wox index 1520379e1..702718965 100644 --- a/UI/Templates/ContactsUI/UIxContactsListViewContainer.wox +++ b/UI/Templates/ContactsUI/UIxContactsListViewContainer.wox @@ -77,6 +77,7 @@
  • diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index 67b63d3bc..4c8a4171a 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -87,10 +87,10 @@ function contactsListCallback(http) { var tmp = document.createElement('div'); $(tmp).update(html); table.replaceChild($(tmp).select("table tbody")[0], tbody); - + var rows = table.tBodies[0].rows; for (var i = 0; i < rows.length; i++) { - var row = $(rows[i]); + var row = $(rows[i]); row.observe("mousedown", onRowClick); row.observe("dblclick", onContactRowDblClick); row.observe("selectstart", listRowMouseDownHandler); @@ -105,7 +105,7 @@ function contactsListCallback(http) { configureSortableTableHeaders(table); TableKit.Resizable.init(table, {'trueResize' : true, 'keepWidth' : true}); } - + if (sorting["attribute"] && sorting["attribute"].length > 0) { var sortHeader; if (sorting["attribute"] == "displayName") @@ -120,13 +120,13 @@ function contactsListCallback(http) { sortHeader = $("phoneHeader"); else sortHeader = null; - + if (sortHeader) { var sortImages = $(table.tHead).select(".sortImage"); $(sortImages).each(function(item) { item.remove(); }); - + var sortImage = createElement("img", "messageSortImage", "sortImage"); sortHeader.insertBefore(sortImage, sortHeader.firstChild); if (sorting["ascending"]) @@ -135,7 +135,7 @@ function contactsListCallback(http) { sortImage.src = ResourcesURL + "/title_sortup_12x12.png"; } } - + var selected = http.callbackData; if (selected) { for (var i = 0; i < selected.length; i++) { @@ -179,16 +179,8 @@ function onAddressBooksContextMenu(event) { function onContactContextMenu(event) { var menu = $("contactMenu"); - var topNode = $('contactsList'); - var selectedNodes = topNode.getSelectedRows(); - - if (selectedNodes.length > 1) { - // TODO: Add support for selection of multiple contacts - } - else { - menu.observe("hideMenu", onContactContextMenuHide); - popupMenu(event, "contactMenu", this); - } + menu.observe("hideMenu", onContactContextMenuHide); + popupMenu(event, "contactMenu", this); } function onContactContextMenuHide(event) { @@ -260,8 +252,10 @@ function contactLoadCallback(http) { cachedContacts[currentAddressBook + "/" + http.callbackData] = content; div.innerHTML = content; } - else + else { log ("ajax problem 2: " + http.status); + refreshCurrentFolder(); + } } var rowSelectionCount = 0; @@ -315,17 +309,11 @@ function onContactSelectionChange(event) { } function onMenuEditContact(event) { - var contactId = document.menuTarget.getAttribute('id'); - - openContactWindow(URLForFolderID(currentAddressBook) - + "/" + contactId + "/edit", contactId); + onToolbarEditSelectedContacts(event); } function onMenuWriteToContact(event) { - var contactId = document.menuTarget.getAttribute('id'); - - openMailComposeWindow(ApplicationBaseURL + currentAddressBook - + "/" + contactId + "/write"); + onToolbarWriteToSelectedContacts(event); if (document.body.hasClassName("popup")) window.close(); @@ -339,7 +327,7 @@ function onMenuAIMContact(event) { } function onMenuDeleteContact(event) { - uixDeleteSelectedContacts(this); + onToolbarDeleteSelectedContacts(event); } function onToolbarEditSelectedContacts(event) { @@ -369,65 +357,46 @@ function onToolbarWriteToSelectedContacts(event) { return false; } - for (var i = 0; i < rows.length; i++) { - var emailCell = $(rows[i]).down('td', 1); - if (emailCell.firstChild) { // .nodeValue is the contact email address - rowsWithEmail++; - openMailComposeWindow(ApplicationBaseURL + currentAddressBook - + "/" + rows[i] + "/write"); - } - } + openMailComposeWindow(ApplicationBaseURL + "../Mail/compose" + + "?folder=" + currentAddressBook.substring(1) + + "&uid=" + rows.join("&uid=")); - if (rowsWithEmail == 0) { - window.alert(labels["The selected contact has no email address."]); - } - else if (document.body.hasClassName("popup")) + if (document.body.hasClassName("popup")) window.close(); - + return false; } -function uixDeleteSelectedContacts(sender) { - var failCount = 0; +function onToolbarDeleteSelectedContacts(event) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); - + if (rows.length == 0) { window.alert(labels["Please select a contact."]); return false; } - + var contactView = $('contactView'); - + for (var i = 0; i < rows.length; i++) { - var url, http, rowElem; - - /* send AJAX request (synchronously) */ - + var url; url = (URLForFolderID(currentAddressBook) + "/" + rows[i] + "/delete"); - http = createHTTPClient(); - http.open("POST", url, false /* not async */); - http.send(""); - http.setRequestHeader("Content-Length", 0); - if (http.status != 200) { /* request failed */ - failCount++; - http = null; - continue; - } - http = null; - - /* remove from page */ - - /* line-through would be nicer, but hiding is OK too */ - rowElem = $(rows[i]); - rowElem.parentNode.removeChild(rowElem); + new Ajax.Request(url, { + method: 'post', + onFailure: function(transport) { + log("Ajax error: can't delete contact"); + refreshCurrentFolder(); + }, + onSuccess: function(transport) { + var row = $(transport.responseText.trim()); + if (row) + row.parentNode.removeChild(row); + } + }); } - - if (failCount > 0) - alert(labels["You cannot delete the selected contact(s)"]); - else - contactView.update(); + + contactView.update(); return false; } diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 51d41b7d0..7982a14f1 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -221,9 +221,9 @@ function openMailComposeWindow(url, wId) { parentWindow = window.opener; var w = parentWindow.open(url, wId, - "width=680,height=520,resizable=1,scrollbars=1,toolbar=0," - + "location=0,directories=0,status=0,menubar=0" - + ",copyhistory=0"); + "width=680,height=520,resizable=1,scrollbars=1,toolbar=0," + + "location=0,directories=0,status=0,menubar=0" + + ",copyhistory=0"); w.focus(); @@ -514,7 +514,7 @@ function acceptMultiSelect(node) { function onRowClick(event) { var node = getTarget(event); var rowIndex = null; - + if (node.tagName == 'TD') { node = node.parentNode; // select TR rowIndex = node.rowIndex - $(node).up('table').down('thead').getElementsByTagName('tr').length; @@ -524,27 +524,29 @@ function onRowClick(event) { var list = node.parentNode; var items = list.childNodesWithTag("li"); for (var i = 0; i < items.length; i++) { - if (items[i] == node) { - rowIndex = i; - break; + if (items[i] == node) { + rowIndex = i; + break; } } } - + var initialSelection = $(node.parentNode).getSelectedNodes(); + if (initialSelection.length > 0 && initialSelection.indexOf(node) >= 0 - && !Event.isLeftClick(event)) + && (!isSafari() && !Event.isLeftClick(event) || + isSafari() && event.ctrlKey == 1)) // Event.isLeftClick is not supported in Safari // Ignore non primary-click (ie right-click) inside current selection return true; - - if ((event.shiftKey == 1 || event.ctrlKey == 1) + + if ((event.shiftKey == 1 || event.metaKey == 1) && (lastClickedRow >= 0) && (acceptMultiSelect(node.parentNode) || acceptMultiSelect(node.parentNode.parentNode))) { - if (event.shiftKey) + if (event.shiftKey) { $(node.parentNode).selectRange(lastClickedRow, rowIndex); - else if (isNodeSelected(node) == true) { + } else if (isNodeSelected(node)) { $(node).deselect(); } else { $(node).selectElement(); @@ -554,7 +556,7 @@ function onRowClick(event) { // Single line selection $(node.parentNode).deselectAll(); $(node).selectElement(); - + if (initialSelection != $(node.parentNode).getSelectedNodes()) { // Selection has changed; fire mousedown event var parentNode = node.parentNode; @@ -564,14 +566,12 @@ function onRowClick(event) { } } lastClickedRow = rowIndex; - + return true; } /* popup menus */ -// var acceptClick = false; - function popupMenu(event, menuId, target) { document.menuTarget = target; @@ -614,7 +614,7 @@ function popupMenu(event, menuId, target) { $(document.body).observe("click", onBodyClickMenuHandler); - preventDefault(event); + Event.stop(event); } function getParentMenu(node) { @@ -1131,7 +1131,6 @@ function getListIndexForFolder(items, owner, folderName) { function listRowMouseDownHandler(event) { preventDefault(event); - //Event.stop(event); } /* tabs */ @@ -1387,16 +1386,6 @@ function onLoadHandler(event) { if (progressImage) progressImage.parentNode.removeChild(progressImage); $(document.body).observe("contextmenu", onBodyClickContextMenu); - /* $(document.body).observe("click", testclic); */ -} - -function testclic(event) { -log("test: " + event.target); -if (event.target) { -log("tag: " + event.target.tagName); -log("id: " + event.target.getAttribute("id")); -log("class: " + event.target.getAttribute("class")); -} } function onBodyClickContextMenu(event) {