ScuttleBot

ui: inline chat messages with toggleable columnar layout Default layout is now inline — time, nick, and text flow as inline spans so text wraps to full width. A toggle button (hamburger icon) in the chat topbar switches to columnar (flex) layout with fixed-width nick/time columns. Preference persisted in localStorage.

lmata 2026-04-03 19:43 trunk
Commit c232ecf74b055de9ad2f73e3f2da87e1124aad2b97bbcca9c85fba207979af70
1 file changed +26 -9
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -140,16 +140,23 @@
140140
.chat-main { flex:1; display:flex; flex-direction:column; min-width:0; }
141141
.chat-topbar { padding:9px 16px; border-bottom:1px solid #30363d; display:flex; align-items:center; gap:10px; flex-shrink:0; background:#161b22; font-size:13px; }
142142
.chat-ch-name { font-weight:600; color:#58a6ff; }
143143
.stream-badge { font-size:11px; color:#8b949e; margin-left:auto; }
144144
.chat-msgs { flex:1; overflow-y:auto; padding:4px 8px; display:flex; flex-direction:column; gap:0; }
145
-.msg-row { display:flex; gap:6px; font-size:13px; line-height:1.4; padding:1px 0; }
146
-.msg-time { color:#8b949e; font-size:11px; flex-shrink:0; padding-top:2px; min-width:36px; }
147
-.msg-nick { font-weight:600; flex-shrink:0; min-width:80px; text-align:right; }
148
-.msg-grouped .msg-nick { visibility:hidden; }
149
-.msg-grouped .msg-time { color:transparent; }
150
-.msg-grouped:hover .msg-time { color:#8b949e; transition:color .1s; }
145
+.msg-row { font-size:13px; line-height:1.4; padding:1px 0; }
146
+.msg-time { color:#8b949e; font-size:11px; margin-right:6px; }
147
+.msg-nick { font-weight:600; margin-right:6px; }
148
+.msg-grouped .msg-nick { display:none; }
149
+.msg-grouped .msg-time { display:none; }
150
+/* columnar layout mode */
151
+.chat-msgs.columnar .msg-row { display:flex; gap:6px; }
152
+.chat-msgs.columnar .msg-time { flex-shrink:0; min-width:36px; padding-top:2px; margin-right:0; }
153
+.chat-msgs.columnar .msg-nick { flex-shrink:0; min-width:80px; text-align:right; margin-right:0; }
154
+.chat-msgs.columnar .msg-text { word-break:break-word; }
155
+.chat-msgs.columnar .msg-grouped .msg-nick { display:inline; visibility:hidden; }
156
+.chat-msgs.columnar .msg-grouped .msg-time { display:inline; color:transparent; }
157
+.chat-msgs.columnar .msg-grouped:hover .msg-time { color:#8b949e; }
151158
.chat-nicklist { width:148px; min-width:0; flex-shrink:0; border-left:1px solid #30363d; display:flex; flex-direction:column; background:#161b22; overflow-y:auto; overflow-x:hidden; transition:width .15s; }
152159
.chat-nicklist.collapsed { width:28px; overflow:hidden; }
153160
.chat-nicklist.collapsed #nicklist-users { display:none; }
154161
.nicklist-head { padding:8px 12px; font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:#8b949e; border-bottom:1px solid #30363d; flex-shrink:0; display:flex; align-items:center; gap:4px; }
155162
.nicklist-nick { padding:5px 12px; font-size:12px; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -248,13 +255,12 @@
248255
/* login */
249256
.login-box { width:90vw; max-width:340px; }
250257
251258
/* chat: tighter on mobile */
252259
.chat-msgs { padding:2px 4px; }
253
- .msg-row { gap:4px; font-size:12px; line-height:1.3; }
254
- .msg-nick { min-width:0; max-width:70px; overflow:hidden; text-overflow:ellipsis; }
255
- .msg-time { min-width:28px; font-size:10px; }
260
+ .msg-row { font-size:12px; line-height:1.3; }
261
+ .msg-time { font-size:10px; }
256262
.chat-input { padding:6px 8px; }
257263
.chat-topbar { padding:6px 10px; gap:6px; font-size:12px; }
258264
}
259265
</style>
260266
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
@@ -467,10 +473,11 @@
467473
<div class="spacer"></div>
468474
<span style="font-size:11px;color:#8b949e;margin-right:6px">chatting as</span>
469475
<select id="chat-identity" style="width:140px;padding:3px 6px;font-size:12px" onchange="saveChatIdentity()">
470476
<option value="">— pick a user —</option>
471477
</select>
478
+ <button class="sm" id="chat-layout-toggle" onclick="toggleChatLayout()" title="toggle compact/columnar" style="font-size:11px;padding:2px 6px">☰</button>
472479
<span class="stream-badge" id="chat-stream-status" style="margin-left:8px"></span>
473480
</div>
474481
<div class="chat-msgs" id="chat-msgs">
475482
<div class="empty" id="chat-placeholder">join a channel to start chatting</div>
476483
</div>
@@ -1802,10 +1809,20 @@
18021809
sel.innerHTML = '<option value="">— pick a user —</option>' +
18031810
options.map(a => `<option value="${esc(a.nick)}"${a.nick===current?' selected':''}>${esc(a.nick)} (${esc(a.type)})</option>`).join('');
18041811
// Restore saved selection.
18051812
if (current) sel.value = current;
18061813
}
1814
+
1815
+function toggleChatLayout() {
1816
+ const el = document.getElementById('chat-msgs');
1817
+ const columnar = el.classList.toggle('columnar');
1818
+ localStorage.setItem('sb_chat_columnar', columnar ? '1' : '0');
1819
+}
1820
+// Restore layout preference on load.
1821
+if (localStorage.getItem('sb_chat_columnar') === '1') {
1822
+ document.getElementById('chat-msgs').classList.add('columnar');
1823
+}
18071824
18081825
async function sendMsg() {
18091826
if (!chatChannel) return;
18101827
const input = document.getElementById('chat-text-input');
18111828
const nick = document.getElementById('chat-identity').value.trim() || 'web';
18121829
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -140,16 +140,23 @@
140 .chat-main { flex:1; display:flex; flex-direction:column; min-width:0; }
141 .chat-topbar { padding:9px 16px; border-bottom:1px solid #30363d; display:flex; align-items:center; gap:10px; flex-shrink:0; background:#161b22; font-size:13px; }
142 .chat-ch-name { font-weight:600; color:#58a6ff; }
143 .stream-badge { font-size:11px; color:#8b949e; margin-left:auto; }
144 .chat-msgs { flex:1; overflow-y:auto; padding:4px 8px; display:flex; flex-direction:column; gap:0; }
145 .msg-row { display:flex; gap:6px; font-size:13px; line-height:1.4; padding:1px 0; }
146 .msg-time { color:#8b949e; font-size:11px; flex-shrink:0; padding-top:2px; min-width:36px; }
147 .msg-nick { font-weight:600; flex-shrink:0; min-width:80px; text-align:right; }
148 .msg-grouped .msg-nick { visibility:hidden; }
149 .msg-grouped .msg-time { color:transparent; }
150 .msg-grouped:hover .msg-time { color:#8b949e; transition:color .1s; }
 
 
 
 
 
 
 
151 .chat-nicklist { width:148px; min-width:0; flex-shrink:0; border-left:1px solid #30363d; display:flex; flex-direction:column; background:#161b22; overflow-y:auto; overflow-x:hidden; transition:width .15s; }
152 .chat-nicklist.collapsed { width:28px; overflow:hidden; }
153 .chat-nicklist.collapsed #nicklist-users { display:none; }
154 .nicklist-head { padding:8px 12px; font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:#8b949e; border-bottom:1px solid #30363d; flex-shrink:0; display:flex; align-items:center; gap:4px; }
155 .nicklist-nick { padding:5px 12px; font-size:12px; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -248,13 +255,12 @@
248 /* login */
249 .login-box { width:90vw; max-width:340px; }
250
251 /* chat: tighter on mobile */
252 .chat-msgs { padding:2px 4px; }
253 .msg-row { gap:4px; font-size:12px; line-height:1.3; }
254 .msg-nick { min-width:0; max-width:70px; overflow:hidden; text-overflow:ellipsis; }
255 .msg-time { min-width:28px; font-size:10px; }
256 .chat-input { padding:6px 8px; }
257 .chat-topbar { padding:6px 10px; gap:6px; font-size:12px; }
258 }
259 </style>
260 <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
@@ -467,10 +473,11 @@
467 <div class="spacer"></div>
468 <span style="font-size:11px;color:#8b949e;margin-right:6px">chatting as</span>
469 <select id="chat-identity" style="width:140px;padding:3px 6px;font-size:12px" onchange="saveChatIdentity()">
470 <option value="">— pick a user —</option>
471 </select>
 
472 <span class="stream-badge" id="chat-stream-status" style="margin-left:8px"></span>
473 </div>
474 <div class="chat-msgs" id="chat-msgs">
475 <div class="empty" id="chat-placeholder">join a channel to start chatting</div>
476 </div>
@@ -1802,10 +1809,20 @@
1802 sel.innerHTML = '<option value="">— pick a user —</option>' +
1803 options.map(a => `<option value="${esc(a.nick)}"${a.nick===current?' selected':''}>${esc(a.nick)} (${esc(a.type)})</option>`).join('');
1804 // Restore saved selection.
1805 if (current) sel.value = current;
1806 }
 
 
 
 
 
 
 
 
 
 
1807
1808 async function sendMsg() {
1809 if (!chatChannel) return;
1810 const input = document.getElementById('chat-text-input');
1811 const nick = document.getElementById('chat-identity').value.trim() || 'web';
1812
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -140,16 +140,23 @@
140 .chat-main { flex:1; display:flex; flex-direction:column; min-width:0; }
141 .chat-topbar { padding:9px 16px; border-bottom:1px solid #30363d; display:flex; align-items:center; gap:10px; flex-shrink:0; background:#161b22; font-size:13px; }
142 .chat-ch-name { font-weight:600; color:#58a6ff; }
143 .stream-badge { font-size:11px; color:#8b949e; margin-left:auto; }
144 .chat-msgs { flex:1; overflow-y:auto; padding:4px 8px; display:flex; flex-direction:column; gap:0; }
145 .msg-row { font-size:13px; line-height:1.4; padding:1px 0; }
146 .msg-time { color:#8b949e; font-size:11px; margin-right:6px; }
147 .msg-nick { font-weight:600; margin-right:6px; }
148 .msg-grouped .msg-nick { display:none; }
149 .msg-grouped .msg-time { display:none; }
150 /* columnar layout mode */
151 .chat-msgs.columnar .msg-row { display:flex; gap:6px; }
152 .chat-msgs.columnar .msg-time { flex-shrink:0; min-width:36px; padding-top:2px; margin-right:0; }
153 .chat-msgs.columnar .msg-nick { flex-shrink:0; min-width:80px; text-align:right; margin-right:0; }
154 .chat-msgs.columnar .msg-text { word-break:break-word; }
155 .chat-msgs.columnar .msg-grouped .msg-nick { display:inline; visibility:hidden; }
156 .chat-msgs.columnar .msg-grouped .msg-time { display:inline; color:transparent; }
157 .chat-msgs.columnar .msg-grouped:hover .msg-time { color:#8b949e; }
158 .chat-nicklist { width:148px; min-width:0; flex-shrink:0; border-left:1px solid #30363d; display:flex; flex-direction:column; background:#161b22; overflow-y:auto; overflow-x:hidden; transition:width .15s; }
159 .chat-nicklist.collapsed { width:28px; overflow:hidden; }
160 .chat-nicklist.collapsed #nicklist-users { display:none; }
161 .nicklist-head { padding:8px 12px; font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:#8b949e; border-bottom:1px solid #30363d; flex-shrink:0; display:flex; align-items:center; gap:4px; }
162 .nicklist-nick { padding:5px 12px; font-size:12px; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -248,13 +255,12 @@
255 /* login */
256 .login-box { width:90vw; max-width:340px; }
257
258 /* chat: tighter on mobile */
259 .chat-msgs { padding:2px 4px; }
260 .msg-row { font-size:12px; line-height:1.3; }
261 .msg-time { font-size:10px; }
 
262 .chat-input { padding:6px 8px; }
263 .chat-topbar { padding:6px 10px; gap:6px; font-size:12px; }
264 }
265 </style>
266 <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
@@ -467,10 +473,11 @@
473 <div class="spacer"></div>
474 <span style="font-size:11px;color:#8b949e;margin-right:6px">chatting as</span>
475 <select id="chat-identity" style="width:140px;padding:3px 6px;font-size:12px" onchange="saveChatIdentity()">
476 <option value="">— pick a user —</option>
477 </select>
478 <button class="sm" id="chat-layout-toggle" onclick="toggleChatLayout()" title="toggle compact/columnar" style="font-size:11px;padding:2px 6px">☰</button>
479 <span class="stream-badge" id="chat-stream-status" style="margin-left:8px"></span>
480 </div>
481 <div class="chat-msgs" id="chat-msgs">
482 <div class="empty" id="chat-placeholder">join a channel to start chatting</div>
483 </div>
@@ -1802,10 +1809,20 @@
1809 sel.innerHTML = '<option value="">— pick a user —</option>' +
1810 options.map(a => `<option value="${esc(a.nick)}"${a.nick===current?' selected':''}>${esc(a.nick)} (${esc(a.type)})</option>`).join('');
1811 // Restore saved selection.
1812 if (current) sel.value = current;
1813 }
1814
1815 function toggleChatLayout() {
1816 const el = document.getElementById('chat-msgs');
1817 const columnar = el.classList.toggle('columnar');
1818 localStorage.setItem('sb_chat_columnar', columnar ? '1' : '0');
1819 }
1820 // Restore layout preference on load.
1821 if (localStorage.getItem('sb_chat_columnar') === '1') {
1822 document.getElementById('chat-msgs').classList.add('columnar');
1823 }
1824
1825 async function sendMsg() {
1826 if (!chatChannel) return;
1827 const input = document.getElementById('chat-text-input');
1828 const nick = document.getElementById('chat-identity').value.trim() || 'web';
1829

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button