Add support for emails on the chat channels

This commit is contained in:
Nicolas Évrard
2026-03-31 18:59:55 +02:00
parent 6a38874d57
commit 44642bec77
22 changed files with 1146 additions and 139 deletions
+232 -59
View File
@@ -3,6 +3,16 @@
(function() {
'use strict';
function add_size(url, size=32) {
if (url) {
url = new URL(url, window.location);
url.searchParams.set('s', size);
return url.href;
} else {
return '';
}
}
class _Chat {
constructor(record) {
this.notify = this.notify.bind(this);
@@ -46,63 +56,221 @@
});
}
_get_followers() {
return Sao.rpc({
'method': 'model.ir.chat.channel.get_followers',
'params': [this.record, {}],
}, Sao.Session.current_session);
}
_search_followers(text) {
if (!text) {
return jQuery.when();
}
return Sao.rpc({
'method': 'model.ir.chat.channel.search_followers',
'params': [this.record, text, {}],
}, Sao.Session.current_session).then(followers => {
return followers.filter(e => {
return !((e.type == 'user') &&
(e.key == Sao.Session.current_session.login));
});
});
}
_add_follower(follower) {
let method;
switch (follower.type) {
case 'user':
method = 'subscribe';
break;
case 'email':
method = 'subscribe_email';
break;
}
Sao.rpc({
'method': `model.ir.chat.channel.${method}`,
'params': [this.record, follower.key, {}],
}, Sao.Session.current_session);
}
_remove_follower(follower) {
let method;
switch (follower.type) {
case 'user':
method = 'unsubscribe';
break;
case 'email':
method = 'unsubscribe_email';
break;
}
Sao.rpc({
'method': `model.ir.chat.channel.${method}`,
'params': [this.record, follower.key, {}],
}, Sao.Session.current_session);
}
__build() {
let el = jQuery('<div/>', {
'class': 'chat',
});
let btn_group = jQuery('<div/>', {
'class': 'btn-group',
'role': 'group',
let toolbar = jQuery('<div/>', {
'class': 'btn-toolbar',
'role': 'toolbar',
}).appendTo(el);
let input_group = jQuery('<div/>', {
'class': 'input-group',
'role': 'group',
}).appendTo(toolbar);
this.subscribe_input = jQuery('<input/>', {
'class': 'form-control input-sm',
'placeholder': Sao.i18n.gettext("Add a follower"),
'title': Sao.i18n.gettext("Subscribe a follower to this channel"),
}).appendTo(input_group);
let avatar_size = 32;
let format = function(val) {
return jQuery('<div/>', {
'title': val.key,
})
.append(jQuery('<img/>', {
'src': add_size(val.avatar_url, avatar_size),
'class': 'img-circle',
'style': `width: ${avatar_size}px; height: ${avatar_size}px;`,
}))
.append(jQuery('<span/>', {
})
.text(val.name));
};
new Sao.common.InputCompletion(
input_group, this._search_followers.bind(this),
(follower) => {
this._add_follower(follower);
this.subscribe_input.val('');
this.subscribe_input.focus();
}, format);
let dropdown = jQuery('<div/>', {
'class': 'btn-group dropdown',
'role': 'group',
}).appendTo(toolbar);
let subscribe_btn = jQuery('<button/>', {
'class': 'btn btn-default pull-right',
'type': 'button',
'title': Sao.i18n.gettext("Toggle notification"),
'class': 'btn btn-default pull-right dropdown-toggle',
'data-toggle': 'dropdown',
'aria-expanded': false,
'aria-haspopup': true,
'title': Sao.i18n.gettext("Show followers"),
}).append(Sao.common.ICONFACTORY.get_icon_img(
'tryton-notification'))
.appendTo(btn_group);
.appendTo(dropdown);
subscribe_btn.uniqueId();
Sao.rpc({
'method': 'model.ir.chat.channel.get_followers',
'params': [this.record, {}],
}, Sao.Session.current_session).then((followers) => {
set_subscribe_state(~followers.users.indexOf(
Sao.Session.current_session.login));
})
this._get_followers().then((followers) => {
let subscribed = followers.some((e) => (
(e.type == 'user') &&
(e.key == Sao.Session.current_session.login)));
set_subscribe_state(subscribed);
});;
let set_subscribe_state = (subscribed) => {
let img;
if (subscribed) {
img = 'tryton-notification-on';
subscribe_btn.addClass('active').off().click(unsubscribe);
subscribe_btn.addClass('active');
} else {
img = 'tryton-notification-off';
subscribe_btn.removeClass('active').off().click(subscribe);
subscribe_btn.removeClass('active');
}
subscribe_btn.html(Sao.common.ICONFACTORY.get_icon_img(img));
}
let subscribe = () => {
let session = Sao.Session.current_session;
Sao.rpc({
'method': 'model.ir.chat.channel.subscribe',
'params': [this.record, {}],
}, session).then(() => {
set_subscribe_state(true);
})
};
let menu = jQuery('<ul/>', {
'class': 'dropdown-menu dropdown-menu-right',
'role': 'menu',
'aria-labelledby': subscribe_btn.id,
}).appendTo(dropdown);
let unsubscribe = () => {
let session = Sao.Session.current_session;
Sao.rpc({
'method': 'model.ir.chat.channel.unsubscribe',
'params': [this.record, {}],
}, session).then(() => {
set_subscribe_state(false);
})
};
dropdown.on('show.bs.dropdown', () => {
menu.empty();
let user_action;
if (subscribe_btn.hasClass('active')) {
user_action = jQuery('<a/>', {
'href': '#',
'class': 'text-danger text-uppercase',
}).text(Sao.i18n.gettext("Unsubscribe"))
.click(() => {
Sao.rpc({
'method': 'model.ir.chat.channel.unsubscribe',
'params': [this.record, {}],
}, Sao.Session.current_session).then(() => {
set_subscribe_state(false);
});
});
} else {
user_action = jQuery('<a/>', {
'href': '#',
'class': 'text-primary text-uppercase',
}).text(Sao.i18n.gettext("Subscribe"))
.click(() => {
Sao.rpc({
'method': 'model.ir.chat.channel.subscribe',
'params': [this.record, {}],
}, Sao.Session.current_session).then(() => {
set_subscribe_state(true);
});
});
}
jQuery('<li/>', {
'role': 'presentation',
}).append(user_action).appendTo(menu);
this._get_followers().then((followers) => {
followers = followers.filter(e => {
return !((e.type == 'user') &&
(e.key == Sao.Session.current_session.login));
});
if (followers.length) {
jQuery('<li/>', {
'class': 'divider',
}).appendTo(menu);
}
let add_follower = (follower) => {
jQuery('<li/>', {
'role': 'presentation',
})
.append(jQuery('<div/>', {
'title': follower.key,
})
.append(jQuery('<img/>', {
'class': 'img-circle chat-avatar',
'src': add_size(follower.avatar_url, avatar_size),
'style': `width: ${avatar_size}px; height: ${avatar_size}px;`,
}))
.append(jQuery('<span/>')
.text(follower.name))
.append(jQuery('<button/>', {
'class': 'btn btn-link',
'title': Sao.i18n.gettext("Unsubscribe"),
'type': 'button',
}).append("&times;").click((evt) => {
Sao.common.sur.run(
Sao.i18n.gettext(
'Are you sure to unsubscribe "%1" from this channel?',
follower.name)
).then(() => {
this._remove_follower(follower);
});
}))
).appendTo(menu);
};
followers.forEach(add_follower);
});
});
this._messages = jQuery('<div/>', {
'class': 'chat-messages',
@@ -167,35 +335,40 @@
}
create_message(message) {
let avatar_size = 32;
let timestamp = Sao.common.format_datetime(
Sao.common.date_format() + ' %X', message.timestamp);
let avatar_url = '';
if (message.avatar_url) {
let url = new URL(message.avatar_url, window.location);
url.searchParams.set('s', avatar_size);
avatar_url = url.href;
if (message.user) {
let avatar_size = 32;
let avatar_url = add_size(message.avatar_url, avatar_size);
return jQuery('<div/>', {
'class': 'media chat-message',
}).append(jQuery('<div/>', {
'class': 'media-left',
}).append(jQuery('<img/>', {
'class': 'media-object img-circle chat-avatar',
'src': avatar_url,
'alt': message.author,
'style': `width: ${avatar_size}px; height: ${avatar_size}px;`,
}))).append(jQuery('<div/>', {
'class': 'media-body well well-sm',
}).append(jQuery('<h6/>', {
'class': 'media-heading',
}).text(message.author)
.append(jQuery('<small/>', {
'class': 'text-muted pull-right',
}).text(timestamp)))
.append(jQuery('<div/>', {
'class': `chat-content chat-content-${message.audience}`,
}).text(message.content)));
} else {
return jQuery('<div/>', {
'class': 'media chat-message system-message',
}).append(jQuery('<div/>', {
'class': 'chat-content',
'title': timestamp,
}).text(message.content));
}
return jQuery('<div/>', {
'class': 'media chat-message',
}).append(jQuery('<div/>', {
'class': 'media-left',
}).append(jQuery('<img/>', {
'class': 'media-object img-circle chat-avatar',
'src': avatar_url,
'alt': message.author,
'style': `width: ${avatar_size}px; height: ${avatar_size}px;`,
}))).append(jQuery('<div/>', {
'class': 'media-body well well-sm',
}).append(jQuery('<h6/>', {
'class': 'media-heading',
}).text(message.author)
.append(jQuery('<small/>', {
'class': 'text-muted pull-right',
}).text(timestamp)))
.append(jQuery('<div/>', {
'class': `chat-content chat-content-${message.audience}`,
}).text(message.content)));
}
}
Sao.Chat = _Chat;
+43
View File
@@ -520,6 +520,40 @@ html[theme="default"] {
padding-left: 5px;
padding-right: 5px;
.btn-toolbar {
display: flex;
.input-group {
flex: 1;
.img-circle {
height: 2em;
margin-inline-end: 1ex;
}
}
ul.dropdown-menu {
width: 20em;
div {
display: flex;
margin: 1ex;
gap: 5px;
img {
align-self: center;
width: 32px;
height: 32px;
}
span {
flex: 1;
align-self: center;
}
}
}
}
.chat-messages-outer {
display: flex;
flex-direction: column;
@@ -535,6 +569,15 @@ html[theme="default"] {
.chat-message {
margin-top: 5px;
&.system-message {
align-items: center;
color: @text-muted;
display: flex;
font-size: small;
justify-content: center;
padding: 0 2ex;
}
}
.chat-content {