webchat-irc 1.0.0

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.
@@ -0,0 +1,692 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>webchatIRC</title>
8
+ <style>
9
+ * {
10
+ font-family: Georgia, 'Times New Roman', serif;
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ html,
17
+ body {
18
+ height: 100%;
19
+ background: #1a1a1e;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .embed-wrapper {
24
+ display: flex;
25
+ flex-direction: column;
26
+ height: 100%;
27
+ }
28
+
29
+
30
+ .tab-bar {
31
+ display: flex;
32
+ background: #111114;
33
+ border-bottom: 2px solid #333;
34
+ flex-shrink: 0;
35
+ }
36
+
37
+ .tab-btn {
38
+ flex: 1;
39
+ padding: 10px 0;
40
+ text-align: center;
41
+ font-family: Georgia, serif;
42
+ font-size: 14px;
43
+ font-weight: bold;
44
+ color: #888;
45
+ background: transparent;
46
+ border: none;
47
+ cursor: pointer;
48
+ transition: color 0.2s, border-bottom 0.2s;
49
+ border-bottom: 2px solid transparent;
50
+ margin-bottom: -2px;
51
+ }
52
+
53
+ .tab-btn:hover {
54
+ color: #bbb;
55
+ }
56
+
57
+ .tab-btn.active {
58
+ color: #fff;
59
+ border-bottom-color: #0069d9;
60
+ }
61
+
62
+ .tab-panel {
63
+ display: none;
64
+ flex-direction: column;
65
+ flex: 1;
66
+ overflow: hidden;
67
+ }
68
+
69
+ .tab-panel.active {
70
+ display: flex;
71
+ }
72
+
73
+ .username-overlay {
74
+ position: absolute;
75
+ inset: 0;
76
+ z-index: 10;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ background: rgba(20, 17, 25, 0.92);
81
+ transition: opacity 0.3s ease, visibility 0.3s ease;
82
+ }
83
+
84
+ .username-overlay.hidden {
85
+ opacity: 0;
86
+ visibility: hidden;
87
+ pointer-events: none;
88
+ }
89
+
90
+ .username-form {
91
+ text-align: center;
92
+ color: #fff;
93
+ padding: 24px;
94
+ }
95
+
96
+ .username-form h2 {
97
+ margin: 0 0 4px;
98
+ font-size: 22px;
99
+ color: #fff;
100
+ }
101
+
102
+ .form-subtitle {
103
+ color: #999;
104
+ margin: 0 0 16px;
105
+ font-size: 13px;
106
+ }
107
+
108
+ .username-input {
109
+ display: block;
110
+ width: 220px;
111
+ margin: 0 auto 12px;
112
+ padding: 9px 12px;
113
+ font-size: 15px;
114
+ font-family: Georgia, serif;
115
+ border: 2px solid #555;
116
+ border-radius: 4px;
117
+ background: #2a2a2e;
118
+ color: #fff;
119
+ outline: none;
120
+ transition: border-color 0.2s ease;
121
+ }
122
+
123
+ .username-input:focus {
124
+ border-color: #0069d9;
125
+ }
126
+
127
+ .username-input::placeholder {
128
+ color: #777;
129
+ }
130
+
131
+ .btn {
132
+ padding: 9px 22px;
133
+ font-family: Georgia, serif;
134
+ font-size: 14px;
135
+ font-weight: bold;
136
+ border: none;
137
+ border-radius: 4px;
138
+ cursor: pointer;
139
+ color: #fff;
140
+ background: #0069d9;
141
+ transition: background 0.2s ease;
142
+ }
143
+
144
+ .btn:hover {
145
+ background: #0053aa;
146
+ }
147
+
148
+ .chat-header {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ padding: 8px 12px 4px;
153
+ background: rgba(20, 17, 25, 0.95);
154
+ }
155
+
156
+ .chat-header-left {
157
+ display: flex;
158
+ align-items: baseline;
159
+ gap: 10px;
160
+ }
161
+
162
+ .chat-header-right {
163
+ display: flex;
164
+ flex-direction: column;
165
+ align-items: flex-end;
166
+ gap: 2px;
167
+ }
168
+
169
+ .chat-header h1 {
170
+ color: #fff;
171
+ font-size: 16px;
172
+ }
173
+
174
+ .channel-label {
175
+ color: #888;
176
+ font-size: 12px;
177
+ font-style: italic;
178
+ }
179
+
180
+ .server-label {
181
+ color: #aaa;
182
+ font-size: 11px;
183
+ }
184
+
185
+ .users-label {
186
+ color: #0069d9;
187
+ font-size: 11px;
188
+ font-weight: bold;
189
+ }
190
+
191
+ .divider {
192
+ border: 1px solid #333;
193
+ margin: 0 12px;
194
+ }
195
+
196
+ #log {
197
+ flex: 1;
198
+ overflow-y: auto;
199
+ scroll-behavior: smooth;
200
+ }
201
+
202
+ #chat-list {
203
+ list-style-type: none;
204
+ }
205
+
206
+ #chat-list>li {
207
+ padding: 5px 10px;
208
+ font-size: 13px;
209
+ word-wrap: break-word;
210
+ color: #ddd;
211
+ background: #242424;
212
+ line-height: 1.5;
213
+ }
214
+
215
+ #chat-list>li:nth-child(odd) {
216
+ background: #2c2c2c;
217
+ }
218
+
219
+ .timestamp {
220
+ color: #666;
221
+ font-size: 11px;
222
+ margin-right: 4px;
223
+ font-family: 'Courier New', monospace;
224
+ }
225
+
226
+ .domain {
227
+ color: #7eb8da;
228
+ font-weight: bold;
229
+ font-size: 12px;
230
+ }
231
+
232
+ .nick {
233
+ color: #c5c5c5;
234
+ font-weight: bold;
235
+ }
236
+
237
+ .text {
238
+ color: #e0e0e0;
239
+ }
240
+
241
+ .self-msg {
242
+ border-left: 3px solid #0069d9;
243
+ }
244
+
245
+ .self-msg .nick {
246
+ color: #8ab4f8;
247
+ }
248
+
249
+ .system-msg {
250
+ background: transparent !important;
251
+ padding: 3px 10px !important;
252
+ }
253
+
254
+ .system-text {
255
+ color: #777;
256
+ font-style: italic;
257
+ font-size: 12px;
258
+ }
259
+
260
+ .chatbox-wrapper {
261
+ display: flex;
262
+ border-top: 1px solid #333;
263
+ }
264
+
265
+ .chatbox {
266
+ flex: 1;
267
+ padding: 10px 12px;
268
+ font-size: 14px;
269
+ font-family: Georgia, serif;
270
+ border: none;
271
+ outline: none;
272
+ background: #333;
273
+ color: #fff;
274
+ min-height: 40px;
275
+ }
276
+
277
+ .chatbox::placeholder {
278
+ color: #777;
279
+ }
280
+
281
+ .chatbox:focus {
282
+ background: #3a3a3e;
283
+ }
284
+
285
+ .send-btn {
286
+ padding: 10px 16px;
287
+ background: #0069d9;
288
+ color: #fff;
289
+ border: none;
290
+ font-family: Georgia, serif;
291
+ font-size: 14px;
292
+ font-weight: bold;
293
+ cursor: pointer;
294
+ transition: background 0.2s ease;
295
+ }
296
+
297
+ .send-btn:hover {
298
+ background: #0053aa;
299
+ }
300
+
301
+ .send-btn:disabled {
302
+ background: #555;
303
+ cursor: default;
304
+ }
305
+
306
+ /* Guestbook Stuff */
307
+
308
+ .gb-panel-inner {
309
+ flex: 1;
310
+ overflow-y: auto;
311
+ padding: 14px;
312
+ }
313
+
314
+ .gb-sign-form {
315
+ background: #222226;
316
+ border: 1px solid #444;
317
+ border-radius: 6px;
318
+ padding: 14px;
319
+ margin-bottom: 14px;
320
+ }
321
+
322
+ .gb-sign-form.signed {
323
+ text-align: center;
324
+ color: #8ab4f8;
325
+ font-style: italic;
326
+ padding: 18px;
327
+ }
328
+
329
+ .gb-sign-form label {
330
+ display: block;
331
+ color: #ccc;
332
+ font-size: 13px;
333
+ margin-bottom: 3px;
334
+ }
335
+
336
+ .gb-sign-form input,
337
+ .gb-sign-form textarea {
338
+ width: 100%;
339
+ padding: 8px 10px;
340
+ font-size: 14px;
341
+ font-family: Georgia, serif;
342
+ border: 2px solid #555;
343
+ border-radius: 4px;
344
+ background: #2a2a2e;
345
+ color: #fff;
346
+ outline: none;
347
+ margin-bottom: 10px;
348
+ transition: border-color 0.2s ease;
349
+ }
350
+
351
+ .gb-sign-form input:focus,
352
+ .gb-sign-form textarea:focus {
353
+ border-color: #0069d9;
354
+ }
355
+
356
+ .gb-sign-form input::placeholder,
357
+ .gb-sign-form textarea::placeholder {
358
+ color: #777;
359
+ }
360
+
361
+ .gb-sign-form textarea {
362
+ resize: vertical;
363
+ min-height: 50px;
364
+ max-height: 100px;
365
+ }
366
+
367
+ .gb-sign-form button {
368
+ padding: 8px 20px;
369
+ background: #0069d9;
370
+ color: #fff;
371
+ border: none;
372
+ border-radius: 4px;
373
+ font-family: Georgia, serif;
374
+ font-size: 14px;
375
+ font-weight: bold;
376
+ cursor: pointer;
377
+ transition: background 0.2s ease;
378
+ }
379
+
380
+ .gb-sign-form button:hover {
381
+ background: #0053aa;
382
+ }
383
+
384
+ .gb-error {
385
+ color: #e74c3c;
386
+ font-size: 12px;
387
+ margin-bottom: 8px;
388
+ }
389
+
390
+ .gb-entries {
391
+ list-style: none;
392
+ }
393
+
394
+ .gb-entry {
395
+ background: #222226;
396
+ border: 1px solid #3a3a3a;
397
+ border-radius: 5px;
398
+ padding: 10px 12px;
399
+ margin-bottom: 8px;
400
+ transition: border-color 0.2s;
401
+ }
402
+
403
+ .gb-entry:hover {
404
+ border-color: #0069d9;
405
+ }
406
+
407
+ .gb-entry-header {
408
+ display: flex;
409
+ justify-content: space-between;
410
+ align-items: baseline;
411
+ margin-bottom: 4px;
412
+ }
413
+
414
+ .gb-entry-name {
415
+ color: #8ab4f8;
416
+ font-weight: bold;
417
+ font-size: 14px;
418
+ }
419
+
420
+ .gb-entry-date {
421
+ color: #666;
422
+ font-size: 11px;
423
+ font-family: 'Courier New', monospace;
424
+ }
425
+
426
+ .gb-entry-msg {
427
+ color: #ddd;
428
+ font-size: 13px;
429
+ line-height: 1.4;
430
+ word-wrap: break-word;
431
+ }
432
+
433
+ .gb-entry-domain {
434
+ color: #7eb8da;
435
+ font-size: 11px;
436
+ margin-top: 4px;
437
+ }
438
+
439
+ .gb-empty {
440
+ text-align: center;
441
+ color: #666;
442
+ font-style: italic;
443
+ padding: 30px 0;
444
+ }
445
+ </style>
446
+ </head>
447
+
448
+ <body>
449
+ <div class="embed-wrapper">
450
+ <div class="tab-bar">
451
+ <button class="tab-btn active" data-tab="chat">💬 Chat</button>
452
+ <button class="tab-btn" data-tab="guestbook">📖 Guestbook</button>
453
+ </div>
454
+
455
+ <div class="tab-panel active" id="tab-chat">
456
+ <div class="username-overlay" id="usernameOverlay">
457
+ <div class="username-form">
458
+ <h2>Join webchat IRC</h2>
459
+ <p class="form-subtitle">Pick a name and start chatting</p>
460
+ <input type="text" id="usernameInput" class="username-input" placeholder="Enter your name..."
461
+ maxlength="16" autocomplete="off">
462
+ <button class="btn" id="connectBtn">Connect</button>
463
+ </div>
464
+ </div>
465
+
466
+ <div class="chat-header">
467
+ <div class="chat-header-left">
468
+ <h1>webchat-IRC</h1>
469
+ <span class="channel-label" id="channelLabel">#webchatirc-general</span>
470
+ </div>
471
+ <div class="chat-header-right">
472
+ <span class="server-label" id="serverLabel"></span>
473
+ <span class="users-label" id="usersLabel"></span>
474
+ </div>
475
+ </div>
476
+ <div class="divider"></div>
477
+ <div id="log">
478
+ <ul id="chat-list"></ul>
479
+ </div>
480
+
481
+ <div class="chatbox-wrapper" id="chatboxWrapper" style="display: none;">
482
+ <input type="text" class="chatbox" id="chatInput" placeholder="Type a message..." maxlength="500"
483
+ autocomplete="off" disabled>
484
+ <button class="send-btn" id="sendBtn" disabled>Send</button>
485
+ </div>
486
+ </div>
487
+
488
+ <div class="tab-panel" id="tab-guestbook">
489
+ <div class="gb-panel-inner">
490
+ <div class="gb-sign-form" id="gbSignForm">
491
+ <label for="gbName">Your name</label>
492
+ <input type="text" id="gbName" placeholder="Enter your name..." maxlength="32" autocomplete="off">
493
+ <label for="gbMessage">Your message</label>
494
+ <textarea id="gbMessage" placeholder="Say something nice..." maxlength="200"></textarea>
495
+ <div class="gb-error" id="gbError" style="display: none;"></div>
496
+ <button id="gbSignBtn">Sign the Guestbook ✍️</button>
497
+ </div>
498
+
499
+ <ul class="gb-entries" id="gbEntries">
500
+ <li class="gb-empty" id="gbEmpty">No signatures yet. Be the first!</li>
501
+ </ul>
502
+ </div>
503
+ </div>
504
+ </div>
505
+
506
+ <script src="/socket.io/socket.io.js"></script>
507
+ <script>
508
+ const tabBtns = document.querySelectorAll('.tab-btn');
509
+ const tabPanels = document.querySelectorAll('.tab-panel');
510
+ let gbLoaded = false;
511
+
512
+ tabBtns.forEach(btn => {
513
+ btn.addEventListener('click', () => {
514
+ const target = btn.dataset.tab;
515
+ tabBtns.forEach(b => b.classList.remove('active'));
516
+ tabPanels.forEach(p => p.classList.remove('active'));
517
+ btn.classList.add('active');
518
+ document.getElementById('tab-' + target).classList.add('active');
519
+
520
+ if (target === 'guestbook' && !gbLoaded) {
521
+ socket.emit('guestbook:load');
522
+ gbLoaded = true;
523
+ }
524
+ });
525
+ });
526
+
527
+ const socket = io();
528
+
529
+ function escapeHTML(str) {
530
+ const div = document.createElement('div');
531
+ div.textContent = str;
532
+ return div.innerHTML;
533
+ }
534
+
535
+ function formatTime(ts) {
536
+ const d = ts ? new Date(ts) : new Date();
537
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
538
+ }
539
+
540
+ function formatDate(iso) {
541
+ const d = new Date(iso);
542
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
543
+ + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
544
+ }
545
+
546
+ const chatList = document.getElementById('chat-list');
547
+ const logContainer = document.getElementById('log');
548
+ const usernameOverlay = document.getElementById('usernameOverlay');
549
+ const usernameInput = document.getElementById('usernameInput');
550
+ const connectBtn = document.getElementById('connectBtn');
551
+ const chatInput = document.getElementById('chatInput');
552
+ const sendBtn = document.getElementById('sendBtn');
553
+ const chatboxWrapper = document.getElementById('chatboxWrapper');
554
+ const serverLabel = document.getElementById('serverLabel');
555
+ const usersLabel = document.getElementById('usersLabel');
556
+ let connected = false;
557
+ let currentUserCount = 0;
558
+
559
+ function addSystemMessage(text) {
560
+ const li = document.createElement('li');
561
+ li.classList.add('system-msg');
562
+ li.innerHTML = `<span class="timestamp">${formatTime()}</span> <span class="system-text">* ${escapeHTML(text)}</span>`;
563
+ chatList.appendChild(li);
564
+ logContainer.scrollTop = logContainer.scrollHeight;
565
+ }
566
+
567
+ function addChatMessage(msg) {
568
+ const li = document.createElement('li');
569
+ li.classList.add('chat-msg');
570
+ if (msg.self) li.classList.add('self-msg');
571
+ li.innerHTML = `<span class="timestamp">${formatTime(msg.timestamp)}</span> <span class="domain">[${escapeHTML(msg.domain)}]</span> <span class="nick">${escapeHTML(msg.nick)}</span>: <span class="text">${escapeHTML(msg.text)}</span>`;
572
+ chatList.appendChild(li);
573
+ logContainer.scrollTop = logContainer.scrollHeight;
574
+ }
575
+
576
+ socket.on('system', text => addSystemMessage(text));
577
+ socket.on('message', msg => addChatMessage(msg));
578
+ socket.on('error_msg', text => addSystemMessage(`ERROR: ${text}`));
579
+
580
+ socket.on('server_info', data => {
581
+ serverLabel.textContent = `Server: ${data.server}`;
582
+ currentUserCount = data.userCount;
583
+ usersLabel.textContent = `${currentUserCount} user${currentUserCount !== 1 ? 's' : ''}`;
584
+ });
585
+
586
+ socket.on('user_joined', () => {
587
+ if (currentUserCount > 0) {
588
+ currentUserCount++;
589
+ usersLabel.textContent = `${currentUserCount} user${currentUserCount !== 1 ? 's' : ''}`;
590
+ }
591
+ });
592
+
593
+ socket.on('user_left', () => {
594
+ if (currentUserCount > 0) {
595
+ currentUserCount--;
596
+ usersLabel.textContent = `${currentUserCount} user${currentUserCount !== 1 ? 's' : ''}`;
597
+ }
598
+ });
599
+
600
+ function doConnect() {
601
+ const username = usernameInput.value.trim() || 'Guest';
602
+ socket.emit('register', {
603
+ domain: window.location.hostname,
604
+ username: username
605
+ });
606
+ usernameOverlay.classList.add('hidden');
607
+ chatboxWrapper.style.display = 'flex';
608
+ chatInput.disabled = false;
609
+ sendBtn.disabled = false;
610
+ chatInput.focus();
611
+ connected = true;
612
+ }
613
+
614
+ connectBtn.addEventListener('click', doConnect);
615
+ usernameInput.addEventListener('keydown', e => { if (e.key === 'Enter') doConnect(); });
616
+ usernameInput.focus();
617
+
618
+ function sendMessage() {
619
+ const text = chatInput.value.trim();
620
+ if (!text || !connected) return;
621
+ socket.emit('chat', text);
622
+ chatInput.value = '';
623
+ chatInput.focus();
624
+ }
625
+
626
+ chatInput.addEventListener('keydown', e => {
627
+ if (e.key === 'Enter') { e.preventDefault(); sendMessage(); }
628
+ });
629
+ sendBtn.addEventListener('click', sendMessage);
630
+
631
+ const gbSignForm = document.getElementById('gbSignForm');
632
+ const gbName = document.getElementById('gbName');
633
+ const gbMessage = document.getElementById('gbMessage');
634
+ const gbSignBtn = document.getElementById('gbSignBtn');
635
+ const gbError = document.getElementById('gbError');
636
+ const gbEntries = document.getElementById('gbEntries');
637
+ const gbEmpty = document.getElementById('gbEmpty');
638
+
639
+ function createGbEntry(entry) {
640
+ const li = document.createElement('li');
641
+ li.className = 'gb-entry';
642
+ li.innerHTML = `
643
+ <div class="gb-entry-header">
644
+ <span class="gb-entry-name">${escapeHTML(entry.name)}</span>
645
+ <span class="gb-entry-date">${formatDate(entry.date)}</span>
646
+ </div>
647
+ <div class="gb-entry-msg">${escapeHTML(entry.message)}</div>
648
+ <div class="gb-entry-domain">from ${escapeHTML(entry.domain)}</div>
649
+ `;
650
+ return li;
651
+ }
652
+
653
+ socket.on('guestbook:entries', entries => {
654
+ gbEntries.innerHTML = '';
655
+ if (!entries.length) {
656
+ gbEntries.appendChild(gbEmpty);
657
+ return;
658
+ }
659
+ [...entries].reverse().forEach(e => gbEntries.appendChild(createGbEntry(e)));
660
+ });
661
+
662
+ socket.on('guestbook:new', entry => {
663
+ if (gbEmpty.parentNode === gbEntries) gbEntries.removeChild(gbEmpty);
664
+ gbEntries.insertBefore(createGbEntry(entry), gbEntries.firstChild);
665
+ });
666
+
667
+ socket.on('guestbook:signed', () => {
668
+ gbSignForm.classList.add('signed');
669
+ gbSignForm.innerHTML = '<p>✅ Thanks for signing the guestbook!</p>';
670
+ });
671
+
672
+ socket.on('guestbook:error', msg => {
673
+ gbError.textContent = msg;
674
+ gbError.style.display = 'block';
675
+ });
676
+
677
+ gbSignBtn.addEventListener('click', () => {
678
+ const name = gbName.value.trim();
679
+ const message = gbMessage.value.trim();
680
+ gbError.style.display = 'none';
681
+ if (!name) { gbError.textContent = 'Please enter your name.'; gbError.style.display = 'block'; return; }
682
+ if (!message) { gbError.textContent = 'Please write a message.'; gbError.style.display = 'block'; return; }
683
+ socket.emit('guestbook:sign', { name, message });
684
+ });
685
+
686
+ gbMessage.addEventListener('keydown', e => {
687
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); gbSignBtn.click(); }
688
+ });
689
+ </script>
690
+ </body>
691
+
692
+ </html>
Binary file