mirror of
https://github.com/lklynet/hypermind.git
synced 2026-05-03 09:30:36 +00:00
Merge PR #49 and resolve conflicts
This commit is contained in:
@@ -64,6 +64,8 @@ Open `http://localhost:3000`. The dashboard updates in **Realtime** via Server-S
|
||||
* `/local <msg>` - Send message only to direct connections.
|
||||
* `/whisper <user> <msg>` - Send a private message.
|
||||
* `/block <user>` - Block a user.
|
||||
* `/timestamp` - Toggle message timestamps.
|
||||
* `/sound` - Toggle sound effects for sent/received messages.
|
||||
* **Easter Eggs:** `/shrug`, `/tableflip`, `/heart`, and more.
|
||||
|
||||
---
|
||||
|
||||
@@ -281,9 +281,26 @@ const promptEl = document.querySelector(".prompt");
|
||||
let myId = null;
|
||||
let myChatHistory = [];
|
||||
let globalChatEnabled = true;
|
||||
let showTimestamp = localStorage.getItem("showTimestamp") === "true";
|
||||
let blockedUsers = new Set(
|
||||
JSON.parse(localStorage.getItem("blockedUsers") || "[]")
|
||||
);
|
||||
|
||||
if (showTimestamp) {
|
||||
terminalOutput.classList.add("show-timestamps");
|
||||
}
|
||||
|
||||
window.toggleTimestamp = () => {
|
||||
showTimestamp = !showTimestamp;
|
||||
localStorage.setItem("showTimestamp", showTimestamp);
|
||||
if (showTimestamp) {
|
||||
terminalOutput.classList.add("show-timestamps");
|
||||
systemStatusBar.innerText = "[SYSTEM] Timestamps enabled";
|
||||
} else {
|
||||
terminalOutput.classList.remove("show-timestamps");
|
||||
systemStatusBar.innerText = "[SYSTEM] Timestamps disabled";
|
||||
}
|
||||
};
|
||||
let nameToId = new Map();
|
||||
|
||||
// Context Menu Logic
|
||||
@@ -487,6 +504,17 @@ const appendMessage = (msg) => {
|
||||
let scopeLabel = msg.scope === "LOCAL" ? "[LOCAL] " : "";
|
||||
if (msg.target) scopeLabel = "[WHISPER] ";
|
||||
|
||||
const timestampSpan = document.createElement("span");
|
||||
timestampSpan.className = "timestamp";
|
||||
const date = new Date();
|
||||
timestampSpan.innerText = `[${date
|
||||
.getHours()
|
||||
.toString()
|
||||
.padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date
|
||||
.getSeconds()
|
||||
.toString()
|
||||
.padStart(2, "0")}]`;
|
||||
|
||||
const senderSpan = document.createElement("span");
|
||||
senderSpan.className = "msg-sender";
|
||||
senderSpan.style.color = senderColor;
|
||||
@@ -517,6 +545,7 @@ const appendMessage = (msg) => {
|
||||
contentSpan.style.opacity = "0.8";
|
||||
}
|
||||
|
||||
div.appendChild(timestampSpan);
|
||||
div.appendChild(senderSpan);
|
||||
div.appendChild(contentSpan);
|
||||
}
|
||||
@@ -526,6 +555,11 @@ const appendMessage = (msg) => {
|
||||
};
|
||||
|
||||
terminalInput.addEventListener("keypress", async (e) => {
|
||||
// Init audio context on first interaction
|
||||
if (window.SoundManager) {
|
||||
window.SoundManager.init();
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
let content = terminalInput.value.trim();
|
||||
if (!content) return;
|
||||
@@ -635,6 +669,7 @@ terminalInput.addEventListener("keypress", async (e) => {
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
if (window.SoundManager) window.SoundManager.playSent();
|
||||
myChatHistory.push(Date.now());
|
||||
updatePromptStatus();
|
||||
} else if (res.status === 429) {
|
||||
@@ -662,6 +697,14 @@ evtSource.onmessage = (event) => {
|
||||
}
|
||||
|
||||
if (data.type === "CHAT") {
|
||||
// Play sounds
|
||||
if (window.SoundManager && data.sender !== myId) {
|
||||
if (data.target === myId) {
|
||||
window.SoundManager.playWhisper();
|
||||
} else {
|
||||
window.SoundManager.playReceived();
|
||||
}
|
||||
}
|
||||
appendMessage(data);
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@
|
||||
<script src="/js/lists.js"></script>
|
||||
<script src="/js/screenname.js"></script>
|
||||
<script type="module" src="/js/commands.js"></script>
|
||||
<script src="/js/sound-manager.js"></script>
|
||||
<script src="/js/version-checker.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -144,4 +145,4 @@
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
export const helpCommand = {
|
||||
description: "Shows available commands",
|
||||
execute: () => {
|
||||
const output = document.getElementById("terminal-output");
|
||||
if (!output) return;
|
||||
description: "Shows available commands",
|
||||
execute: () => {
|
||||
const output = document.getElementById("terminal-output");
|
||||
if (!output) return;
|
||||
|
||||
const createHelpSection = (title, items) => {
|
||||
const section = document.createElement("div");
|
||||
section.style.marginBottom = "10px";
|
||||
section.style.color = "#aaa";
|
||||
const createHelpSection = (title, items) => {
|
||||
const section = document.createElement("div");
|
||||
section.style.marginBottom = "10px";
|
||||
section.style.color = "#aaa";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.style.fontWeight = "bold";
|
||||
header.style.color = "#fff";
|
||||
header.style.marginBottom = "4px";
|
||||
header.innerText = title;
|
||||
section.appendChild(header);
|
||||
const header = document.createElement("div");
|
||||
header.style.fontWeight = "bold";
|
||||
header.style.color = "#fff";
|
||||
header.style.marginBottom = "4px";
|
||||
header.innerText = title;
|
||||
section.appendChild(header);
|
||||
|
||||
items.forEach((item) => {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `<span style="color: #4ade80">${item.cmd}</span> - ${item.desc}`;
|
||||
section.appendChild(div);
|
||||
});
|
||||
items.forEach((item) => {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `<span style="color: #4ade80">${item.cmd}</span> - ${item.desc}`;
|
||||
section.appendChild(div);
|
||||
});
|
||||
|
||||
return section;
|
||||
};
|
||||
return section;
|
||||
};
|
||||
|
||||
const helpContainer = document.createElement("div");
|
||||
helpContainer.className = "system-message";
|
||||
helpContainer.style.padding = "10px";
|
||||
helpContainer.style.borderTop = "1px dashed #333";
|
||||
helpContainer.style.borderBottom = "1px dashed #333";
|
||||
helpContainer.style.margin = "10px 0";
|
||||
const helpContainer = document.createElement("div");
|
||||
helpContainer.className = "system-message";
|
||||
helpContainer.style.padding = "10px";
|
||||
helpContainer.style.borderTop = "1px dashed #333";
|
||||
helpContainer.style.borderBottom = "1px dashed #333";
|
||||
helpContainer.style.margin = "10px 0";
|
||||
|
||||
// system
|
||||
const systemCmds = [
|
||||
{
|
||||
cmd: "/whisper <user> <msg>",
|
||||
desc: "Send a private message",
|
||||
},
|
||||
{ cmd: "/block <user>", desc: "Block messages from a user" },
|
||||
{ cmd: "/unblock <user>", desc: "Unblock a user" },
|
||||
{
|
||||
cmd: "/local <msg>",
|
||||
desc: "Send message to direct peers only (Global by default)",
|
||||
},
|
||||
{ cmd: "/clear", desc: "Clear chat history" },
|
||||
{ cmd: "/help", desc: "Show this help menu" },
|
||||
];
|
||||
helpContainer.appendChild(
|
||||
createHelpSection("System Commands", systemCmds)
|
||||
);
|
||||
// system
|
||||
const systemCmds = [
|
||||
{
|
||||
cmd: "/whisper <user> <msg>",
|
||||
desc: "Send a private message",
|
||||
},
|
||||
{ cmd: "/block <user>", desc: "Block messages from a user" },
|
||||
{ cmd: "/unblock <user>", desc: "Unblock a user" },
|
||||
{
|
||||
cmd: "/local <msg>",
|
||||
desc: "Send message to direct peers only (Global by default)",
|
||||
},
|
||||
{ cmd: "/clear", desc: "Clear chat history" },
|
||||
{ cmd: "/timestamp", desc: "Toggle timestamps" },
|
||||
{ cmd: "/sound", desc: "Toggle sound effects" },
|
||||
{ cmd: "/help", desc: "Show this help menu" },
|
||||
];
|
||||
helpContainer.appendChild(createHelpSection("System Commands", systemCmds));
|
||||
|
||||
// Formatting
|
||||
const formatCmds = [
|
||||
{ cmd: "**text**", desc: "Bold" },
|
||||
{ cmd: "*text*", desc: "Italics" },
|
||||
{ cmd: "__text__", desc: "Underline" },
|
||||
{ cmd: "~~text~~", desc: "Strikethrough" },
|
||||
{ cmd: "`text`", desc: "Code" },
|
||||
];
|
||||
helpContainer.appendChild(createHelpSection("Formatting", formatCmds));
|
||||
// Formatting
|
||||
const formatCmds = [
|
||||
{ cmd: "**text**", desc: "Bold" },
|
||||
{ cmd: "*text*", desc: "Italics" },
|
||||
{ cmd: "__text__", desc: "Underline" },
|
||||
{ cmd: "~~text~~", desc: "Strikethrough" },
|
||||
{ cmd: "`text`", desc: "Code" },
|
||||
];
|
||||
helpContainer.appendChild(createHelpSection("Formatting", formatCmds));
|
||||
|
||||
// Easter Eggs
|
||||
const eggs = Object.entries(window.ChatCommands.replacements).map(
|
||||
([k, v]) => ({
|
||||
cmd: k,
|
||||
desc: v,
|
||||
})
|
||||
);
|
||||
helpContainer.appendChild(createHelpSection("Easter Eggs", eggs));
|
||||
// Easter Eggs
|
||||
const eggs = Object.entries(window.ChatCommands.replacements).map(
|
||||
([k, v]) => ({
|
||||
cmd: k,
|
||||
desc: v,
|
||||
})
|
||||
);
|
||||
helpContainer.appendChild(createHelpSection("Easter Eggs", eggs));
|
||||
|
||||
output.appendChild(helpContainer);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
},
|
||||
output.appendChild(helpContainer);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export const soundCommand = {
|
||||
description: "Toggles sound effects",
|
||||
execute: () => {
|
||||
if (window.SoundManager) {
|
||||
const enabled = window.SoundManager.toggle();
|
||||
const status = enabled ? "enabled" : "disabled";
|
||||
const output = document.getElementById("terminal-output");
|
||||
if (output) {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `<span style="color: #aaa">[SYSTEM] Sound effects ${status}</span>`;
|
||||
output.appendChild(div);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export const timestampCommand = {
|
||||
description: "Toggles timestamps on and off",
|
||||
execute: () => {
|
||||
if (window.toggleTimestamp) {
|
||||
window.toggleTimestamp();
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,8 @@ import { clearCommand } from "./chat-commands/clear.js";
|
||||
import { frenzyCommand } from "./chat-commands/frenzy.js";
|
||||
import { helpCommand } from "./chat-commands/help.js";
|
||||
import { replacements } from "./chat-commands/replacements.js";
|
||||
import { timestampCommand } from "./chat-commands/timestamp.js";
|
||||
import { soundCommand } from "./chat-commands/sound.js";
|
||||
|
||||
const formatMessage = (text) => {
|
||||
if (!text) return "";
|
||||
@@ -30,6 +32,8 @@ const actions = {
|
||||
"/clear": clearCommand,
|
||||
"/frenzy": frenzyCommand,
|
||||
"/help": helpCommand,
|
||||
"/timestamp": timestampCommand,
|
||||
"/sound": soundCommand,
|
||||
};
|
||||
|
||||
const processInput = (input) => {
|
||||
@@ -54,3 +58,4 @@ const ChatCommands = {
|
||||
};
|
||||
|
||||
window.ChatCommands = ChatCommands;
|
||||
export { ChatCommands };
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
class SoundManager {
|
||||
constructor() {
|
||||
this.ctx = null;
|
||||
this.enabled = localStorage.getItem("soundEnabled") === "true";
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.ctx) {
|
||||
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
if (AudioContext) {
|
||||
this.ctx = new AudioContext();
|
||||
}
|
||||
}
|
||||
// Resume context if it's suspended (browsers auto-suspend)
|
||||
if (this.ctx && this.ctx.state === "suspended") {
|
||||
this.ctx.resume();
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.enabled = !this.enabled;
|
||||
localStorage.setItem("soundEnabled", this.enabled);
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
// Helper to play a tone
|
||||
playTone(freq, type, duration, volume = 0.1) {
|
||||
if (!this.enabled) return;
|
||||
this.init();
|
||||
if (!this.ctx) return;
|
||||
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
|
||||
|
||||
gain.gain.setValueAtTime(volume, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(this.ctx.destination);
|
||||
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + duration);
|
||||
}
|
||||
|
||||
playSent() {
|
||||
// Satisfying "pop" or "click" for sending
|
||||
// Sine wave starting at 600Hz dropping fast
|
||||
if (!this.enabled) return;
|
||||
this.playTone(600, "sine", 0.15, 0.1);
|
||||
}
|
||||
|
||||
playReceived() {
|
||||
// Soft "bloop" for receiving
|
||||
// Sine wave starting at 400Hz
|
||||
if (!this.enabled) return;
|
||||
this.playTone(400, "sine", 0.15, 0.1);
|
||||
}
|
||||
|
||||
playWhisper() {
|
||||
// Distinct "ding" for whispers
|
||||
// Two tones slightly separated
|
||||
if (!this.enabled) return;
|
||||
this.init();
|
||||
if (!this.ctx) return;
|
||||
|
||||
const now = this.ctx.currentTime;
|
||||
|
||||
// Tone 1
|
||||
const osc1 = this.ctx.createOscillator();
|
||||
const gain1 = this.ctx.createGain();
|
||||
osc1.frequency.setValueAtTime(800, now);
|
||||
gain1.gain.setValueAtTime(0.1, now);
|
||||
gain1.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
|
||||
osc1.connect(gain1);
|
||||
gain1.connect(this.ctx.destination);
|
||||
osc1.start(now);
|
||||
osc1.stop(now + 0.3);
|
||||
|
||||
// Tone 2 (higher)
|
||||
const osc2 = this.ctx.createOscillator();
|
||||
const gain2 = this.ctx.createGain();
|
||||
osc2.frequency.setValueAtTime(1200, now + 0.1);
|
||||
gain2.gain.setValueAtTime(0.1, now + 0.1);
|
||||
gain2.gain.exponentialRampToValueAtTime(0.01, now + 0.4);
|
||||
osc2.connect(gain2);
|
||||
gain2.connect(this.ctx.destination);
|
||||
osc2.start(now + 0.1);
|
||||
osc2.stop(now + 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
window.SoundManager = new SoundManager();
|
||||
@@ -338,6 +338,17 @@ a { color: var(--color-text-anchor-link); text-decoration: none; border-bottom:
|
||||
color: var(--color-terminal-output-message);
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
display: none;
|
||||
color: #888;
|
||||
font-size: 0.8em;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.show-timestamps .timestamp {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Context Menu */
|
||||
.context-menu {
|
||||
display: none;
|
||||
|
||||
Reference in New Issue
Block a user