diff options
| author | Tejas Tank <tta@openerp.com> | 2012-08-17 16:16:10 +0530 |
|---|---|---|
| committer | Tejas Tank <tta@openerp.com> | 2012-08-17 16:16:10 +0530 |
| commit | f4f0d42a70d62320b0d0b0d3aab16336b696a485 (patch) | |
| tree | ac01c07c02a5284eb67d044e92e3255506e70a08 /addons/mail/static/src | |
| parent | ae1ed5900caf1d0429ef731343af4ae364d989ae (diff) | |
| parent | 6666d5993f9753c6ec3aec721b41ebd3fbc6c18c (diff) | |
Merged with latest.
bzr revid: tta@openerp.com-20120817104610-h7pova57capzm1by
Diffstat (limited to 'addons/mail/static/src')
| -rw-r--r-- | addons/mail/static/src/css/mail.css | 271 | ||||
| -rw-r--r-- | addons/mail/static/src/css/mail_compose_message.css | 16 | ||||
| -rw-r--r-- | addons/mail/static/src/css/mail_group.css | 14 | ||||
| -rw-r--r-- | addons/mail/static/src/img/_fp.png | bin | 7725 -> 0 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/attachment.png | bin | 0 -> 1142 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/checklist.png | bin | 0 -> 976 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/feeds-hover.png | bin | 15696 -> 0 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/feeds.png | bin | 5647 -> 0 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/formatting.png | bin | 0 -> 3466 bytes | |||
| -rw-r--r-- | addons/mail/static/src/js/mail.js | 1124 | ||||
| -rw-r--r-- | addons/mail/static/src/js/mail_followers.js | 122 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/mail.xml | 131 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/mail_followers.xml | 31 |
13 files changed, 1117 insertions, 592 deletions
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 0cf932f9a25..28a32b8c7a3 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -1,7 +1,6 @@ - -/* ------------------------------ */ -/* Wall */ -/* ------------------------------ */ +/* ------------------------------------------------------------ */ +/* Wall +/* ------------------------------------------------------------ */ .openerp div.oe_mail_wall { overflow: auto; @@ -9,6 +8,17 @@ background: white; } +.openerp div.oe_mail_wall_main { + float: left; + width: 560px; + margin: 8px; +} + +.openerp div.oe_mail_wall_aside { + margin-left: 565px; + margin: 8px; +} + .openerp div.oe_mail_wall_action { padding: 8px; background: #eee; @@ -21,36 +31,32 @@ clear: both; } -.openerp .oe_mail_wall_action textarea { +.openerp div.oe_mail_wall_action .oe_mail_msg_content { + width: 484px; +} + +.openerp div.oe_mail_wall_action textarea.oe_mail_compose_textarea, +.openerp div.oe_mail_wall_action div.oe_mail_compose_message_body_text textarea { width: 474px; height: 60px; padding: 4px; - margin-bottom: 8px; - float: right; + margin-bottom: 2px; } -/* 2 columns view */ -.openerp div.oe_mail_wall_main { - float: left; - width: 560px; - margin: 8px; +.openerp ul.oe_mail_wall_threads { + margin-top: 8px; } -.openerp div.oe_mail_wall_aside { - margin-left: 565px; - margin: 8px; -} +/* Specific display of threads in the wall */ +/* ------------------------------------------------------------ */ -/* Threads */ -.openerp .oe_mail_wall_threads { - margin-top: 8px; -} -.openerp .oe_mail_wall_threads textarea { - height: 40px; +.openerp ul.oe_mail_wall_threads .oe_mail_msg_content textarea { + width: 434px; + height: 30px; padding: 4px; } -.openerp .oe_mail_wall_thread:first .oe_mail_msg_notification { +.openerp li.oe_mail_wall_thread:first .oe_mail_msg_notification { border-top: 0; } @@ -64,7 +70,7 @@ width: 486px; } -.openerp div.oe_mail_msg_content li{ +.openerp div.oe_mail_msg_content > li { float: left; margin-right: 3px; } @@ -79,40 +85,32 @@ } -/* ------------------------------ */ -/* RecordThread */ -/* ------------------------------ */ +/* ------------------------------------------------------------ */ +/* RecordThread +/* ------------------------------------------------------------ */ -.openerp div.oe_mail_recthread { +.openerp .oe_form div.oe_chatter { overflow: auto; } -/* Left-side CSS */ -.openerp div.oe_mail_recthread_actions { - margin-bottom: 8px; -} - -.openerp div.oe_mail_recthread_followers { - margin-bottom: 8px; -} - -.openerp div.oe_mail_recthread_followers img.oe_mail_msg_image { - width: 28px; - height: 28px; - margin: 4px; +.openerp div.oe_mail_recthread { + /*overflow: auto;*/ } -/* RecordThread: 2 columns view */ -.openerp div.oe_mail_recthread_left { +.openerp div.oe_mail_recthread_main { float: left; width: 560px; } -.openerp div.oe_mail_recthread_right { +.openerp div.oe_mail_recthread_aside { float: right; width: 250px; } +.openerp div.oe_mail_recthread_actions { + margin-bottom: 8px; +} + .openerp div.oe_mail_recthread_actions button { width: 120px; } @@ -144,16 +142,17 @@ background-image: linear-gradient(to bottom, #dc5f59, #b33630); } -.openerp textarea.oe_mail_action_textarea { - height: 60px; - padding: 5px; +.openerp div.oe_mail_recthread_followers { + margin-bottom: 8px; } -/* ------------------------------ */ -/* ThreadDisplay */ -/* ------------------------------ */ + +/* ------------------------------------------------------------ */ +/* Thread +/* ------------------------------------------------------------ */ .openerp div.oe_mail_thread_action { + display: none; white-space: normal; padding: 8px; background: #eee; @@ -166,6 +165,26 @@ clear: both; } +/* default textarea (oe_mail_compose_textarea), and body_text textarea for compose form view */ +.openerp .oe_mail_msg_content textarea.oe_mail_compose_textarea, +.openerp .oe_mail_msg_content div.oe_mail_compose_message_body_text textarea { + width: 474px; + height: 60px; + padding: 4px; + font-size: 12px; + border: 1px solid #cccccc; +} + +/* default textarea (oe_mail_compose_textarea), and body_text textarea for compose form view */ +.openerp .oe_mail_msg_content textarea.oe_mail_compose_textarea:focus, +.openerp .oe_mail_msg_content div.oe_mail_compose_message_body_text textarea:focus { + outline: 0; + border-color: rgba(82, 168, 236, 0.8); + -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); + -box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); +} + .openerp div.oe_mail_thread_display { white-space: normal; } @@ -174,7 +193,7 @@ margin-left: 66px; } -.openerp div.oe_mail_thread_subthread .oe_mail_thread_msg:last-child { +.openerp div.oe_mail_thread_subthread li.oe_mail_thread_msg:last-child { margin-bottom: 8px; } @@ -183,18 +202,23 @@ border-bottom: 1px solid #D2D9E7; } -.openerp .oe_mail_thread_msg:after { +.openerp li.oe_mail_thread_msg:after { content: ""; display: block; clear: both; } -.openerp .oe_mail_thread_msg > div:after { +.openerp li.oe_mail_thread_msg > div:after { content: ""; display: block; clear: both; } +.openerp div.oe_mail_msg { + padding: 0; + margin: 0 0 4px 0; +} + .openerp .oe_mail_msg_notification, .openerp .oe_mail_msg_comment, .openerp .oe_mail_msg_email { @@ -203,11 +227,6 @@ border-top: 1px solid #ccc; } -.openerp .oe_email_icon { - width: 50px; - height: 50px; -} - .openerp div.oe_mail_thread_subthread .oe_mail_msg_comment { background: #eee; } @@ -230,9 +249,18 @@ clear: both; } -.openerp img.oe_mail_msg_image { +.openerp img.oe_mail_icon { width: 50px; height: 50px; +} + +.openerp img.oe_mail_thumbnail { + width: 28px; + height: 28px; + margin: 4px; +} + +.openerp img.oe_mail_frame { text-align: center; overflow: hidden; -moz-border-radius: 3px; @@ -247,48 +275,60 @@ clip: rect(5px, 40px, 45px, 0px); } -/* ------------------------------ */ -/* Styling (should be openerp) */ -/* ------------------------------ */ +.openerp .oe_mail_invisible { + display: none; +} + +/* ------------------------------------------------------------ */ +/* mail.compose.message form view & OpenERP hacks +/* ------------------------------------------------------------ */ -.openerp input.oe_mail, textarea.oe_mail { - width: 432px; - padding: 4px; - font-size: 12px; - border: 1px solid #cccccc; - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -ms-transition: border linear 0.2s, box-shadow linear 0.2s; - -o-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; +/* form_view: delete white background */ +.openerp .oe_mail_msg_content div.oe_formview { + background-color: transparent; } -.openerp input.oe_mail:focus, textarea.oe_mail:focus { - outline: 0; - border-color: rgba(82, 168, 236, 0.8); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - -box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); +.openerp .oe_mail_msg_content div.oe_form_nosheet { + margin: 0px; } -.openerp div.oe_mail_msg { - padding: 0; - margin: 0 0 4px 0; +.openerp .oe_mail_msg_content table.oe_form_group { + margin: 0px; } -.openerp .oe_mail_oe_bold { - font-weight: bold; +.openerp .oe_mail_msg_content table.oe_form_field, +.openerp .oe_mail_msg_content div.oe_form_field { + padding: 0px; +} + +.openerp .oe_mail_msg_content td.oe_form_group_cell { + vertical-align: bottom; +} + +/* subject: change width */ +.openerp .oe_mail_msg_content .oe_form .oe_form_field input { + width: 472px; +} + +/* body_html: cleditor */ +.openerp .oe_mail_msg_content div.cleditorMain { + border: 1px solid #cccccc; } -/* ------------------------------ */ -/* Messages layout */ -/* ------------------------------ */ +/* destination_partner_ids */ +.openerp .oe_mail_msg_content div.text-core { + height: 22px !important; + width: 472px; +} + +/* buttons */ +.openerp .oe_mail_msg_content .oe_mail_compose_message_icons button.oe_form_button { + padding: 1px; +} + +/* ------------------------------------------------------------ */ +/* Messages layout +/* ------------------------------------------------------------ */ .openerp .oe_mail_msg .oe_mail_msg_title { margin: 0; @@ -309,6 +349,11 @@ color: #888; } +.openerp .oe_mail_msg_footer li { + float: left; + margin-right: 3px; +} + .openerp .oe_mail_msg_body { margin-bottom: .5em; text-align: justify; @@ -318,12 +363,16 @@ display: inline; } -/* Read more/less link */ -.openerp .oe_mail_msg_content .expand, -.openerp .oe_mail_msg_content .reduce { +.openerp .oe_mail_oe_bold { font-weight: bold; } +/* Read more/less link */ +.openerp .oe_mail_msg_content .oe_mail_reduce { + position: absolute; + right: 0; +} + /* Dropdown menu */ .openerp .oe_mail_msg_content .oe_dropdown_toggle { position: absolute; @@ -360,35 +409,3 @@ padding: 0; list-style-type: square; } - - -/* ------------------------------ */ -/* Group Form */ -/* ------------------------------ */ - -div.oe_mail_group_advanced_details { - display: none; -} - -.oe_form_sheetbg.openerp_mail_group_sheet { - min-height: 0px; - max-height: none; -} - -.oe_form_sheetbg.openerp_mail_group_sheet .oe_form_sheet { - min-height: 0px; - max-height: none; - padding: 0px 18px; - max-width: 80%; -} - -/* Resize group logo */ -.oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_image > img { - max-width: 100px; - max-height: 100px; -} - -/* Resize group description */ -.oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_text > textarea { - height: 40px; -} diff --git a/addons/mail/static/src/css/mail_compose_message.css b/addons/mail/static/src/css/mail_compose_message.css new file mode 100644 index 00000000000..4cd1339ded4 --- /dev/null +++ b/addons/mail/static/src/css/mail_compose_message.css @@ -0,0 +1,16 @@ +/* ------------------------------ */ +/* Compose Message Wizard Form */ +/* ------------------------------ */ + +.openerp tr td .oe_form_field.oe_mail_compose_message_invisible { + display: none; +} + +.openerp .oe_mail_compose_message_icons { + text-align: right; +} + +.openerp .oe_mail_compose_message_icons img { + width: 20px; + height: 20px; +} diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css index b03be086fc6..8f4ca61d531 100644 --- a/addons/mail/static/src/css/mail_group.css +++ b/addons/mail/static/src/css/mail_group.css @@ -20,12 +20,6 @@ max-width: 80%; } -/* Resize group logo */ -.openerp .oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_image > img { - max-width: 100px; - max-height: 100px; -} - /* Resize group description */ .openerp .oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_text > textarea { height: 40px; @@ -63,14 +57,8 @@ .oe_group_photo { width: 100px; - height: auto; - clip: rect(0px, 100px, 100px, 0px); -} - -.oe_group_photo_wide { height: 100px; - width: auto; - clip: rect(0px, 115px, 100px, 15px); + clip: rect(0px, 100px, 100px, 0px); } .oe_group_details { diff --git a/addons/mail/static/src/img/_fp.png b/addons/mail/static/src/img/_fp.png Binary files differdeleted file mode 100644 index 17dce2a047c..00000000000 --- a/addons/mail/static/src/img/_fp.png +++ /dev/null diff --git a/addons/mail/static/src/img/attachment.png b/addons/mail/static/src/img/attachment.png Binary files differnew file mode 100644 index 00000000000..5cc0c33257d --- /dev/null +++ b/addons/mail/static/src/img/attachment.png diff --git a/addons/mail/static/src/img/checklist.png b/addons/mail/static/src/img/checklist.png Binary files differnew file mode 100644 index 00000000000..d252606ff53 --- /dev/null +++ b/addons/mail/static/src/img/checklist.png diff --git a/addons/mail/static/src/img/feeds-hover.png b/addons/mail/static/src/img/feeds-hover.png Binary files differdeleted file mode 100644 index 67070d3e7df..00000000000 --- a/addons/mail/static/src/img/feeds-hover.png +++ /dev/null diff --git a/addons/mail/static/src/img/feeds.png b/addons/mail/static/src/img/feeds.png Binary files differdeleted file mode 100644 index ac2293c0bf5..00000000000 --- a/addons/mail/static/src/img/feeds.png +++ /dev/null diff --git a/addons/mail/static/src/img/formatting.png b/addons/mail/static/src/img/formatting.png Binary files differnew file mode 100644 index 00000000000..cf45fdf192f --- /dev/null +++ b/addons/mail/static/src/img/formatting.png diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 2140bbe474a..8443c6dbf86 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -4,211 +4,656 @@ openerp.mail = function(session) { var mail = session.mail = {}; + openerp_mail_followers(session, mail); // import mail_followers.js + /** - * Add records to sorted_comments array - * @param {Array} records records from mail.message sorted by date desc - * @returns {Object} cs comments_structure: dict - * cs.model_to_root_ids = {model: [root_ids], } - * cs.new_root_ids = [new_root_ids] - * cs.root_ids = [root_ids] - * cs.msgs = {record.id: record,} - * cs.tree_struct = {record.id: { - * 'level': record_level in hierarchy, 0 is root, - * 'msg_nbr': number of childs, - * 'direct_childs': [msg_ids], - * 'all_childs': [msg_ids], - * 'for_thread_msgs': [records], - * 'ancestors': [msg_ids], } } + * ------------------------------------------------------------ + * FormView + * ------------------------------------------------------------ + * + * Override of formview do_action method, to catch all return action about + * mail.compose.message. The purpose is to bind 'Send by e-mail' buttons + * and redirect them to the Chatter. */ - function tools_sort_comments(cs, records, parent_id) { - var cur_iter = 0; var max_iter = 10; var modif = true; - while ( modif && (cur_iter++) < max_iter) { - modif = false; - _(records).each(function (record) { - // root and not yet recorded - if ( (record.parent_id == false || record.parent_id[0] == parent_id) && ! cs['msgs'][record.id]) { - // add to model -> root_list ids - if (! cs['model_to_root_ids'][record.model]) cs['model_to_root_ids'][record.model] = [record.id]; - else cs['model_to_root_ids'][record.model].push(record.id); - // add root data - cs['new_root_ids'].push(record.id); - // add record - cs['tree_struct'][record.id] = {'level': 0, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [record], 'msg_nbr': -1, 'ancestors': []}; - cs['msgs'][record.id] = record; - modif = true; - } - // not yet recorded, but parent is recorded - else if (! cs['msgs'][record.id] && cs['msgs'][record.parent_id[0]]) { - var parent_level = cs['tree_struct'][record.parent_id[0]]['level']; - // update parent structure - cs['tree_struct'][record.parent_id[0]]['direct_childs'].push(record.id); - cs['tree_struct'][record.parent_id[0]]['for_thread_msgs'].push(record); - // update ancestors structure - for (ancestor_id in cs['tree_struct'][record.parent_id[0]]['ancestors']) { - cs['tree_struct'][ancestor_id]['all_childs'].push(record.id); + + session.web.FormView = session.web.FormView.extend({ + do_action: function(action, on_close) { + if (action.res_model == 'mail.compose.message' && this.fields && this.fields.message_ids && this.fields.message_ids.view.get("actual_mode") != 'create') { + var record_thread = this.fields.message_ids; + var thread = record_thread.thread; + thread.instantiate_composition_form('comment', true, false, 0, action.context); + return false; + } + else { + return this._super(action, on_close); + } + }, + }); + + + /** + * ------------------------------------------------------------ + * ChatterUtils + * ------------------------------------------------------------ + * + * This class holds a few tools method that will be used by + * the various Chatter widgets. + */ + + mail.ChatterUtils = { + + /** + * mail_int_mapping: structure to keep a trace of internal links mapping + * mail_int_mapping['model'] = { + * 'name_get': [[id,label], [id,label], ...] + * 'fetch_ids': [id, id, ...] } */ + //var mail_int_mapping = {}; + + /** + * mail_msg_struct: structure to orrganize chatter messages + */ + //var mail_msg_struct = {}; // TODO: USE IT OR NOT :) + + /* generic chatter events binding */ + bind_events: function(widget) { + // event: click on an internal link to a document: model, login + widget.$element.delegate('a.oe_mail_internal_link', 'click', function (event) { + event.preventDefault(); + // lazy implementation: fetch data and try to redirect + if (! event.srcElement.dataset.resModel) return false; + else var res_model = event.srcElement.dataset.resModel; + var res_login = event.srcElement.dataset.resLogin; + if (! res_login) return false; + var ds = new session.web.DataSet(widget, res_model); + var defer = ds.call('search', [[['login', '=', res_login]]]).pipe(function (records) { + if (records[0]) { + widget.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(records[0]), views: [[false, 'form']]}); } - // add record - cs['tree_struct'][record.id] = {'level': parent_level+1, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [], 'msg_nbr': -1, 'ancestors': []}; - cs['msgs'][record.id] = record; - modif = true; + else return false; + }); + }); + }, + + /** get an image in /web/binary/image?... */ + get_image: function(session_prefix, session_id, model, field, id) { + return session_prefix + '/web/binary/image?session_id=' + session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); + }, + + /** checks if tue current user is the message author */ + is_author: function (widget, message_user_id) { + return (widget.session && widget.session.uid != 0 && widget.session.uid == message_user_id); + }, + + /** + * Add records to comments_structure array + * @param {Array} records records from mail.message sorted by date desc + * @returns {Object} cs comments_structure: dict + * cs.model_to_root_ids = {model: [root_ids], } + * cs.new_root_ids = [new_root_ids] + * cs.root_ids = [root_ids] + * cs.msgs = {record.id: record,} + * cs.tree_struct = {record.id: { + * 'level': record_level in hierarchy, 0 is root, + * 'msg_nbr': number of childs, + * 'direct_childs': [msg_ids], + * 'all_childs': [msg_ids], + * 'for_thread_msgs': [records], + * 'ancestors': [msg_ids], } } + */ + records_struct_add_records: function(cs, records, parent_id) { + var cur_iter = 0; var max_iter = 10; var modif = true; + while ( modif && (cur_iter++) < max_iter) { + modif = false; + _(records).each(function (record) { + // root and not yet recorded + if ( (record.parent_id == false || record.parent_id[0] == parent_id) && ! cs['msgs'][record.id]) { + // add to model -> root_list ids + if (! cs['model_to_root_ids'][record.model]) cs['model_to_root_ids'][record.model] = [record.id]; + else cs['model_to_root_ids'][record.model].push(record.id); + // add root data + cs['new_root_ids'].push(record.id); + // add record + cs['tree_struct'][record.id] = {'level': 0, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [record], 'msg_nbr': -1, 'ancestors': []}; + cs['msgs'][record.id] = record; + modif = true; + } + // not yet recorded, but parent is recorded + else if (! cs['msgs'][record.id] && cs['msgs'][record.parent_id[0]]) { + var parent_level = cs['tree_struct'][record.parent_id[0]]['level']; + // update parent structure + cs['tree_struct'][record.parent_id[0]]['direct_childs'].push(record.id); + cs['tree_struct'][record.parent_id[0]]['for_thread_msgs'].push(record); + // update ancestors structure + for (ancestor_id in cs['tree_struct'][record.parent_id[0]]['ancestors']) { + cs['tree_struct'][ancestor_id]['all_childs'].push(record.id); + } + // add record + cs['tree_struct'][record.id] = {'level': parent_level+1, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [], 'msg_nbr': -1, 'ancestors': []}; + cs['msgs'][record.id] = record; + modif = true; + } + }); + } + return cs; + }, + + /* copy cs.new_root_ids into cs.root_ids */ + records_struct_update_after_display: function(cs) { + // update TODO + cs['root_ids'] = _.union(cs['root_ids'], cs['new_root_ids']); + cs['new_root_ids'] = []; + return cs; + }, + + /** + * CONTENT MANIPULATION + * + * Regular expressions + * - (^|\s)@((\w|@|\.)*): @login@log.log, supports inner '@' for + * logins that are emails + * 1. '(void)' + * 2. login@log.log + * - (^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]: [ir.attachment,3|My Label], + * for internal links to model ir.attachment, id=3, and with + * optional label 'My Label'. Note that having a '|Label' is not + * mandatory, because the regex should still be correct. + * 1. '(void)' + * 2. 'ir' + * 3. 'attachment' + * 4. '3' + * 5. 'My Label' + */ + + /** Removes html tags, except b, em, br, ul, li */ + do_text_remove_html_tags: function (string) { + var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('<(/)?(b|em|br|br /|ul|li|div)\\s*>', 'gi'), '<$1$2>'); + return html; + }, + + /** Replaces line breaks by html line breaks (br) */ + do_text_nl2br: function (str, is_xhtml) { + var break_tag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>'; + return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ break_tag +'$2'); + }, + + /* Add a prefix before each new line of the original string */ + do_text_quote: function (str, prefix) { + return str.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ break_tag +'$2' + prefix || '> '); + }, + + /** + * Replaces some expressions + * - @login - shorcut to link to a res.user, given its login + * - [ir.attachment,3|My Label] - shortcut to an internal + * document + * - :name - shortcut to an image + */ + do_replace_expressions: function (string) { + var self = this; + var icon_list = ['al', 'pinky'] + /* shortcut to user: @login */ + var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); + var regex_res = regex_login.exec(string); + while (regex_res != null) { + var login = regex_res[2]; + string = string.replace(regex_res[0], regex_res[1] + '<a href="#" class="oe_mail_internal_link" data-res-model="res.users" data-res-login = ' + login + '>@' + login + '</a>'); + regex_res = regex_login.exec(string); + } + /* shortcut for internal document */ + var regex_login = new RegExp(/(^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]/g); + var regex_res = regex_login.exec(string); + while (regex_res != null) { + var res_model = regex_res[2] + '.' + regex_res[3]; + var res_id = regex_res[4]; + if (! regex_res[5]) { + var label = res_model + ':' + res_id } + else { + var label = regex_res[5]; + } + string = string.replace(regex_res[0], regex_res[1] + '<a href="#model=' + res_model + '&id=' + res_id + '>' + label + '</a>'); + regex_res = regex_login.exec(string); + } + /* special shortcut: :name, try to find an icon if in list */ + var regex_login = new RegExp(/(^|\s):((\w)*)/g); + var regex_res = regex_login.exec(string); + while (regex_res != null) { + var icon_name = regex_res[2]; + if (_.include(icon_list, icon_name)) + string = string.replace(regex_res[0], regex_res[1] + '<img src="/mail/static/src/img/_' + icon_name + '.png" width="22px" height="22px" alt="' + icon_name + '"/>'); + regex_res = regex_login.exec(string); + } + return string; + }, + + /** + * Checks a string to find an expression that will be replaced + * by an internal link and requiring a name_get to replace + * the expression. + * :param mapping: structure to keep a trace of internal links mapping + * mapping['model'] = { + * name_get': [[id,label], [id,label], ...] + * 'to_fetch_ids': [id, id, ...] + * } + * CURRENTLY NOT IMPLEMENTED */ + do_check_for_name_get_mapping: function(string, mapping) { + /* shortcut to user: @login */ + //var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); + //var regex_res = regex_login.exec(string); + //while (regex_res != null) { + //var login = regex_res[2]; + //if (! ('res.users' in this.map_hash)) { this.map_hash['res.users']['name'] = []; } + //this.map_hash['res.users']['login'].push(login); + //regex_res = regex_login.exec(string); + //} + /* document link with name_get: [res.model,name] */ + /* internal link with id: [res.model,id], or [res.model,id|display_name] */ + //var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g); + //regex_res = regex_intlink.exec(string); + //while (regex_res != null) { + //var res_model = regex_res[2] + '.' + regex_res[3]; + //var res_name = regex_res[4]; + //if (! (res_model in this.map_hash)) { this.map_hash[res_model]['name'] = []; } + //this.map_hash[res_model]['name'].push(res_name); + //regex_res = regex_intlink.exec(string); + //} + }, + + /** + * Updates the mapping; check for to_fetch_ids for each recorded + * model, and perform a name_get to update the mapping. + * CURRENTLY NOT IMPLEMENTED */ + do_update_name_get_mapping: function(mapping) { + }, + }; + + + /** + * ------------------------------------------------------------ + * ComposeMessage widget + * ------------------------------------------------------------ + * + * This widget handles the display of a form to compose a new message. + * This form is an OpenERP form_view, build on a mail.compose.message + * wizard. + */ + + mail.ComposeMessage = session.web.Widget.extend({ + template: 'mail.compose_message', + + /** + * @param {Object} parent parent + * @param {Object} [params] + * @param {String} [params.res_model] res_model of document [REQUIRED] + * @param {Number} [params.res_id] res_id of record [REQUIRED] + * @param {Number} [params.email_mode] true/false, tells whether + * we are in email sending mode + * @param {Number} [params.formatting] true/false, tells whether + * we are in advance formatting mode + * @param {String} [params.model] mail.compose.message.mode (see + * composition wizard) + * @param {Number} [params.msg_id] id of a message in case we are in + * reply mode + */ + init: function(parent, params) { + var self = this; + this._super(parent); + // options + this.params = params || {}; + this.params.context = params.context || {}; + this.params.email_mode = params.email_mode || false; + this.params.formatting = params.formatting || false; + this.params.mode = params.mode || 'comment'; + this.params.form_xml_id = params.form_xml_id || 'email_compose_message_wizard_form_chatter'; + this.params.form_view_id = false; + if (this.params.mode == 'reply') { + this.params.active_id = this.params.msg_id; + } else { + this.params.active_id = this.params.res_id; + } + this.email_mode = false; + this.formatting = false; + }, + + /** + * Reinitialize the widget field values to the default values. The + * purpose is to avoid to destroy and re-build a form view. Default + * values are therefore given as for an onchange. */ + reinit: function() { + var self = this; + if (! this.form_view) return; + var call_defer = this.ds_compose.call('default_get', [['subject', 'body_text', 'body_html', 'dest_partner_ids'], this.ds_compose.get_context()]).then( + function (result) { + self.form_view.on_processed_onchange({'value': result}, []); + }); + return call_defer; + }, + + /** + * Override-hack of do_action: clean the form */ + do_action: function(action, on_close) { + // this.init_comments(); + return this._super(action, on_close); + }, + + /** + * Widget start function + * - builds and initializes the form view */ + start: function() { + var self = this; + this._super.apply(this, arguments); + // customize display: add avatar, clean previous content + var user_avatar = mail.ChatterUtils.get_image(this.session.prefix, + this.session.session_id, 'res.users', 'image_small', this.session.uid); + this.$element.find('img.oe_mail_icon').attr('src', user_avatar); + this.$element.find('div.oe_mail_msg_content').empty(); + // create a context for the default_get of the compose form + var widget_context = { + 'active_model': this.params.res_model, + 'active_id': this.params.active_id, + 'mail.compose.message.mode': this.params.mode, + }; + var context = _.extend({}, this.params.context, widget_context); + this.ds_compose = new session.web.DataSetSearch(this, 'mail.compose.message', context); + // find the id of the view to display in the chatter form + var data_ds = new session.web.DataSetSearch(this, 'ir.model.data'); + var deferred_form_id =data_ds.call('get_object_reference', ['mail', this.params.form_xml_id]).then( function (result) { + if (result) { + self.params.form_view_id = result[1]; } + }).pipe(this.proxy('create_form_view')); + return deferred_form_id; + }, + + /** + * Create a FormView, then append it to the to widget DOM. */ + create_form_view: function () { + var self = this; + // destroy previous form_view if any + if (this.form_view) { this.form_view.destroy(); } + // create the FormView + this.form_view = new session.web.FormView(this, this.ds_compose, this.params.form_view_id, { + action_buttons: false, + pager: false, + initial_mode: 'edit', + disable_autofocus: true, }); - } - return cs; - } + // add the form, bind events, activate the form + var msg_node = this.$element.find('div.oe_mail_msg_content'); + return $.when(this.form_view.appendTo(msg_node)).pipe(function() { + self.bind_events(); + self.form_view.do_show(); + if (self.params.email_mode) { self.toggle_email_mode(); } + if (self.params.formatting) { self.toggle_formatting_mode(); } + }); + }, + + destroy: function() { + this._super.apply(this, arguments); + }, + + /** + * Bind events in the widget. Each event is slightly described + * in the function. */ + bind_events: function() { + var self = this; + this.$element.find('button.oe_form_button').click(function (event) { + event.preventDefault(); + }); + // event: click on 'Send an Email' link that toggles the form for + // sending an email (partner_ids) + this.$element.find('a.oe_mail_compose_message_email').click(function (event) { + event.preventDefault(); + self.toggle_email_mode(); + }); + // event: click on 'Formatting' icon-link that toggles the advanced + // formatting options for writing a message (subject, body_html) + this.$element.find('a.oe_mail_compose_message_formatting').click(function (event) { + event.preventDefault(); + self.toggle_formatting_mode(); + }); + // event: click on 'Attachment' icon-link that opens the dialog to + // add an attachment. + this.$element.find('a.oe_mail_compose_message_attachment').click(function (event) { + event.preventDefault(); + // not yet implemented + self.set_body_value('attachment', 'attachment'); + }); + // event: click on 'Checklist' icon-link that toggles the options + // for adding checklist. + this.$element.find('a.oe_mail_compose_message_checklist').click(function (event) { + event.preventDefault(); + // not yet implemented + self.set_body_value('checklist', 'checklist'); + }); + }, + + /** + * Toggle the formatting mode. */ + toggle_formatting_mode: function() { + var self = this; + this.formatting = ! this.formatting; + // calls onchange + var call_defer = this.ds_compose.call('onchange_formatting', [[], this.formatting, this.params.res_model, this.params.res_id]).then( + function (result) { + self.form_view.on_processed_onchange(result, []); + }); + // update context of datasetsearch + this.ds_compose.context.formatting = this.formatting; + // toggle display + this.$element.find('span.oe_mail_compose_message_subject').toggleClass('oe_mail_compose_message_invisible'); + this.$element.find('div.oe_mail_compose_message_body_text').toggleClass('oe_mail_compose_message_invisible'); + this.$element.find('div.oe_mail_compose_message_body_html').toggleClass('oe_mail_compose_message_invisible'); + }, + + /** + * Toggle the email mode. */ + toggle_email_mode: function() { + var self = this; + this.email_mode = ! this.email_mode; + // calls onchange + var call_defer = this.ds_compose.call('onchange_email_mode', [[], this.email_mode, this.params.res_model, this.params.res_id]).then( + function (result) { + self.form_view.on_processed_onchange(result, []); + }); + // update context of datasetsearch + this.ds_compose.context.email_mode = this.email_mode; + // update 'Post' button -> 'Send' + // update 'Send an Email' link -> 'Post a comment' + if (this.email_mode) { + this.$element.find('button.oe_mail_compose_message_button_send').html('<span>Send</span>'); + this.$element.find('a.oe_mail_compose_message_email').html('Comment'); + } else { + this.$element.find('button.oe_mail_compose_message_button_send').html('<span>Post</span>'); + this.$element.find('a.oe_mail_compose_message_email').html('Send an Email'); + } + // toggle display + this.$element.find('div.oe_mail_compose_message_partner_ids').toggleClass('oe_mail_compose_message_invisible'); + }, + + /** + * Update the values of the composition form; with possible different + * values for body_text and body_html. */ + set_body_value: function(body_text, body_html) { + this.form_view.fields.body_text.set_value(body_text); + this.form_view.fields.body_html.set_value(body_html); + }, + }), /** - * ThreadDisplay widget: this widget handles the display of a thread of - * messages. The [thread_level] parameter sets the thread level number: + * ------------------------------------------------------------ + * Thread Widget + * ------------------------------------------------------------ + * + * This widget handles the display of a thread of messages. The + * [thread_level] parameter sets the thread level number: * - root message * - - sub message (parent_id = root message) * - - - sub sub message (parent id = sub message) * - - sub message (parent_id = root message) - * This widget has 2 ways of initialization, either you give records to be rendered, - * either it will fetch [limit] messages related to [res_model]:[res_id]. + * This widget has 2 ways of initialization, either you give records + * to be rendered, either it will fetch [limit] messages related to + * [res_model]:[res_id]. */ + mail.Thread = session.web.Widget.extend({ - template: 'mail.Thread', + template: 'mail.thread', /** * @param {Object} parent parent * @param {Object} [params] - * @param {String} [params.res_model] res_model of mail.thread object - * @param {Number} [params.res_id] res_id of record - * @param {Number} [params.parent_id=false] parent_id of message - * @param {Number} [params.uid] user id - * @param {Number} [params.thread_level=0] number of levels in the thread (only 0 or 1 currently) - * @param {Number} [params.msg_more_limit=100] number of character to display before having a "show more" link; - * note that the text will not be truncated if it does not have 110% of - * the parameter (ex: 110 characters needed to be truncated and be displayed - * as a 100-characters message) - * @param {Number} [params.limit=10] maximum number of messages to fetch + * @param {String} [params.res_model] res_model of document [REQUIRED] + * @param {Number} [params.res_id] res_id of record [REQUIRED] + * @param {Number} [params.uid] user id [REQUIRED] + * @param {Bool} [params.parent_id=false] parent_id of message + * @param {Number} [params.thread_level=0] number of levels in the thread + * (only 0 or 1 currently) + * @param {Bool} [params.is_wall=false] thread is displayed in the wall + * @param {Number} [params.msg_more_limit=150] number of character to + * display before having a "show more" link; note that the text + * will not be truncated if it does not have 110% of the parameter + * (ex: 110 characters needed to be truncated and be displayed as + * a 100-characters message) + * @param {Number} [params.limit=100] maximum number of messages to fetch * @param {Number} [params.offset=0] offset for fetching messages * @param {Number} [params.records=null] records to show instead of fetching messages */ init: function(parent, params) { this._super(parent); + // options this.params = params; this.params.parent_id = this.params.parent_id || false; this.params.thread_level = this.params.thread_level || 0; - this.params.msg_more_limit = this.params.msg_more_limit || 290; + this.params.is_wall = this.params.is_wall || (this.params.records != undefined) || false; + this.params.msg_more_limit = this.params.msg_more_limit || 250; this.params.limit = this.params.limit || 100; + // this.params.limit = 3; // tmp for testing this.params.offset = this.params.offset || 0; this.params.records = this.params.records || null; // datasets and internal vars - this.ds = new session.web.DataSet(this, this.params.res_model); - this.ds_users = new session.web.DataSet(this, 'res.users'); - this.ds_msg = new session.web.DataSet(this, 'mail.message'); - this.sorted_comments = {'root_ids': [], 'root_id_msg_list': {}}; + this.ds = new session.web.DataSetSearch(this, this.params.res_model); + this.ds_users = new session.web.DataSetSearch(this, 'res.users'); + this.ds_msg = new session.web.DataSetSearch(this, 'mail.message'); this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}}; // display customization vars this.display = {}; this.display.show_post_comment = this.params.show_post_comment || false; - this.display.show_reply = (this.params.thread_level > 0); - this.display.show_delete = true; - this.display.show_hide = this.params.show_hide || false; + this.display.show_reply = (this.params.thread_level > 0 && this.params.is_wall); + this.display.show_delete = ! this.params.is_wall; + this.display.show_hide = this.params.is_wall; + this.display.show_reply_by_email = ! this.params.is_wall; this.display.show_more = (this.params.thread_level == 0); - // not used currently - this.intlinks_mapping = {}; }, start: function() { - var self = this; - this._super.apply(this, arguments); - // customize display - if (! this.display.show_post_comment) { - this.$element.find('div.oe_mail_thread_action').hide(); - } // add events - this.add_events(); - + this.bind_events(); // display user, fetch comments this.display_current_user(); - - if (this.params.records) { - var display_done = this.display_comments_from_parameters(this.params.records); - } else { - var display_done = this.init_comments(); + if (this.params.records) var display_done = this.display_comments_from_parameters(this.params.records); + else var display_done = this.init_comments(); + // customize display + $.when(display_done).then(this.proxy('do_customize_display')); + // add message composition form view + if (this.display.show_post_comment) { + var compose_done = this.instantiate_composition_form(); } - return display_done + return display_done && compose_done; }, - - add_events: function() { + + /** + * Override-hack of do_action: automatically reload the chatter. + * Normally it should be called only when clicking on 'Post/Send' + * in the composition form. */ + do_action: function(action, on_close) { + this.init_comments(); + if (this.compose_message_widget) { + this.compose_message_widget.reinit(); } + return this._super(action, on_close); + }, + + instantiate_composition_form: function(mode, email_mode, formatting, msg_id, context) { + if (this.compose_message_widget) { + this.compose_message_widget.destroy(); + } + this.compose_message_widget = new mail.ComposeMessage(this, { + 'extended_mode': false, 'uid': this.params.uid, 'res_model': this.params.res_model, + 'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id, + 'email_mode': email_mode || false, 'formatting': formatting || false, + 'context': context || false } ); + var composition_node = this.$element.find('div.oe_mail_thread_action'); + composition_node.empty(); + var compose_done = this.compose_message_widget.appendTo(composition_node); + return compose_done; + }, + + do_customize_display: function() { + if (this.display.show_post_comment) { this.$element.find('div.oe_mail_thread_action').eq(0).show(); } + }, + + /** + * Bind events in the widget. Each event is slightly described + * in the function. */ + bind_events: function() { var self = this; + // generic events from Chatter Mixin + mail.ChatterUtils.bind_events(this); // event: click on 'more' at bottom of thread this.$element.find('button.oe_mail_button_more').click(function () { self.do_more(); }); - // event: writing in textarea - this.$element.find('textarea.oe_mail_action_textarea').keyup(function (event) { + // event: writing in basic textarea of composition form (quick reply) + this.$element.find('textarea.oe_mail_compose_textarea').keyup(function (event) { var charCode = (event.which) ? event.which : window.event.keyCode; if (event.shiftKey && charCode == 13) { this.value = this.value+"\n"; } else if (charCode == 13) { return self.do_comment(); } }); - // event: click on 'reply' in msg + // event: click on 'Reply' in msg this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply', 'click', function (event) { var act_dom = $(this).parents('div.oe_mail_thread_display').find('div.oe_mail_thread_action:first'); act_dom.toggle(); event.preventDefault(); }); - // event: click on 'delete' in msg + // event: click on 'attachment(s)' in msg + this.$element.delegate('a.oe_mail_msg_view_attachments', 'click', function (event) { + var act_dom = $(this).parent().parent().parent().find('.oe_mail_msg_attachments'); + act_dom.toggle(); + event.preventDefault(); + }); + // event: click on 'Delete' in msg side menu this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_delete', 'click', function (event) { - //console.log('deleting'); if (! confirm(_t("Do you really want to delete this message?"))) { return false; } var msg_id = event.srcElement.dataset.id; if (! msg_id) return false; var call_defer = self.ds_msg.unlink([parseInt(msg_id)]); - $(event.srcElement).parents('.oe_mail_thread_msg').eq(0).hide(); + $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide(); if (self.params.thread_level > 0) { $(event.srcElement).parents('.oe_mail_thread').eq(0).hide(); } - return false; + event.preventDefault(); + return call_defer; }); - // event: click on 'hide' in msg + // event: click on 'Hide' in msg side menu this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_hide', 'click', function (event) { - //console.log('hiding'); if (! confirm(_t("Do you really want to hide this thread ?"))) { return false; } var msg_id = event.srcElement.dataset.id; if (! msg_id) return false; - //console.log(msg_id); var call_defer = self.ds.call('message_remove_pushed_notifications', [[self.params.res_id], [parseInt(msg_id)], true]); - $(event.srcElement).parents('.oe_mail_thread_msg').eq(0).hide(); + $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide(); if (self.params.thread_level > 0) { $(event.srcElement).parents('.oe_mail_thread').eq(0).hide(); } - return false; - }); - // event: click on an internal link - this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_internal_link', 'click', function (event) { - // lazy implementation: fetch data and try to redirect - if (! event.srcElement.dataset.resModel) return false; - else var res_model = event.srcElement.dataset.resModel; - var res_login = event.srcElement.dataset.resLogin; - var res_id = event.srcElement.dataset.resId; - if ((! res_login) && (! res_id)) return false; - if (! res_id) { - var ds = new session.web.DataSet(self, res_model); - var defer = ds.call('search', [[['login', '=', res_login]]]).then(function (records) { - if (records[0]) { - self.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(records[0]), views: [[false, 'form']]}); - } - else return false; - }); - } - else self.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(res_id), views: [[false, 'form']]}); - }); - // event: click on 'attachment(s)' in msg - this.$element.delegate('a.oe_mail_msg_view_attachments', 'click', function (event) { - var act_dom = $(this).parent().parent().parent().find('.oe_mail_msg_attachments'); - act_dom.toggle(); - return false; + event.preventDefault(); + return call_defer; }); - // see more - this.$element.on('click','a.oe_mail_msg_more', function (event) { - $(this).siblings('.oe_mail_msg_tail').show(); - $(this).hide(); - return false; + // event: click on "Reply" in msg side menu (email style) + this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply_by_email', 'click', function (event) { + var msg_id = event.srcElement.dataset.msg_id; + var email_mode = (event.srcElement.dataset.type == 'email'); + var formatting = (event.srcElement.dataset.formatting == 'html'); + if (! msg_id) return false; + self.instantiate_composition_form('reply', email_mode, formatting, msg_id); + event.preventDefault(); }); }, @@ -219,21 +664,22 @@ openerp.mail = function(session) { init_comments: function() { var self = this; this.params.offset = 0; - this.sorted_comments = {'root_ids': [], 'root_id_msg_list': {}}; this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}}; this.$element.find('div.oe_mail_thread_display').empty(); - domain = this.get_fetch_domain(this.sorted_comments); + var domain = this.get_fetch_domain(this.comments_structure); return this.fetch_comments(this.params.limit, this.params.offset, domain).then(); }, fetch_comments: function (limit, offset, domain) { var self = this; - var defer = this.ds.call('message_load', [[this.params.res_id], ( (limit+1)||(this.params.limit+1) ), (offset||this.params.offset), (domain||[]), (this.params.thread_level > 0), (this.sorted_comments['root_ids'])]); - $.when(defer).then(function (records) { + var defer = this.ds.call('message_read', [[this.params.res_id], (this.params.thread_level > 0), (this.comments_structure['root_ids']), + (limit+1) || (this.params.limit+1), offset||this.params.offset, domain||undefined ]).then(function (records) { if (records.length <= self.params.limit) self.display.show_more = false; - else { self.display.show_more = true; records.pop(); } - + // else { self.display.show_more = true; records.pop(); } + // else { self.display.show_more = true; records.splice(0, 1); } + else { self.display.show_more = true; } self.display_comments(records); + // TODO: move to customize display if (self.display.show_more == true) self.$element.find('div.oe_mail_thread_more:last').show(); else self.$element.find('div.oe_mail_thread_more:last').hide(); }); @@ -245,6 +691,7 @@ openerp.mail = function(session) { if (records.length > 0 && records.length < (records[0].child_ids.length+1) ) this.display.show_more = true; else this.display.show_more = false; var defer = this.display_comments(records); + // TODO: move to customize display if (this.display.show_more == true) $('div.oe_mail_thread_more').eq(-2).show(); else $('div.oe_mail_thread_more').eq(-2).hide(); return defer; @@ -252,7 +699,8 @@ openerp.mail = function(session) { display_comments: function (records) { var self = this; - + // sort the records + mail.ChatterUtils.records_struct_add_records(this.comments_structure, records, this.params.parent_id); //build attachments download urls and compute time-relative from dates for (var k in records) { records[k].timerelative = $.timeago(records[k].date); @@ -263,11 +711,10 @@ openerp.mail = function(session) { } } } - this.cs = this.sort_comments_tmp(records); _(records).each(function (record) { var sub_msgs = []; if ((record.parent_id == false || record.parent_id[0] == self.params.parent_id) && self.params.thread_level > 0 ) { - var sub_list = self.cs['tree_struct'][record.id]['direct_childs']; + var sub_list = self.comments_structure['tree_struct'][record.id]['direct_childs']; _(records).each(function (record) { //if (record.parent_id == false || record.parent_id[0] == self.params.parent_id) return; if (_.indexOf(sub_list, record.id) != -1) { @@ -276,124 +723,78 @@ openerp.mail = function(session) { }); self.display_comment(record); self.thread = new mail.Thread(self, {'res_model': self.params.res_model, 'res_id': self.params.res_id, 'uid': self.params.uid, - 'records': sub_msgs, 'thread_level': (self.params.thread_level-1), 'parent_id': record.id}); - self.$element.find('.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread"/>'); + 'records': sub_msgs, 'thread_level': (self.params.thread_level-1), 'parent_id': record.id, + 'is_wall': self.params.is_wall}); + self.$element.find('li.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread"/>'); self.thread.appendTo(self.$element.find('div.oe_mail_thread_subthread:last')); } else if (self.params.thread_level == 0) { self.display_comment(record); } }); + mail.ChatterUtils.records_struct_update_after_display(this.comments_structure); // update offset for "More" buttons if (this.params.thread_level == 0) this.params.offset += records.length; }, - /** - * Display a record - */ + /** Displays a record, performs text/link formatting */ display_comment: function (record) { - record.body = this.do_text_nl2br(record.body, true); + record.body = mail.ChatterUtils.do_text_nl2br($.trim(record.body), true); + // if (record.type == 'email' && record.state == 'received') { if (record.type == 'email') { record.mini_url = ('/mail/static/src/img/email_icon.png'); - } else { - record.mini_url = this.thread_get_avatar('res.users', 'avatar', record.user_id[0]); + } else { + record.mini_url = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', record.user_id[0]); } // body text manipulation - record.body = this.do_clean_text(record.body); - record.body = this.do_replace_internal_links(record.body); - + if (record.subtype == 'plain') { + record.body = mail.ChatterUtils.do_text_remove_html_tags(record.body); + } + record.body = mail.ChatterUtils.do_replace_expressions(record.body); // format date according to the user timezone record.date = session.web.format_value(record.date, {type:"datetime"}); - var rendered = session.web.qweb.render('mail.Thread.message', {'record': record, 'thread': this, 'params': this.params, 'display': this.display}); - $(rendered).appendTo(this.$element.children('div.oe_mail_thread_display:first')); + // is the user the author ? + record.is_author = mail.ChatterUtils.is_author(this, record.user_id[0]); + // render + var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.params, 'display': this.display}); // expand feature - this.$element.find('div.oe_mail_msg_body:last').expander({ + $(rendered).appendTo(this.$element.children('div.oe_mail_thread_display:first')); + this.$element.find('div.oe_mail_msg_record_body').expander({ slicePoint: this.params.msg_more_limit, - expandText: 'see more', - userCollapseText: 'see less', + expandText: 'read more', + userCollapseText: '[^]', detailClass: 'oe_mail_msg_tail', moreClass: 'oe_mail_expand', - lesClass: 'oe_mail_reduce', + lessClass: 'oe_mail_reduce', }); }, - - /** - * Add records to sorted_comments array - * @param {Array} records records from mail.message sorted by date desc - * @returns {Object} sc sorted_comments: dict { - * 'root_id_list': list or root_ids - * 'root_id_msg_list': {'record_id': [ancestor_ids]}, still sorted by date desc - * 'id_to_root': {'root_id': [records]}, still sorted by date desc - * } - */ - sort_comments: function (records) { - var self = this; - sc = {'root_id_list': [], 'root_id_msg_list': {}, 'id_to_root': {}} - var cur_iter = 0; var max_iter = 10; var modif = true; - /* step1: get roots */ - while ( modif && (cur_iter++) < max_iter) { - modif = false; - _(records).each(function (record) { - if ( (record.parent_id == false || record.parent_id[0] == self.params.parent_id) && (_.indexOf(sc['root_id_list'], record.id) == -1)) { - sc['root_id_list'].push(record.id); - sc['root_id_msg_list'][record.id] = []; - self.sorted_comments['root_ids'].push(record.id); - modif = true; - } - else { - if (_.indexOf(sc['root_id_list'], record.parent_id[0]) != -1) { - sc['id_to_root'][record.id] = record.parent_id[0]; - modif = true; - } - else if ( sc['id_to_root'][record.parent_id[0]] ) { - sc['id_to_root'][record.id] = sc['id_to_root'][record.parent_id[0]]; - modif = true; - } - } - }); - } - /* step2: add records */ - _(records).each(function (record) { - var root_id = sc['id_to_root'][record.id]; - if (! root_id) return; - sc['root_id_msg_list'][root_id].push(record); - //self.sorted_comments['root_id_msg_list'][root_id].push(record.id); - }); - return sc; - }, - - /** - * Add records to comments_structure object: see function for details - */ - sort_comments_tmp: function(records) { - return tools_sort_comments(this.comments_structure, records, this.params.parent_id); - }, - + display_current_user: function () { - return this.$element.find('img.oe_mail_msg_image').attr('src', this.thread_get_avatar('res.users', 'avatar', this.params.uid)); + var avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.params.uid); + return this.$element.find('img.oe_mail_icon').attr('src', avatar); }, do_comment: function () { var comment_node = this.$element.find('textarea'); var body_text = comment_node.val(); comment_node.val(''); - return this.ds.call('message_append_note', [[this.params.res_id], 'Reply', body_text, this.params.parent_id, 'comment', 'html']).then( + return this.ds.call('message_append_note', [[this.params.res_id], '', body_text, this.params.parent_id, 'comment', 'plain']).then( this.proxy('init_comments')); }, /** * Create a domain to fetch new comments according to - * comment already present in sorted_comments - * @param {Object} sorted_comments (see sort_comments) + * comment already present in comments_structure + * @param {Object} comments_structure (see chatter utils) * @returns {Array} fetch_domain (OpenERP domain style) */ - get_fetch_domain: function (sorted_comments) { + get_fetch_domain: function (comments_structure) { var domain = []; - var ids = sorted_comments.root_ids.slice(); + var ids = comments_structure.root_ids.slice(); var ids2 = []; // must be child of current parent if (this.params.parent_id) { domain.push(['id', 'child_of', this.params.parent_id]); } - _(sorted_comments.root_ids).each(function (id) { // each record + _(comments_structure.root_ids).each(function (id) { // each record ids.push(id); ids2.push(id); }); @@ -413,208 +814,100 @@ openerp.mail = function(session) { }, do_more: function () { - domain = this.get_fetch_domain(this.sorted_comments); + domain = this.get_fetch_domain(this.comments_structure); return this.fetch_comments(this.params.limit, this.params.offset, domain); }, - - /** - * - * var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); - * var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g); - */ - do_replace_internal_links: function (string) { - var self = this; - var icon_list = ['al', 'pinky'] - /* shortcut to user: @login */ - var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); - var regex_res = regex_login.exec(string); - while (regex_res != null) { - var login = regex_res[2]; - string = string.replace(regex_res[0], regex_res[1] + '<a href="#" class="oe_mail_internal_link" data-res-model="res.users" data-res-login = ' + login + '>@' + login + '</a>'); - regex_res = regex_login.exec(string); - } - /* special shortcut: :name, try to find an icon if in list */ - var regex_login = new RegExp(/(^|\s):((\w)*)/g); - var regex_res = regex_login.exec(string); - while (regex_res != null) { - var icon_name = regex_res[2]; - if (_.include(icon_list, icon_name)) - string = string.replace(regex_res[0], regex_res[1] + '<img src="/mail/static/src/img/_' + icon_name + '.png" width="22px" height="22px" alt="' + icon_name + '"/>'); - regex_res = regex_login.exec(string); - } - return string; - }, - - thread_get_avatar: function(model, field, id) { - return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); - }, - - /** Removes html tags, except b, em, br */ - do_clean_text: function (string) { - var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('<(/)?(b|em|br|br /)\\s*>', 'gi'), '<$1$2>'); - return html; - }, - - /** Replaces line bracks by html line breaks (br) */ - do_text_nl2br: function (str, is_xhtml) { - var break_tag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>'; - return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ break_tag +'$2'); - }, - - /** - * - * var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); - * var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g); - */ - do_check_internal_links: function(string) { - /* shortcut to user: @login */ - var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g); - var regex_res = regex_login.exec(string); - while (regex_res != null) { - var login = regex_res[2]; - if (! ('res.users' in this.map_hash)) { this.map_hash['res.users']['name'] = []; } - this.map_hash['res.users']['login'].push(login); - regex_res = regex_login.exec(string); - } - /* internal links: #res.model,name */ - var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g); - regex_res = regex_intlink.exec(string); - while (regex_res != null) { - var res_model = regex_res[2] + '.' + regex_res[3]; - var res_name = regex_res[4]; - if (! (res_model in this.map_hash)) { this.map_hash[res_model]['name'] = []; } - this.map_hash[res_model]['name'].push(res_name); - regex_res = regex_intlink.exec(string); - } - }, - - /** checks if tue current user is the message author */ - _is_author: function (id) { - return (this.session.uid == id); - }, - }); - session.web.form.widgets.add( 'Thread', 'openerp.mail.Thread'); + + + /** + * ------------------------------------------------------------ + * mail_thread Widget + * ------------------------------------------------------------ + * + * This widget handles the display of the Chatter on documents. + */ + + /* Add mail_thread widget to registry */ + session.web.form.widgets.add('mail_thread', 'openerp.mail.RecordThread'); /** mail_thread widget: thread of comments */ mail.RecordThread = session.web.form.AbstractField.extend({ - template: 'mail.RecordThread', + // QWeb template to use when rendering the object + template: 'mail.record_thread', init: function() { this._super.apply(this, arguments); - this.see_subscribers = true; - this.thread = null; this.params = this.get_definition_options(); this.params.thread_level = this.params.thread_level || 0; - // datasets + this.thread = null; this.ds = new session.web.DataSet(this, this.view.model); this.ds_users = new session.web.DataSet(this, 'res.users'); }, - + start: function() { - this._super.apply(this, arguments); - var self = this; - // bind buttons - this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); }).hide(); - this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); }) - .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) - .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); + // NB: all the widget should be modified to check the actual_mode property on view, not use + // any other method to know if the view is in create mode anymore + this.view.on("change:actual_mode", this, this._check_visibility); + this._check_visibility(); + mail.ChatterUtils.bind_events(this); + this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); }); + if (! this.params.see_subscribers_options) { + this.$element.find('button.oe_mail_button_followers').hide(); } + this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); }); this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); }) .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); this.reinit(); }, - - destroy: function () { - this._super.apply(this, arguments); + + _check_visibility: function() { + this.$element.toggle(this.view.get("actual_mode") !== "create"); }, - reinit: function() { - this.see_subscribers = true; - this.$element.find('button.oe_mail_button_followers').html('Hide followers') - this.$element.find('button.oe_mail_button_follow').hide(); - this.$element.find('button.oe_mail_button_unfollow').hide(); + destroy: function () { + this._super.apply(this, arguments); }, set_value: function() { this._super.apply(this, arguments); - var self = this; - this.reinit(); if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { this.$element.find('.oe_mail_thread').hide(); return; } - // fetch followers - var fetch_sub_done = this.fetch_subscribers(); // create and render Thread widget - this.$element.find('div.oe_mail_recthread_left').empty(); + this.$element.find('div.oe_mail_recthread_main').empty(); if (this.thread) this.thread.destroy(); this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid, - 'thread_level': this.params.thread_level, 'show_post_comment': true, 'limit': 15}); - var thread_done = this.thread.appendTo(this.$element.find('div.oe_mail_recthread_left')); - return fetch_sub_done && thread_done; - }, - - fetch_subscribers: function () { - return this.ds.call('message_read_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers')); - }, - - display_subscribers: function (records) { - var self = this; - this.is_subscriber = false; - var user_list = this.$element.find('ul.oe_mail_followers_display').empty(); - this.$element.find('div.oe_mail_recthread_followers h4').html('Followers (' + records.length + ')'); - _(records).each(function (record) { - if (record.id == self.session.uid) { self.is_subscriber = true; } - var mini_url = self.thread_get_avatar('res.users', 'avatar', record.id); - $('<li><img class="oe_mail_oe_left oe_mail_msg_image" src="' + mini_url + '"/>' + - '<a href="#" class="oe_mail_internal_link" data-res-model="res.users" data-res-id="' + record.id + '">' + record.name + '</a></li>').appendTo(user_list); - }); - if (self.is_subscriber) { - self.$element.find('button.oe_mail_button_follow').hide(); - self.$element.find('button.oe_mail_button_unfollow').show(); } - else { - self.$element.find('button.oe_mail_button_follow').show(); - self.$element.find('button.oe_mail_button_unfollow').hide(); } - }, - - do_follow: function () { - return this.ds.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('fetch_subscribers')); - }, - - do_unfollow: function () { - var self = this; - return this.ds.call('message_unsubscribe', [[this.view.datarecord.id]]).then(function (record) { - if (record == false) self.do_notify("Impossible to unsubscribe", "You are automatically subscribed to this record. You cannot unsubscribe."); - }).pipe(this.proxy('fetch_subscribers')); - }, - - do_toggle_followers: function () { - this.see_subscribers = ! this.see_subscribers; - if (this.see_subscribers) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); } - else { this.$element.find('button.oe_mail_button_followers').html('Display followers'); } - this.$element.find('div.oe_mail_recthread_followers').toggle(); - }, - - thread_get_avatar: function(model, field, id) { - return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); + 'thread_level': this.params.thread_level, 'show_post_comment': true, 'limit': 15}); + var thread_done = this.thread.appendTo(this.$element.find('div.oe_mail_recthread_main')); + return thread_done; }, }); - session.web.form.widgets.add( 'mail_thread', 'openerp.mail.RecordThread'); - /** WallView widget: a wall of messages */ - mail.WallView = session.web.Widget.extend({ - template: 'mail.Wall', + + /** + * ------------------------------------------------------------ + * WallView Widget + * ------------------------------------------------------------ + * + * This widget handles the display of the Chatter on the Wall. + */ + + /* Add WallView widget to registry */ + session.web.client_actions.add('mail.wall', 'session.mail.Wall'); + + /* WallView widget: a wall of messages */ + mail.Wall = session.web.Widget.extend({ + template: 'mail.wall', /** * @param {Object} parent parent * @param {Object} [params] * @param {Number} [params.limit=20] number of messages to show and fetch * @param {Number} [params.search_view_id=false] search view id for messages - * @var {Array} sorted_comments records sorted by res_model and res_id - * records.res_model = {res_ids} - * records.res_model.res_id = [records] + * @var {Array} comments_structure (see chatter utils) */ init: function (parent, params) { this._super(parent); @@ -622,6 +915,8 @@ openerp.mail = function(session) { this.params.limit = params.limit || 25; this.params.domain = params.domain || []; this.params.context = params.context || {}; + this.params.res_model = params.res_model || false; + this.params.res_id = params.res_id || false; this.params.search_view_id = params.search_view_id || false; this.params.thread_level = params.thread_level || 1; this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}}; @@ -637,26 +932,49 @@ openerp.mail = function(session) { start: function () { this._super.apply(this, arguments); - var self = this; this.display_current_user(); // add events this.add_event_handlers(); // load mail.message search view var search_view_ready = this.load_search_view(this.params.search_view_id, {}, false); + // load composition form + var compose_done = this.instantiate_composition_form(); // fetch first threads var comments_ready = this.init_and_fetch_comments(this.params.limit, 0); - return (search_view_ready && comments_ready); + return (search_view_ready && comments_ready && compose_done); }, - - stop: function () { + + /** + * Override-hack of do_action: automatically reload the chatter. + * Normally it should be called only when clicking on 'Post/Send' + * in the composition form. */ + do_action: function(action, on_close) { + this.init_and_fetch_comments(); + if (this.compose_message_widget) { + this.compose_message_widget.reinit(); } + return this._super(action, on_close); + }, + + destroy: function () { this._super.apply(this, arguments); }, - + + instantiate_composition_form: function(mode, msg_id) { + if (this.compose_message_widget) { + this.compose_message_widget.destroy(); + } + this.compose_message_widget = new mail.ComposeMessage(this, { + 'extended_mode': false, 'uid': this.session.uid, 'res_model': this.params.res_model, + 'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id }); + var composition_node = this.$element.find('div.oe_mail_wall_action'); + composition_node.empty(); + var compose_done = this.compose_message_widget.appendTo(composition_node); + return compose_done; + }, + /** Add events */ add_event_handlers: function () { var self = this; - // post a comment - this.$element.find('.oe_mail_wall_action button').click(function () { return self.do_comment(); }); // display more threads this.$element.find('button.oe_mail_wall_button_more').click(function () { return self.do_more(); }); }, @@ -699,13 +1017,9 @@ openerp.mail = function(session) { }, display_current_user: function () { - return this.$element.find('img.oe_mail_msg_image').attr('src', this.thread_get_avatar('res.users', 'avatar', this.session.uid)); - }, - - thread_get_avatar: function(model, field, id) { - return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); - }, - + //return this.$element.find('img.oe_mail_msg_image').attr('src', this.thread_get_avatar('res.users', 'avatar', this.session.uid)); + }, + /** * Initializes the wall and calls fetch_comments * @param {Number} limit: number of notifications to fetch @@ -735,8 +1049,8 @@ openerp.mail = function(session) { else var fetch_domain = this.search['domain']; if (additional_context) var fetch_context = _.extend(this.search['context'], additional_context); else var fetch_context = this.search['context']; - return this.ds_thread.call('get_pushed_messages', - [[this.session.uid], (limit || 0), (offset || 0), fetch_domain, true, [], fetch_context]).then(this.proxy('display_comments')); + return this.ds_thread.call('message_get_pushed_messages', + [[this.session.uid], true, [], (limit || 0), (offset || 0), fetch_domain, fetch_context]).then(this.proxy('display_comments')); }, /** @@ -745,16 +1059,16 @@ openerp.mail = function(session) { display_comments: function (records) { var self = this; this.do_update_show_more(records.length >= self.params.limit); - this.sort_comments(records); + mail.ChatterUtils.records_struct_add_records(this.comments_structure, records, false); _(this.comments_structure['new_root_ids']).each(function (root_id) { var records = self.comments_structure.tree_struct[root_id]['for_thread_msgs']; var model_name = self.comments_structure.msgs[root_id]['model']; var res_id = self.comments_structure.msgs[root_id]['res_id']; - var render_res = session.web.qweb.render('mail.WallThreadContainer', {}); + var render_res = session.web.qweb.render('mail.wall_thread_container', {}); $('<li class="oe_mail_wall_thread">').html(render_res).appendTo(self.$element.find('ul.oe_mail_wall_threads')); var thread = new mail.Thread(self, { 'res_model': model_name, 'res_id': res_id, 'uid': self.session.uid, 'records': records, - 'parent_id': false, 'thread_level': self.params.thread_level, 'show_hide': true} + 'parent_id': false, 'thread_level': self.params.thread_level, 'show_hide': true, 'is_wall': true} ); self.thread_list.push(thread); return thread.appendTo(self.$element.find('li.oe_mail_wall_thread:last')); @@ -765,18 +1079,10 @@ openerp.mail = function(session) { }, /** - * Add records to comments_structure object: see function for details - */ - sort_comments: function(records) { - tools_sort_comments(this.comments_structure, records, false); - }, - - /** * Create a domain to fetch new comments according to - * comments already present in sorted_comments + * comments already present in comments_structure * - for each model: * -- should not be child of already displayed ids - * @param {Object} sorted_comments (see sort_comments) * @returns {Array} fetch_domain (OpenERP domain style) */ get_fetch_domain: function () { @@ -801,17 +1107,5 @@ openerp.mail = function(session) { var domain = this.get_fetch_domain(); return this.fetch_comments(this.params.limit, 0, domain); }, - - /** Action: Posts a comment */ - do_comment: function () { - var comment_node = this.$element.find('.oe_mail_wall_action textarea'); - var body_text = comment_node.val(); - comment_node.val(''); - var call_done = this.ds_users.call('message_append_note', [[this.session.uid], 'Tweet', body_text, false, 'comment', 'html']).then(this.proxy('init_and_fetch_comments')); - }, }); - session.web.client_actions.add('mail.all_feeds', 'session.mail.WallView'); - }; - -// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js new file mode 100644 index 00000000000..0e920f73041 --- /dev/null +++ b/addons/mail/static/src/js/mail_followers.js @@ -0,0 +1,122 @@ +openerp_mail_followers = function(session, mail) { + var _t = session.web._t, + _lt = session.web._lt; + + var mail_followers = session.mail_followers = {}; + + /** + * ------------------------------------------------------------ + * mail_followers Widget + * ------------------------------------------------------------ + * + * This widget handles the display of a list of records as a vetical + * list, with an image on the left. The widget itself is a floatting + * right-sided box. + * This widget is mainly used to display the followers of records + * in OpenChatter. + */ + + /* Add the widget to registry */ + session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers'); + + mail_followers.Followers = session.web.form.AbstractField.extend({ + template: 'mail.followers', + + init: function() { + this._super.apply(this, arguments); + this.params = {}; + this.params.image = this.node.attrs.image || 'image_small'; + this.params.title = this.node.attrs.title || 'Followers'; + this.params.display_followers = true; + this.params.display_control = this.node.attrs.display_control || false; + this.params.display_actions = this.node.attrs.display_actions || false; + this.ds_model = new session.web.DataSetSearch(this, this.view.model); + this.ds_follow = new session.web.DataSetSearch(this, this.field.relation); + }, + + start: function() { + var self = this; + // NB: all the widget should be modified to check the actual_mode property on view, not use + // any other method to know if the view is in create mode anymore + this.view.on("change:actual_mode", this, this._check_visibility); + this._check_visibility(); + this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); }); + if (! this.params.display_control) { + this.$element.find('button.oe_mail_button_followers').hide(); } + this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); }) + .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) + .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); + this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); }) + .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) + .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); + this.reinit(); + }, + + _check_visibility: function() { + this.$element.toggle(this.view.get("actual_mode") !== "create"); + }, + + destroy: function () { + this._super.apply(this, arguments); + }, + + reinit: function() { + this.params.display_followers = true; + this.params.display_control = this.node.attrs.display_control || false; + this.params.display_actions = this.node.attrs.display_actions || false; + this.$element.find('button.oe_mail_button_followers').html('Hide followers') + this.$element.find('button.oe_mail_button_follow').hide(); + this.$element.find('button.oe_mail_button_unfollow').hide(); + }, + + set_value: function(value_) { + this.reinit(); + if (! this.view.datarecord.id || + session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { + this.$element.find('div.oe_mail_recthread_aside').hide(); + return; + } + return this.fetch_subscribers(value_); + }, + + fetch_subscribers: function (value_) { + return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers')); + }, + + /** + * Display the followers. + * TODO: replace the is_subscriber check by fields read */ + display_subscribers: function (records) { + var self = this; + this.is_subscriber = false; + var user_list = this.$element.find('ul.oe_mail_followers_display').empty(); + this.$element.find('div.oe_mail_recthread_followers h4').html(this.params.title + ' (' + records.length + ')'); + _(records).each(function (record) { + if (record.id == self.session.uid) { self.is_subscriber = true; } + record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.users', 'image_small', record.id); + $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(user_list); + }); + if (this.is_subscriber) { + this.$element.find('button.oe_mail_button_follow').hide(); + this.$element.find('button.oe_mail_button_unfollow').show(); } + else { + this.$element.find('button.oe_mail_button_follow').show(); + this.$element.find('button.oe_mail_button_unfollow').hide(); } + }, + + do_follow: function () { + return this.ds_model.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); + }, + + do_unfollow: function () { + return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); + }, + + do_toggle_followers: function () { + this.params.see_subscribers = ! this.params.see_subscribers; + if (this.params.see_subscribers) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); } + else { this.$element.find('button.oe_mail_button_followers').html('Show followers'); } + this.$element.find('div.oe_mail_recthread_followers').toggle(); + }, + }); +}; diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 982fb8aeb2f..f67c4e8676f 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -1,7 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <template> - <div t-name="mail.Wall" class="oe_view_manager oe_mail_wall oe_view_manager_global"> + <!-- + wall main template + Template used to display the communication history in the wall. + --> + <div t-name="mail.wall" class="oe_view_manager oe_mail_wall oe_view_manager_current"> <table class="oe_view_manager_header"> <colgroup> <col width="33%"/> @@ -9,10 +13,13 @@ <col width="34%"/> </colgroup> <tbody> - <tr class="oe_header_row"> + <tr class="oe_header_row oe_header_row_top"> <td colspan="2"> <h2 class="oe_view_title"> - <span class="oe_view_title_text">All Feeds</span> + <span class="oe_view_title_text"> + <t t-if="! widget.params.res_model">News Feeds</t> + <t t-if="widget.params.res_model" t-esc="widget.params.res_model"/> + </span> </h2> </td> <td><div class="oe_view_manager_view_search" t-opentag="true"/></td> @@ -21,30 +28,44 @@ </table> <div class="oe_mail_wall_main"> <div class="oe_mail_wall_action"> - <img class="oe_mail_msg_image oe_left" alt="User img"/> - <textarea class="oe_mail oe_mail_wall_action_textarea" placeholder="What are you working on?"/> - <button class="oe_right oe_mail_wall_button_comment" type="button">Post comment</button> + <!-- call the composition form --> + <t t-call ="mail.compose_message"/> </div> - <div class="oe_clear"></div> + <div class="oe_clear"/> <ul class="oe_mail_wall_threads"> + <!-- contains threads --> </ul> <div class="oe_mail_wall_more"> - <button class="oe_mail_wall_button_more" type="button">See more discussions</button> + <button class="oe_mail_wall_button_more" type="button">See more...</button> </div> </div> - <div class="oe_mail_wall_aside"></div> + <div class="oe_mail_wall_aside"> + <!-- contains currently nothing --> + </div> </div> - <t t-name="mail.WallThreadContainer"> + <!-- + wall_thread_container template for the wall + Each discussion thread is contained inside this template + --> + <t t-name="mail.wall_thread_container"> </t> - <div t-name="mail.RecordThread" class="oe_mail_recthread"> - <div class="oe_mail_recthread_left"></div> - <div class="oe_mail_recthread_right"> + <!-- + record_thread main template + Template used to display the communication history in documents + form view. + --> + <div t-name="mail.record_thread" class="oe_mail_recthread"> + <!-- <h4>History and Comments</h4> --> + <div class="oe_mail_recthread_main"> + <!-- contains the document thread --> + </div> + <div class="oe_mail_recthread_aside"> <div class="oe_mail_recthread_actions"> - <button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button> + <button type="button" class="oe_mail_button_follow">Follow</button> <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button> - <button type="button" class="oe_mail_button_followers">Display followers</button> + <button type="button" class="oe_mail_button_followers">Show followers</button> </div> <div class="oe_mail_recthread_followers"> <h4>Followers</h4> @@ -53,45 +74,80 @@ </div> </div> - <div t-name="mail.Thread" class="oe_mail oe_mail_thread"> + <!-- + mail.compose_message template + This template holds the composition form to write a note or send + an e-mail. It contains by default a textarea, that will be replaced + by another composition form in the main wall composition form, or + for main thread composition form in document form view. + --> + <t t-name="mail.compose_message"> + <div> + <img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/> + <div class="oe_mail_msg_content"> + <!-- contains the composition form --> + <!-- default content: old basic textarea --> + <textarea class="oe_mail_compose_textarea" placeholder="Add your comment here..." onfocus="this.value = '';"/> + </div> + <div class="oe_clear"/> + </div> + </t> + + <!-- + thread template + This template holds a thread of comments. It begins with an actions + container, holding the composition form. Then come the various + messages. Then comes the 'more' button. + --> + <ul t-name="mail.thread" class="oe_mail oe_mail_thread"> <div class="oe_mail_thread_action"> - <img class="oe_mail_msg_image oe_left" alt="User img"/> - <textarea class="oe_mail oe_mail_action_textarea" placeholder="Add your comment here..." onfocus="this.value = '';"/> + <!-- contains the composition box (form + image) --> + <t t-call="mail.compose_message"/> + </div> + <div class="oe_mail_thread_display"> + <!-- contains the threads --> </div> - <div class="oe_mail_thread_display"></div> <div class="oe_mail_thread_more"> <button class="oe_mail_button_more" type="button">Load more messages</button> </div> - </div> + </ul> <!-- default layout --> - <div t-name="mail.Thread.message" class="oe_mail oe_mail_thread_msg"> + <li t-name="mail.thread.message" class="oe_mail oe_mail_thread_msg"> <div t-attf-class="oe_mail_msg_#{record.type}"> - <img class="oe_email_icon oe_left" t-att-src="record.mini_url"/> + <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.mini_url"/> <div class="oe_mail_msg_content"> + <!-- dropdown menu with message options and actions --> <span class="oe_dropdown_toggle oe_dropdown_arrow"> <ul class="oe_dropdown_menu"> - <t t-if="thread._is_author(record.user_id[0]) && display['show_delete']"> - <li><a href="#" t-attf-data-id='{record.id}' class="oe_mail_msg_delete">Delete</a></li> - </t> - <t t-if="!thread._is_author(record.user_id[0]) && display['show_hide']"> - <li><a href="#" t-attf-data-id='{record.id}' class="oe_mail_msg_hide">Hide</a></li> + <t t-if="display['show_delete']"> + <li t-if="record.is_author"><a href="#" class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li> </t> - <li t-if="record.type == 'email'"><a href="#" class="oe_mail_msg_details">Details</a></li> + <li t-if="display['show_hide']"><a href="#" class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Hide</a></li> +<!-- Uncomment when adding subtype hiding + <li t-if="display['show_hide']"> + <a href="#" class="oe_mail_msg_hide_type" t-attf-data-subtype='{record.subtype}'>Hide '<t t-esc="record.subtype"/>' for this document</a> + </li> --> + <li><a href="#" t-attf-data-msg_id="{record.id}" t-attf-data-type="{record.type}" t-attf-data-formatting="{record.content_subtype}" class="oe_mail_msg_reply_by_email">Reply by email</a></li> + <li t-if="record.type == 'email'"><a t-attf-href="#model=mail.message&id=#{record.id}" class="oe_mail_msg_details">Details</a></li> </ul> </span> + <!-- message itself --> <div class="oe_mail_msg"> - <h1 t-if="record.subject && record.subject != 'Reply'" class="oe_mail_msg_title"> + <h1 t-if="record.subject" class="oe_mail_msg_title"> <t t-raw="record.subject"/> </h1> - <div t-if="params.thread_level > 0" class="oe_mail_msg_subtitle"> - <a href="#" t-attf-data-res-model='{params.res_model}' t-attf-data-res-id='{params.res_id}' class="oe_mail_internal_link"><t t-raw="record.record_name"/></a> - </div> <div class="oe_mail_msg_body"> - <a href="#" data-res-model='res.users' t-attf-data-res-id='{record.user_id[0]}' class="oe_mail_internal_link"><t t-raw="record.user_id[1]"/></a> - <t t-raw="record.body"/> + + <div class="oe_mail_msg_record_body"> + <a t-attf-href="#model=#{params.res_model}&id=#{params.res_id}" t-if="(params.thread_level > 0) & (!record.subject)"><t t-raw="record.record_name"/></a> + <t t-raw="record.body"/> + </div> </div> + <div class="oe_clear"/> <ul class="oe_mail_msg_footer"> + <li t-if="record.subject & params.thread_level > 0"><a t-attf-href="#model=#{params.res_model}&id=#{params.res_id}"><t t-raw="record.record_name"/></a></li> + <li><a t-attf-href="#model=res.users&id=#{record.user_id[0]}"><t t-raw="record.user_id[1]"/></a></li> <li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li> <li t-if="display['show_reply']"><a href="#" class="oe_mail_msg_reply">Reply</a></li> <!-- uncomment when merging vote @@ -99,8 +155,8 @@ --> <li t-if="record.attachment_ids.length > 0"> <a href="#" class="oe_mail_msg_view_attachments"> - <t t-if="record.attachment_ids.length == 1">Attachment</t> - <t t-if="record.attachment_ids.length > 1">Attachments (<t t-raw="record.attachment_ids.length"/>)</t> + <t t-if="record.attachment_ids.length == 1">1 Attachment</t> + <t t-if="record.attachment_ids.length > 1"><t t-raw="record.attachment_ids.length"/> Attachments</t> </a> </li> </ul> @@ -115,5 +171,6 @@ </div> </div> </div> - </div> + </li> + </template> diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml new file mode 100644 index 00000000000..53c899b9569 --- /dev/null +++ b/addons/mail/static/src/xml/mail_followers.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<template> + + <!-- + followers main template + Template used to display the followers and the actions in a record. + --> + <div t-name="mail.followers" class="oe_mail_recthread_aside"> + <div class="oe_mail_recthread_actions"> + <button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button> + <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button> + <button type="button" class="oe_mail_button_followers">Show followers</button> + </div> + <div class="oe_mail_recthread_followers"> + <t t-if="widget.params.title"> + <h4><t t-raw="widget.params.title"/></h4> + </t> + <ul class="oe_mail_followers_display"></ul> + </div> + </div> + + <!-- + followers.partner template + Template used to display a partner following the record + --> + <li t-name="mail.followers.partner"> + <img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/> + <a t-attf-href="#model=res.users&id=#{record.id}"><t t-raw="record.name"/></a> + </li> + +</template> |
