diff options
| author | Divyesh Makwana (Open ERP) <mdi@tinyerp.com> | 2012-04-12 12:06:52 +0530 |
|---|---|---|
| committer | Divyesh Makwana (Open ERP) <mdi@tinyerp.com> | 2012-04-12 12:06:52 +0530 |
| commit | 492c856bbcec5a855bcf38dd2dbd26c0892e8b24 (patch) | |
| tree | 6e2398a1a5bacc60a26c702c231783fa204d0140 /addons/mail/static/src | |
| parent | 2df24b6d9212e2a52d75e616db668dce7afa75ef (diff) | |
| parent | e44315701dd1bdb6996f24c40be7ac0640aaffe5 (diff) | |
[Merge] Merge with main branch
bzr revid: mdi@tinyerp.com-20120412063652-e4vgtl5cbr117c4g
Diffstat (limited to 'addons/mail/static/src')
| -rw-r--r-- | addons/mail/static/src/css/mail.css | 259 | ||||
| -rw-r--r-- | addons/mail/static/src/css/mail_group.css | 79 | ||||
| -rw-r--r-- | addons/mail/static/src/img/_al.png | bin | 0 -> 1017 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/_fp.png | bin | 0 -> 7725 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/_pinky.png | bin | 0 -> 7725 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/email_icon.png | bin | 0 -> 2772 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/feeds-hover.png | bin | 0 -> 41761 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/feeds.png | bin | 0 -> 13316 bytes | |||
| -rw-r--r-- | addons/mail/static/src/img/groupdefault.png | bin | 0 -> 19910 bytes | |||
| -rw-r--r-- | addons/mail/static/src/js/mail.js | 779 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/mail.xml | 98 |
11 files changed, 1215 insertions, 0 deletions
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css new file mode 100644 index 00000000000..d920455537f --- /dev/null +++ b/addons/mail/static/src/css/mail.css @@ -0,0 +1,259 @@ +/* ------------------------------ */ +/* Wall */ +/* ------------------------------ */ + +div.oe_mail_wall { + overflow: auto; + padding: 5px; +} + +div.oe_mail_wall_search { + width: 55%; +} + +/* 2 columns view */ +div.oe_mail_wall_left { + float: left; + width: 65%; +} + +div.oe_mail_wall_right { + float: right; + width: 34%; +} + +.oe_mail_wall_thread div.oe_mail_thread_act .oe_mail_action_textarea, div.oe_mail_thread_subthread .oe_mail_action_textarea { + height: 20px; + padding: 2px; +} + +.oe_mail_thread_subthread div.oe_mail_thread_act .oe_mail_msg_image, .oe_mail_thread_subthread div.oe_mail_thread_display .oe_mail_msg_image { + width: 30px; + height: 30px; +} + +.oe_mail_thread_subthread .oe_mail_msg_content, .oe_mail_thread_subthread .oe_mail_msg_content { + margin-left: 40px; +} + +div.oe_mail_wall_more { + text-align: center; + display: none; +} + +/* ------------------------------ */ +/* RecordThread */ +/* ------------------------------ */ + +.oe_mail_recthread { + overflow: auto; + padding: 5px 5px 5px 5px; +} + +/* Left-side CSS */ +.oe_mail_recthread_actions { + margin-bottom: 10px; +} + +.oe_mail_recthread_followers { + margin-bottom: 10px; +} + +.oe_mail_followers_action, .oe_mail_followers_display { +} + +/* RecordThread: 2 columns view */ +.oe_mail_recthread_left { + float: left; + width: 55%; +} + +.oe_mail_recthread_right { + float: right; + width: 25%; + margin-right: 5%; +} + +.oe_mail_button_follow, .oe_mail_button_unfollow { + width: 120px; +} + +.oe_mail_button_followers { + display: inline; + width: 120px; +} + +.oe_mail_followers h4 { + margin: 0 0 8px; +} + +/* ------------------------------ */ +/* ThreadDisplay */ +/* ------------------------------ */ + +div.oe_mail_thread_act { + white-space: normal; + margin-bottom: 5px; + padding: 4px; +} + +div.oe_mail_thread_display { + white-space: normal; + margin-bottom: 5px; +} + +div.oe_mail_thread_subthread { + margin-top: 8px; + padding-left: 5%; +} + +div.oe_mail_thread_more { + display: none; + border-bottom: 1px solid #D2D9E7; +} + +li.oe_mail_thread_msg { + padding: 1px; +} + +.notification, .email { + background: #E0E0E0; + padding: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + border-radius: 4px; +} + +.comment { + padding: 4px; +} + +.notification:after, .email:after, .comment:after { + content: ""; + display: block; + clear: both; +} + +li.oe_mail_thread_msg:after, div.oe_mail_thread_act:after { + content: ""; + display: block; + clear: both; +} + +.oe_mail_msg_content { + margin-left: 60px; +} + +.oe_mail_msg_content:after { + content: ""; + display: block; + clear: both; +} + +.oe_mail_action_textarea { + height: 50px; + padding: 5px; +} + +.oe_mail_msg_image { + margin-right: 8px; + width: 45px; + height: 45px; + text-align: center; + overflow: hidden; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + clip: rect(5px, 40px, 45px, 0px); +} + +p.oe_mail_msg_p_email_header { + border-bottom: 1px solid #D2D9E7; +} + +.oe_mail_msg_body a.reduce, .oe_mail_msg_body_short a.expand { + color: #4E43E7; +} + +/* ------------------------------ */ +/* Styling (should be openerp) */ +/* ------------------------------ */ + +ul.oe_mail { + padding: 0; + margin: 0; +} + +li.oe_mail { + list-style-type: none; + margin-bottom: 2px; +} + +input.oe_mail, textarea.oe_mail { + width: 100%; + 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; +} + +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); +} + +p.oe_mail_msg { + padding: 0; + margin: 0; +} + +.oe_mail_oe_right { + float: right; + margin-right: 8px; +} + +.oe_mail_oe_left { + float: left; + margin-right: 8px; +} + +.oe_mail_oe_fade { + color: #888888; +} + +.oe_mail_oe_bold { + font-weight: bold; +} + +a.oe_mail_oe_intlink { + color: #8786b7; +} + +.oe_mail_oe_warning, .oe_mail_oe_warning a { + color: #C03000; +} + +.oe_mail_oe_space { + margin-left: 15px; +} diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css new file mode 100644 index 00000000000..90261499753 --- /dev/null +++ b/addons/mail/static/src/css/mail_group.css @@ -0,0 +1,79 @@ +.oe_group_vignette { + padding: 8px 0; + min-height: 100px; +} + +.oe_group_image, .oe_group_details { + display: inline-block; + vertical-align: top; +} + +.oe_group_image { + width: 100px; + height: 100px; + text-align: center; + overflow: hidden; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -o-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); +} + +.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); +} + +.oe_group_details { + width: 220px; + font-size: 13px; + padding: 2px 5px; + color: #4c4c4c; + min-height: 120px; +} + +.oe_group_details a, .oe_group_details a:hover { + font-weight: bold; + color: #4c4c4c; +} + +.oe_group_details h4 { + margin: 0; + font-size: 13px; +} + +.oe_group_details h4 a { + color: #4c4c4c; +} + +.oe_group_details h4 a:hover { + text-decoration: underline; +} + +.oe_group_details ul { + margin: 3px 0 5px; + padding: 0; + list-style: none; +} + +.oe_group_details li { + margin: 2px 0; +} + +.oe_group_description { +} + +.oe_kanban_record div.true { +} diff --git a/addons/mail/static/src/img/_al.png b/addons/mail/static/src/img/_al.png Binary files differnew file mode 100644 index 00000000000..f6843ddf95c --- /dev/null +++ b/addons/mail/static/src/img/_al.png diff --git a/addons/mail/static/src/img/_fp.png b/addons/mail/static/src/img/_fp.png Binary files differnew file mode 100644 index 00000000000..17dce2a047c --- /dev/null +++ b/addons/mail/static/src/img/_fp.png diff --git a/addons/mail/static/src/img/_pinky.png b/addons/mail/static/src/img/_pinky.png Binary files differnew file mode 100644 index 00000000000..69c18a32976 --- /dev/null +++ b/addons/mail/static/src/img/_pinky.png diff --git a/addons/mail/static/src/img/email_icon.png b/addons/mail/static/src/img/email_icon.png Binary files differnew file mode 100644 index 00000000000..6390d9e6e69 --- /dev/null +++ b/addons/mail/static/src/img/email_icon.png diff --git a/addons/mail/static/src/img/feeds-hover.png b/addons/mail/static/src/img/feeds-hover.png Binary files differnew file mode 100644 index 00000000000..81f098d35c2 --- /dev/null +++ b/addons/mail/static/src/img/feeds-hover.png diff --git a/addons/mail/static/src/img/feeds.png b/addons/mail/static/src/img/feeds.png Binary files differnew file mode 100644 index 00000000000..5ca849e168c --- /dev/null +++ b/addons/mail/static/src/img/feeds.png diff --git a/addons/mail/static/src/img/groupdefault.png b/addons/mail/static/src/img/groupdefault.png Binary files differnew file mode 100644 index 00000000000..fd826837266 --- /dev/null +++ b/addons/mail/static/src/img/groupdefault.png diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js new file mode 100644 index 00000000000..fbe817c2c81 --- /dev/null +++ b/addons/mail/static/src/js/mail.js @@ -0,0 +1,779 @@ +openerp.mail = function(session) { + var _t = session.web._t, + _lt = session.web._lt; + + var mail = session.mail = {}; + + /** + * 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], } } + */ + 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); + } + // 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; + } + + /* Add ThreadDisplay widget to registry */ + session.web.form.widgets.add( + 'Thread', 'openerp.mail.Thread'); + session.web.page.readonly.add( + 'Thread', 'openerp.mail.Thread'); + + /** + * ThreadDisplay 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]. + */ + mail.Thread = session.web.Widget.extend({ + template: '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 {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); + 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 || 100; + this.params.limit = this.params.limit || 100; + 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.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_more = (this.params.thread_level == 0); + // not used currently + this.intlinks_mapping = {}; + }, + + start: function() { + this._super.apply(this, arguments); + // customize display + if (! this.display.show_post_comment) this.$element.find('div.oe_mail_thread_act').hide(); + // add events + this.add_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(); + return display_done + }, + + add_events: function() { + var self = 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) { + 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 + 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_act:first'); + act_dom.toggle(); + event.preventDefault(); + }); + // event: click on 'delete' in msg + 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('li.oe_mail_thread_msg').eq(0).hide(); + if (self.params.thread_level > 0) { + $(event.srcElement).parents('ul.oe_mail_thread').eq(0).hide(); + } + return false; + }); + // event: click on 'hide' in msg + 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('li.oe_mail_thread_msg').eq(0).hide(); + if (self.params.thread_level > 0) { + $(event.srcElement).parents('ul.oe_mail_thread').eq(0).hide(); + } + return false; + }); + // event: click on an internal link + this.$element.find('div.oe_mail_thread_display').delegate('a.intlink', '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']]}); + }); + }, + + destroy: function () { + this._super.apply(this, arguments); + }, + + 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); + 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) { + if (records.length <= self.params.limit) self.display.show_more = false; + else { self.display.show_more = true; records.pop(); } + self.display_comments(records); + 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(); + }); + return defer; + }, + + display_comments_from_parameters: function (records) { + 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); + 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; + }, + + display_comments: function (records) { + var self = this; + this.cs = this.sort_comments_tmp(records); + + /* WIP: map matched regexp -> records to browse with name */ + //_(records).each(function (record) { + //self.do_check_internal_links(record.body_text); + //}); + + _(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']; + _(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) { + sub_msgs.push(record); + } + }); + 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('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); + } + }); + // update offset for "More" buttons + if (this.params.thread_level == 0) this.params.offset += records.length; + }, + + /** + * Display a record + */ + display_comment: function (record) { + 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]); } + // body text manipulation + record.body = this.do_clean_text(record.body); + record.tr_body = this.do_truncate_string(record.body, this.params.msg_more_limit); + record.body = this.do_replace_internal_links(record.body); + if (record.tr_body) record.tr_body = this.do_replace_internal_links(record.tr_body); + // render + $(session.web.qweb.render('ThreadMsg', {'record': record, 'thread': this, 'params': this.params, 'display': this.display}) + ).appendTo(this.$element.children('div.oe_mail_thread_display:first')); + // truncated: hide full-text, show summary, add buttons + if (record.tr_body) { + var node_body = this.$element.find('span.oe_mail_msg_body:last').append(' <a href="#" class="reduce">[ ... Show less]</a>'); + var node_body_short = this.$element.find('span.oe_mail_msg_body_short:last').append(' <a href="#" class="expand">[ ... Show more]</a>'); + node_body.hide(); + node_body.find('a:last').click(function() { node_body.hide(); node_body_short.show(); return false; }); + node_body_short.find('a:last').click(function() { node_body_short.hide(); node_body.show(); return false; }); + } + }, + + /** + * 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)); + }, + + 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( + 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) + * @returns {Array} fetch_domain (OpenERP domain style) + */ + get_fetch_domain: function (sorted_comments) { + var domain = []; + var ids = sorted_comments.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 + ids.push(id); + ids2.push(id); + }); + if (this.params.parent_id != false) { + ids2.push(this.params.parent_id); + } + // must not be children of already fetched messages + if (ids.length > 0) { + domain.push('&'); + domain.push('!'); + domain.push(['id', 'child_of', ids]); + } + if (ids2.length > 0) { + domain.push(['id', 'not in', ids2]); + } + return domain; + }, + + do_more: function () { + domain = this.get_fetch_domain(this.sorted_comments); + return this.fetch_comments(this.params.limit, this.params.offset, domain); + }, + + 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="intlink oe_mail_oe_intlink" 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 || ''); + }, + + do_truncate_string: function(string, max_length) { + if (string.length <= (max_length * 1.2)) return false; + else return string.slice(0, max_length); + }, + + do_clean_text: function (string) { + var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('<(/)?(b|em)\\s*>', 'gi'), '<$1$2>'); + return html; + }, + + /** + * + * var regex_login = new RegExp(/(^|\s)@(\w*[a-zA-Z_.]+\w*\s)/g); + * 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); + }, + + }); + + + /* Add ThreadView widget to registry */ + session.web.form.widgets.add( + 'ThreadView', 'openerp.mail.RecordThread'); + session.web.page.readonly.add( + 'ThreadView', 'openerp.mail.RecordThread'); + + /* ThreadView widget: thread of comments */ + mail.RecordThread = session.web.form.Field.extend({ + // QWeb template to use when rendering the object + form_template: 'RecordThread', + + init: function() { + this._super.apply(this, arguments); + this.see_subscribers = true; + this.thread = null; + // datasets + 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'); }).mouseleave(function () { $(this).html('Not following'); }); + this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); }) + .mouseover(function () { $(this).html('Unfollow'); }).mouseleave(function () { $(this).html('Following'); }); + this.reinit(); + }, + + destroy: function () { + this._super.apply(this, arguments); + }, + + 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(); + }, + + set_value: function() { + this._super.apply(this, arguments); + var self = this; + this.reinit(); + if (! this.view.datarecord.id) { this.$element.find('ul.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(); + if (this.thread) this.thread.destroy(); + // hack: for groups and users + if (this.view.model == 'mail.group') thread_level = 1; + if (this.view.model == 'res.users') thread_level = 1; + else thread_level = 0; + this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid, + 'thread_level': 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_get_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers')); + }, + + display_subscribers: function (records) { + var self = this; + this.is_subscriber = false; + var sub_node = this.$element.find('div.oe_mail_recthread_followers') + sub_node.empty(); + $('<h4/>').html('Followers (' + records.length + ')').appendTo(sub_node); + _(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); + $('<img class="oe_mail_oe_left oe_mail_msg_image" src="' + mini_url + '" title="' + record.name + '" alt="' + record.name + '"/>').appendTo(sub_node); + }); + 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 || ''); + }, + }); + + + /* Add WallView widget to registry */ + session.web.client_actions.add('mail.all_feeds', 'session.mail.WallView'); + + /* WallView widget: a wall of messages */ + mail.WallView = session.web.Widget.extend({ + template: '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] + */ + init: function (parent, params) { + this._super(parent); + this.params = {}; + this.params.limit = params.limit || 25; + this.params.domain = params.domain || []; + this.params.context = params.context || {}; + 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': {}}; + this.display_show_more = true; + this.thread_list = []; + this.search = {'domain': [], 'context': {}, 'groupby': {}} + this.search_results = {'domain': [], 'context': {}, 'groupby': {}} + // datasets + this.ds_msg = new session.web.DataSet(this, 'mail.message'); + this.ds_thread = new session.web.DataSet(this, 'mail.thread'); + this.ds_users = new session.web.DataSet(this, 'res.users'); + }, + + start: function () { + this._super.apply(this, arguments); + var self = this; + // 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); + // fetch first threads + var comments_ready = this.init_and_fetch_comments(this.params.limit, 0); + return (search_view_ready && comments_ready); + }, + + stop: function () { + this._super.apply(this, arguments); + }, + + /** Add events */ + add_event_handlers: function () { + var self = this; + // post a comment + this.$element.find('button.oe_mail_wall_button_comment').click(function () { return self.do_comment(); }); + // display more threads + this.$element.find('button.oe_mail_wall_button_more').click(function () { return self.do_more(); }); + }, + + /** + * Loads the mail.message search view + * @param {Number} view_id id of the search view to load + * @param {Object} defaults ?? + * @param {Boolean} hidden some kind of trick we do not care here + */ + load_search_view: function (view_id, defaults, hidden) { + var self = this; + this.searchview = new session.web.SearchView(this, this.ds_msg, view_id || false, defaults || {}, hidden || false); + var search_view_loaded = this.searchview.appendTo(this.$element.find('div.oe_mail_wall_search')); + return $.when(search_view_loaded).then(function () { + self.searchview.on_search.add(self.do_searchview_search); + }); + }, + + /** + * Aggregate the domains, contexts and groupbys in parameter + * with those from search form, and then calls fetch_comments + * to actually fetch comments + * @param {Array} domains + * @param {Array} contexts + * @param {Array} groupbys + */ + do_searchview_search: function(domains, contexts, groupbys) { + var self = this; + this.rpc('/web/session/eval_domain_and_context', { + domains: domains || [], + contexts: contexts || [], + group_by_seq: groupbys || [] + }, function (results) { + self.search_results['context'] = results.context; + self.search_results['domain'] = results.domain; + self.search_results['groupby'] = results.group_by; + return self.init_and_fetch_comments(); + }); + }, + + /** + * Initializes the wall and calls fetch_comments + * @param {Number} limit: number of notifications to fetch + * @param {Number} offset: offset in notifications search + * @param {Array} domain + * @param {Array} context + */ + init_and_fetch_comments: function() { + this.search['domain'] = _.union(this.params.domain, this.search_results.domain); + this.search['context'] = _.extend(this.params.context, this.search_results.context); + this.display_show_more = true; + this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}}; + this.$element.find('div.oe_mail_wall_threads').empty(); + return this.fetch_comments(this.params.limit, 0); + }, + + /** + * Fetches wall messages + * @param {Number} limit: number of notifications to fetch + * @param {Number} offset: offset in notifications search + * @param {Array} domain + * @param {Array} context + */ + fetch_comments: function (limit, offset, additional_domain, additional_context) { + var self = this; + if (additional_domain) var fetch_domain = this.search['domain'].concat(additional_domain); + 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')); + }, + + /** + * @param {Array} records records to show in threads + */ + display_comments: function (records) { + var self = this; + this.do_update_show_more(records.length >= self.params.limit); + this.sort_comments(records); + _(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('WallThreadContainer', {}); + $('<div class="oe_mail_wall_thread">').html(render_res).appendTo(self.$element.find('div.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} + ); + self.thread_list.push(thread); + return thread.appendTo(self.$element.find('div.oe_mail_wall_thread:last')); + }); + // update TODO + this.comments_structure['root_ids'] = _.union(this.comments_structure['root_ids'], this.comments_structure['new_root_ids']); + this.comments_structure['new_root_ids'] = []; + }, + + /** + * 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 + * - 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 () { + var self = this; + var model_to_root = {}; + var fetch_domain = []; + _(this.comments_structure['model_to_root_ids']).each(function (sc_model, model_name) { + fetch_domain.push('|', ['model', '!=', model_name], '!', ['id', 'child_of', sc_model]); + }); + return fetch_domain; + }, + + /** Display update: show more button */ + do_update_show_more: function (new_value) { + if (new_value != undefined) this.display_show_more = new_value; + if (this.display_show_more) this.$element.find('div.oe_mail_wall_more:last').show(); + else this.$element.find('div.oe_mail_wall_more:last').hide(); + }, + + /** Action: Shows more discussions */ + do_more: function () { + 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('textarea.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')); + }, + }); +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml new file mode 100644 index 00000000000..0f49902b6b3 --- /dev/null +++ b/addons/mail/static/src/xml/mail.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<template> + + <div t-name="Wall" class="oe_mail_wall"> + <div class="oe_mail_wall_search"> + </div> + <div class="oe_mail_wall_left"> + <div class="oe_mail_wall_act"> + <textarea class="oe_mail oe_mail_wall_action_textarea" placeholder="Add a personnal message here..."/><br /> + <button class="oe_mail_wall_button_comment" type="button">Post comment</button> + </div> + <div class="oe_mail_wall_threads"> + </div> + <div class="oe_mail_wall_more"> + <button class="oe_mail_wall_button_more" type="button">See more discussions</button> + </div> + </div> + <div class="oe_mail_wall_right"></div> + </div> + + <t t-name="WallThreadContainer"> + </t> + + <div t-name="RecordThread" class="oe_mail_recthread"> + <h4>History and Comments</h4> + <div class="oe_mail_recthread_left"> + </div> + <div class="oe_mail_recthread_right"> + <div class="oe_mail_recthread_actions"> + <button type="button" class="oe_mail_button_follow">Not following</button> + <button type="button" class="oe_mail_button_unfollow">Following</button> + <button type="button" class="oe_mail_button_followers">Display followers</button> + </div> + <div class="oe_mail_recthread_followers"> + <h4>Followers</h4> + <div class="oe_mail_followers_display"> + </div> + </div> + </div> + </div> + + <ul t-name="Thread" class="oe_mail_thread oe_mail"> + <div class="oe_mail_thread_act"> + <img class="oe_mail_msg_image oe_mail_oe_left" alt="User img"/> + <div class="oe_mail_msg_content"> + <textarea class="oe_mail_action_textarea oe_mail" placeholder="Add your comment here..." onfocus="this.value = '';"/><br /> + </div> + </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> + </ul> + + <li t-name="ThreadMsg" t-attf-class="oe_mail_thread_msg oe_mail"> + <div t-attf-class="{record.type}"> + <img class="oe_mail_msg_image oe_mail_oe_left" t-att-src="record.mini_url"/> + <div class="oe_mail_msg_content"> + <t t-if="record.type == 'email'"><t t-call="EmailDisplay" /></t> + <t t-if="record.type == 'notification' || record.type == 'comment'"><t t-call="NoteDisplay" /></t> + </div> + <t t-if="record.type == 'tmp'"><t t-call="ThreadDisplay" /></t> + </div> + </li> + + <t t-name="NoteDisplay"> + <p class="oe_mail_msg"> + <a href="#" class="intlink oe_mail_oe_intlink" t-attf-data-res-model='{params.res_model}' t-attf-data-res-id='{params.res_id}'><t t-raw="record.record_name"/></a> + <span class="oe_mail_msg_body"><t t-raw="record.body"/></span> + <t t-if="record.tr_body"><span class="oe_mail_msg_body_short"><t t-raw="record.tr_body"/></span></t> + <br /> + <span class="oe_mail_oe_fade"> + <a href="#" class="intlink oe_mail_oe_intlink" data-res-model='res.users' t-attf-data-res-id='{record.user_id[0]}'><t t-raw="record.user_id[1]"/></a> + on <t t-raw="record.date"/> + </span> + <t t-if="display['show_reply']"><span class="oe_mail_oe_space"><a href="#" class="oe_mail_msg_reply oe_mail_oe_intlink">Reply</a></span></t> + <t t-if="display['show_delete']"> + <t t-if="thread._is_author(record.user_id[0])"><span class="oe_mail_oe_space"><a href="#" t-attf-data-id='{record.id}' class="oe_mail_msg_delete oe_mail_oe_intlink">Delete</a></span></t> + </t> + <t t-if="display['show_hide']"> + <t t-if="!(thread._is_author(record.user_id[0]))"><span class="oe_mail_oe_space"><a href="#" t-attf-data-id='{record.id}' class="oe_mail_msg_hide oe_mail_oe_intlink">Hide</a></span></t> + </t> + </p> + </t> + + <t t-name="EmailDisplay"> + <p class="oe_mail_msg oe_mail_msg_p_email_header"> + <span class="oe_mail_oe_bold">From:</span> <t t-esc="record.email_from"/> on <span class="oe_mail_oe_fade"><t t-raw="record.date"/></span><br /> + <span class="oe_mail_oe_bold">To:</span> <t t-esc="record.email_to"/><br /> + <span class="oe_mail_oe_bold">Subject:</span> <t t-esc="record.subject"/><br /> + </p> + <p class="oe_mail_msg"> + <span class="oe_mail_msg_body"><t t-raw="record.body"/></span> + <t t-if="record.tr_body"><span class="oe_mail_msg_body_short"><t t-raw="record.tr_body"/></span></t> + </p> + </t> + +</template> |
