@@ -8,6 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
88import { faCheck , faEnvelope } from '@fortawesome/free-solid-svg-icons' ;
99import { OdooTheme } from '../../../utils/Themes' ;
1010import { _t } from '../../../utils/Translator' ;
11+ import PostalMime from 'postal-mime' ;
1112
1213//total attachments size threshold in megabytes
1314const SIZE_THRESHOLD_TOTAL = 40 ;
@@ -33,132 +34,117 @@ class Logger extends React.Component<LoggerProps, LoggerState> {
3334 } ;
3435 }
3536
36- private fetchAttachmentContent ( attachment , index ) : Promise < any > {
37- return new Promise < any > ( ( resolve ) => {
38- if ( attachment . size > SIZE_THRESHOLD_SINGLE_ELEMENT * 1024 * 1024 ) {
39- resolve ( {
40- name : attachment . name ,
41- inline : attachment . isInline && attachment . contentType . indexOf ( 'image' ) >= 0 ,
42- oversize : true ,
43- index : index ,
44- } ) ;
45- }
46- Office . context . mailbox . item . getAttachmentContentAsync ( attachment . id , ( asyncResult ) => {
47- resolve ( {
48- name : attachment . name ,
49- content : asyncResult . value . content ,
50- inline : attachment . isInline && attachment . contentType . indexOf ( 'image' ) >= 0 ,
51- oversize : false ,
52- index : index ,
53- } ) ;
54- } ) ;
55- } ) ;
37+ private arrayBufferToBase64 ( buffer ) {
38+ const bytes = new Uint8Array ( buffer ) ;
39+ const chunkSize = 0x8000 ; // 32KB
40+ let binary = '' ;
41+
42+ for ( let i = 0 ; i < bytes . length ; i += chunkSize ) {
43+ binary += String . fromCharCode ( ...bytes . subarray ( i , i + chunkSize ) ) ;
44+ }
45+
46+ return btoa ( binary ) ;
5647 }
5748
5849 private logRequest = async ( event ) : Promise < any > => {
5950 event . stopPropagation ( ) ;
6051
6152 this . setState ( { logged : 1 } ) ;
62- Office . context . mailbox . item . body . getAsync ( Office . CoercionType . Html , async ( result ) => {
53+ Office . context . mailbox . item . getAsFileAsync ( async ( result ) => {
54+ if ( ! result . value && result . error ) {
55+ this . context . showHttpErrorMessage ( result . error ) ;
56+ this . setState ( { logged : 0 } ) ;
57+ return ;
58+ }
59+
60+ const parser = new PostalMime ( ) ;
61+ const email = await parser . parse ( atob ( result . value ) ) ;
62+ const doc = new DOMParser ( ) . parseFromString ( email . html , 'text/html' ) ;
63+
64+ let node : Element = doc . getElementById ( 'appendonsend' ) ;
65+ // Remove the history and only log the most recent message.
66+ while ( node ) {
67+ const next = node . nextElementSibling ;
68+ node . parentNode . removeChild ( node ) ;
69+ node = next ;
70+ }
6371 const msgHeader = `<div>${ _t ( 'From : %(email)s' , {
64- email : Office . context . mailbox . item . sender . emailAddress ,
72+ email : email . from . address ,
6573 } ) } </div>`;
74+ doc . body . insertAdjacentHTML ( 'afterbegin' , msgHeader ) ;
6675 const msgFooter = `<br/><div class="text-muted font-italic">${ _t (
6776 'Logged from' ,
6877 ) } <a href="https://www.odoo.com/documentation/master/applications/productivity/mail_plugins.html" target="_blank">${ _t (
6978 'Outlook Inbox' ,
7079 ) } </a></div>`;
71- const body = result . value . split ( '<div id="x_appendonsend"></div>' ) [ 0 ] ; // Remove the history and only log the most recent message.
72- const message = msgHeader + body + msgFooter ;
73- const doc = new DOMParser ( ) . parseFromString ( message , 'text/html' ) ;
74- const officeAttachmentDetails = Office . context . mailbox . item . attachments ;
75- let totalSize = 0 ;
76- const promises : any [ ] = [ ] ;
77- const requestJson = {
78- res_id : this . props . resId ,
79- model : this . props . model ,
80- message : message ,
81- attachments : [ ] ,
82- } ;
83-
84- //check if attachment size is bigger then the threshold
85- officeAttachmentDetails . forEach ( ( officeAttachment ) => {
86- totalSize += officeAttachment . size ;
87- } ) ;
80+ doc . body . insertAdjacentHTML ( 'beforeend' , msgFooter ) ;
8881
82+ const totalSize = email . attachments . reduce ( ( sum , attachment ) => {
83+ return sum + attachment . content . byteLength ;
84+ } , 0 ) ;
8985 if ( totalSize > SIZE_THRESHOLD_TOTAL * 1024 * 1024 ) {
9086 const warningMessage = _t (
9187 'Warning: Attachments could not be logged in Odoo because their total size' +
9288 ' exceeded the allowed maximum.' ,
9389 {
94- size : SIZE_THRESHOLD_SINGLE_ELEMENT ,
90+ size : SIZE_THRESHOLD_TOTAL ,
9591 } ,
9692 ) ;
9793 doc . body . innerHTML += `<div class="text-danger">${ warningMessage } </div>` ;
98- } else {
99- officeAttachmentDetails . forEach ( ( attachment , index ) => {
100- promises . push ( this . fetchAttachmentContent ( attachment , index ) ) ;
101- } ) ;
94+ email . attachments = [ ] ;
10295 }
10396
104- const results = await Promise . all ( promises ) ;
105-
106- let attachments = [ ] ;
107- let oversizeAttachments = [ ] ;
108- let inlineAttachments = [ ] ;
109-
110- results . forEach ( ( result ) => {
111- if ( result . inline ) {
112- inlineAttachments [ result . index ] = result ;
97+ const standardAttachments = [ ] ;
98+ const oversizedAttachments = [ ] ;
99+ const inlineAttachments = { } ;
100+ for ( const attachment of email . attachments ) {
101+ if ( attachment . disposition === 'inline' ) {
102+ inlineAttachments [ attachment . contentId ] = attachment ;
103+ } else if ( attachment . content . byteLength > SIZE_THRESHOLD_SINGLE_ELEMENT * 1024 * 1024 ) {
104+ oversizedAttachments . push ( attachment . filename ) ;
113105 } else {
114- if ( result . oversize ) {
115- oversizeAttachments . push ( {
116- name : result . name ,
117- } ) ;
118- } else {
119- attachments . push ( [ result . name , result . content ] ) ;
120- }
121- }
122- } ) ;
123- // a counter is needed to map img tags with attachments, as outlook does not provide
124- // an id that enables us to match an img with an attachment.
125- let j = 0 ;
126- const imageElements = doc . getElementsByTagName ( 'img' ) ;
127-
128- inlineAttachments . forEach ( ( inlineAttachment ) => {
129- if ( inlineAttachment != null && inlineAttachment . error == undefined ) {
130- if ( inlineAttachment . oversize ) {
131- imageElements [ j ] . setAttribute (
132- 'alt' ,
133- _t ( 'Could not display image %(attachmentName)s, size is over limit' , {
134- attachmentName : inlineAttachment . name ,
135- } ) ,
136- ) ;
137- } else {
138- const fileExtension = inlineAttachment . name . split ( '.' ) [ 1 ] ;
139- imageElements [ j ] . setAttribute (
140- 'src' ,
141- `data:image/${ fileExtension } ;base64, ${ inlineAttachment . content } ` ,
142- ) ;
143- }
144- j ++ ;
106+ standardAttachments . push ( [ attachment . filename , attachment . content ] ) ;
145107 }
146- } ) ;
108+ }
147109
148- if ( oversizeAttachments . length > 0 ) {
149- const attachmentNames = oversizeAttachments . map ( ( attachment ) => `"${ attachment . name } "` ) . join ( ', ' ) ;
110+ if ( oversizedAttachments . length > 0 ) {
150111 const warningMessage = _t (
151112 'Warning: Could not fetch the attachments %(attachments)s as their sizes are bigger then the maximum size of %(size)sMB per each attachment.' ,
152113 {
153- attachments : attachmentNames ,
154- size : SIZE_THRESHOLD_TOTAL ,
114+ attachments : oversizedAttachments . join ( ', ' ) ,
115+ size : SIZE_THRESHOLD_SINGLE_ELEMENT ,
155116 } ,
156117 ) ;
157118 doc . body . innerHTML += `<div class="text-danger">${ warningMessage } </div>` ;
158119 }
159120
160- requestJson . message = doc . body . innerHTML ;
161- requestJson . attachments = attachments ;
121+ const imageElements = Array . from ( doc . getElementsByTagName ( 'img' ) ) . filter ( ( img ) =>
122+ img . getAttribute ( 'src' ) ?. startsWith ( 'cid:' ) ,
123+ ) ;
124+ imageElements . forEach ( ( element ) => {
125+ const attachment = inlineAttachments [ `<${ element . src . replace ( / ^ c i d : / , '' ) } >` ] ;
126+ if ( attachment ?. content . byteLength > SIZE_THRESHOLD_SINGLE_ELEMENT * 1024 * 1024 ) {
127+ element . setAttribute (
128+ 'alt' ,
129+ _t ( 'Could not display image %(attachmentName)s, size is over limit' , {
130+ attachmentName : attachment . filename ,
131+ } ) ,
132+ ) ;
133+ } else if ( attachment ) {
134+ const fileExtension = attachment . filename . split ( '.' ) [ 1 ] ;
135+ element . setAttribute (
136+ 'src' ,
137+ `data:image/${ fileExtension } ;base64, ${ this . arrayBufferToBase64 ( attachment . content ) } ` ,
138+ ) ;
139+ }
140+ } ) ;
141+
142+ const requestJson = {
143+ res_id : this . props . resId ,
144+ model : this . props . model ,
145+ message : doc . documentElement . outerHTML ,
146+ attachments : standardAttachments ,
147+ } ;
162148
163149 const logRequest = sendHttpRequest (
164150 HttpVerb . POST ,
0 commit comments