Skip to content

Commit 28a2014

Browse files
committed
Merge PR #49 and resolve conflicts
2 parents 4dd9ca1 + 9b63103 commit 28a2014

File tree

9 files changed

+248
-66
lines changed

9 files changed

+248
-66
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Open `http://localhost:3000`. The dashboard updates in **Realtime** via Server-S
6464
* `/local <msg>` - Send message only to direct connections.
6565
* `/whisper <user> <msg>` - Send a private message.
6666
* `/block <user>` - Block a user.
67+
* `/timestamp` - Toggle message timestamps.
68+
* `/sound` - Toggle sound effects for sent/received messages.
6769
* **Easter Eggs:** `/shrug`, `/tableflip`, `/heart`, and more.
6870

6971
---

public/app.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,26 @@ const promptEl = document.querySelector(".prompt");
281281
let myId = null;
282282
let myChatHistory = [];
283283
let globalChatEnabled = true;
284+
let showTimestamp = localStorage.getItem("showTimestamp") === "true";
284285
let blockedUsers = new Set(
285286
JSON.parse(localStorage.getItem("blockedUsers") || "[]")
286287
);
288+
289+
if (showTimestamp) {
290+
terminalOutput.classList.add("show-timestamps");
291+
}
292+
293+
window.toggleTimestamp = () => {
294+
showTimestamp = !showTimestamp;
295+
localStorage.setItem("showTimestamp", showTimestamp);
296+
if (showTimestamp) {
297+
terminalOutput.classList.add("show-timestamps");
298+
systemStatusBar.innerText = "[SYSTEM] Timestamps enabled";
299+
} else {
300+
terminalOutput.classList.remove("show-timestamps");
301+
systemStatusBar.innerText = "[SYSTEM] Timestamps disabled";
302+
}
303+
};
287304
let nameToId = new Map();
288305

289306
// Context Menu Logic
@@ -487,6 +504,17 @@ const appendMessage = (msg) => {
487504
let scopeLabel = msg.scope === "LOCAL" ? "[LOCAL] " : "";
488505
if (msg.target) scopeLabel = "[WHISPER] ";
489506

507+
const timestampSpan = document.createElement("span");
508+
timestampSpan.className = "timestamp";
509+
const date = new Date();
510+
timestampSpan.innerText = `[${date
511+
.getHours()
512+
.toString()
513+
.padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date
514+
.getSeconds()
515+
.toString()
516+
.padStart(2, "0")}]`;
517+
490518
const senderSpan = document.createElement("span");
491519
senderSpan.className = "msg-sender";
492520
senderSpan.style.color = senderColor;
@@ -517,6 +545,7 @@ const appendMessage = (msg) => {
517545
contentSpan.style.opacity = "0.8";
518546
}
519547

548+
div.appendChild(timestampSpan);
520549
div.appendChild(senderSpan);
521550
div.appendChild(contentSpan);
522551
}
@@ -526,6 +555,11 @@ const appendMessage = (msg) => {
526555
};
527556

528557
terminalInput.addEventListener("keypress", async (e) => {
558+
// Init audio context on first interaction
559+
if (window.SoundManager) {
560+
window.SoundManager.init();
561+
}
562+
529563
if (e.key === "Enter") {
530564
let content = terminalInput.value.trim();
531565
if (!content) return;
@@ -635,6 +669,7 @@ terminalInput.addEventListener("keypress", async (e) => {
635669
});
636670

637671
if (res.ok) {
672+
if (window.SoundManager) window.SoundManager.playSent();
638673
myChatHistory.push(Date.now());
639674
updatePromptStatus();
640675
} else if (res.status === 429) {
@@ -662,6 +697,14 @@ evtSource.onmessage = (event) => {
662697
}
663698

664699
if (data.type === "CHAT") {
700+
// Play sounds
701+
if (window.SoundManager && data.sender !== myId) {
702+
if (data.target === myId) {
703+
window.SoundManager.playWhisper();
704+
} else {
705+
window.SoundManager.playReceived();
706+
}
707+
}
665708
appendMessage(data);
666709
return;
667710
}

public/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<script src="/js/lists.js"></script>
2525
<script src="/js/screenname.js"></script>
2626
<script type="module" src="/js/commands.js"></script>
27+
<script src="/js/sound-manager.js"></script>
2728
<script src="/js/version-checker.js"></script>
2829
</head>
2930

@@ -144,4 +145,4 @@
144145
<script src="/app.js"></script>
145146
</body>
146147

147-
</html>
148+
</html>

public/js/chat-commands/help.js

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,76 @@
11
export const helpCommand = {
2-
description: "Shows available commands",
3-
execute: () => {
4-
const output = document.getElementById("terminal-output");
5-
if (!output) return;
2+
description: "Shows available commands",
3+
execute: () => {
4+
const output = document.getElementById("terminal-output");
5+
if (!output) return;
66

7-
const createHelpSection = (title, items) => {
8-
const section = document.createElement("div");
9-
section.style.marginBottom = "10px";
10-
section.style.color = "#aaa";
7+
const createHelpSection = (title, items) => {
8+
const section = document.createElement("div");
9+
section.style.marginBottom = "10px";
10+
section.style.color = "#aaa";
1111

12-
const header = document.createElement("div");
13-
header.style.fontWeight = "bold";
14-
header.style.color = "#fff";
15-
header.style.marginBottom = "4px";
16-
header.innerText = title;
17-
section.appendChild(header);
12+
const header = document.createElement("div");
13+
header.style.fontWeight = "bold";
14+
header.style.color = "#fff";
15+
header.style.marginBottom = "4px";
16+
header.innerText = title;
17+
section.appendChild(header);
1818

19-
items.forEach((item) => {
20-
const div = document.createElement("div");
21-
div.innerHTML = `<span style="color: #4ade80">${item.cmd}</span> - ${item.desc}`;
22-
section.appendChild(div);
23-
});
19+
items.forEach((item) => {
20+
const div = document.createElement("div");
21+
div.innerHTML = `<span style="color: #4ade80">${item.cmd}</span> - ${item.desc}`;
22+
section.appendChild(div);
23+
});
2424

25-
return section;
26-
};
25+
return section;
26+
};
2727

28-
const helpContainer = document.createElement("div");
29-
helpContainer.className = "system-message";
30-
helpContainer.style.padding = "10px";
31-
helpContainer.style.borderTop = "1px dashed #333";
32-
helpContainer.style.borderBottom = "1px dashed #333";
33-
helpContainer.style.margin = "10px 0";
28+
const helpContainer = document.createElement("div");
29+
helpContainer.className = "system-message";
30+
helpContainer.style.padding = "10px";
31+
helpContainer.style.borderTop = "1px dashed #333";
32+
helpContainer.style.borderBottom = "1px dashed #333";
33+
helpContainer.style.margin = "10px 0";
3434

35-
// system
36-
const systemCmds = [
37-
{
38-
cmd: "/whisper &lt;user&gt; &lt;msg&gt;",
39-
desc: "Send a private message",
40-
},
41-
{ cmd: "/block &lt;user&gt;", desc: "Block messages from a user" },
42-
{ cmd: "/unblock &lt;user&gt;", desc: "Unblock a user" },
43-
{
44-
cmd: "/local &lt;msg&gt;",
45-
desc: "Send message to direct peers only (Global by default)",
46-
},
47-
{ cmd: "/clear", desc: "Clear chat history" },
48-
{ cmd: "/help", desc: "Show this help menu" },
49-
];
50-
helpContainer.appendChild(
51-
createHelpSection("System Commands", systemCmds)
52-
);
35+
// system
36+
const systemCmds = [
37+
{
38+
cmd: "/whisper &lt;user&gt; &lt;msg&gt;",
39+
desc: "Send a private message",
40+
},
41+
{ cmd: "/block &lt;user&gt;", desc: "Block messages from a user" },
42+
{ cmd: "/unblock &lt;user&gt;", desc: "Unblock a user" },
43+
{
44+
cmd: "/local &lt;msg&gt;",
45+
desc: "Send message to direct peers only (Global by default)",
46+
},
47+
{ cmd: "/clear", desc: "Clear chat history" },
48+
{ cmd: "/timestamp", desc: "Toggle timestamps" },
49+
{ cmd: "/sound", desc: "Toggle sound effects" },
50+
{ cmd: "/help", desc: "Show this help menu" },
51+
];
52+
helpContainer.appendChild(createHelpSection("System Commands", systemCmds));
5353

54-
// Formatting
55-
const formatCmds = [
56-
{ cmd: "**text**", desc: "Bold" },
57-
{ cmd: "*text*", desc: "Italics" },
58-
{ cmd: "__text__", desc: "Underline" },
59-
{ cmd: "~~text~~", desc: "Strikethrough" },
60-
{ cmd: "`text`", desc: "Code" },
61-
];
62-
helpContainer.appendChild(createHelpSection("Formatting", formatCmds));
54+
// Formatting
55+
const formatCmds = [
56+
{ cmd: "**text**", desc: "Bold" },
57+
{ cmd: "*text*", desc: "Italics" },
58+
{ cmd: "__text__", desc: "Underline" },
59+
{ cmd: "~~text~~", desc: "Strikethrough" },
60+
{ cmd: "`text`", desc: "Code" },
61+
];
62+
helpContainer.appendChild(createHelpSection("Formatting", formatCmds));
6363

64-
// Easter Eggs
65-
const eggs = Object.entries(window.ChatCommands.replacements).map(
66-
([k, v]) => ({
67-
cmd: k,
68-
desc: v,
69-
})
70-
);
71-
helpContainer.appendChild(createHelpSection("Easter Eggs", eggs));
64+
// Easter Eggs
65+
const eggs = Object.entries(window.ChatCommands.replacements).map(
66+
([k, v]) => ({
67+
cmd: k,
68+
desc: v,
69+
})
70+
);
71+
helpContainer.appendChild(createHelpSection("Easter Eggs", eggs));
7272

73-
output.appendChild(helpContainer);
74-
output.scrollTop = output.scrollHeight;
75-
},
73+
output.appendChild(helpContainer);
74+
output.scrollTop = output.scrollHeight;
75+
},
7676
};

public/js/chat-commands/sound.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const soundCommand = {
2+
description: "Toggles sound effects",
3+
execute: () => {
4+
if (window.SoundManager) {
5+
const enabled = window.SoundManager.toggle();
6+
const status = enabled ? "enabled" : "disabled";
7+
const output = document.getElementById("terminal-output");
8+
if (output) {
9+
const div = document.createElement("div");
10+
div.innerHTML = `<span style="color: #aaa">[SYSTEM] Sound effects ${status}</span>`;
11+
output.appendChild(div);
12+
output.scrollTop = output.scrollHeight;
13+
}
14+
}
15+
},
16+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const timestampCommand = {
2+
description: "Toggles timestamps on and off",
3+
execute: () => {
4+
if (window.toggleTimestamp) {
5+
window.toggleTimestamp();
6+
}
7+
},
8+
};

public/js/commands.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { clearCommand } from "./chat-commands/clear.js";
66
import { frenzyCommand } from "./chat-commands/frenzy.js";
77
import { helpCommand } from "./chat-commands/help.js";
88
import { replacements } from "./chat-commands/replacements.js";
9+
import { timestampCommand } from "./chat-commands/timestamp.js";
10+
import { soundCommand } from "./chat-commands/sound.js";
911

1012
const formatMessage = (text) => {
1113
if (!text) return "";
@@ -30,6 +32,8 @@ const actions = {
3032
"/clear": clearCommand,
3133
"/frenzy": frenzyCommand,
3234
"/help": helpCommand,
35+
"/timestamp": timestampCommand,
36+
"/sound": soundCommand,
3337
};
3438

3539
const processInput = (input) => {
@@ -54,3 +58,4 @@ const ChatCommands = {
5458
};
5559

5660
window.ChatCommands = ChatCommands;
61+
export { ChatCommands };

public/js/sound-manager.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
class SoundManager {
3+
constructor() {
4+
this.ctx = null;
5+
this.enabled = localStorage.getItem("soundEnabled") === "true";
6+
}
7+
8+
init() {
9+
if (!this.ctx) {
10+
const AudioContext = window.AudioContext || window.webkitAudioContext;
11+
if (AudioContext) {
12+
this.ctx = new AudioContext();
13+
}
14+
}
15+
// Resume context if it's suspended (browsers auto-suspend)
16+
if (this.ctx && this.ctx.state === "suspended") {
17+
this.ctx.resume();
18+
}
19+
}
20+
21+
toggle() {
22+
this.enabled = !this.enabled;
23+
localStorage.setItem("soundEnabled", this.enabled);
24+
return this.enabled;
25+
}
26+
27+
// Helper to play a tone
28+
playTone(freq, type, duration, volume = 0.1) {
29+
if (!this.enabled) return;
30+
this.init();
31+
if (!this.ctx) return;
32+
33+
const osc = this.ctx.createOscillator();
34+
const gain = this.ctx.createGain();
35+
36+
osc.type = type;
37+
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
38+
39+
gain.gain.setValueAtTime(volume, this.ctx.currentTime);
40+
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
41+
42+
osc.connect(gain);
43+
gain.connect(this.ctx.destination);
44+
45+
osc.start();
46+
osc.stop(this.ctx.currentTime + duration);
47+
}
48+
49+
playSent() {
50+
// Satisfying "pop" or "click" for sending
51+
// Sine wave starting at 600Hz dropping fast
52+
if (!this.enabled) return;
53+
this.playTone(600, "sine", 0.15, 0.1);
54+
}
55+
56+
playReceived() {
57+
// Soft "bloop" for receiving
58+
// Sine wave starting at 400Hz
59+
if (!this.enabled) return;
60+
this.playTone(400, "sine", 0.15, 0.1);
61+
}
62+
63+
playWhisper() {
64+
// Distinct "ding" for whispers
65+
// Two tones slightly separated
66+
if (!this.enabled) return;
67+
this.init();
68+
if (!this.ctx) return;
69+
70+
const now = this.ctx.currentTime;
71+
72+
// Tone 1
73+
const osc1 = this.ctx.createOscillator();
74+
const gain1 = this.ctx.createGain();
75+
osc1.frequency.setValueAtTime(800, now);
76+
gain1.gain.setValueAtTime(0.1, now);
77+
gain1.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
78+
osc1.connect(gain1);
79+
gain1.connect(this.ctx.destination);
80+
osc1.start(now);
81+
osc1.stop(now + 0.3);
82+
83+
// Tone 2 (higher)
84+
const osc2 = this.ctx.createOscillator();
85+
const gain2 = this.ctx.createGain();
86+
osc2.frequency.setValueAtTime(1200, now + 0.1);
87+
gain2.gain.setValueAtTime(0.1, now + 0.1);
88+
gain2.gain.exponentialRampToValueAtTime(0.01, now + 0.4);
89+
osc2.connect(gain2);
90+
gain2.connect(this.ctx.destination);
91+
osc2.start(now + 0.1);
92+
osc2.stop(now + 0.4);
93+
}
94+
}
95+
96+
window.SoundManager = new SoundManager();

0 commit comments

Comments
 (0)