Чат: аватар собеседника в списке ЛС + переключатель комнат
- Аватар співрозмовника напроти особистого повідомлення (лівий список): chatBuildChannelRow отримав опцію avatar; id співрозмовника ЛС резолвиться в chatResolveDmLabel і підвантажує фото (chatApplyAvatar). Групові ЛС — без фото, з ініціалами. - Верхній перемикач «кімнат» — команди Mattermost (Офіс / Аптеки / Склад): chatRenderTeamSwitcher/chatSelectTeam; chatRenderChannelList фільтрує канали за обраною командою; за замовчуванням — перша команда. ЛС показуються завжди. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+79
-11
@@ -23,6 +23,8 @@ var chatUploadedFileNames = {};
|
||||
var chatFileBlobCache = {};
|
||||
var chatDmPickerSelected = {};
|
||||
var chatReactionPickerEl = null;
|
||||
var chatCurrentTeamId = null; // обрана «кімната» (команда Mattermost)
|
||||
var chatDmOtherUserId = {}; // channelId -> id співрозмовника (для аватара ЛС)
|
||||
|
||||
function chatPanelOpen(id) {
|
||||
var panel = document.getElementById(id);
|
||||
@@ -71,6 +73,8 @@ function chatInit() {
|
||||
chatUploadedFileNames = {};
|
||||
chatFileBlobCache = {};
|
||||
chatDmPickerSelected = {};
|
||||
chatCurrentTeamId = null;
|
||||
chatDmOtherUserId = {};
|
||||
|
||||
chatLoadTeams().then(chatLoadChannels);
|
||||
chatConnectSocket();
|
||||
@@ -140,9 +144,43 @@ function chatResolveUsernames(posts) {
|
||||
function chatLoadTeams() {
|
||||
return chatApiFetch('/users/me/teams').then(function (teams) {
|
||||
teams.forEach(function (team) { chatTeamsById[team.id] = team; });
|
||||
var sorted = teams.slice().sort(function (a, b) { return a.display_name.localeCompare(b.display_name); });
|
||||
if (!chatCurrentTeamId && sorted.length) chatCurrentTeamId = sorted[0].id;
|
||||
chatRenderTeamSwitcher();
|
||||
}).catch(function (e) { console.error(e); });
|
||||
}
|
||||
|
||||
// Верхній перемикач «кімнат» — команди Mattermost (Офіс / Аптеки / Склад).
|
||||
function chatRenderTeamSwitcher() {
|
||||
var el = document.getElementById('chat-team-switcher');
|
||||
if (!el) return;
|
||||
var teams = Object.keys(chatTeamsById).map(function (id) { return chatTeamsById[id]; })
|
||||
.sort(function (a, b) { return a.display_name.localeCompare(b.display_name); });
|
||||
if (teams.length <= 1) { el.innerHTML = ''; return; } // перемикати нема що
|
||||
|
||||
el.innerHTML = '';
|
||||
var group = document.createElement('div');
|
||||
group.style.cssText = 'display:inline-flex; gap:0.25rem; background:var(--color-muted); border-radius:var(--radius); padding:0.25rem;';
|
||||
teams.forEach(function (t) {
|
||||
var active = t.id === chatCurrentTeamId;
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.textContent = t.display_name;
|
||||
btn.style.cssText = 'padding:0.3125rem 0.875rem; font-size:0.8125rem; font-weight:500; border:none; cursor:pointer; border-radius:calc(var(--radius) - 2px); transition:all 150ms;' +
|
||||
(active ? ' background:var(--color-card); color:var(--color-card-foreground); box-shadow:0 1px 2px rgb(0 0 0/0.08);'
|
||||
: ' background:transparent; color:var(--color-muted-foreground);');
|
||||
btn.onclick = function () { chatSelectTeam(t.id); };
|
||||
group.appendChild(btn);
|
||||
});
|
||||
el.appendChild(group);
|
||||
}
|
||||
|
||||
function chatSelectTeam(teamId) {
|
||||
chatCurrentTeamId = teamId;
|
||||
chatRenderTeamSwitcher();
|
||||
chatRenderChannelList();
|
||||
}
|
||||
|
||||
function chatLoadChannels() {
|
||||
return chatApiFetch('/users/me/channels').then(function (channels) {
|
||||
chatChannels = channels
|
||||
@@ -161,8 +199,10 @@ function chatLoadChannels() {
|
||||
|
||||
function chatResolveDmLabel(channel) {
|
||||
var promise;
|
||||
var otherId = null;
|
||||
if (channel.type === 'D') {
|
||||
var otherId = channel.name.split('__').filter(function (id) { return id !== chatConfig.currentUserId; })[0];
|
||||
otherId = channel.name.split('__').filter(function (id) { return id !== chatConfig.currentUserId; })[0];
|
||||
chatDmOtherUserId[channel.id] = otherId;
|
||||
promise = chatResolveUserIds([otherId]).then(function () {
|
||||
return chatUserNames[otherId] || otherId;
|
||||
});
|
||||
@@ -178,6 +218,11 @@ function chatResolveDmLabel(channel) {
|
||||
chatDmLabels[channel.id] = label;
|
||||
var labelEl = document.getElementById('chat-dm-label-' + channel.id);
|
||||
if (labelEl) labelEl.textContent = label;
|
||||
var avEl = document.getElementById('chat-dm-avatar-' + channel.id);
|
||||
if (avEl) {
|
||||
avEl.textContent = (label || '').slice(0, 2).toUpperCase();
|
||||
if (otherId) chatApplyAvatar(avEl, otherId); // фото — лише для особистих (не групових)
|
||||
}
|
||||
if (chatCurrentChannelId === channel.id) chatUpdateHeader(channel);
|
||||
}).catch(function (e) { console.error(e); });
|
||||
}
|
||||
@@ -186,15 +231,19 @@ function chatRenderChannelList() {
|
||||
var el = document.getElementById('chat-channel-list');
|
||||
if (!el) return;
|
||||
|
||||
if (!chatChannels.length) {
|
||||
var list = chatChannels.filter(function (ch) {
|
||||
return !chatCurrentTeamId || ch.team_id === chatCurrentTeamId;
|
||||
});
|
||||
|
||||
if (!list.length) {
|
||||
el.innerHTML = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">Немає каналів</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
el.innerHTML = '';
|
||||
chatChannels.forEach(function (ch) {
|
||||
var team = chatTeamsById[ch.team_id];
|
||||
var row = chatBuildChannelRow(ch.id, '#' + ch.display_name, team ? team.display_name : '');
|
||||
list.forEach(function (ch) {
|
||||
// Команда обрана перемикачем зверху — назву команди у рядок не дублюємо.
|
||||
var row = chatBuildChannelRow(ch.id, '#' + ch.display_name, '');
|
||||
el.appendChild(row);
|
||||
});
|
||||
}
|
||||
@@ -210,33 +259,52 @@ function chatRenderDmList() {
|
||||
|
||||
el.innerHTML = '';
|
||||
chatDmChannels.forEach(function (ch) {
|
||||
var row = chatBuildChannelRow(ch.id, chatDmLabels[ch.id] || '…', ch.type === 'G' ? 'Групова розмова' : '');
|
||||
var row = chatBuildChannelRow(ch.id, chatDmLabels[ch.id] || '…',
|
||||
ch.type === 'G' ? 'Групова розмова' : '', {avatar: true});
|
||||
el.appendChild(row);
|
||||
// Якщо співрозмовник ЛС уже відомий — одразу показати його аватар.
|
||||
if (ch.type === 'D' && chatDmOtherUserId[ch.id]) {
|
||||
chatApplyAvatar(document.getElementById('chat-dm-avatar-' + ch.id), chatDmOtherUserId[ch.id]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function chatBuildChannelRow(channelId, label, subLabel) {
|
||||
function chatBuildChannelRow(channelId, label, subLabel, opts) {
|
||||
opts = opts || {};
|
||||
var row = document.createElement('div');
|
||||
row.id = 'chat-channel-row-' + channelId;
|
||||
row.style.cssText = 'padding:0.5rem 0.625rem; border-bottom:1px solid var(--color-border); cursor:pointer;' +
|
||||
row.style.cssText = 'display:flex; align-items:center; gap:0.5rem; padding:0.5rem 0.625rem; border-bottom:1px solid var(--color-border); cursor:pointer;' +
|
||||
(channelId === chatCurrentChannelId ? ' background:hsl(0 72% 44% / 0.06);' : '');
|
||||
row.onmouseover = function () { if (channelId !== chatCurrentChannelId) row.style.background = 'var(--color-muted)'; };
|
||||
row.onmouseout = function () { if (channelId !== chatCurrentChannelId) row.style.background = ''; };
|
||||
row.onclick = function () { chatSelectChannel(channelId); };
|
||||
|
||||
if (opts.avatar) {
|
||||
// Кружок-аватар співрозмовника ЛС; фото підвантажиться в chatResolveDmLabel.
|
||||
var av = document.createElement('div');
|
||||
av.id = 'chat-dm-avatar-' + channelId;
|
||||
av.style.cssText = 'width:1.75rem; height:1.75rem; border-radius:50%; background:hsl(205 80% 55% / 0.15); color:hsl(205 80% 45%); display:flex; align-items:center; justify-content:center; font-size:0.5625rem; font-weight:700; flex-shrink:0;';
|
||||
av.textContent = (label && label !== '…') ? label.slice(0, 2).toUpperCase() : '';
|
||||
row.appendChild(av);
|
||||
}
|
||||
|
||||
var textCol = document.createElement('div');
|
||||
textCol.style.cssText = 'min-width:0; flex:1;';
|
||||
|
||||
var name = document.createElement('div');
|
||||
name.id = 'chat-dm-label-' + channelId;
|
||||
name.style.cssText = 'font-size:0.8125rem; font-weight:500; color:var(--color-foreground);';
|
||||
name.style.cssText = 'font-size:0.8125rem; font-weight:500; color:var(--color-foreground); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;';
|
||||
name.textContent = label;
|
||||
row.appendChild(name);
|
||||
textCol.appendChild(name);
|
||||
|
||||
if (subLabel) {
|
||||
var sub = document.createElement('div');
|
||||
sub.style.cssText = 'font-size:0.6875rem; color:var(--color-muted-foreground);';
|
||||
sub.textContent = subLabel;
|
||||
row.appendChild(sub);
|
||||
textCol.appendChild(sub);
|
||||
}
|
||||
|
||||
row.appendChild(textCol);
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
{% if chat_state == 'ready' %}
|
||||
{{ chat_config|json_script:"chat-config-data" }}
|
||||
{{ chat_colleagues|json_script:"chat-colleagues-data" }}
|
||||
<div id="chat-app" style="display:grid; grid-template-columns:260px 1fr; gap:1.25rem; height:calc(100vh - 12rem); max-height:640px;">
|
||||
<!-- Перемикач «кімнат» (команди Mattermost: Офіс / Аптеки / Склад) -->
|
||||
<div id="chat-team-switcher" style="margin-bottom:1rem;"></div>
|
||||
<div id="chat-app" style="display:grid; grid-template-columns:260px 1fr; gap:1.25rem; height:calc(100vh - 15rem); max-height:640px;">
|
||||
|
||||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); display:flex; flex-direction:column; overflow:hidden;">
|
||||
<div style="padding:0.75rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between;">
|
||||
|
||||
Reference in New Issue
Block a user