HTML Composing, see changelog

Monotone-Parent: b895c283a3da52c67fd91339f704dc8cd49b743f
Monotone-Revision: 5f0e6bd5b6298ffdd908398135216841ba3b6909

Monotone-Author: crobert@inverse.ca
Monotone-Date: 2009-06-23T17:53:18
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
C Robert 2009-06-23 17:53:18 +00:00
parent 445224aa8f
commit f9337f183f
13 changed files with 236 additions and 113 deletions

View File

@ -1,3 +1,19 @@
2009-06-23 Cyril Robert <crobert@inverse.ca>
* SoObjects/Mailer/NSString+Mail.m (stringByConvertingCRLNToHTML): Added
* UI/MailPartViewers/UIxMailPartTextViewer.m (stringByConvertingCRLNToHTML):
Removed
* SoObjects/Mailer/SOGoDraftObject.m: Added support for html content type
* SoObjects/Mailer/SOGoMailObject+Draft.m: Added support for html content
type in mail reply
* SoObjects/Mailer/SOGoMailReply.m: Added support for html in mail editor
* UI/PreferencesUI/UIxPreferences.m: Added option to se content type for
mail editing
* UI/WebServerResources/GNUmakefile: Added fckeditor entry
* UI/WebServerResources/UIxMailEditor.js: Plugged-in FCKEditor
* UI/WebServerResources/UIxPreferences.js: Plugged-in FCKEditor, handle html
toggle for signature box
2009-06-19 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2009-06-19 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoGCSFolder.m (-davSyncCollection:): deleted * SoObjects/SOGo/SOGoGCSFolder.m (-davSyncCollection:): deleted

View File

@ -28,6 +28,7 @@
@interface NSString (SOGoExtension) @interface NSString (SOGoExtension)
- (NSString *) htmlToText; - (NSString *) htmlToText;
- (NSString *) stringByConvertingCRLNToHTML;
- (int) indexOf: (unichar) _c; - (int) indexOf: (unichar) _c;
- (NSString *) decodedSubject; - (NSString *) decodedSubject;

View File

@ -23,6 +23,7 @@
#import <Foundation/NSArray.h> #import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h> #import <Foundation/NSDictionary.h>
#import <Foundation/NSObject.h> #import <Foundation/NSObject.h>
#import <Foundation/NSException.h>
#import <SaxObjC/SaxAttributes.h> #import <SaxObjC/SaxAttributes.h>
#import <SaxObjC/SaxContentHandler.h> #import <SaxObjC/SaxContentHandler.h>
#import <SaxObjC/SaxLexicalHandler.h> #import <SaxObjC/SaxLexicalHandler.h>
@ -350,6 +351,79 @@
return [handler result]; return [handler result];
} }
#define paddingBuffer 8192
static inline char *
convertChars (const char *oldString, unsigned int oldLength,
unsigned int *newLength)
{
const char *currentChar, *upperLimit;
char *newString, *destChar, *reallocated;
unsigned int length, maxLength;
maxLength = oldLength + paddingBuffer;
newString = NSZoneMalloc (NULL, maxLength + 1);
destChar = newString;
currentChar = oldString;
length = 0;
upperLimit = oldString + oldLength;
while (currentChar < upperLimit)
{
switch (*currentChar)
{
case '\r': break;
case '\n':
length = destChar - newString;
if (length + paddingBuffer > maxLength - 6)
{
maxLength += paddingBuffer;
reallocated = NSZoneRealloc (NULL, newString, maxLength + 1);
if (reallocated)
{
newString = reallocated;
destChar = newString + length;
}
else
[NSException raise: NSMallocException
format: @"reallocation failed in %s",
__PRETTY_FUNCTION__];
}
strcpy (destChar, "<br />");
destChar += 6;
break;
default:
*destChar = *currentChar;
destChar++;
}
currentChar++;
}
*destChar = 0;
*newLength = destChar - newString;
return newString;
}
- (NSString *) stringByConvertingCRLNToHTML
{
NSString *convertedString;
char *newString;
unsigned int newLength;
newString
= convertChars ([self cStringUsingEncoding: NSUTF8StringEncoding],
[self lengthOfBytesUsingEncoding: NSUTF8StringEncoding],
&newLength);
convertedString = [[NSString alloc] initWithBytes: newString
length: newLength
encoding: NSUTF8StringEncoding];
[convertedString autorelease];
NSZoneFree (NULL, newString);
return convertedString;
}
- (int) indexOf: (unichar) _c - (int) indexOf: (unichar) _c
{ {
int i, len; int i, len;

View File

@ -70,6 +70,7 @@
#import "SOGoDraftObject.h" #import "SOGoDraftObject.h"
static NSString *contentTypeValue = @"text/plain; charset=utf-8"; static NSString *contentTypeValue = @"text/plain; charset=utf-8";
static NSString *htmlContentTypeValue = @"text/html; charset=utf-8";
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc", static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
@"from", @"replyTo", @"message-id", @"from", @"replyTo", @"message-id",
nil}; nil};
@ -879,17 +880,24 @@ static BOOL showTextAttachmentsInline = NO;
- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map - (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map
{ {
NGMimeMessage *message; NGMimeMessage *message;
NSUserDefaults *ud;
// BOOL addSuffix; // BOOL addSuffix;
id body; id body;
ud = [[context activeUser] userDefaults];
[map setObject: @"text/plain" forKey: @"content-type"]; [map setObject: @"text/plain" forKey: @"content-type"];
body = text; body = text;
if (body) if (body)
{ {
// if ([body isKindOfClass:[NSString class]]) // if ([body isKindOfClass:[NSString class]])
/* Note: just 'utf8' is displayed wrong in Mail.app */ /* Note: just 'utf8' is displayed wrong in Mail.app */
[map setObject: contentTypeValue if ([[ud stringForKey: @"ComposeMessagesType"] isEqualToString: @"html"])
forKey: @"content-type"]; [map setObject: htmlContentTypeValue
forKey: @"content-type"];
else
[map setObject: contentTypeValue
forKey: @"content-type"];
// body = [body dataUsingEncoding:NSUTF8StringEncoding]; // body = [body dataUsingEncoding:NSUTF8StringEncoding];
// else if ([body isKindOfClass:[NSData class]] && addSuffix) { // else if ([body isKindOfClass:[NSData class]] && addSuffix) {
// body = [[body mutableCopy] autorelease]; // body = [[body mutableCopy] autorelease];

View File

@ -88,6 +88,26 @@
return newSubject; return newSubject;
} }
- (NSString *) _convertRawContentForEditing: (NSString *) raw
rawHtml: (BOOL) html
{
NSString *rc;
NSUserDefaults *ud;
BOOL htmlComposition;
ud = [[context activeUser] userDefaults];
htmlComposition = [[ud stringForKey: @"ComposeMessagesType"]
isEqualToString: @"html"];
if (html && !htmlComposition)
rc = [raw htmlToText];
else if (!html && htmlComposition)
rc = [raw stringByConvertingCRLNToHTML];
else
rc = raw;
return rc;
}
- (NSString *) _contentForEditingFromKeys: (NSArray *) keys - (NSString *) _contentForEditingFromKeys: (NSArray *) keys
{ {
@ -104,29 +124,27 @@
types = [keys objectsForKey: @"mimeType" notFoundMarker: @""]; types = [keys objectsForKey: @"mimeType" notFoundMarker: @""];
index = [types indexOfObject: @"text/plain"]; index = [types indexOfObject: @"text/plain"];
if (index == NSNotFound) if (index == NSNotFound)
{ {
index = [types indexOfObject: @"text/html"]; index = [types indexOfObject: @"text/html"];
htmlContent = YES; htmlContent = YES;
} }
else else
htmlContent = NO; htmlContent = NO;
if (index != NSNotFound) if (index != NSNotFound)
{ {
contentKey = [keys objectAtIndex: index]; contentKey = [keys objectAtIndex: index];
parts = [self fetchPlainTextStrings: parts = [self fetchPlainTextStrings:
[NSArray arrayWithObject: contentKey]]; [NSArray arrayWithObject: contentKey]];
if ([parts count] > 0) if ([parts count] > 0)
{ {
rawPart = [[parts allValues] objectAtIndex: 0]; rawPart = [[parts allValues] objectAtIndex: 0];
if (htmlContent) content = [self _convertRawContentForEditing: rawPart
content = [rawPart htmlToText]; rawHtml: htmlContent];
else }
content = rawPart; }
}
}
} }
return content; return content;
} }

View File

@ -25,6 +25,7 @@
#import <SoObjects/SOGo/SOGoDateFormatter.h> #import <SoObjects/SOGo/SOGoDateFormatter.h>
#import <SoObjects/SOGo/SOGoUser.h> #import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserDefaults.h>
#import "SOGoMailObject+Draft.h" #import "SOGoMailObject+Draft.h"
#import "SOGoMailReply.h" #import "SOGoMailReply.h"
@ -74,20 +75,32 @@
- (NSString *) messageBody - (NSString *) messageBody
{ {
NSString *s; NSString *s;
NSUserDefaults *ud;
ud = [[context activeUser] userDefaults];
s = [sourceMail contentForEditing]; s = [sourceMail contentForEditing];
if (s) if (s)
{ {
NSRange r; if ([[ud objectForKey: @"ComposeMessagesType"] isEqualToString: @"html"])
{
s = [NSString stringWithFormat: @"<blockquote type=\"cite\">%@</blockquote>", s];
}
else
{
NSRange r;
r = [s rangeOfString: @"\n-- \n" options: NSBackwardsSearch]; r = [s rangeOfString: @"\n-- \n" options: NSBackwardsSearch];
if (r.length) if (r.length)
s = [s substringToIndex: r.location]; s = [s substringToIndex: r.location];
s = [s stringByApplyingMailQuoting]; //adds "> " on each line
}
} }
return [s stringByApplyingMailQuoting]; return s;
} }
@end @end

View File

@ -33,92 +33,10 @@
#import <NGExtensions/NSString+misc.h> #import <NGExtensions/NSString+misc.h>
#import <SoObjects/SOGo/NSString+Utilities.h> #import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/Mailer/NSString+Mail.h>
#import "UIxMailPartTextViewer.h" #import "UIxMailPartTextViewer.h"
@interface NSString (SOGoMailUIExtension)
- (NSString *) stringByConvertingCRLNToHTML;
@end
@implementation NSString (SOGoMailUIExtension)
#define paddingBuffer 8192
static inline char *
convertChars (const char *oldString, unsigned int oldLength,
unsigned int *newLength)
{
const char *currentChar, *upperLimit;
char *newString, *destChar, *reallocated;
unsigned int length, maxLength;
maxLength = oldLength + paddingBuffer;
newString = NSZoneMalloc (NULL, maxLength + 1);
destChar = newString;
currentChar = oldString;
length = 0;
upperLimit = oldString + oldLength;
while (currentChar < upperLimit)
{
switch (*currentChar)
{
case '\r': break;
case '\n':
length = destChar - newString;
if (length + paddingBuffer > maxLength - 6)
{
maxLength += paddingBuffer;
reallocated = NSZoneRealloc (NULL, newString, maxLength + 1);
if (reallocated)
{
newString = reallocated;
destChar = newString + length;
}
else
[NSException raise: NSMallocException
format: @"reallocation failed in %s",
__PRETTY_FUNCTION__];
}
strcpy (destChar, "<br />");
destChar += 6;
break;
default:
*destChar = *currentChar;
destChar++;
}
currentChar++;
}
*destChar = 0;
*newLength = destChar - newString;
return newString;
}
- (NSString *) stringByConvertingCRLNToHTML
{
NSString *convertedString;
char *newString;
unsigned int newLength;
newString
= convertChars ([self cStringUsingEncoding: NSUTF8StringEncoding],
[self lengthOfBytesUsingEncoding: NSUTF8StringEncoding],
&newLength);
convertedString = [[NSString alloc] initWithBytes: newString
length: newLength
encoding: NSUTF8StringEncoding];
[convertedString autorelease];
NSZoneFree (NULL, newString);
return convertedString;
}
@end
@implementation UIxMailPartTextViewer @implementation UIxMailPartTextViewer
- (NSString *) flatContentAsString - (NSString *) flatContentAsString

View File

@ -584,6 +584,27 @@ static BOOL defaultShowSubscribedFoldersOnly = NO;
return [userDefaults stringForKey: @"SignaturePlacement"]; return [userDefaults stringForKey: @"SignaturePlacement"];
} }
- (NSArray *) composeMessagesType
{
return [NSArray arrayWithObjects: @"text", @"html", nil];
}
- (NSString *) itemComposeMessagesText
{
return [self labelForKey: [NSString stringWithFormat: @"composemessagestype_%@", item]];
}
- (NSString *) userComposeMessagesType
{
return [userDefaults stringForKey: @"ComposeMessagesType"];
}
- (void) setUserComposeMessagesType: (NSString *) newType
{
[userDefaults setObject: newType forKey: @"ComposeMessagesType"];
}
- (void) setUserSignaturePlacement: (NSString *) newSignaturePlacement - (void) setUserSignaturePlacement: (NSString *) newSignaturePlacement
{ {
[userDefaults setObject: newSignaturePlacement forKey: @"SignaturePlacement"]; [userDefaults setObject: newSignaturePlacement forKey: @"SignaturePlacement"];

View File

@ -10,7 +10,7 @@
className="UIxPageFrame" className="UIxPageFrame"
title="panelTitle" title="panelTitle"
const:popup="YES" const:popup="YES"
const:jsFiles="UIxMailToSelection.js"> const:jsFiles="UIxMailToSelection.js,fckeditor/fckeditor.js">
<script type="text/javascript"> <script type="text/javascript">
var mailIsReply = <var:string value="isMailReply"/>; var mailIsReply = <var:string value="isMailReply"/>;
</script> </script>

View File

@ -10,6 +10,7 @@
className="UIxPageFrame" className="UIxPageFrame"
title="title" title="title"
const:popup="YES" const:popup="YES"
const:jsFiles="fckeditor/fckeditor.js"
> >
<form id="mainForm" var:href="ownPath"> <form id="mainForm" var:href="ownPath">
<div class="tabsContainer" id="preferencesTabs"> <div class="tabsContainer" id="preferencesTabs">
@ -118,6 +119,11 @@
const:id="signaturePlacementList" const:id="signaturePlacementList"
string="itemSignaturePlacementText" string="itemSignaturePlacementText"
selection="userSignaturePlacement"/> selection="userSignaturePlacement"/>
<br /><label><var:string label:value="Compose messages in"/></label>
<var:popup list="composeMessagesType" item="item"
const:id="composeMessagesType"
string="itemComposeMessagesText"
selection="userComposeMessagesType"/>
<!-- <label><input <!-- <label><input
const:name="inTheOffice" type="radio" const:value="YES" const:name="inTheOffice" type="radio" const:value="YES"
var:selection="inTheOffice"/> var:selection="inTheOffice"/>

View File

@ -12,6 +12,7 @@ install ::
else \ else \
mkdir -p $(SOGO_WEBSERVERRESOURCESDIR); \ mkdir -p $(SOGO_WEBSERVERRESOURCESDIR); \
cp $(WEBSERVER_RESOURCE_FILES) $(SOGO_WEBSERVERRESOURCESDIR); \ cp $(WEBSERVER_RESOURCE_FILES) $(SOGO_WEBSERVERRESOURCESDIR); \
cp -r fckeditor $(SOGO_WEBSERVERRESOURCESDIR); \
fi fi
clean :: clean ::

View File

@ -570,6 +570,15 @@ function initMailEditor() {
focusField.focus(); focusField.focus();
initializePriorityMenu(); initializePriorityMenu();
var composeMode = UserDefaults["ComposeMessagesType"];
if (composeMode == "html") {
var oFCKeditor = new FCKeditor ('text');
oFCKeditor.BasePath = "/SOGo.woa/WebServerResources/fckeditor/";
oFCKeditor.ToolbarSet = 'Basic';
oFCKeditor.ReplaceTextarea ();
}
onWindowResize (null);
} }
function initializePriorityMenu() { function initializePriorityMenu() {
@ -720,7 +729,14 @@ function onWindowResize(event) {
textarea.setStyle({ 'top': hr.offsetTop + 'px' }); textarea.setStyle({ 'top': hr.offsetTop + 'px' });
// Resize the textarea (message content) // Resize the textarea (message content)
textarea.rows = Math.floor((window.height() - textarea.offsetTop) / rowheight); var composeMode = UserDefaults["ComposeMessagesType"];
if (composeMode == "html") {
var editor = $('text___Frame');
editor.height = Math.floor(window.height() - editor.offsetTop) + "px";
editor.style.height = Math.floor(window.height() - editor.offsetTop) + "px";
editor.setStyle({ 'top': hr.offsetTop + 'px' });
}
textarea.rows = Math.floor((window.height() - textarea.offsetTop) / rowheight);
} }
function onMailEditorClose(event) { function onMailEditorClose(event) {

View File

@ -33,8 +33,22 @@ function initPreferences() {
_setupEvents(true); _setupEvents(true);
if (typeof (initAdditionalPreferences) != "undefined") if (typeof (initAdditionalPreferences) != "undefined")
initAdditionalPreferences(); initAdditionalPreferences();
$("replyPlacementList").observe("change", onReplyPlacementListChange); $("replyPlacementList").observe ("change", onReplyPlacementListChange);
onReplyPlacementListChange(); onReplyPlacementListChange();
var oFCKeditor = new FCKeditor ('signature');
oFCKeditor.BasePath = "/SOGo.woa/WebServerResources/fckeditor/";
oFCKeditor.ToolbarSet = 'Basic';
oFCKeditor.ReplaceTextarea ();
$('signature___Frame').style.height = "150px";
$('signature___Frame').height = "150px";
if (UserDefaults["ComposeMessagesType"] != "html") {
$("signature").style.display = 'inline';
$('signature___Frame').style.display = 'none';
}
$("composeMessagesType").observe ("change", onComposeMessagesTypeChange);
} }
function onReplyPlacementListChange() { function onReplyPlacementListChange() {
@ -48,4 +62,21 @@ function onReplyPlacementListChange() {
} }
} }
function onComposeMessagesTypeChange () {
var textArea = $('signature');
var oEditor = FCKeditorAPI.GetInstance('signature');
var editor = $('signature___Frame');
if ($("composeMessagesType").value == 0) {
textArea.style.display = 'inline';
editor.style.display = 'none';
textArea.value = oEditor.GetData();
}
else {
textArea.style.display = 'none';
editor.style.display = '';
oEditor.SetHTML(textArea.value);
}
}
document.observe("dom:loaded", initPreferences); document.observe("dom:loaded", initPreferences);