From 1373283947eaa2b27692cd6f478158e48b19aee04bd52542f81fadae94d31c81 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 2 Sep 2026 14:56:33 +0400 Subject: [PATCH] Refine direct message profile data --- .../shine/db/dao/UserProfileStateDAO.java | 35 ++++++++++--------- .../Net_GetDirectMessages_Handler.java | 17 +++++---- shine-UI/js/pages/channels-list.js | 17 +++++++-- shine-UI/js/services/user-profile-params.js | 5 --- shine-UI/styles/components.css | 32 +++++++++++++++++ 5 files changed, 77 insertions(+), 29 deletions(-) diff --git a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java index 250a0bd3..42feb1af 100644 --- a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java +++ b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java @@ -7,6 +7,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import shine.db.MsgSubType; + /** Fast denormalized projection for user cards shown in dialogs/relations/profile lists. */ public final class UserProfileStateDAO { private static volatile UserProfileStateDAO instance; @@ -32,25 +34,26 @@ public final class UserProfileStateDAO { } - /** Effective outgoing social relation with priority close_friend > friend > contact > none. */ + /** + * Effective outgoing social relation with priority close_friend > friend > contact > none. + * + * Use the canonical relation lookup instead of comparing only connections_state.to_login: + * older/current blocks may address a user by blockchain name, so a direct to_login-only + * query can incorrectly report "none" even when ListContacts/Connections sees the relation. + */ public String getEffectiveRelationType(Connection c, String ownerLogin, String targetLogin) throws SQLException { if (ownerLogin == null || ownerLogin.isBlank() || targetLogin == null || targetLogin.isBlank()) return "none"; - try (PreparedStatement ps = c.prepareStatement(""" - SELECT CASE - WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=10) THEN 'close_friend' - WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=14) THEN 'friend' - WHEN EXISTS(SELECT 1 FROM connections_state WHERE LOWER(login)=LOWER(?) AND LOWER(to_login)=LOWER(?) AND rel_type=20) THEN 'contact' - ELSE 'none' END AS relation_type - """)) { - int i = 1; - for (int n = 0; n < 3; n++) { - ps.setString(i++, ownerLogin); - ps.setString(i++, targetLogin); - } - try (ResultSet rs = ps.executeQuery()) { - return rs.next() ? rs.getString("relation_type") : "none"; - } + ConnectionsStateDAO relations = ConnectionsStateDAO.getInstance(); + if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_CLOSE_FRIEND)) { + return "close_friend"; } + if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_FRIEND)) { + return "friend"; + } + if (relations.hasOutgoingByRelTypeCanonical(c, ownerLogin, targetLogin, MsgSubType.CONNECTION_CONTACT)) { + return "contact"; + } + return "none"; } public List listRelations(Connection c, String ownerLogin, String listType, int limit, int offset) throws SQLException { diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/messages/Net_GetDirectMessages_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/messages/Net_GetDirectMessages_Handler.java index 8113a996..0649ba4d 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/messages/Net_GetDirectMessages_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/messages/Net_GetDirectMessages_Handler.java @@ -130,12 +130,17 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler { private static Net_GetDirectMessages_Response.Avatar parseAvatar(String value) { Net_GetDirectMessages_Response.Avatar out = new Net_GetDirectMessages_Response.Avatar(); String raw = value == null ? "" : value.trim(); - java.util.regex.Matcher ar = java.util.regex.Pattern.compile("(?:^|,)\\s*AR:([A-Za-z0-9_-]{43})(?:,|$)").matcher(raw); - if (!ar.find()) ar = java.util.regex.Pattern.compile("AR:([A-Za-z0-9_-]{43})").matcher(raw); - if (ar.find()) out.setAr(ar.group(1)); - java.util.regex.Matcher sha = java.util.regex.Pattern.compile("(?:^|,)\\s*SHA256:([A-Fa-f0-9]{64})(?:,|$)").matcher(raw); - if (!sha.find()) sha = java.util.regex.Pattern.compile("SHA256:([A-Fa-f0-9]{64})").matcher(raw); - if (sha.find()) out.setSha256Hex(sha.group(1).toLowerCase()); + + // Do not call Matcher.find() twice on the same matcher: the first successful + // call advances it and the second one can make a perfectly valid avatar vanish. + java.util.regex.Matcher ar = java.util.regex.Pattern.compile("AR:([A-Za-z0-9_-]{43})").matcher(raw); + if (ar.find()) { + out.setAr(ar.group(1)); + } + java.util.regex.Matcher sha = java.util.regex.Pattern.compile("SHA256:([A-Fa-f0-9]{64})").matcher(raw); + if (sha.find()) { + out.setSha256Hex(sha.group(1).toLowerCase()); + } return out; } diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index 08dddfc7..622fe6b4 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -52,6 +52,18 @@ function cleanChannelMessagePreview(text) { || (parsed.attachments.length ? 'Вложение' : 'Ждем ваших начинаний'); } +// Keep the channel-list preview tolerant to small API naming changes. The current +// server uses lastMessage.text/createdAtMs; legacy/alternate payloads are accepted +// so the third line (last message + time) does not silently disappear. +function resolveChannelLastMessage(summary) { + const row = summary?.lastMessage || summary?.latestMessage || summary?.last_message || null; + if (!row || typeof row !== 'object') return { text: '', createdAtMs: 0 }; + return { + text: String(row.text ?? row.messageText ?? row.preview ?? row.body ?? '').trim(), + createdAtMs: Number(row.createdAtMs ?? row.timeMs ?? row.created_at_ms ?? 0), + }; +} + function isChannelsDemoMode() { try { const qs = new URLSearchParams(window.location.search); @@ -719,6 +731,7 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) { const isOwn = bucketKey === 'own'; const title = displayTitle || channelName; const technicalLabel = `${ownerLogin} / ${channelName}`; + const lastMessage = resolveChannelLastMessage(summary); return { id: rowId, @@ -737,10 +750,10 @@ function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) { channelDescription, channelTypeCode, channelTypeVersion, - messagePreview: cleanChannelMessagePreview(summary?.lastMessage?.text), + messagePreview: cleanChannelMessagePreview(lastMessage.text), messagesCount: Number(summary?.messagesCount || 0), unreadCount: Number(summary?.unreadCount || 0), - lastMessageAt: Number(summary?.lastMessage?.createdAtMs || 0), + lastMessageAt: Number(lastMessage.createdAtMs || 0), isOwnChannel: isOwn, isSubscribed: !isOwn, notificationsEnabled: notificationsState[rowId] === true, diff --git a/shine-UI/js/services/user-profile-params.js b/shine-UI/js/services/user-profile-params.js index 58408d2b..33dd57b1 100644 --- a/shine-UI/js/services/user-profile-params.js +++ b/shine-UI/js/services/user-profile-params.js @@ -116,9 +116,6 @@ export async function loadProfileSnapshot(login) { }); } - const firstName = String(fields.find((field) => field.key === 'first_name')?.value || '').trim(); - const lastName = String(fields.find((field) => field.key === 'last_name')?.value || '').trim(); - const latestAccountRole = loadLatestByAliasesFromItems(items, ['account_role']); const rawAccountRole = String(latestAccountRole?.value || '').trim().toLowerCase(); const accountRole = rawAccountRole === PROFILE_ACCOUNT_ROLE_PRIMARY || rawAccountRole === PROFILE_ACCOUNT_ROLE_NON_VOTING @@ -160,8 +157,6 @@ export async function loadProfileSnapshot(login) { return { fields, - firstName, - lastName, toggles, accountRole, accountRoleTimeMs: latestAccountRole?.timeMs || 0, diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index c5e21174..484a9552 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -10759,3 +10759,35 @@ body.chat-topbar-overlay .page-header.app-topbar-shell .header-center { font-weight: 500; color: rgba(189,207,232,0.72); } + +/* ===== DM header regression fix (2026-09-02) ===== + * The peer identity button intentionally spans the whole free header area. + * Its avatar uses a 119% glass overlay, so neither the center cell nor the + * button may clip overflow. Text keeps its own ellipsis boundary instead. + */ +body.chat-topbar-overlay .page-header.app-topbar-shell .header-center { + overflow: visible !important; +} + +.chat-header-peer-btn { + overflow: visible !important; + padding-inline: 2px !important; +} + +.chat-header-peer-btn .chat-header-avatar-slot { + width: 44px !important; + height: 44px !important; + min-width: 44px !important; + min-height: 44px !important; + flex: 0 0 44px !important; + overflow: visible !important; +} + +.chat-header-peer-btn .chat-header-avatar-slot .chat-header-avatar { + overflow: visible !important; +} + +.chat-header-peer-text { + flex: 1 1 auto; + overflow: hidden; +}