Remove Armored Chat

Signed-off-by: Armored Dragon <publicmail@armoreddragon.com>
This commit is contained in:
Armored Dragon 2024-05-17 16:45:21 -05:00
parent 4fe8017540
commit 1bbbbe51ea
No known key found for this signature in database
GPG key ID: C7207ACC3382AD8B
22 changed files with 0 additions and 1216 deletions

View file

@ -1,25 +0,0 @@
# Armored Chat
Armored Chat is a light-weight chat application that allows members of a world to communicate with text.
## Features
- (wip) E2EE Direct messages
- (wip) Group chats
- (?) Message signing
## Encryption
TODO:
- Key exchange
- When and where
- How
## Group chats
TODO:
- How
- Limitations

View file

@ -1,258 +0,0 @@
//
// armored_chat.js
//
// Created by Armored Dragon, 2024.
// Copyright 2024 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function () {
"use strict";
// TODO: Encryption + PMs
// TODO: Open in external web browser
var app_is_visible = false;
var settings = {
max_history: 250,
compact_chat: false,
external_window: false,
};
var app_data = { current_page: "domain" };
// Global vars
var ac_tablet;
var chat_overlay_window;
var app_button;
const channels = ["domain", "local", "system"];
var max_local_distance = 20; // Maximum range for the local chat
var message_history = Settings.getValue("ArmoredChat-Messages", []);
startup();
function startup() {
ac_tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
app_button = ac_tablet.addButton({
icon: Script.resolvePath("./img/icon_white.png"),
activeIcon: Script.resolvePath("./img/icon_black.png"),
text: "CHAT",
isActive: app_is_visible,
});
// When script ends, remove itself from tablet
Script.scriptEnding.connect(function () {
console.log("Shutting Down");
ac_tablet.removeButton(app_button);
chat_overlay_window.close();
});
// Overlay button toggle
app_button.clicked.connect(toggleMainChatWindow);
_openWindow();
}
function toggleMainChatWindow() {
app_is_visible = !app_is_visible;
console.log(`App is now ${app_is_visible ? "visible" : "hidden"}`);
app_button.editProperties({ isActive: app_is_visible });
chat_overlay_window.visible = app_is_visible;
// External window was closed; the window does not exist anymore
if (chat_overlay_window.title == "" && app_is_visible) {
_openWindow();
}
}
function _openWindow() {
chat_overlay_window = new Desktop.createWindow(Script.resourcesPath() + "qml/hifi/tablet/DynamicWebview.qml", {
title: "Chat",
size: { x: 550, y: 400 },
additionalFlags: Desktop.ALWAYS_ON_TOP,
visible: app_is_visible, // FIXME Invalid?
presentationMode: Desktop.PresentationMode.VIRTUAL,
});
chat_overlay_window.closed.connect(toggleMainChatWindow);
chat_overlay_window.sendToQml({ url: Script.resolvePath("./index.html") });
chat_overlay_window.webEventReceived.connect(onWebEventReceived);
}
// Initialize default message subscriptions
Messages.subscribe("chat");
// Messages.subscribe("system");
Messages.messageReceived.connect(receivedMessage);
function receivedMessage(channel, message) {
channel = channel.toLowerCase();
if (channel !== "chat") return;
console.log(`Received message:\n${message}`);
var message = JSON.parse(message);
message.channel = message.channel.toLowerCase();
// For now, while we are working on superseding Floof, we will allow compatibility with it.
// If for_app exists, it came from us and we are just sending the message so Floof can read it.
// We don't need to listen to this message.
if (message.for_app) return;
// Check the channel is valid
if (!channels.includes(message.channel)) return;
// If message is local, and if player is too far away from location, don't do anything
if (channel === "local" && Vec3.distance(MyAvatar.position, message.position) < max_local_distance) return;
// NOTE: Floof chat compatibility.
message.type = "show_message";
// Update web view of to new message
_emitEvent({ type: "show_message", ...message });
// Save message to our history
let saved_message = message;
delete saved_message.position;
saved_message.timeString = new Date().toLocaleTimeString(undefined, { hour12: false });
saved_message.dateString = new Date().toLocaleDateString(undefined, {
month: "long",
day: "numeric",
});
message_history.push(saved_message);
if (message_history.length > settings.max_history) message_history.shift();
Settings.setValue("ArmoredChat-Messages", message_history);
// Display on popup chat area
_overlayMessage({ sender: message.displayName, message: message });
}
function onWebEventReceived(event) {
console.log(`New web event:\n${event}`);
// FIXME: Lazy!
// Checks to see if the event is a JSON object
if (!event.includes("{")) return;
var parsed = JSON.parse(event);
switch (parsed.type) {
case "page_update":
app_data.current_page = parsed.page;
break;
case "send_message":
_sendMessage(parsed.message);
break;
case "open_url":
new OverlayWebWindow({ source: parsed.url.toString(), width: 500, height: 400 });
break;
case "setting_update":
// Update local settings
settings[parsed.setting_name] = parsed.setting_value;
// Save local settings
_saveSettings();
switch (parsed.setting_name) {
case "external_window":
chat_overlay_window.presentationMode = parsed.setting_value ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL;
break;
case "max_history":
let new_history = message_history.splice(0, message_history.length - settings.max_history);
Settings.setValue("ArmoredChat-Messages", new_history);
break;
}
break;
case "initialized":
// https://github.com/overte-org/overte/issues/824
chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to the initial state (false)
_loadSettings();
break;
case "action":
switch (parsed.action) {
case "clear_history":
Settings.setValue("ArmoredChat-Messages", []);
break;
}
}
}
//
// Sending messages
// These functions just shout out their messages. We are listening to messages in an other function, and will record all heard messages there
function _sendMessage(message) {
Messages.sendMessage(
"chat",
JSON.stringify({
position: MyAvatar.position,
message: message,
displayName: MyAvatar.sessionDisplayName,
channel: app_data.current_page,
action: "send_chat_message",
})
);
// FloofyChat Compatibility
Messages.sendMessage(
"Chat",
JSON.stringify({
position: MyAvatar.position,
message: message,
displayName: MyAvatar.sessionDisplayName,
channel: app_data.current_page.charAt(0).toUpperCase() + app_data.current_page.slice(1),
type: "TransmitChatMessage",
for_app: "Floof",
})
);
// Show overlay of the message you sent
_overlayMessage({ sender: MyAvatar.sessionDisplayName, message: message });
}
function _overlayMessage(message) {
// Floofchat compatibility
// This makes it so that our own messages are not rendered.
// For now, Floofchat has priority over notifications as they use a strange system I don't want to touch yet.
if (!message.action) return;
Messages.sendLocalMessage(
"Floof-Notif",
JSON.stringify({
sender: message.sender,
text: message.message,
color: { red: 122, green: 122, blue: 122 },
})
);
}
function _loadSettings() {
settings = Settings.getValue("ArmoredChat-Config", settings);
_emitEvent({ type: "setting_update", setting_name: "max_history", setting_value: Number(settings.max_history) });
// Compact chat
if (settings.compact_chat) {
_emitEvent({ type: "setting_update", setting_name: "compact_chat", setting_value: true });
}
// External Window
if (settings.external_window) {
chat_overlay_window.presentationMode = settings.external_window ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL;
_emitEvent({ type: "setting_update", setting_name: "external_window", setting_value: true });
}
// Refill the history with the saved messages
message_history.forEach((message) => {
delete message.action;
_emitEvent({ type: "show_message", ...message });
});
}
function _saveSettings() {
console.log("Saving config");
Settings.setValue("ArmoredChat-Config", settings);
}
/**
* Emit a packet to the HTML front end. Easy communication!
* @param {Object} packet - The Object packet to emit to the HTML
* @param {("setting_update"|"show_message")} packet.type - The type of packet it is
*/
function _emitEvent(packet = { type: "" }) {
chat_overlay_window.emitScriptEvent(JSON.stringify(packet));
}
})();

View file

@ -1,22 +0,0 @@
body .page .content.message-list .message {
display: grid;
box-sizing: border-box;
grid-template-columns: 1fr 1fr;
grid-gap: inherit;
padding: 2px;
margin-bottom: 5px;
}
body .page .content.message-list .message .pfp {
display: none !important;
}
body .page .content.message-list .message .name {
color: #dbdbdb;
}
body .page .content.message-list .message .timestamp {
text-align: right;
color: #dbdbdb;
}
body .page .content.message-list .message .body {
grid-column-start: 1;
grid-column-end: 3;
}

View file

@ -1,29 +0,0 @@
body {
.page {
.content.message-list {
.message {
display: grid;
box-sizing: border-box;
grid-template-columns: 1fr 1fr;
grid-gap: inherit;
padding: 2px;
margin-bottom: 5px;
.pfp {
display: none !important;
}
.name {
color: #dbdbdb;
}
.timestamp {
text-align: right;
color: #dbdbdb;
}
.body {
grid-column-start: 1;
grid-column-end: 3;
}
}
}
}
}

View file

@ -1,22 +0,0 @@
(function () {
// TODO: Sign messages
// TODO: Verify signatures
let rsa = forge.pki.rsa;
let keypair;
function newKeyPair() {
// 2048 bits. Not the most super-duper secure length of 4096.
// This value must remain low to ensure lower-power machines can use.
// We will generate new keys automatically every so often and will also allow user to refresh keys.
keypair = rsa.generateKeyPair({ bits: 2048, workers: -1 });
}
function encrypt(message) {
if (!keypair) return null;
return keypair.publicKey.encrypt("Test message");
}
function decrypt(message) {
if (!keypair) return null;
return keypair.privateKey.decrypt(encrypted);
}
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 B

View file

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="32"
viewBox="0 -960 760 640"
width="38"
version="1.1"
id="svg4"
sodipodi:docname="send.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:export-filename="send_black.png"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="21.395833"
inkscape:cx="17.363194"
inkscape:cy="16.031159"
inkscape:window-width="2560"
inkscape:window-height="1366"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 0,-320 v -640 l 760,320 z M 60,-413 604,-640 60,-870 v 168 l 242,62 -242,60 z m 0,0 v -457 z"
id="path2"
style="fill:#000000" />
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="31.049999"
viewBox="0 -960 640 620.99998"
width="32"
version="1.1"
id="svg4"
sodipodi:docname="user.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:export-filename="user_white.png"
inkscape:export-xdpi="309.17874"
inkscape:export-ydpi="309.17874"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="21.395833"
inkscape:cx="15.306719"
inkscape:cy="15.119766"
inkscape:window-width="1826"
inkscape:window-height="1233"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<path
d="m 320,-660 q -66,0 -108,-42 -42,-42 -42,-108 0,-66 42,-108 42,-42 108,-42 66,0 108,42 42,42 42,108 0,66 -42,108 -42,42 -108,42 z M 0,-339 v -94 q 0,-38 19,-65 19,-27 49,-41 67,-30 128.5,-45 61.5,-15 123.5,-15 62,0 123,15.5 61,15.5 127.921,44.694 31.301,14.126 50.19,40.966 Q 640,-471 640,-433 v 94 z m 60,-60 h 520 v -34 q 0,-16 -9.5,-30.5 Q 561,-478 547,-485 483,-516 430,-527.5 377,-539 320,-539 q -57,0 -111,11.5 -54,11.5 -117,42.5 -14,7 -23,21.5 -9,14.5 -9,30.5 z m 260,-321 q 39,0 64.5,-25.5 Q 410,-771 410,-810 410,-849 384.5,-874.5 359,-900 320,-900 q -39,0 -64.5,25.5 -25.5,25.5 -25.5,64.5 0,39 25.5,64.5 25.5,25.5 64.5,25.5 z m 0,-90 z m 0,411 z"
id="path2"
style="fill:#ffffff" />
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,217 +0,0 @@
body {
background-color: black;
color: white;
margin: 0;
height: 100vh;
width: 100vw;
font-family: Verdana, Geneva, Tahoma, sans-serif;
display: flex;
flex-direction: column;
}
body .header {
width: 100%;
height: 40px;
display: flex;
flex-direction: row;
}
body .header button {
width: 60px;
height: 100%;
border-radius: 0;
border: 0;
margin: 0;
padding: 0;
box-sizing: border-box;
transition: width ease-in-out 0.2s;
}
body .header button.active {
background-color: #6667ab;
color: white;
width: 100px;
}
body .header .left {
margin: 0 auto 0 0;
}
body .header .right {
margin: 0 0 0 auto;
}
body .page {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
width: 100%;
}
body .page .content {
width: 100%;
background-color: #111;
flex-grow: 1;
}
body .page .content.message-list {
overflow-y: auto;
overflow-x: hidden;
width: 100%;
}
body .page .content.message-list .message:nth-child(even) {
background-color: #1a1a1a;
}
body .page .content.message-list .message {
display: grid;
box-sizing: border-box;
grid-template-columns: 80px 5fr;
grid-gap: 0.75rem;
padding: 0.8rem 0.15rem;
width: 100%;
overflow-y: auto;
}
body .page .content.message-list .message .pfp {
height: 30px;
width: auto;
display: flex;
}
body .page .content.message-list .message .pfp img {
height: 100%;
width: auto;
margin: auto;
border-radius: 50px;
background-color: black;
}
body .page .content.message-list .message .name {
font-size: 1.15rem;
color: #dbdbdb;
}
body .page .content.message-list .message .body {
width: 100%;
word-wrap: anywhere;
overflow-x: hidden;
}
body .page .content.message-list .message .body a {
color: white;
}
body .page .content.message-list .message .body .image-container {
width: 100%;
max-width: 400px;
max-height: 300px;
}
body .page .content.message-list .message .body .image-container img {
width: auto;
height: 100%;
}
body .page .content.message-list .message .embeds {
width: 100%;
word-wrap: anywhere;
overflow-x: hidden;
overflow-x: hidden;
}
body .page .content.message-list .message .embeds a {
color: white;
}
body .page .content.message-list .message .embeds .image-container {
width: 100%;
max-width: 400px;
max-height: 300px;
}
body .page .content.message-list .message .embeds .image-container img {
max-width: 400px;
max-height: 300px;
}
body .page .content.message-list .message .timestamp {
text-align: center;
color: #dbdbdb;
}
body .page .content.settings .setting {
width: 100%;
display: flex;
padding: 0.5rem 0.25rem;
box-sizing: border-box;
}
body .page .content.settings .setting input {
margin: auto 0 auto auto;
height: 20px;
}
body .page .content.settings .setting-button input {
width: 100px;
}
body .page .content.settings .setting-value input {
width: 70px;
}
body .page .content.settings .setting-toggle input {
width: 20px;
}
body .page .content.settings .setting:nth-child(even) {
background-color: #1a1a1a;
}
body .footer {
width: 100%;
height: 40px;
}
body .footer.text-entry {
display: Flex;
flex-direction: row;
}
body .footer.text-entry input {
flex-grow: 1;
margin-right: 0;
border: 0;
font-size: 1.3rem;
min-width: 0;
}
body .footer.text-entry button {
width: 75px;
border: 0;
border-radius: 0;
}
body .hidden {
display: none !important;
}
button {
width: 100px;
height: 100%;
background-color: white;
border-radius: 2px;
border: 0;
cursor: pointer;
outline: none;
}
button span {
width: 100%;
height: 100%;
}
button span img {
max-height: 70%;
width: auto;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
button:focus {
filter: brightness(50%);
}
input {
outline: none;
}
input:focus {
background-color: lightgray;
}
@media screen and (max-width: 400px) {
body .header {
height: 30px;
}
body .header button {
width: 50px;
}
body .page .content.message-list .message {
grid-template-columns: 80px 5fr;
grid-gap: 0.75rem;
padding: 0.8rem 0.15rem;
}
body .page .content.message-list .message .pfp {
height: 30px;
}
}

View file

@ -1,301 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/node-forge@1.0.0/dist/forge.min.js"></script>
<link rel="stylesheet" href="index.css" />
<!-- Compact messages -->
<!-- <link id="compact-messages" rel="stylesheet" href="compact-messages.css" /> -->
</head>
<body>
<!-- The primary page. This is where people will chat -->
<div class="header">
<div class="left">
<button data-target="domain-chat" class="active">
<span><img src="./img/ui/world_white.png" /></span>
</button>
<button data-target="local-chat">
<span><img src="./img/ui/social_black.png" /></span>
</button>
<!-- <button data-target="pm-chat">
<span><img src="./img/ui/user_black.png" /></span>
</button> -->
</div>
<div class="right">
<button data-target="settings">
<span><img src="./img/ui/settings_black.png" /></span>
</button>
</div>
</div>
<div id="domain-chat" class="page chat">
<div class="content message-list"></div>
</div>
<!-- Local Chat -->
<div id="local-chat" class="page hidden">
<div class="content message-list"></div>
</div>
<!-- DM page. -->
<div id="pm-chat" class="page hidden">
<div class="content">PM content</div>
</div>
<!-- Settings page. Adjust the chat here -->
<div id="settings" class="page hidden">
<div class="content settings">
<!-- <div class="setting setting-toggle">
<span>Show typing indicator</span>
<input id="typing-indicator-toggle" type="checkbox" />
</div> -->
<!-- <div class="setting setting-toggle">
<span>Show speech bubbles</span>
<input id="speech-bubble-toggle" type="checkbox" />
</div> -->
<div class="setting setting-toggle">
<span>Compact Mode</span>
<input id="compact-message-toggle" type="checkbox" />
</div>
<div class="setting setting-toggle">
<span>External Window</span>
<input id="external-window-toggle" type="checkbox" />
</div>
<div class="setting setting-button">
<span>Erase history</span>
<input id="erase-history" type="button" title="Clear" value="Clear" />
</div>
<div class="setting setting-value">
<span>Max history</span>
<input id="max-history" type="number" title="Maximum entires to store" />
</div>
</div>
</div>
<div class="footer text-entry">
<input id="message-entry" type="text" placeholder="Enter message here..." />
<button id="send-message">
<span><img src="./img/ui/send_black.png" /></span>
</button>
</div>
</body>
</html>
<script>
"use strict";
_emitEvent({ type: "initialized" }); // Tell the script we are ready
const qs = (target) => document.querySelector(target);
const qsa = (target) => document.querySelectorAll(target);
var scroll_distance = 100000; // The scroll distance for the chat window to automatically scroll down with.
var compact_mode = false; // Compact messages
var external_window = false;
// Start listening for new messages
EventBridge.scriptEventReceived.connect(_newScriptEvent);
// HTML Event listeners
qs("#send-message").addEventListener("click", _sendMessage);
qs("#message-entry").addEventListener("keyup", (event) => {
if (event.keyCode === 13) _sendMessage(); // Enter key, send message
});
// Add event listeners to all nav-buttons
qsa(".header button").forEach((button) => {
button.addEventListener("click", () => {
_switchPage(button.dataset.target, button);
if (button.dataset.target === "settings") {
qs(".footer.text-entry").classList.add("hidden");
} else {
qs(".footer.text-entry").classList.remove("hidden");
}
});
});
function _switchPage(target, button) {
// Hide all pages
qsa(".page").forEach((page) => page.classList.add("hidden"));
// Deactivate all nav-buttons
qsa(".header button").forEach((button) => deactivateButton(button));
// Show target page
qs(`#${target}`).classList.remove("hidden");
// Sets active for target button
activateButton(button);
function deactivateButton(button) {
button.querySelector("img").src = button.querySelector("img").src.replace("_white", "_black");
button.classList.remove("active");
}
function activateButton(button) {
button.querySelector("img").src = button.querySelector("img").src.replace("_black", "_white");
button.classList.add("active");
button.blur();
// Tell script where we are at
_emitEvent({ type: "page_update", page: button.dataset.target.replace("-chat", "") });
}
}
function _sendMessage() {
qs("#send-message").blur();
// Don't send empty messages.
if (qs("#message-entry").value.length === 0) return;
// Send what is in the text area as a message
_emitEvent({ type: "send_message", message: qs("#message-entry").value });
// Clear the message area
qs("#message-entry").value = "";
}
function _newScriptEvent(message) {
message = JSON.parse(message);
switch (message.type) {
case "show_message":
_showMessage(message);
break;
case "setting_update":
_newSetting(message);
break;
}
}
// Settings
qs("#compact-message-toggle").addEventListener("change", function () {
_toggleCompactMode();
_emitEvent({ type: "setting_update", setting_name: "compact_chat", setting_value: compact_mode });
});
function _toggleCompactMode() {
compact_mode = !compact_mode;
if (compact_mode) {
// Add the stylesheet to the head
document.head.insertAdjacentHTML("beforeend", '<link id="compact-messages-ss" rel="stylesheet" href="compact-messages.css" />');
} else {
// Remove the compact messages stylesheet
qs("#compact-messages-ss").remove();
}
}
qs("#external-window-toggle").addEventListener("change", function () {
external_window = !external_window;
_emitEvent({ type: "setting_update", setting_name: "external_window", setting_value: external_window });
});
qs("#erase-history").addEventListener("click", () => {
let response = confirm("Are you sure you want to erase all messages?");
if (response) _emitEvent({ type: "action", action: "clear_history" });
// _emitEvent({ type: "setting_update", setting_name: "max_history", setting_value: compact_mode });
});
qs("#max-history").addEventListener("change", () => {
_emitEvent({ type: "setting_update", setting_name: "max_history", setting_value: qs("#max-history").value });
});
// TODO: Limit embeds to 3.
function _showMessage(message) {
var target = message.channel + "-chat";
// Clone template message
let message_template = qs("#message-listing");
let message_clone = message_template.content.cloneNode(true);
let message_embeds = "";
message_clone.querySelector(".body").innerHTML = "";
message_clone.querySelector(".embeds").innerHTML = "";
// Youtube embeds
let yt_url = message.message.match(/(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g);
if (yt_url) {
yt_url.forEach((url) => {
message_embeds += `<iframe class="z-depth-2" width='420' height='236' src='https://www.youtube.com/embed/${url.toString().split("/")[3]}' frameborder='0'></iframe><br>`;
});
}
// Image embeds
let image_link = message.message.match(/.+.(png|jpg|jpeg|webp)/g);
if (image_link) {
image_link.forEach((image) => {
message_embeds += `<span class='image-container'><img src='${image}'></span><br>`;
});
}
// Linkify links
let link_url = message.message.match(/(?:^|\s)(https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+(?:\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)?(?=\s|$)/g);
if (link_url) {
link_url.forEach((link) => {
message_embeds += `<a href='#' onclick='_emitEvent({type:"open_url", url: "${link}" })'>${link}</a><br>`;
message.message = message.message.replace(link, "");
});
}
// Update template data to message data
message_clone.querySelector(".name").innerText = message.displayName;
if (!message.timeString) {
message_clone.querySelector(".timestamp").innerText = new Date().toLocaleTimeString(undefined, { hour12: false });
message_clone.querySelector(".timestamp").title = new Date().toLocaleDateString(undefined, {
month: "long",
day: "numeric",
});
} else {
message_clone.querySelector(".timestamp").innerText = message.timeString;
message_clone.querySelector(".timestamp").title = message.dateString;
}
message_clone.querySelector(".embeds").innerHTML = message_embeds;
message_clone.querySelector(".body").innerText = message.message;
// Append to the message list
qs("#" + target + " .message-list").appendChild(message_clone);
// Scroll to the bottom of the page
qs("#" + target + " .message-list").scrollTop = scroll_distance;
// Increase scroll distance so it will continue to work for future messages.
scroll_distance = scroll_distance + 100000;
}
/**
* Called when the script is initialized and we are loading the user settings
*/
function _newSetting(message) {
switch (message.setting_name) {
case "compact_chat":
qs("#compact-message-toggle").checked = true;
_toggleCompactMode();
break;
case "external_window":
qs("#external-window-toggle").checked = true;
external_window = true;
break;
case "max_history":
qs(`#max-history`).value = message.setting_value;
break;
}
}
/**
* Emit a packet to the HTML front end. Easy communication!
* @param {Object} packet - The Object packet to emit to the HTML
* @param {("setting_update"|"send_message"|"page_update"|"open_url"|"initialized")} packet.type - The type of packet it is
*/
function _emitEvent(packet = { type: "" }) {
EventBridge.emitWebEvent(JSON.stringify(packet));
}
</script>
<template id="message-listing">
<div class="message">
<div class="pfp"><img src="./img/ui/user_white.png" /></div>
<div class="name">[NAME]</div>
<div class="timestamp" title="[DATE]">[TIMESTAMP]</div>
<div>
<div class="embeds">[EMBEDS]</div>
<div class="body">[CONTENT]</div>
</div>
</div>
</template>
<!-- <script src="encrpytion.js"></script> -->

View file

@ -1,258 +0,0 @@
body {
background-color: black;
color: white;
margin: 0;
height: 100vh;
width: 100vw;
font-family: Verdana, Geneva, Tahoma, sans-serif;
display: flex;
flex-direction: column;
.header {
width: 100%;
height: 40px;
display: flex;
flex-direction: row;
button {
width: 60px;
height: 100%;
border-radius: 0;
border: 0;
margin: 0;
padding: 0;
box-sizing: border-box;
transition: width ease-in-out 0.2s;
}
button.active {
background-color: #6667ab;
color: white;
width: 100px;
}
.left {
margin: 0 auto 0 0;
}
.right {
margin: 0 0 0 auto;
}
}
.page {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
width: 100%;
.content {
width: 100%;
background-color: #111;
flex-grow: 1;
}
.content.message-list {
overflow-y: auto;
overflow-x: hidden;
width: 100%;
.message:nth-child(even) {
background-color: #1a1a1a;
}
.message {
display: grid;
box-sizing: border-box;
grid-template-columns: 80px 5fr;
grid-gap: 0.75rem;
padding: 0.8rem 0.15rem;
width: 100%;
overflow-y: auto;
.pfp {
height: 30px;
width: auto;
display: flex;
img {
height: 100%;
width: auto;
margin: auto;
border-radius: 50px;
background-color: black;
}
}
.name {
font-size: 1.15rem;
color: #dbdbdb;
}
.body {
width: 100%;
word-wrap: anywhere;
overflow-x: hidden;
a {
color: white;
}
.image-container {
width: 100%;
max-width: 400px;
max-height: 300px;
img {
width: auto;
height: 100%;
}
}
}
.embeds {
width: 100%;
word-wrap: anywhere;
overflow-x: hidden;
overflow-x: hidden;
a {
color: white;
}
.image-container {
width: 100%;
max-width: 400px;
max-height: 300px;
img {
max-width: 400px;
max-height: 300px;
}
}
}
.timestamp {
text-align: center;
color: #dbdbdb;
}
}
}
.content.settings {
.setting {
width: 100%;
display: flex;
padding: 0.5rem 0.25rem;
box-sizing: border-box;
input{
margin: auto 0 auto auto;
height: 20px;
}
}
.setting-button{
input {
width: 100px;
}
}
.setting-value{
input {
width: 70px;
}
}
.setting-toggle {
input {
width: 20px;
}
}
.setting:nth-child(even) {
background-color: #1a1a1a;
}
}
}
.footer {
width: 100%;
height: 40px;
}
.footer.text-entry {
display: Flex;
flex-direction: row;
input {
flex-grow: 1;
margin-right: 0;
border: 0;
font-size: 1.3rem;
min-width: 0;
}
button {
width: 75px;
border: 0;
border-radius: 0;
}
}
.hidden {
display: none !important;
}
}
button {
width: 100px;
height: 100%;
background-color: white;
border-radius: 2px;
border: 0;
cursor: pointer;
outline: none;
span {
width: 100%;
height: 100%;
img {
max-height: 70%;
width: auto;
user-select: none;
-webkit-user-drag: none;
}
}
}
button:focus {
filter: brightness(50%);
}
input {
outline: none;
}
input:focus {
background-color: lightgray;
}
@media screen and (max-width: 400px) {
body {
.header {
height: 30px;
button {
width: 50px;
}
}
.page {
.content.message-list {
.message {
grid-template-columns: 80px 5fr;
grid-gap: 0.75rem;
padding: 0.8rem 0.15rem;
.pfp {
height: 30px;
}
}
}
}
}
}