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.
Commit
c232ecf74b055de9ad2f73e3f2da87e1124aad2b97bbcca9c85fba207979af70
Parent
a19b71942f74d61…
1 file changed
+26
-9
+26
-9
| --- internal/api/ui/index.html | ||
| +++ internal/api/ui/index.html | ||
| @@ -140,16 +140,23 @@ | ||
| 140 | 140 | .chat-main { flex:1; display:flex; flex-direction:column; min-width:0; } |
| 141 | 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 | 142 | .chat-ch-name { font-weight:600; color:#58a6ff; } |
| 143 | 143 | .stream-badge { font-size:11px; color:#8b949e; margin-left:auto; } |
| 144 | 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; } | |
| 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; } | |
| 151 | 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; } |
| 152 | 159 | .chat-nicklist.collapsed { width:28px; overflow:hidden; } |
| 153 | 160 | .chat-nicklist.collapsed #nicklist-users { display:none; } |
| 154 | 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; } |
| 155 | 162 | .nicklist-nick { padding:5px 12px; font-size:12px; color:#8b949e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } |
| @@ -248,13 +255,12 @@ | ||
| 248 | 255 | /* login */ |
| 249 | 256 | .login-box { width:90vw; max-width:340px; } |
| 250 | 257 | |
| 251 | 258 | /* chat: tighter on mobile */ |
| 252 | 259 | .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; } | |
| 256 | 262 | .chat-input { padding:6px 8px; } |
| 257 | 263 | .chat-topbar { padding:6px 10px; gap:6px; font-size:12px; } |
| 258 | 264 | } |
| 259 | 265 | </style> |
| 260 | 266 | <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script> |
| @@ -467,10 +473,11 @@ | ||
| 467 | 473 | <div class="spacer"></div> |
| 468 | 474 | <span style="font-size:11px;color:#8b949e;margin-right:6px">chatting as</span> |
| 469 | 475 | <select id="chat-identity" style="width:140px;padding:3px 6px;font-size:12px" onchange="saveChatIdentity()"> |
| 470 | 476 | <option value="">— pick a user —</option> |
| 471 | 477 | </select> |
| 478 | + <button class="sm" id="chat-layout-toggle" onclick="toggleChatLayout()" title="toggle compact/columnar" style="font-size:11px;padding:2px 6px">☰</button> | |
| 472 | 479 | <span class="stream-badge" id="chat-stream-status" style="margin-left:8px"></span> |
| 473 | 480 | </div> |
| 474 | 481 | <div class="chat-msgs" id="chat-msgs"> |
| 475 | 482 | <div class="empty" id="chat-placeholder">join a channel to start chatting</div> |
| 476 | 483 | </div> |
| @@ -1802,10 +1809,20 @@ | ||
| 1802 | 1809 | sel.innerHTML = '<option value="">— pick a user —</option>' + |
| 1803 | 1810 | options.map(a => `<option value="${esc(a.nick)}"${a.nick===current?' selected':''}>${esc(a.nick)} (${esc(a.type)})</option>`).join(''); |
| 1804 | 1811 | // Restore saved selection. |
| 1805 | 1812 | if (current) sel.value = current; |
| 1806 | 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 | +} | |
| 1807 | 1824 | |
| 1808 | 1825 | async function sendMsg() { |
| 1809 | 1826 | if (!chatChannel) return; |
| 1810 | 1827 | const input = document.getElementById('chat-text-input'); |
| 1811 | 1828 | const nick = document.getElementById('chat-identity').value.trim() || 'web'; |
| 1812 | 1829 |
| --- 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 |