[IMP] mail: subtype adapted for css onhover box

bzr revid: chm@openerp.com-20120926071709-v51ra4mzn2hp121j
This commit is contained in:
Christophe Matthieu 2012-09-26 09:17:09 +02:00
parent bf3cb4bcd6
commit 4ddb50dbf9
7 changed files with 165 additions and 104 deletions

View File

@ -201,6 +201,7 @@ class mail_message(osv.Model):
partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
except (orm.except_orm, osv.except_osv):
partner_ids = []
return {
'id': msg.id,
'type': msg.type,
@ -216,6 +217,7 @@ class mail_message(osv.Model):
'partner_ids': partner_ids,
'child_ids': [],
'child_nbr': child_nbr,
'parent_id': msg.parent_id and msg.parent_id.id or False,
'vote_user_ids': vote_ids,
'has_voted': has_voted
}

View File

@ -625,11 +625,6 @@ class mail_thread(osv.AbstractModel):
``(name,content)``, where content is NOT base64 encoded
:return: ID of newly created mail.message
"""
# message_post
# [26] False notification mt_crm_stage False
# message_post
# [26] False notification mt_crm_won False
context = context or {}
attachments = attachments or []
@ -653,6 +648,7 @@ class mail_thread(osv.AbstractModel):
}
attachment_ids.append((0, 0, data_attach))
# get subtype
if subtype:
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', subtype)
subtype_id = ref and ref[1] or False
@ -684,11 +680,10 @@ class mail_thread(osv.AbstractModel):
for x in ('from', 'to', 'cc'):
values.pop(x, None)
print "------------------------------------------"
print values, "mail_thread 688"
print "--------≃============----------------------------------"
return messages.create(cr, uid, values, context=context)
added_message_id = messages.create(cr, uid, values, context=context)
added_message = self.pool.get('mail.message').message_read(cr, uid, [added_message_id])
return added_message
#------------------------------------------------------
# Followers API

View File

@ -50,14 +50,6 @@
.openerp div.oe_mail_recthread_aside input {
cursor:pointer;
}
.openerp div.oe_mail_recthread_subtypes {
display:none;
}
.openerp div.oe_mail_recthread_aside button span {
position: absolute;
top:-7px;
right:5px;
}
/* Specific display of threads in the wall */
/* ------------------------------------------------------------ */
@ -108,7 +100,7 @@
width: 120px;
}
.openerp button.oe_mail_button_mouseout {
.openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
color: white;
background-color: #8a89ba;
background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
@ -118,9 +110,7 @@
background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
}
.openerp button.oe_mail_button_mouseover {
display: none;
.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
color: white;
background-color: #dc5f59;
background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
@ -131,10 +121,40 @@
background-image: linear-gradient(to bottom, #dc5f59, #b33630);
}
.openerp .oe_mail_recthread_aside .oe_follower span {
display:none;
}
.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
display:block;
}
.openerp div.oe_mail_recthread_followers {
margin-bottom: 8px;
}
/* ------------------------------------------------------------ */
/* subtypes
/* ------------------------------------------------------------ */
.openerp .oe_mouse_subtypes {
display:inline-block;
position: relative;
z-index: 5;
}
.openerp .oe_mouse_subtypes .oe_recthread_subtypes {
position: absolute;
z-index: 2;
}
.openerp .oe_mouse_subtypes.oe_mouseout .oe_recthread_subtypes {
display: none;
}
.openerp .oe_mouse_subtypes.oe_mouseover .oe_recthread_subtypes {
display: block;
}
/* ------------------------------------------------------------ */
/* Thread
/* ------------------------------------------------------------ */

View File

@ -110,8 +110,8 @@ openerp.mail = function(session) {
this._super.apply(this, arguments);
// customize display: add avatar, clean previous content
var user_avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
this.$el.find('img.oe_mail_icon').attr('src', user_avatar);
this.$el.find('div.oe_mail_msg_content').empty();
this.$('img.oe_mail_icon').attr('src', user_avatar);
this.$('div.oe_mail_msg_content').empty();
// create a context for the dataset and default_get of the wizard
var context = _.extend({}, this.options.context);
this.ds_compose = new session.web.DataSetSearch(this, 'mail.compose.message', context);
@ -139,21 +139,21 @@ openerp.mail = function(session) {
disable_autofocus: true,
});
// add the form, bind events, activate the form
var msg_node = this.$el.find('div.oe_mail_msg_content');
var msg_node = this.$('div.oe_mail_msg_content');
return $.when(this.form_view.appendTo(msg_node)).pipe(this.proxy('postprocess_create_form_view'));
},
postprocess_create_form_view: function () {
// handle attachment button
this.fileupload_id = _.uniqueId('oe_fileupload');
var button_attach = this.$el.find('button.oe_mail_compose_message_attachment');
var button_attach = this.$('button.oe_mail_compose_message_attachment');
var rendered = session.web.qweb.render('mail.compose_message.add_attachment', {'widget': this});
$(rendered).insertBefore(button_attach);
// move the button inside div.oe_hidden_input_file
var input_node = this.$el.find('input[name=ufile]');
var input_node = this.$('input[name=ufile]');
button_attach.detach().insertAfter(input_node);
// set the function called when attachments are added
this.$el.find('input.oe_form_binary_file').change(this.on_attachment_change);
this.$('input.oe_form_binary_file').change(this.on_attachment_change);
this.bind_events();
this.form_view.do_show();
},
@ -161,7 +161,7 @@ openerp.mail = function(session) {
on_attachment_change: function (event) {
var $target = $(event.target);
if ($target.val() !== '') {
this.$el.find('form.oe_form_binary_form').submit();
this.$('form.oe_form_binary_form').submit();
session.web.blockUI();
}
},
@ -178,11 +178,11 @@ openerp.mail = function(session) {
},
display_attachments: function () {
var attach_node = this.$el.find('div.oe_mail_compose_message_attachments');
var attach_node = this.$('div.oe_mail_compose_message_attachments');
var rendered = session.web.qweb.render('mail.thread.message.attachments', {'record': this});
attach_node.empty();
$(rendered).appendTo(attach_node);
this.$el.find('.oe_mail_msg_attachments').show();
this.$('.oe_mail_msg_attachments').show();
var composer_attachment_ids = _.pluck(this.attachment_ids, 'id');
var onchange_like = {'value': {'attachment_ids': composer_attachment_ids}}
this.form_view.on_processed_onchange(onchange_like, []);
@ -283,6 +283,7 @@ openerp.mail = function(session) {
show_dd_delete: options.show_dd_delete || false,
show_dd_hide: options.show_dd_hide || false,
truncate_limit: options.truncate_limit || 250,
mail_wall: options.mail_wall || this
}
// datasets and internal vars
this.records = {};
@ -309,7 +310,7 @@ openerp.mail = function(session) {
* - show_header_compose: show the composition form in the header */
do_customize_display: function() {
if (this.options.show_header_compose) {
this.$el.find('div.oe_mail_thread_action').eq(0).show();
this.$('div.oe_mail_thread_action').eq(0).show();
}
},
@ -321,7 +322,7 @@ openerp.mail = function(session) {
// event: click on 'More' at bottom of thread
this.$el.on('click', 'a.oe_mail_fetch_more', this.do_message_fetch_more);
// event: writing in basic textarea of composition form (quick reply)
this.$el.find('textarea.oe_mail_compose_textarea').keyup(function (event) {
this.$('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.message_post(); }
@ -405,7 +406,7 @@ openerp.mail = function(session) {
this.compose_message_widget = new mail.ComposeMessage(this, {
'context': _.extend(context || {}, this.context),
});
var composition_node = this.$el.find('div.oe_mail_thread_action');
var composition_node = this.$('div.oe_mail_thread_action');
composition_node.empty();
var compose_done = this.compose_message_widget.appendTo(composition_node);
return compose_done;
@ -418,7 +419,7 @@ openerp.mail = function(session) {
/** Clean the thread */
message_clean: function() {
this.$el.find('div.oe_mail_thread_display').empty();
this.$('div.oe_mail_thread_display').empty();
},
/** Fetch messages
@ -467,9 +468,10 @@ openerp.mail = function(session) {
'show_reply': self.options.show_reply && self.options.thread_level > 1,
'show_reply_by_email': self.options.show_reply_by_email,
'show_dd_hide': self.options.show_dd_hide,
'show_dd_delete': self.options.show_dd_delete });
self.$el.find('li.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread"/>');
self.thread.appendTo(self.$el.find('div.oe_mail_thread_subthread:last'));
'show_dd_delete': self.options.show_dd_delete,
'mail_wall': self.options.mail_wall });
self.$('li.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread" data-msg_id="'+record.res_id+'"/>');
self.thread.appendTo(self.$('div.oe_mail_thread_subthread:last'));
}
});
},
@ -497,7 +499,7 @@ openerp.mail = function(session) {
// render, add the expand feature
var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'options': this.options});
$(rendered).appendTo(this.$el.children('div.oe_mail_thread_display:first'));
this.$el.find('div.oe_mail_msg_body').expander({
this.$('div.oe_mail_msg_body').expander({
slicePoint: this.options.truncate_limit,
expandText: 'read more',
userCollapseText: '[^]',
@ -526,19 +528,41 @@ openerp.mail = function(session) {
display_user_avatar: function () {
var avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
return this.$el.find('img.oe_mail_icon').attr('src', avatar);
return this.$('img.oe_mail_icon').attr('src', avatar);
},
/* Display the message if if the msg_id don't exists.
* If the record have a parent, insert parent or inside parent */
message_fetch_new_data: function(records) {
var self=this;
_(records).each(function (record) {
if( !self.options.mail_wall.$('.oe_mail_thread_msg[data-msg_id="'+record.id+'"]').size() ) {
self.display_record( record );
}
});
},
search_thread: function() {
},
animated_destroy: function(animated) {
//graphic effects
this.destroy();
},
/*post a message and flatch the message*/
message_post: function (body) {
var self = this;
if (! body) {
var comment_node = this.$el.find('textarea');
var comment_node = this.$('textarea');
var body = comment_node.val();
comment_node.val('');
}
return this.ds_thread.call('message_post', [
[this.context.default_res_id], body, false, 'comment', this.context.default_parent_id, undefined]
).pipe(self.message_clean()).pipe(self.message_fetch(false));
[this.context.default_res_id], body, false, 'comment', false, this.context.default_parent_id, undefined])
.then(this.proxy('message_fetch_new_data'));
},
/** Action: 'shows more' to fetch new messages */
@ -585,7 +609,7 @@ openerp.mail = function(session) {
var self = this;
this._super.apply(this, arguments);
if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
this.$el.find('oe_mail_thread').hide();
this.$('oe_mail_thread').hide();
return;
}
// update context
@ -597,14 +621,14 @@ openerp.mail = function(session) {
// create and render Thread widget
var show_header_compose = this.view.is_action_enabled('edit') ||
(this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value());
this.$el.find('div.oe_mail_recthread_main').empty();
this.$('div.oe_mail_recthread_main').empty();
var thread = new mail.Thread(self, domain, this.options.context,
{ 'thread_level': this.options.thread_level,
'show_header_compose': show_header_compose,
'use_composer': show_header_compose,
'show_dd_delete': true,
'show_reply_by_email': show_header_compose });
return thread.appendTo(this.$el.find('div.oe_mail_recthread_main'));
return thread.appendTo(this.$('div.oe_mail_recthread_main'));
},
});
@ -656,7 +680,7 @@ openerp.mail = function(session) {
load_searchview: function (defaults, hidden) {
var self = this;
this.searchview = new session.web.SearchView(this, this.ds_msg, false, defaults || {}, hidden || false);
return this.searchview.appendTo(this.$el.find('.oe_view_manager_view_search')).then(function () {
return this.searchview.appendTo(this.$('.oe_view_manager_view_search')).then(function () {
self.searchview.on_search.add(self.do_searchview_search);
});
},
@ -681,21 +705,29 @@ openerp.mail = function(session) {
return self.message_render();
});
},
search_thread: function(id) {
var self=this;
/*this.thread*/
},
/** Clean and display the threads */
message_render: function () {
this.$el.find('ul.oe_mail_wall_threads').empty();
this.$('ul.oe_mail_wall_threads').empty();
var domain = this.options.domain.concat(this.search_results['domain']);
var render_res = session.web.qweb.render('mail.wall_thread_container', {});
$(render_res).appendTo(this.$el.find('ul.oe_mail_wall_threads'));
var thread = new mail.Thread(this, domain, this.options.context,
$(render_res).appendTo(this.$('ul.oe_mail_wall_threads'));
this.thread = new mail.Thread(this, domain, this.options.context,
{ 'thread_level': this.options.thread_level,
'use_composer': true,
'show_reply': this.options.thread_level > 0,
'show_dd_hide': true,
'mail_wall': self
}
);
return thread.appendTo(this.$el.find('li.oe_mail_wall_thread:last'));
return this.thread.appendTo(this.$('li.oe_mail_wall_thread:last'));
},
});
};

View File

@ -51,13 +51,33 @@ openerp_mail_followers = function(session, mail) {
bind_events: function() {
var self = this;
this.$el.find('button.oe_mail_button_unfollow').on('click', function () { self.do_unfollow(); })
.mouseover(function () { $(this).removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover').find('p').html('Unfollow');})
.mouseleave(function () { $(this).removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout').find('p').html('Following'); });
this.$('div.oe_mouse_subtypes')
.on('mouseover', function () {
$(this).removeClass('oe_mouseout').addClass('oe_mouseover');
self.display_subtypes();
})
.on('mouseleave', function () {
$(this).removeClass('oe_mouseover').addClass('oe_mouseout');
self.display_subtypes();
});
this.$el.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); });
this.$el.on('click', 'ul.oe_mail_subtypes input', function () {self.do_update_subscription(); })
this.$el.on('click', 'button.oe_mail_button_invite', function(event) {
this.$('button.oe_follower')
.on('click', function () {
if($(this).hasClass('oe_notfollow'))
self.do_follow();
else
self.do_unfollow();
})
.on('mouseover', function () {
$(this).removeClass('oe_mouseout').addClass('oe_mouseover');
})
.on('mouseleave', function () {
$(this).removeClass('oe_mouseover').addClass('oe_mouseout');
});
this.$el.on('click', 'ul.oe_subtypes input', function () { self.do_update_subscription(); })
this.$el.on('click', 'button.oe_invite', function(event) {
action = {
type: 'ir.actions.act_window',
res_model: 'mail.wizard.invite',
@ -72,9 +92,6 @@ openerp_mail_followers = function(session, mail) {
}
self.do_action(action, function() { self.read_value(); });
});
this.$el.find('button span')
.click(function (e) { self.display_subtypes(); e.stopPropagation(); })
},
read_value: function() {
@ -111,7 +128,7 @@ openerp_mail_followers = function(session, mail) {
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function (error, event) {
event.preventDefault();
var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty();
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
// format content: Followers (You and 0 other) // Followers (3)
var content = this.options.title;
if (this.message_is_follower) {
@ -120,7 +137,7 @@ openerp_mail_followers = function(session, mail) {
else {
content += ' (' + this.value.length + ')'
}
this.$el.find('div.oe_mail_recthread_followers h4').html(content);
this.$('div.oe_mail_recthread_followers h4').html(content);
this.display_buttons();
return $.when();
},
@ -128,29 +145,29 @@ openerp_mail_followers = function(session, mail) {
/** Display the followers, evaluate is_follower directly */
display_followers: function (records) {
var self = this;
var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty();
this.$el.find('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
_(records).each(function (record) {
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
this.$('div.oe_mail_recthread_followers h4').html(this.options.title + (records.length>=5 ? ' (' + records.length + ')' : '') );
console.log(records);
for(var i=0; i<records.length&&i<5; i++) {
var record=records[i];
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
});
}
self.set_is_follower(records);
},
display_buttons: function () {
if (this.message_is_follower) {
this.$el.find('button.oe_mail_button_follow').hide();
this.$el.find('button.oe_mail_button_unfollow').show();
this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
}
else {
this.$el.find('button.oe_mail_button_follow').show();
this.$el.find('button.oe_mail_button_unfollow').hide();
this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
}
if (this.view.is_action_enabled('edit'))
this.$el.find('span.oe_mail_invite_wrapper').hide();
this.$('span.oe_mail_invite_wrapper').hide();
else
this.$el.find('span.oe_mail_invite_wrapper').show();
this.$('span.oe_mail_invite_wrapper').show();
},
set_subtypes:function(data){
@ -159,47 +176,37 @@ openerp_mail_followers = function(session, mail) {
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$el.find('ul.oe_mail_subtypes') );
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
});
},
/** Display subtypes: {'name': default, followed} */
display_subtypes: function (visible) {
var self = this;
var recthread_subtypes = self.$el.find('.oe_mail_recthread_subtypes');
subtype_list_ul = self.$el.find('ul.oe_mail_subtypes');
var recthread_subtypes = self.$('.oe_recthread_subtypes');
subtype_list_ul = self.$('ul.oe_subtypes');
if(recthread_subtypes.is(":visible")) {
self.hidden_subtypes();
} else {
if(subtype_list_ul.is(":empty")) {
var context = new session.web.CompoundContext(this.build_context(), {});
this.ds_model.call('get_message_subtypes',[[self.view.datarecord.id], context]).pipe(this.proxy('set_subtypes'));
}
recthread_subtypes.show();
if(subtype_list_ul.is(":empty")) {
var context = new session.web.CompoundContext(this.build_context(), {});
this.ds_model.call('get_message_subtypes',[[self.view.datarecord.id], context]).pipe(this.proxy('set_subtypes'));
}
},
hidden_subtypes: function (){
this.$el.find('.oe_mail_recthread_subtypes').hide();
},
do_follow: function () {
var self =this;
_(this.$el.find('.oe_msg_subtype_check')).each(function(record){
_(this.$('.oe_msg_subtype_check')).each(function(record){
$(record).attr('checked','checked');
});
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, undefined, context]).pipe(function(value_){
self.read_value(value_);
if(!self.$el.find('.oe_mail_recthread_subtypes').is(":visible"))
if(!self.$('.oe_recthread_subtypes').is(":visible"))
self.display_subtypes(true);
});
},
do_unfollow: function () {
_(this.$el.find('.oe_msg_subtype_check')).each(function(record){
_(this.$('.oe_msg_subtype_check')).each(function(record){
$(record).attr('checked',false);
});
var context = new session.web.CompoundContext(this.build_context(), {});
@ -211,7 +218,7 @@ openerp_mail_followers = function(session, mail) {
var self = this;
var checklist = new Array();
_(this.$el.find('.oe_msg_subtype_check')).each(function(record){
_(this.$('.oe_msg_subtype_check')).each(function(record){
if($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')))}
});

View File

@ -67,7 +67,7 @@
<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 = '';"/>
<textarea class="oe_mail_compose_textarea" placeholder="Add your comment here..."/>
</div>
<div class="oe_clear"/>
</div>
@ -107,11 +107,12 @@
</ul>
<!-- default layout -->
<li 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" t-attf-data-msg_id="{record.id}">
<div t-attf-class="oe_mail_msg_#{record.type} oe_semantic_html_override">
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.avatar"/>
<div class="oe_mail_msg_content">
<!-- dropdown menu with message options and actions -->
<span class="placeholder-mail-thread-message-vote"/>
<span class="oe_dropdown_toggle oe_dropdown_arrow">
<ul class="oe_dropdown_menu">
<li t-if="record.is_author and options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
@ -162,7 +163,7 @@
</li>
<!-- expandable message layout -->
<li t-name="mail.thread.message.expandable" class="oe_mail oe_mail_thread_msg">
<li t-name="mail.thread.message.expandable" class="oe_mail oe_mail_thread_msg" t-attf-data-msg_id="{record.id}">
<div t-attf-class="oe_mail_msg_expandable oe_semantic_html_override">
<div class="oe_mail_msg_content">
<!-- message itself -->
@ -198,11 +199,11 @@
-->
<li t-name="mail.thread.message.vote">
<t t-if='record.vote_user_ids.length > 0'>
<span class="oe_left oe_mail_vote_count"><t t-esc="record.vote_user_ids.length"/> votes</span>
<span class="oe_left oe_mail_vote_count"><t t-esc="record.vote_user_ids.length"/> people agree</span>
</t>
<button t-attf-class="oe_mail_msg_vote oe_tag" t-attf-data-msg_id="{record.id}">
<t t-if="! record.has_voted"><span>+1</span></t>
<t t-if="record.has_voted"><span>-1</span></t>
<t t-if="! record.has_voted"><span>Agree</span></t>
<t t-if="record.has_voted"><span>Unagree</span></t>
</button>
</li>

View File

@ -7,15 +7,19 @@
-->
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
<div class="oe_mail_recthread_actions">
<button type="button" class="oe_mail_button_follow"><p>Follow</p><span class="oe_e">S</span></button>
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout"><p>Following</p><span class="oe_e">S</span></button>
<button type="button" class="oe_mail_button_invite"><p>Invite</p></button>
<div class="oe_mail_recthread_subtypes">
<h4>Message types to follow</h4>
<ul class="oe_mail_subtypes"></ul>
<div class="oe_mouse_subtypes">
<button type="button" class="oe_follower oe_notfollow">
<span class="oe_follow">Follow</span>
<span class="oe_unfollow">Unfollow</span>
<span class="oe_following">Following</span>
</button>
<div class="oe_recthread_subtypes">
<ul class="oe_subtypes"></ul>
</div>
</div>
</div>
<div class="oe_mail_recthread_followers">
<button type="button" class="oe_invite"><span>Invite</span></button>
<t t-if="widget.options.title">
<h4><t t-raw="widget.options.title"/></h4>
</t>