init
This commit is contained in:
63
ui/_/code/components/EmailJoinForm.js
Normal file
63
ui/_/code/components/EmailJoinForm.js
Normal file
@@ -0,0 +1,63 @@
|
||||
css(`
|
||||
email-join-form {
|
||||
display: flex
|
||||
}
|
||||
`)
|
||||
|
||||
export default class EmailJoinForm extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.querySelector('#submit-button').addEventListener('click', () => this.submitEmail());
|
||||
}
|
||||
|
||||
isValidEmail(email) {
|
||||
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,16}$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
$(this).find('#form-message')
|
||||
.css('color', 'red')
|
||||
.text(message);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
$(this).find('#form-message')
|
||||
.css('color', 'green')
|
||||
.text(message);
|
||||
}
|
||||
|
||||
clearError() {
|
||||
this.querySelector('#form-message').textContent = '';
|
||||
}
|
||||
|
||||
async submitEmail() {
|
||||
const email = this.querySelector('#email-input').value.trim();
|
||||
this.clearError();
|
||||
|
||||
if (!this.isValidEmail(email)) {
|
||||
this.showError('Please enter a valid email address.');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/join', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
this.showSuccess('Email sent.');
|
||||
} else {
|
||||
const error = await res.text();
|
||||
this.showError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("email-join-form", EmailJoinForm);
|
||||
1173
ui/_/code/quill.js
Normal file
1173
ui/_/code/quill.js
Normal file
File diff suppressed because one or more lines are too long
111
ui/_/code/shared.css
Normal file
111
ui/_/code/shared.css
Normal file
@@ -0,0 +1,111 @@
|
||||
:root {
|
||||
--main: var(--brown);
|
||||
--accent: var(--gold);
|
||||
|
||||
--tan: #FFDFB4;
|
||||
--gold: #F2B36F;
|
||||
--divider: #bb7c36;
|
||||
--green: #0857265c;
|
||||
--red: #BC1C02;
|
||||
--brown: #812A18;
|
||||
--darkbrown: #3f0808;
|
||||
|
||||
--accent2: var(--green);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--main: var(--brown);
|
||||
--accent: var(--gold);
|
||||
--accent2: var(--gold);
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Canterbury';
|
||||
src: url('/_/fonts/Canterbury/Canterbury.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Bona Nova';
|
||||
src: url('/_/fonts/BonaNova/BonaNova-Regular.woff') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Bona Nova';
|
||||
src: url('/_/fonts/BonaNova/BonaNova-Bold.woff') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
font-family: 'Bona Nova', sans-serif;
|
||||
font-size: 16px;
|
||||
background-color: var(--main);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body, html{
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#title {
|
||||
padding: 5px 10px;
|
||||
font-size: 1.7rem;
|
||||
position: fixed; top: 4.5vh; left: 6vw;
|
||||
cursor: pointer; z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
text-underline-offset: 5px;
|
||||
transition: background .02s, color .2s;
|
||||
user-select: none;
|
||||
color: var(--accent);
|
||||
display: inline-block; /* makes background and padding behave */
|
||||
padding: 0.2em 0.5em; /* adds breathing room */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
}
|
||||
|
||||
a:active {
|
||||
background: var(--red); /* background color works now */
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
color: var(--accent);
|
||||
padding: 0.5em;
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--accent2);
|
||||
padding-left: 1em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--accent)
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: 1px solid var(--red);
|
||||
}
|
||||
62
ui/_/code/ws/Connection.js
Normal file
62
ui/_/code/ws/Connection.js
Normal file
@@ -0,0 +1,62 @@
|
||||
class Connection {
|
||||
connectionTries = 0
|
||||
ws;
|
||||
linkCreated;
|
||||
wsStatus;
|
||||
|
||||
constructor(receiveCB) {
|
||||
this.init()
|
||||
this.receiveCB = receiveCB
|
||||
}
|
||||
|
||||
init() {
|
||||
if(window.location.hostname.includes("local")) {
|
||||
this.ws = new WebSocket("ws://" + window.location.host)
|
||||
} else {
|
||||
this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname)
|
||||
}
|
||||
this.ws.addEventListener('open', () => {
|
||||
this.connectionTries = 0
|
||||
console.log("Websocket connection established.");
|
||||
this.ws.addEventListener('message', this.receiveCB)
|
||||
});
|
||||
this.ws.addEventListener("close", () => {
|
||||
this.checkOpen();
|
||||
console.log('Websocket Closed')
|
||||
})
|
||||
}
|
||||
|
||||
async checkOpen() {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
return true
|
||||
} else {
|
||||
await this.sleep(this.connectionTries < 20 ? 5000 : 60000)
|
||||
this.connectionTries++
|
||||
console.log('Reestablishing connection')
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
||||
sleep = (time) => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
send = (msg) => {
|
||||
console.log("sending")
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(msg);
|
||||
}
|
||||
else if(this.connectionTries === 0) {
|
||||
setTimeout(() => {
|
||||
this.send(msg)
|
||||
}, 100)
|
||||
}
|
||||
else {
|
||||
console.error('No websocket connection: Cannot send message');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Connection
|
||||
45
ui/_/code/ws/Socket.js
Normal file
45
ui/_/code/ws/Socket.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import Connection from "./Connection.js";
|
||||
|
||||
export default class Socket {
|
||||
connection;
|
||||
disabled = true;
|
||||
requestID = 1;
|
||||
pending = new Map();
|
||||
|
||||
constructor() {
|
||||
this.connection = new Connection(this.receive);
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
if(this.connection.checkOpen()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
send(msg) {
|
||||
return new Promise(resolve => {
|
||||
const id = (++this.requestID).toString();
|
||||
this.pending.set(id, resolve);
|
||||
this.connection.send(JSON.stringify({ id, ...msg }));
|
||||
});
|
||||
}
|
||||
|
||||
receive = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.id && this.pending.has(msg.id)) {
|
||||
this.pending.get(msg.id)(msg);
|
||||
this.pending.delete(msg.id);
|
||||
return;
|
||||
} else {
|
||||
this.onBroadcast(msg)
|
||||
}
|
||||
}
|
||||
|
||||
onBroadcast(msg) {
|
||||
window.dispatchEvent(new CustomEvent(msg.event, {
|
||||
detail: msg.msg
|
||||
}));
|
||||
}
|
||||
}
|
||||
8
ui/_/code/zod.js
Normal file
8
ui/_/code/zod.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user