diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs
index 75b0394..29e6ccd 100644
--- a/ChatTwo/Http/Processing.cs
+++ b/ChatTwo/Http/Processing.cs
@@ -50,7 +50,7 @@ public class Processing
if (chunk is IconChunk { } icon)
{
return IconUtil.GfdFileView.TryGetEntry((uint) icon.Icon, out _)
- ? $""
+ ? $""
: "";
}
@@ -63,7 +63,7 @@ public class Processing
// The emote name should be safe, it is checked against a list from BTTV.
// Still sanitizing it for the extra safety.
if (image is { Failed: false })
- return $"";
+ return $"";
}
var colour = text.Foreground;
@@ -92,4 +92,4 @@ public class Processing
return string.Empty;
}
-}
\ No newline at end of file
+}
diff --git a/ChatTwo/Http/static/start.css b/ChatTwo/Http/static/start.css
index 32c2141..725ec27 100644
--- a/ChatTwo/Http/static/start.css
+++ b/ChatTwo/Http/static/start.css
@@ -38,7 +38,7 @@
* {
color: var(--fg);
font-family: Lodestone, 'Inter var', sans-serif;
- font-feature-settings: 'tnum';
+ font-feature-settings: 'tnum', 'calt' 0; /* calt appears to be on by default */
}
html {
@@ -52,17 +52,58 @@ body {
background-color: var(--bg-input-hover);
}
-body > main {
+body > main.chat {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background-color: var(--bg);
border-radius: 20px;
-
box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.5);
}
+body > main.auth {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ width: 100%;
+ height: 100%;
+
+ h1 {
+ font-size: 1.5rem;
+ font-weight: 400;
+ }
+
+ input { width: 150px; }
+
+ input, button {
+ padding: 5px 20px;
+ font-size: 1rem;
+ border: 3px solid transparent;
+ border-radius: 20px;
+ background-color: var(--bg-input);
+
+ &:focus {
+ outline: 2px solid var(--focus-color);
+ }
+ }
+
+ button {
+ padding: 5px 15px;
+ border: 3px solid var(--bg-input);
+ background-image: var(--gradient-clickable);
+ cursor: pointer;
+
+ &:hover {
+ border-color: var(--bg-input-hover);
+ background-color: var(--bg-input-hover);
+ background-image: var(--gradient-clickable-hover);
+ }
+ }
+}
+
/* message list */
section#messages {
position: relative;
@@ -84,6 +125,7 @@ section#messages {
width: calc(100% - 200px);
height: 200px;
background-image: radial-gradient(50% 20% at 50% 100%, #60a0ff40, transparent);
+ pointer-events: none;
}
}
@@ -101,6 +143,10 @@ section#messages {
color: var(--fg-faint);
text-align: right;
}
+
+ .message {
+ white-space: pre-wrap;
+ }
}
}
@@ -216,28 +262,64 @@ section#input {
}
}
+/* icons, emotes */
+.gfd-icon {
+ display: inline-block;
+ position: relative;
+ vertical-align: middle;
+ zoom: 0.75;
+
+ &::before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ transform: translateY(-50%);
+ pointer-events: none;
+ }
+}
+
+.emote-icon {
+ display: inline-block;
+ position: relative;
+ width: 2rem;
+ vertical-align: middle;
+
+ img {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 2rem;
+ transform: translateY(-50%);
+ pointer-events: none;
+ }
+}
+
/*** mobile ***/
@media ((max-width: 600px) and (orientation: portrait)) or (max-width: 400px) {
-body {
- padding: 0;
-}
-
-body > main {
- border-radius: 0;
- box-shadow: none;
-}
-
-section#input {
- button {
- max-width: 0;
- padding-left: 1.5rem;
- padding-right: 1.5rem;
- color: transparent;
+ body {
+ padding: 0;
}
- button::before {
- left: 50%;
- transform: translateX(-50%) translateY(-50%);
+ body > main {
+ border-radius: 0;
+ box-shadow: none;
+ }
+
+ section#input {
+ button {
+ max-width: 0;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ color: transparent;
+ }
+
+ button::before {
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ }
}
}
-}
diff --git a/ChatTwo/Http/static/start.js b/ChatTwo/Http/static/start.js
index 25d2d79..3655bae 100644
--- a/ChatTwo/Http/static/start.js
+++ b/ChatTwo/Http/static/start.js
@@ -1,21 +1,20 @@
// websocket connection
class SSEConnection {
constructor() {
- this.socket = new EventSource('/sse', );
+ this.socket = new EventSource('/sse');
- this.socket.addEventListener('close', (event) => {
- console.log("Closing SSE connection.")
- this.socket.close()
+ this.socket.addEventListener('close', () => {
+ console.log('Closing SSE connection.');
+ this.socket.close();
});
this.socket.addEventListener('switch-channel', (event) => {
- updateChannelHint(JSON.parse(event.data).channel)
+ updateChannelHint(JSON.parse(event.data).channel);
});
// New messages that are able to be directly processed
this.socket.addEventListener('new-message', (event) => {
- for (let message of JSON.parse(event.data).messages)
- {
+ for (let message of JSON.parse(event.data).messages) {
addMessage(message);
}
});
@@ -23,15 +22,13 @@ class SSEConnection {
// New messages, that require a clean message list before processing
this.socket.addEventListener('bulk-messages', (event) => {
clearMessages();
-
- for (let message of JSON.parse(event.data).messages)
- {
+ for (let message of JSON.parse(event.data).messages) {
addMessage(message);
}
});
this.socket.addEventListener('channel-list', (event) => {
- updateChannelOptions(JSON.parse(event.data).channels)
+ updateChannelOptions(JSON.parse(event.data).channels);
});
}
@@ -56,7 +53,7 @@ document.getElementById('channel-select').addEventListener('change', (event) =>
'Accept': 'application/json',
'Content-Type': 'application/json'
},
- body: JSON.stringify({channel: event.target.value})
+ body: JSON.stringify({ channel: event.target.value })
});
const content = await rawResponse.json();
@@ -71,30 +68,39 @@ function updateChannelOptions(channels) {
// clear existing channels
select.innerHTML = '';
- for (let [ name, channel ] of Object.entries(channels))
- {
+ for (const [ name, channel ] of Object.entries(channels)) {
let option = document.createElement('option');
option.text = name;
option.value = channel;
-
select.appendChild(option)
}
}
// functions for handling the message list
-function scrollMessagesToBottom() {
- const messagesContainer = document.querySelector('#messages > .scroll-container');
- messagesContainer.scrollTop = messagesContainer.scrollHeight - messagesContainer.offsetHeight;
-}
-
function messagesContainerIsScrolledToBottom() {
const messagesContainer = document.querySelector('#messages > .scroll-container');
- return messagesContainer.scrollTop == messagesContainer.scrollHeight - messagesContainer.offsetHeight;
+ return messagesContainer.scrollTop >= messagesContainer.scrollHeight - messagesContainer.offsetHeight;
+}
+
+// calculate timestamp width
+// to ensure that all timestamps have the same width. some typefaces have the same width across
+// all number glyphs, others do not. the solution below is very rudimentary; at the very least,
+// delaying it to account for font loading might make sense. perhaps there's an even better way?
+let maxTimestampWidth = 0;
+function calculateTimestampWidth(timestamp) {
+ const widthProbe = document.getElementById('timestamp-width-probe');
+ widthProbe.innerText = timestamp;
+
+ if (widthProbe.clientWidth > maxTimestampWidth) {
+ maxTimestampWidth = widthProbe.clientWidth;
+ document.body.style.setProperty('--timestamp-width', (Math.ceil(maxTimestampWidth) + 1) + 'px');
+ }
}
function addMessage(messageData) {
const scrolledToBottom = messagesContainerIsScrolledToBottom();
+ calculateTimestampWidth(messageData.timestamp);
const liMessage = document.createElement('li');
const spanTimestamp = document.createElement('span');
@@ -102,16 +108,15 @@ function addMessage(messageData) {
const spanMessage = document.createElement('span');
spanMessage.classList.add('message');
- // suggestions, update with actual data model
spanTimestamp.innerText = messageData.timestamp;
- spanMessage.innerHTML = messageData.messageHTML; // or build HTML in here
+ spanMessage.innerHTML = messageData.messageHTML;
liMessage.appendChild(spanTimestamp);
liMessage.appendChild(spanMessage);
document.getElementById('messages-list').appendChild(liMessage);
if (scrolledToBottom) {
- scrollMessagesToBottom();
+ liMessage.scrollIntoView();
}
}
@@ -119,7 +124,7 @@ function clearMessages() {
document.getElementById('messages-list').innerHTML = '';
}
-//
+// add indicator signaling more messages
document.querySelector('#messages > .scroll-container').addEventListener('scroll', () => {
const messagesContainer = document.querySelector('#messages > .scroll-container');
if (!messagesContainerIsScrolledToBottom()) {
@@ -137,14 +142,6 @@ document.querySelector('#input > form').addEventListener('submit', (event) => {
const chatInput = document.getElementById('chat-input');
const message = chatInput.value;
- fetch('/send', {
- method: 'POST',
- body: message,
- headers: {
- 'Content-type': 'application/txt; charset=UTF-8'
- }
- });
-
(async () => {
const rawResponse = await fetch('/send', {
method: 'POST',
@@ -152,7 +149,7 @@ document.querySelector('#input > form').addEventListener('submit', (event) => {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
- body: JSON.stringify({message: message})
+ body: JSON.stringify({ message: message })
});
const content = await rawResponse.json();
@@ -164,26 +161,14 @@ document.querySelector('#input > form').addEventListener('submit', (event) => {
});
-// calculate timestamp width
-// to ensure that all timestamps have the same width. some typefaces have the same width across
-// all number glyphs, others do not. the solution below is very rudimentary; at the very least,
-// delaying it to account for font loading might make sense. perhaps there's an even better way?
-window.setTimeout(() => {
- const widthProbe = document.getElementById('timestamp-width-probe');
- widthProbe.innerText = '88:88'; // assume 8 to be widest glyph
- document.body.style.setProperty('--timestamp-width', Math.ceil(widthProbe.clientWidth) + 'px');
-}, 100);
-
-// From kizer
+// from kizer, gfd icons
async function AddGfdStylesheet(gfdPath, texPath) {
const texPromise = LoadTexAsBlob(texPath);
const gfdPromise = LoadGfd(gfdPath);
const texUrl = URL.createObjectURL(await texPromise);
const gfd = await gfdPromise;
- let width = 0;
- let height = 0;
- let stylesheets = [];
+ const stylesheets = [];
for (const entry of gfd) {
if (entry.width * entry.height <= 0)
continue;
@@ -202,32 +187,30 @@ async function AddGfdStylesheet(gfdPath, texPath) {
`background-position: -${entry.left}px -${entry.top}px`,
`background-image: url('${texUrl}')`,
`width: ${entry.width}px`,
- `height: ${entry.height}px`,
- `margin-bottom: -20px`
- ].join(";"),
+ `height: ${entry.height}px`
+ ].join(';'),
[
`background-position: -${entry.left * 2}px -${entry.top * 2 + 341}px`,
`background-image: url('${texUrl}')`,
`width: ${entry.width * 2}px`,
- `height: ${entry.height * 2}px`,
- `margin-bottom: -40px`
- ].join(";")
+ `height: ${entry.height * 2}px`
+ ].join(';'),
+ entry.width
];
}
- let stylesheet = ".gfd-icon::before { content: ''; display: inline-block; overflow: hidden; vertical-align: top; height:0; }\n";
+ let stylesheet = '';
for (const entry of stylesheets) {
if (!entry)
continue;
stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-${x}::before`).join(', ')}{${entry[1]};}`;
stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-hq-${x}::before`).join(', ')}{${entry[2]};}`;
+ stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-${x}`).join(', ')}{width:${entry[3]}px;}`;
+ stylesheet += `\n${entry[0].map(x => `.gfd-icon.gfd-icon-hq-${x}`).join(', ')}{width:${entry[3] * 2}px;}`;
}
- stylesheet += "\n.emote-icon { content: ''; display: inline-block; overflow: hidden; vertical-align: top; }";
- stylesheet += `\n.emote-icon.emote-icon-hq {width: ${width * 2}px; height: ${height * 2}px; margin-bottom: -40px}`;
- const styleNode = document.createElement("style");
- styleNode.type = "text/css";
+ const styleNode = document.createElement('style');
styleNode.appendChild(document.createTextNode(stylesheet));
document.head.appendChild(styleNode);
}
@@ -235,7 +218,7 @@ async function AddGfdStylesheet(gfdPath, texPath) {
async function LoadTexAsBlob(path) {
const tex = ParseTex(await (await fetch(path)).arrayBuffer());
if (tex.format !== 0x1450) // B8G8R8A8
- throw "Not supported";
+ throw 'Not supported';
const dataArray = new Uint8ClampedArray(tex.buffer, tex.offsetToSurface[0], tex.width * tex.height * 4);
for (let i = 0; i < dataArray.length; i += 4) {
@@ -297,4 +280,4 @@ function ParseTex(arrayBuffer) {
};
}
-AddGfdStylesheet("/files/gfdata.gfd", "/files/fonticon_ps5.tex");
+AddGfdStylesheet('/files/gfdata.gfd', '/files/fonticon_ps5.tex');
diff --git a/ChatTwo/Http/templates/auth.html b/ChatTwo/Http/templates/auth.html
index cd3fe76..b6153a6 100644
--- a/ChatTwo/Http/templates/auth.html
+++ b/ChatTwo/Http/templates/auth.html
@@ -3,17 +3,20 @@