solid-chat 0.0.10 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +24 -22
- package/package.json +1 -1
- package/src/chatListPane.js +104 -2
- package/src/longChatPane.js +4 -0
package/index.html
CHANGED
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
70
70
|
<div class="header-actions">
|
|
71
|
-
<button class="icon-btn" id="soundToggle" title="Toggle notification sound">🔔</button>
|
|
72
71
|
</div>
|
|
73
72
|
</div>
|
|
74
73
|
|
|
@@ -76,8 +75,8 @@
|
|
|
76
75
|
|
|
77
76
|
<div class="sidebar-footer">
|
|
78
77
|
<select id="sidebarThemeSelect" class="sidebar-theme-select" title="Switch theme">
|
|
79
|
-
<option value="wave">Wave</option>
|
|
80
78
|
<option value="solid">Solid</option>
|
|
79
|
+
<option value="wave">Wave</option>
|
|
81
80
|
<option value="telegram">Telegram</option>
|
|
82
81
|
<option value="signal">Signal</option>
|
|
83
82
|
</select>
|
|
@@ -92,14 +91,6 @@
|
|
|
92
91
|
<span id="userStatus" class="user-status">Loading...</span>
|
|
93
92
|
</div>
|
|
94
93
|
<div class="header-right" id="headerLoginArea">
|
|
95
|
-
<div class="theme-switcher">
|
|
96
|
-
<select id="themeSelect" title="Switch theme">
|
|
97
|
-
<option value="wave">Wave</option>
|
|
98
|
-
<option value="solid">Solid</option>
|
|
99
|
-
<option value="telegram">Telegram</option>
|
|
100
|
-
<option value="signal">Signal</option>
|
|
101
|
-
</select>
|
|
102
|
-
</div>
|
|
103
94
|
<span id="loginArea"></span>
|
|
104
95
|
</div>
|
|
105
96
|
</div>
|
|
@@ -128,7 +119,7 @@
|
|
|
128
119
|
|
|
129
120
|
<script type="module">
|
|
130
121
|
import { longChatPane } from './src/longChatPane.js'
|
|
131
|
-
import { chatListPane, addChat, updateChatPreview } from './src/chatListPane.js'
|
|
122
|
+
import { chatListPane, addChat, updateChatPreview, incrementUnread, resetUnread } from './src/chatListPane.js'
|
|
132
123
|
|
|
133
124
|
// Theme management
|
|
134
125
|
const THEMES = {
|
|
@@ -144,9 +135,7 @@ function loadTheme(themeName) {
|
|
|
144
135
|
themeLink.href = theme.file
|
|
145
136
|
localStorage.setItem('solidchat-theme', themeName)
|
|
146
137
|
|
|
147
|
-
// Update
|
|
148
|
-
const select = document.getElementById('themeSelect')
|
|
149
|
-
if (select) select.value = themeName
|
|
138
|
+
// Update sidebar select
|
|
150
139
|
const sidebarSelect = document.getElementById('sidebarThemeSelect')
|
|
151
140
|
if (sidebarSelect) sidebarSelect.value = themeName
|
|
152
141
|
|
|
@@ -169,10 +158,6 @@ function initTheme() {
|
|
|
169
158
|
const saved = localStorage.getItem('solidchat-theme') || 'solid'
|
|
170
159
|
loadTheme(saved)
|
|
171
160
|
|
|
172
|
-
document.getElementById('themeSelect').addEventListener('change', (e) => {
|
|
173
|
-
loadTheme(e.target.value)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
161
|
document.getElementById('sidebarThemeSelect').addEventListener('change', (e) => {
|
|
177
162
|
loadTheme(e.target.value)
|
|
178
163
|
})
|
|
@@ -425,7 +410,11 @@ function toggleSound() {
|
|
|
425
410
|
|
|
426
411
|
function updateSoundButton() {
|
|
427
412
|
const btn = document.getElementById('soundToggle')
|
|
428
|
-
if (btn)
|
|
413
|
+
if (btn) {
|
|
414
|
+
btn.innerHTML = soundEnabled
|
|
415
|
+
? '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>'
|
|
416
|
+
: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/><line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" stroke-width="2"/></svg>'
|
|
417
|
+
}
|
|
429
418
|
}
|
|
430
419
|
|
|
431
420
|
// Subscribe to real-time updates for a resource
|
|
@@ -500,6 +489,9 @@ async function subscribeWebSocketChannel2023(uri, authFetch) {
|
|
|
500
489
|
console.log('WebSocketChannel2023 notification:', event.data)
|
|
501
490
|
// Any message means the resource changed
|
|
502
491
|
playNotificationSound()
|
|
492
|
+
if (document.hidden) {
|
|
493
|
+
incrementUnread(uri)
|
|
494
|
+
}
|
|
503
495
|
setTimeout(() => refreshChat(), 500)
|
|
504
496
|
}
|
|
505
497
|
|
|
@@ -537,6 +529,9 @@ function connectLegacyWebSocket(updatesVia, uri) {
|
|
|
537
529
|
if (updatedUri === uri || uri.startsWith(updatedUri)) {
|
|
538
530
|
console.log('Chat updated, refreshing...')
|
|
539
531
|
playNotificationSound()
|
|
532
|
+
if (document.hidden) {
|
|
533
|
+
incrementUnread(uri)
|
|
534
|
+
}
|
|
540
535
|
setTimeout(() => refreshChat(), 500)
|
|
541
536
|
}
|
|
542
537
|
}
|
|
@@ -908,15 +903,22 @@ function showToast(message) {
|
|
|
908
903
|
|
|
909
904
|
// Make createChat and copyShareLink available globally for chatListPane
|
|
910
905
|
window.solidChat = { createChat, copyShareLink, getMyPodRoot }
|
|
906
|
+
window.toggleSound = toggleSound
|
|
911
907
|
|
|
912
908
|
// Initialize: handle auth redirect first, then render sidebar
|
|
913
909
|
async function init() {
|
|
914
910
|
// Initialize theme
|
|
915
911
|
initTheme()
|
|
916
912
|
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
913
|
+
// Sound button is now created in chatListPane with onclick handler
|
|
914
|
+
|
|
915
|
+
// Clear unread count when user returns to tab
|
|
916
|
+
document.addEventListener('visibilitychange', () => {
|
|
917
|
+
if (!document.hidden && currentChatUri) {
|
|
918
|
+
resetUnread(currentChatUri)
|
|
919
|
+
chatListPane.setActiveChat(currentChatUri)
|
|
920
|
+
}
|
|
921
|
+
})
|
|
920
922
|
|
|
921
923
|
// Handle auth redirect callback (if returning from IdP)
|
|
922
924
|
await handleAuthRedirect()
|
package/package.json
CHANGED
package/src/chatListPane.js
CHANGED
|
@@ -19,6 +19,45 @@ const MEETING = {
|
|
|
19
19
|
|
|
20
20
|
// Storage key for localStorage
|
|
21
21
|
const STORAGE_KEY = 'solidchat-chats'
|
|
22
|
+
const UNREAD_KEY = 'solidchat-unread'
|
|
23
|
+
|
|
24
|
+
// Get unread count for a chat
|
|
25
|
+
function getUnreadCount(uri) {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(localStorage.getItem(UNREAD_KEY) || '{}')
|
|
28
|
+
return data[uri] || 0
|
|
29
|
+
} catch {
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Increment unread count for a chat (call when new message arrives)
|
|
35
|
+
function incrementUnread(uri) {
|
|
36
|
+
try {
|
|
37
|
+
const data = JSON.parse(localStorage.getItem(UNREAD_KEY) || '{}')
|
|
38
|
+
data[uri] = (data[uri] || 0) + 1
|
|
39
|
+
localStorage.setItem(UNREAD_KEY, JSON.stringify(data))
|
|
40
|
+
renderChatList()
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.warn('Failed to increment unread:', e)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Reset unread count (call when chat is opened)
|
|
47
|
+
function resetUnread(uri) {
|
|
48
|
+
try {
|
|
49
|
+
const data = JSON.parse(localStorage.getItem(UNREAD_KEY) || '{}')
|
|
50
|
+
data[uri] = 0
|
|
51
|
+
localStorage.setItem(UNREAD_KEY, JSON.stringify(data))
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.warn('Failed to reset unread:', e)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Legacy alias for compatibility
|
|
58
|
+
function setLastRead(uri) {
|
|
59
|
+
resetUnread(uri)
|
|
60
|
+
}
|
|
22
61
|
|
|
23
62
|
// Default global chats
|
|
24
63
|
const DEFAULT_CHATS = [
|
|
@@ -96,6 +135,24 @@ const styles = `
|
|
|
96
135
|
background: rgba(255,255,255,0.3);
|
|
97
136
|
}
|
|
98
137
|
|
|
138
|
+
.sound-toggle-btn {
|
|
139
|
+
background: none;
|
|
140
|
+
border: none;
|
|
141
|
+
color: rgba(255,255,255,0.7);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
padding: 6px;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
border-radius: 50%;
|
|
148
|
+
transition: all 0.2s;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.sound-toggle-btn:hover {
|
|
152
|
+
color: white;
|
|
153
|
+
background: rgba(255,255,255,0.15);
|
|
154
|
+
}
|
|
155
|
+
|
|
99
156
|
.chat-list {
|
|
100
157
|
flex: 1;
|
|
101
158
|
overflow-y: auto;
|
|
@@ -164,6 +221,21 @@ const styles = `
|
|
|
164
221
|
flex-shrink: 0;
|
|
165
222
|
}
|
|
166
223
|
|
|
224
|
+
.chat-item-badge {
|
|
225
|
+
background: #e74c3c;
|
|
226
|
+
color: white;
|
|
227
|
+
font-size: 11px;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
min-width: 18px;
|
|
230
|
+
height: 18px;
|
|
231
|
+
border-radius: 9px;
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
justify-content: center;
|
|
235
|
+
padding: 0 5px;
|
|
236
|
+
margin-left: 8px;
|
|
237
|
+
}
|
|
238
|
+
|
|
167
239
|
.chat-item-preview {
|
|
168
240
|
font-size: 13px;
|
|
169
241
|
color: var(--text-muted);
|
|
@@ -555,6 +627,15 @@ function renderChatList() {
|
|
|
555
627
|
header.appendChild(title)
|
|
556
628
|
header.appendChild(time)
|
|
557
629
|
|
|
630
|
+
// Show unread count badge
|
|
631
|
+
const unreadCount = getUnreadCount(chat.uri)
|
|
632
|
+
if (unreadCount > 0) {
|
|
633
|
+
const badge = dom.createElement('div')
|
|
634
|
+
badge.className = 'chat-item-badge'
|
|
635
|
+
badge.textContent = unreadCount > 99 ? '99+' : unreadCount
|
|
636
|
+
header.appendChild(badge)
|
|
637
|
+
}
|
|
638
|
+
|
|
558
639
|
const preview = dom.createElement('div')
|
|
559
640
|
preview.className = 'chat-item-preview'
|
|
560
641
|
preview.textContent = chat.lastMessage || 'No messages yet'
|
|
@@ -577,6 +658,7 @@ function renderChatList() {
|
|
|
577
658
|
|
|
578
659
|
item.onclick = () => {
|
|
579
660
|
activeUri = chat.uri
|
|
661
|
+
setLastRead(chat.uri)
|
|
580
662
|
renderChatList()
|
|
581
663
|
if (onSelectCallback) {
|
|
582
664
|
onSelectCallback(chat.uri)
|
|
@@ -859,13 +941,33 @@ export const chatListPane = {
|
|
|
859
941
|
title.className = 'sidebar-title'
|
|
860
942
|
title.textContent = 'Chats'
|
|
861
943
|
|
|
944
|
+
// Sound toggle button (subtle white bell like Telegram)
|
|
945
|
+
const soundBtn = dom.createElement('button')
|
|
946
|
+
soundBtn.className = 'sound-toggle-btn'
|
|
947
|
+
soundBtn.id = 'soundToggle'
|
|
948
|
+
soundBtn.title = 'Toggle notification sound'
|
|
949
|
+
soundBtn.innerHTML = localStorage.getItem('solidchat-sound') !== 'false'
|
|
950
|
+
? '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>'
|
|
951
|
+
: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/><path d="M3.27 3L2 4.27l2.92 2.92C4.34 8.16 4 9.5 4 11v5l-2 2v1h14.73l2 2L20 19.73 3.27 3z"/></svg>'
|
|
952
|
+
soundBtn.onclick = () => {
|
|
953
|
+
if (window.toggleSound) {
|
|
954
|
+
window.toggleSound()
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
862
958
|
const addBtn = dom.createElement('button')
|
|
863
959
|
addBtn.className = 'add-chat-btn'
|
|
864
960
|
addBtn.textContent = '+'
|
|
865
961
|
addBtn.title = 'Add or create chat'
|
|
866
962
|
addBtn.onclick = () => showAddModal(dom, options.webId)
|
|
867
963
|
|
|
868
|
-
|
|
964
|
+
// Group title and sound button together (flush)
|
|
965
|
+
const titleGroup = dom.createElement('div')
|
|
966
|
+
titleGroup.style.cssText = 'display: flex; align-items: center; gap: 8px;'
|
|
967
|
+
titleGroup.appendChild(title)
|
|
968
|
+
titleGroup.appendChild(soundBtn)
|
|
969
|
+
|
|
970
|
+
header.appendChild(titleGroup)
|
|
869
971
|
header.appendChild(addBtn)
|
|
870
972
|
|
|
871
973
|
// Chat list
|
|
@@ -931,4 +1033,4 @@ export const chatListPane = {
|
|
|
931
1033
|
}
|
|
932
1034
|
|
|
933
1035
|
// Export for use in index.html
|
|
934
|
-
export { addChat, removeChat, updateChatPreview }
|
|
1036
|
+
export { addChat, removeChat, updateChatPreview, setLastRead, incrementUnread, resetUnread }
|
package/src/longChatPane.js
CHANGED
|
@@ -695,6 +695,10 @@ function renderMessageContent(dom, content) {
|
|
|
695
695
|
img.alt = 'Image'
|
|
696
696
|
img.loading = 'lazy'
|
|
697
697
|
img.onclick = () => window.open(part, '_blank')
|
|
698
|
+
img.onload = () => {
|
|
699
|
+
const mc = img.closest('.messages-container')
|
|
700
|
+
if (mc) mc.scrollTop = mc.scrollHeight
|
|
701
|
+
}
|
|
698
702
|
wrapper.appendChild(img)
|
|
699
703
|
container.appendChild(wrapper)
|
|
700
704
|
} else if (VIDEO_EXT.test(part)) {
|