solid-chat 0.0.5 → 0.0.10

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.
Files changed (3) hide show
  1. package/bin/cli.js +20 -5
  2. package/index.html +144 -247
  3. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -26,7 +26,7 @@ const MIME_TYPES = {
26
26
  '.ttf': 'font/ttf'
27
27
  }
28
28
 
29
- const port = parseInt(process.argv[2]) || 3000
29
+ let port = parseInt(process.argv[2]) || 3333
30
30
 
31
31
  const server = createServer((req, res) => {
32
32
  let filePath = join(ROOT, req.url === '/' ? 'index.html' : req.url)
@@ -61,9 +61,10 @@ const server = createServer((req, res) => {
61
61
  }
62
62
  })
63
63
 
64
- server.listen(port, () => {
65
- const url = `http://localhost:${port}`
66
- console.log(`
64
+ function startServer(p) {
65
+ server.listen(p, () => {
66
+ const url = `http://localhost:${p}`
67
+ console.log(`
67
68
  ╭─────────────────────────────────────╮
68
69
  │ │
69
70
  │ Solid Chat running at: │
@@ -73,5 +74,19 @@ server.listen(port, () => {
73
74
  │ │
74
75
  ╰─────────────────────────────────────╯
75
76
  `)
76
- open(url)
77
+ open(url)
78
+ })
79
+ }
80
+
81
+ server.on('error', (err) => {
82
+ if (err.code === 'EADDRINUSE') {
83
+ console.log(`Port ${port} in use, trying ${port + 1}...`)
84
+ port++
85
+ startServer(port)
86
+ } else {
87
+ console.error('Server error:', err.message)
88
+ process.exit(1)
89
+ }
77
90
  })
91
+
92
+ startServer(port)
package/index.html CHANGED
@@ -28,244 +28,98 @@
28
28
  <link rel="manifest" href="manifest.json">
29
29
 
30
30
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
31
- <style>
32
- :root {
33
- --gradient-start: #667eea;
34
- --gradient-end: #9f7aea;
35
- --bg: #f7f8fc;
36
- --text: #2d3748;
37
- --text-muted: #a0aec0;
38
- --accent: #805ad5;
39
- }
40
31
 
32
+ <!-- Theme CSS - loaded dynamically -->
33
+ <link id="theme-css" rel="stylesheet" href="themes/wave.css">
34
+
35
+ <style>
36
+ /* Base styles that don't change between themes */
41
37
  * { box-sizing: border-box; margin: 0; padding: 0; }
42
38
 
43
39
  body {
44
40
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
45
41
  background: var(--bg);
46
42
  color: var(--text);
47
- min-height: 100vh;
48
- }
49
-
50
- .app-header {
51
- background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
52
- color: white;
53
- padding: 16px 24px;
54
- box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
55
- display: flex;
56
- align-items: center;
57
- gap: 16px;
58
- }
59
-
60
- .app-header h1 {
61
- font-size: 1.25rem;
62
- font-weight: 600;
63
- margin-bottom: 2px;
64
- }
65
-
66
- .app-header p {
67
- font-size: 0.8rem;
68
- opacity: 0.9;
69
- }
70
-
71
- .app-header button {
72
- padding: 6px 14px;
73
- background: rgba(255,255,255,0.2);
74
- color: white;
75
- border: 1px solid rgba(255,255,255,0.3);
76
- border-radius: 6px;
77
- font-size: 12px;
78
- font-weight: 500;
79
- cursor: pointer;
80
- transition: background 0.2s;
81
- }
82
-
83
- .app-header button:hover {
84
- background: rgba(255,255,255,0.3);
85
- }
86
-
87
- .app-header input {
88
- padding: 6px 10px;
89
- border: 1px solid rgba(255,255,255,0.3);
90
- border-radius: 6px;
91
- font-size: 12px;
92
- background: rgba(255,255,255,0.15);
93
- color: white;
94
- width: 140px;
95
- }
96
-
97
- .app-header input::placeholder {
98
- color: rgba(255,255,255,0.7);
99
- }
100
-
101
- #chatContainer {
102
- background: white;
103
- overflow: hidden;
104
- height: 100%;
105
- }
106
-
107
- .placeholder {
108
- display: flex;
109
- flex-direction: column;
110
- align-items: center;
111
- justify-content: center;
112
- height: 100%;
113
- color: var(--text-muted);
114
- text-align: center;
115
- }
116
-
117
- .placeholder-icon {
118
- font-size: 64px;
119
- margin-bottom: 16px;
120
- }
121
-
122
- .placeholder h2 {
123
- color: var(--text);
124
- margin-bottom: 8px;
125
- }
126
-
127
- .loading-spinner {
128
- width: 40px;
129
- height: 40px;
130
- border: 3px solid #e0e0e0;
131
- border-top-color: var(--primary);
132
- border-radius: 50%;
133
- animation: spin 0.8s linear infinite;
134
- margin-bottom: 16px;
135
- }
136
-
137
- @keyframes spin {
138
- to { transform: rotate(360deg); }
139
- }
140
-
141
- @keyframes fadeInUp {
142
- from {
143
- opacity: 0;
144
- transform: translate(-50%, 20px);
145
- }
146
- to {
147
- opacity: 1;
148
- transform: translate(-50%, 0);
149
- }
150
- }
151
-
152
- .error {
153
- background: #fff3f3;
154
- border: 1px solid #ffcdd2;
155
- color: #c62828;
156
- padding: 16px;
157
- border-radius: 8px;
158
- margin-top: 16px;
159
- }
160
-
161
- /* App layout with sidebar */
162
- .app-layout {
163
- display: flex;
164
- height: calc(100vh - 68px);
165
- }
166
-
167
- .sidebar {
168
- width: 320px;
169
- flex-shrink: 0;
170
- height: 100%;
171
- overflow: hidden;
172
- }
173
-
174
- .main-content {
175
- flex: 1;
43
+ height: 100vh;
176
44
  overflow: hidden;
177
- background: var(--bg);
178
- height: 100%;
179
- }
180
-
181
- /* Mobile hamburger */
182
- .mobile-menu-btn {
183
- display: none;
184
- width: 40px;
185
- height: 40px;
186
- background: rgba(255,255,255,0.2);
187
- border: none;
188
- border-radius: 8px;
189
- color: white;
190
- font-size: 24px;
191
- cursor: pointer;
192
- align-items: center;
193
- justify-content: center;
194
- }
195
-
196
- .sidebar-overlay {
197
- display: none;
198
- position: fixed;
199
- top: 0;
200
- left: 0;
201
- right: 0;
202
- bottom: 0;
203
- background: rgba(0,0,0,0.5);
204
- z-index: 99;
205
- }
206
-
207
- @media (max-width: 768px) {
208
- .mobile-menu-btn {
209
- display: flex;
210
- }
211
-
212
- .sidebar {
213
- position: fixed;
214
- top: 0;
215
- left: -320px;
216
- height: 100vh;
217
- z-index: 100;
218
- transition: left 0.3s ease;
219
- box-shadow: 4px 0 20px rgba(0,0,0,0.2);
220
- }
221
-
222
- .sidebar.open {
223
- left: 0;
224
- }
225
-
226
- .sidebar-overlay.open {
227
- display: block;
228
- }
229
-
230
- .app-layout {
231
- height: calc(100vh - 68px);
232
- }
233
-
234
- .main-content {
235
- width: 100%;
236
- }
237
45
  }
238
46
  </style>
239
47
  </head>
240
48
  <body>
241
49
 
242
- <header class="app-header">
243
- <button class="mobile-menu-btn" id="mobileMenuBtn">☰</button>
244
- <div>
245
- <h1>Solid Chat <span id="appVersion" style="font-size: 12px; font-weight: 400; opacity: 0.8;"></span></h1>
246
- <p>Decentralized messaging for the web</p>
247
- </div>
248
- <div id="headerLoginArea" style="margin-left: auto; display: flex; align-items: center; gap: 8px;">
249
- <button id="soundToggle" title="Toggle notification sound" style="background: none; border: none; font-size: 18px; cursor: pointer; padding: 4px;">🔔</button>
250
- <span id="userStatus" style="font-size: 13px; opacity: 0.9;">Loading...</span>
251
- <span id="loginArea"></span>
50
+ <div class="app-wrapper">
51
+ <div class="app-banner">
52
+ <div class="brand">
53
+ <h1>Solid Chat</h1>
54
+ <p>Decentralized messaging</p>
55
+ </div>
252
56
  </div>
253
- </header>
254
57
 
255
- <div class="sidebar-overlay" id="sidebarOverlay"></div>
58
+ <div class="sidebar-overlay" id="sidebarOverlay"></div>
59
+
60
+ <div class="app-container">
61
+ <!-- Left Panel - Chat List -->
62
+ <aside class="left-panel" id="leftPanel">
63
+ <div class="panel-header">
64
+ <div class="header-left">
65
+ <div class="user-avatar" id="userAvatar">S</div>
66
+ <div>
67
+ <div class="header-title">Solid Chat <span class="header-version" id="appVersion"></span></div>
68
+ </div>
69
+ </div>
70
+ <div class="header-actions">
71
+ <button class="icon-btn" id="soundToggle" title="Toggle notification sound">🔔</button>
72
+ </div>
73
+ </div>
256
74
 
257
- <div class="app-layout">
258
- <aside class="sidebar" id="sidebar"></aside>
75
+ <div class="sidebar" id="sidebar"></div>
259
76
 
260
- <main class="main-content">
261
- <div id="chatContainer">
262
- <div class="placeholder" id="placeholder">
263
- <div class="placeholder-icon">💬</div>
264
- <h2>Welcome to Solid Chat</h2>
265
- <p>Select a chat from the sidebar<br>or add a new one with the + button.</p>
77
+ <div class="sidebar-footer">
78
+ <select id="sidebarThemeSelect" class="sidebar-theme-select" title="Switch theme">
79
+ <option value="wave">Wave</option>
80
+ <option value="solid">Solid</option>
81
+ <option value="telegram">Telegram</option>
82
+ <option value="signal">Signal</option>
83
+ </select>
266
84
  </div>
267
- </div>
268
- </main>
85
+ </aside>
86
+
87
+ <!-- Right Panel - Chat -->
88
+ <main class="right-panel">
89
+ <div class="content-header">
90
+ <div class="header-left">
91
+ <button class="mobile-menu-btn" id="mobileMenuBtn">☰</button>
92
+ <span id="userStatus" class="user-status">Loading...</span>
93
+ </div>
94
+ <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
+ <span id="loginArea"></span>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="chat-area">
108
+ <div id="chatContainer">
109
+ <div class="placeholder" id="placeholder">
110
+ <div class="placeholder-icon">💬</div>
111
+ <h2>Welcome to Solid Chat</h2>
112
+ <p>Send and receive messages from your Solid POD.<br>Your data stays with you, always.<br><br>Select a chat from the sidebar or add a new one.</p>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </main>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="status-badge" id="statusBadge">
121
+ <div class="status-dot"></div>
122
+ <span>Connected</span>
269
123
  </div>
270
124
 
271
125
  <!-- Load rdflib and Solid auth -->
@@ -276,6 +130,54 @@
276
130
  import { longChatPane } from './src/longChatPane.js'
277
131
  import { chatListPane, addChat, updateChatPreview } from './src/chatListPane.js'
278
132
 
133
+ // Theme management
134
+ const THEMES = {
135
+ wave: { name: 'Wave', file: 'themes/wave.css' },
136
+ solid: { name: 'Solid', file: 'themes/solid.css' },
137
+ telegram: { name: 'Telegram', file: 'themes/telegram.css' },
138
+ signal: { name: 'Signal', file: 'themes/signal.css' }
139
+ }
140
+
141
+ function loadTheme(themeName) {
142
+ const theme = THEMES[themeName] || THEMES.wave
143
+ const themeLink = document.getElementById('theme-css')
144
+ themeLink.href = theme.file
145
+ localStorage.setItem('solidchat-theme', themeName)
146
+
147
+ // Update selects
148
+ const select = document.getElementById('themeSelect')
149
+ if (select) select.value = themeName
150
+ const sidebarSelect = document.getElementById('sidebarThemeSelect')
151
+ if (sidebarSelect) sidebarSelect.value = themeName
152
+
153
+ // Update title based on theme
154
+ const titles = { wave: 'Wave', solid: 'Solid Chat', telegram: 'Telegram', signal: 'Signal' }
155
+ document.title = titles[themeName] || 'Solid Chat'
156
+ document.querySelector('.header-title').innerHTML = `${titles[themeName]} <span class="header-version" id="appVersion"></span>`
157
+
158
+ // Reload version
159
+ fetch('./package.json')
160
+ .then(r => r.json())
161
+ .then(pkg => {
162
+ const el = document.getElementById('appVersion')
163
+ if (el) el.textContent = `v${pkg.version}`
164
+ })
165
+ .catch(() => {})
166
+ }
167
+
168
+ function initTheme() {
169
+ const saved = localStorage.getItem('solidchat-theme') || 'solid'
170
+ loadTheme(saved)
171
+
172
+ document.getElementById('themeSelect').addEventListener('change', (e) => {
173
+ loadTheme(e.target.value)
174
+ })
175
+
176
+ document.getElementById('sidebarThemeSelect').addEventListener('change', (e) => {
177
+ loadTheme(e.target.value)
178
+ })
179
+ }
180
+
279
181
  // Wait for libraries to be available
280
182
  async function waitForLibraries() {
281
183
  return new Promise((resolve) => {
@@ -299,17 +201,19 @@ const placeholder = document.getElementById('placeholder')
299
201
  const userStatus = document.getElementById('userStatus')
300
202
  const loginArea = document.getElementById('loginArea')
301
203
  const sidebar = document.getElementById('sidebar')
204
+ const leftPanel = document.getElementById('leftPanel')
302
205
  const mobileMenuBtn = document.getElementById('mobileMenuBtn')
303
206
  const sidebarOverlay = document.getElementById('sidebarOverlay')
207
+ const statusBadge = document.getElementById('statusBadge')
304
208
 
305
209
  // Mobile menu toggle
306
210
  mobileMenuBtn.addEventListener('click', () => {
307
- sidebar.classList.toggle('open')
211
+ leftPanel.classList.toggle('open')
308
212
  sidebarOverlay.classList.toggle('open')
309
213
  })
310
214
 
311
215
  sidebarOverlay.addEventListener('click', () => {
312
- sidebar.classList.remove('open')
216
+ leftPanel.classList.remove('open')
313
217
  sidebarOverlay.classList.remove('open')
314
218
  })
315
219
 
@@ -341,14 +245,18 @@ async function handleAuthRedirect() {
341
245
  function updateAuthUI(isLoggedIn) {
342
246
  if (isLoggedIn && currentWebId) {
343
247
  const shortId = currentWebId.split('//')[1]?.split('/')[0] || currentWebId
344
- userStatus.innerHTML = `Logged in as <strong><a href="${currentWebId}" target="_blank" style="color: white; text-decoration: underline; text-underline-offset: 2px;">${shortId}</a></strong>`
345
- loginArea.innerHTML = `<button id="logoutBtn">Logout</button>`
248
+ userStatus.innerHTML = `Logged in as <a href="${currentWebId}" target="_blank">${shortId}</a>`
249
+ loginArea.innerHTML = `<button class="btn btn-secondary" id="logoutBtn">Logout</button>`
346
250
  document.getElementById('logoutBtn').addEventListener('click', handleLogout)
251
+
252
+ // Update avatar with user initial
253
+ const initial = shortId.charAt(0).toUpperCase()
254
+ document.getElementById('userAvatar').textContent = initial
347
255
  } else {
348
256
  userStatus.textContent = 'Not logged in'
349
257
  loginArea.innerHTML = `
350
- <input type="text" id="idpInput" placeholder="e.g. solidweb.org" style="padding: 8px 12px; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 13px; width: 160px;">
351
- <button id="loginBtn">Login</button>
258
+ <input type="text" class="input-field" id="idpInput" placeholder="e.g. solidweb.org">
259
+ <button class="btn btn-primary" id="loginBtn">Login</button>
352
260
  `
353
261
  document.getElementById('loginBtn').addEventListener('click', handleLogin)
354
262
  document.getElementById('idpInput').addEventListener('keydown', (e) => {
@@ -706,7 +614,7 @@ async function loadChat(uri) {
706
614
  }
707
615
 
708
616
  // Close mobile sidebar if open
709
- sidebar.classList.remove('open')
617
+ leftPanel.classList.remove('open')
710
618
  sidebarOverlay.classList.remove('open')
711
619
 
712
620
  chatContainer.innerHTML = `
@@ -989,19 +897,6 @@ function showToast(message) {
989
897
  const toast = document.createElement('div')
990
898
  toast.className = 'toast'
991
899
  toast.textContent = message
992
- toast.style.cssText = `
993
- position: fixed;
994
- bottom: 24px;
995
- left: 50%;
996
- transform: translateX(-50%);
997
- background: #1e1e2e;
998
- color: white;
999
- padding: 12px 24px;
1000
- border-radius: 8px;
1001
- font-size: 14px;
1002
- z-index: 9999;
1003
- animation: fadeInUp 0.3s ease;
1004
- `
1005
900
  document.body.appendChild(toast)
1006
901
 
1007
902
  setTimeout(() => {
@@ -1016,13 +911,8 @@ window.solidChat = { createChat, copyShareLink, getMyPodRoot }
1016
911
 
1017
912
  // Initialize: handle auth redirect first, then render sidebar
1018
913
  async function init() {
1019
- // Load and display version
1020
- fetch('./package.json')
1021
- .then(r => r.json())
1022
- .then(pkg => {
1023
- document.getElementById('appVersion').textContent = `v${pkg.version}`
1024
- })
1025
- .catch(() => {})
914
+ // Initialize theme
915
+ initTheme()
1026
916
 
1027
917
  // Set initial sound button state
1028
918
  updateSoundButton()
@@ -1047,6 +937,13 @@ async function init() {
1047
937
  })
1048
938
  sidebar.appendChild(sidebarElement)
1049
939
 
940
+ // Move theme selector above Discover Chats button
941
+ const discoverBtn = sidebar.querySelector('.discover-btn')
942
+ const themeSelect = document.getElementById('sidebarThemeSelect')
943
+ if (discoverBtn && themeSelect) {
944
+ discoverBtn.parentNode.insertBefore(themeSelect.parentNode, discoverBtn)
945
+ }
946
+
1050
947
  // Check for ?chat= deep link first, then ?uri= (legacy), then default
1051
948
  const deepLinkedChat = await handleDeepLink()
1052
949
  if (deepLinkedChat) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-chat",
3
- "version": "0.0.5",
3
+ "version": "0.0.10",
4
4
  "description": "Modern chat panes for Solid pods - longChatPane and chatListPane",
5
5
  "type": "module",
6
6
  "main": "src/index.js",