Redesign message viewer as Telegram-style chat with bubbles, replies, infinite scroll, and orientation

- Chat bubbles with 1px border-radius, angular tail arrows
- Own messages right (blue), others left (dark) via sender_id vs user_id
- Reply preview with left border accent, clickable scroll to original
- Infinite scroll up via IntersectionObserver on scroll-sentinel
- Date separators (Today/Yesterday/date) between messages
- Full-height messages panel with sidebar (channels) + header
- Backend: reply_to_message data in load_messages(), user_id exposed in auth snapshot
- Auto-scroll to bottom on channel load (triple scrollTop fallback)
- history.scrollRestoration = manual to prevent mid-page jumps
This commit is contained in:
2026-05-27 01:01:18 +02:00
parent 1dfe41950e
commit c5361f2271
4 changed files with 532 additions and 106 deletions
+245 -37
View File
@@ -46,35 +46,25 @@ button {
font: inherit;
}
.app-shell,
.viewer-shell {
.app-shell {
min-height: 100vh;
display: grid;
}
.app-shell,
.viewer-shell {
grid-template-columns: 300px minmax(0, 1fr);
}
.sidebar,
.viewer-sidebar {
.sidebar {
background: var(--panel-alt);
border-right: 1px dashed var(--line);
padding: 28px 24px;
}
.content,
.viewer-main {
.content {
padding: 28px;
max-width: 1320px;
width: 100%;
}
.brand-block h1,
.viewer-sidebar-head h1,
.viewer-header h2,
.panel-header h2,
.dialog-header h2 {
margin: 0;
color: var(--accent);
@@ -105,8 +95,7 @@ button {
.nav-links,
.jobs-list,
.continuous-form,
.viewer-channel-list {
.continuous-form {
display: grid;
gap: 10px;
}
@@ -227,10 +216,7 @@ input,
}
.panel-header,
.viewer-sidebar-head,
.viewer-header,
.dialog-header,
.message-meta,
.job-head {
display: flex;
align-items: flex-start;
@@ -489,6 +475,45 @@ tbody tr:hover {
display: none !important;
}
/* ---------- Viewer layout: sidebar + chat ---------- */
.viewer-shell {
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
height: 100vh;
overflow: hidden;
}
.viewer-sidebar {
background: var(--panel-alt);
border-right: 1px dashed var(--line);
padding: 28px 24px;
display: flex;
flex-direction: column;
gap: 20px;
overflow-y: auto;
}
.viewer-sidebar-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.viewer-sidebar-head h1 {
margin: 0;
color: var(--accent);
font-size: 1.45rem;
text-transform: uppercase;
letter-spacing: 0;
}
.viewer-channel-list {
display: grid;
gap: 10px;
}
.viewer-channel-item {
display: grid;
gap: 4px;
@@ -496,6 +521,9 @@ tbody tr:hover {
padding: 12px;
text-align: left;
cursor: pointer;
border: 1px solid var(--line);
background: var(--panel);
color: var(--text-color);
}
.viewer-channel-item.active {
@@ -503,52 +531,232 @@ tbody tr:hover {
box-shadow: inset 3px 0 0 var(--accent);
}
.viewer-channel-name,
.message-author {
.viewer-channel-name {
color: var(--accent);
font-weight: 700;
}
.viewer-channel-meta,
.message-date {
.viewer-channel-meta {
color: var(--dim);
font-size: 0.82rem;
}
/* ---------- Main panel ---------- */
.viewer-main {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.viewer-header {
flex-shrink: 0;
padding: 20px 28px;
border-bottom: 1px dashed var(--line);
background: var(--panel-alt);
}
.viewer-header h2 {
margin: 0;
color: var(--accent);
font-size: 1.45rem;
text-transform: uppercase;
letter-spacing: 0;
}
.messages-list {
display: grid;
flex: 1;
overflow-y: auto;
padding: 12px 28px 20px;
display: flex;
flex-direction: column;
}
.scroll-sentinel {
flex-shrink: 0;
height: 1px;
width: 100%;
}
/* ---------- Date separator ---------- */
.chat-date-sep {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
align-content: start;
margin: 12px 0;
font-size: 0.78rem;
color: var(--dim);
text-transform: uppercase;
letter-spacing: 0.03em;
flex-shrink: 0;
}
.message-card {
max-width: 820px;
.chat-date-sep::before,
.chat-date-sep::after {
content: "";
flex: 1;
height: 1px;
background: var(--line);
}
.message-reply:empty,
.message-text:empty,
.message-footer:empty,
.message-media:empty {
.chat-date-sep:empty {
display: none;
}
.message-text {
/* ---------- Bubble wrap ---------- */
.chat-bubble-wrap {
display: flex;
justify-content: flex-start;
flex-shrink: 0;
}
.chat-bubble-wrap--own {
justify-content: flex-end;
}
/* ---------- Chat bubble ---------- */
.chat-bubble {
max-width: 75%;
padding: 7px 11px;
background: #1c1c1c;
border: 1px solid var(--line);
border-radius: 1px;
margin-bottom: 4px;
overflow-wrap: anywhere;
word-break: break-word;
position: relative;
}
.chat-bubble::before {
content: "";
position: absolute;
top: 8px;
border: 6px solid transparent;
}
.chat-bubble-wrap:not(.chat-bubble-wrap--own) .chat-bubble::before {
left: -12px;
border-right-color: #1c1c1c;
border-left: 0;
}
.chat-bubble-wrap--own .chat-bubble::before {
right: -12px;
border-left-color: #1e3a5f;
border-right: 0;
}
.chat-bubble-wrap--own .chat-bubble {
background: #1e3a5f;
border-color: #2b5278;
}
.chat-bubble--highlight {
animation: chat-highlight 0.5s ease-in-out 3;
}
@keyframes chat-highlight {
0%, 100% { box-shadow: 0 0 0 0 rgba(139, 180, 248, 0); }
50% { box-shadow: 0 0 0 3px rgba(139, 180, 248, 0.4); }
}
/* ---------- Sender ---------- */
.chat-sender {
font-size: 0.82rem;
color: var(--info);
font-weight: 700;
margin-bottom: 2px;
}
.chat-sender:empty {
display: none;
}
/* ---------- Reply preview ---------- */
.chat-reply {
display: flex;
gap: 6px;
margin-bottom: 4px;
padding: 4px 0;
cursor: pointer;
}
.chat-reply.hidden {
display: none;
}
.chat-reply-border {
flex-shrink: 0;
width: 2px;
background: var(--accent);
border-radius: 1px;
opacity: 0.5;
}
.chat-reply-body {
min-width: 0;
}
.chat-reply-author {
font-size: 0.78rem;
color: var(--info);
font-weight: 700;
}
.chat-reply-text {
font-size: 0.82rem;
color: var(--dim);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ---------- Message text ---------- */
.chat-text {
white-space: pre-wrap;
line-height: 1.45;
}
.message-media {
margin-top: 12px;
.chat-text:empty {
display: none;
}
.message-media img,
.message-media video {
/* ---------- Media ---------- */
.chat-media {
margin-top: 6px;
}
.chat-media:empty {
display: none;
}
.chat-media img,
.chat-media video {
display: block;
max-width: 100%;
border: 1px solid var(--line);
}
.message-footer {
margin-top: 10px;
/* ---------- Footer ---------- */
.chat-footer {
margin-top: 4px;
font-size: 0.72rem;
color: var(--dim);
text-align: right;
}
.chat-footer:empty {
display: none;
}
.api-docs {