diff options
-rw-r--r-- | BossDeer.html | 471 | ||||
-rw-r--r-- | index.html | 16 | ||||
-rw-r--r-- | lib/htmlbuilder.js | 46 | ||||
-rw-r--r-- | lib/key.js | 16 | ||||
-rw-r--r-- | lib/sd.js | 61 | ||||
-rw-r--r-- | lib/stores.js | 12 | ||||
-rw-r--r-- | main.js | 24 | ||||
-rw-r--r-- | pages.js | 13 | ||||
-rw-r--r-- | pages/login/page.html | 11 | ||||
-rw-r--r-- | pages/login/page.js | 18 | ||||
-rw-r--r-- | pages/main/page.html | 7 | ||||
-rw-r--r-- | pages/main/page.js | 74 | ||||
-rw-r--r-- | pages/test/page.html | 1 | ||||
-rw-r--r-- | pages/test/page.js | 3 | ||||
-rw-r--r-- | shitstylse.css | 61 |
15 files changed, 834 insertions, 0 deletions
diff --git a/BossDeer.html b/BossDeer.html new file mode 100644 index 0000000..e8b3a95 --- /dev/null +++ b/BossDeer.html @@ -0,0 +1,471 @@ +<!DOCTYPE html> +<html lang="en-US"><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title>BossDeer</title> + <meta name="apple-mobile-web-app-capable" content="yes"> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> + <meta charset="UTF-8"> + <style> + @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap'); + body { + font-family: 'Noto Sans', sans-serif; + margin: 0px; + background-color: #2e2d2b; + color: #ede4d5; + } + button { + background-color: #4d4a45; + color: #ede4d5; + border: none; + border-radius: 4px; + padding: 8px; + font-family: 'Noto Sans', sans-serif; + margin-bottom: 4px; + } + button:hover { + background-color: #403d39; + cursor: pointer; + } + input { + background-color: #403d39; + color: #ede4d5; + border: none; + border-radius: 4px; + padding: 8px; + font-family: 'Noto Sans', sans-serif; + margin-bottom: 4px; + } + .scene { + margin: 8px; + } + #error-bar { + background-color: #f2b149; + color: #2e2d2b; + width: 100%; + padding: 4px; + box-sizing: border-box; + } + .text-clickable { + text-decoration: underline; + cursor: pointer; + } + .hidden { + display: none; + } + .post { + background-color: #403d39; + padding: 6px; + border-radius: 4px; + margin-bottom: 8px; + } + .mono { + font-family: monospace; + } + #ms-msg { + width:70vw; + box-sizing: border-box; + } + #ms-button-post { + box-sizing: border-box; + } + .pfp { + border-radius: 100%; + width: 36px; + height: 36px; + float: left; + margin-right: 8px; + margin-top: 2px; + border: 2px #383531 solid; + } + a { + color: #ede4d5; + } + .attachment { + margin-left:4px; + margin-right:4px; + max-height:15vw; + } + .rl-guidelines { + background-color: #999085; + color: black; + border: none; + border-radius: 4px; + padding: 8px; + font-family: serif; + margin: 4px; + width: 90vw; + } + </style> + </head> + <body> + <div id="error-bar" class="hidden"><span onclick="closePopup();" class="text-clickable">Close</span> - <span id="error-text">Connecting...<span></span></span></div> + <div class="scene"> + <div id="loading" class="hidden"><center>Taking too long to load? Try <span onclick="logOut();" class="text-clickable">a full reset</span>.</center></div> + <div id="connection-lost" class="hidden"><center>Connection was lost.<br><span onclick="window.location.reload();" class="text-clickable">Reload</span>.</center></div> + <div id="register-login" class=""><center> + <input id="rl-username" placeholder="Username..." maxlength="20"><br> + <input id="rl-password" placeholder="Password..." type="password"><br> + <input id="rl-invitecode" placeholder="Invite code..." maxlength="16"><br> + <button onclick="logIn();">Log in</button> + <button onclick="register();">Register</button><br><br> + <small>(You only need to provide an invite code when registering.)<br>(Please ensure you have read the <a href="https://deer.meltland.dev/GUIDELINES.txt">GUIDELINES</a> (also provided below) before creating an account.)</small><br> + <embed class="rl-guidelines" type="text/plain" src="BossDeer_files/GUIDELINES.txt"><br> + <small id="rl-version">1.0.5b - SOKTDEER-2024.11.30-02.07</small> + </center></div> + <div id="main-scene" class="hidden"> + <button id="ms-name" disabled="disabled">@...</button> | <button onclick="switchScene('main-config');">Settings</button> <button onclick="alert('Not done yet!');">Inbox</button> <button id="ms-button-mod" class="hidden" onclick="switchScene('main-moderation');">Moderation</button><br> + <small id="ms-ulist">0 users online ()</small><br><br><center> + <button onclick="addAttachment();">+</button> <input id="ms-msg" maxlength="2000" onkeydown="if (event.keyCode == 13) {sendPost();}" placeholder="What's on your mind?"> <button id="ms-button-post" onclick="sendPost();">Post</button><br><small id="ms-details"></small></center><br> + <div id="ms-posts"><div class="post"><span><b>SoktDeer</b> (<span class="mono">@soktdeer</span>)</span><br><small>11/30/2024, 9:15:07 AM - <span class="text-clickable" onclick="reply(0);">Reply</span></small><br><span>Welcome to SoktDeer! Start chatting!</span></div></div> + </div> + <div id="main-config" class="hidden"> + <button onclick="switchScene('main-scene');">Return to Home</button><br> + <h2>Profile</h2> + <input id="mc-display-name" placeholder="Display name..." maxlength="20"> <button onclick="setDisplayName();">Set display name</button><br> + <input id="mc-avatar" placeholder="Avatar URL..." maxlength="656"> <button onclick="setAvatar();">Set avatar URL</button> + <h2>Misc</h2> + <button onclick="updateStg('moderation')">Toggle Moderation tab</button><br><br> + <button onclick="logOut();">Log out</button><br> + <small id="mc-version">1.0.5b - SOKTDEER-2024.11.30-02.07</small> + </div> + <div id="main-moderation" class="hidden"> + <button onclick="switchScene('main-scene');">Return to Home</button><br> + <h2>Ban</h2> + <input id="mm-username-ban" placeholder="Username..."><br> + <input id="mm-until-ban" type="datetime-local"><br> + <input id="mm-reason-ban" placeholder="Reason..."><br> + <button onclick="ban();">Ban User</button> + <h2>Invite code</h2> + <span id="mm-invite-code"></span><br> + <button onclick="genInviteCode();">Generate invite code</button><br> + <button onclick="resetInvites();">Reset invite codes</button> + </div> + </div> + + <script> +// hello guys gals and gays! +// keep in mind that unlike rome, this was infact built in a day +// so please dont judge so much! + +// let us commence and define the lame shit here + +document.getElementById("rl-username").value = ""; +document.getElementById("rl-password").value = ""; +document.getElementById("rl-invitecode").value = ""; + +function displayError (errText) { + document.getElementById("error-text").innerText = errText; + document.getElementById("error-bar").classList.remove("hidden"); +}; + +function closePopup () { + document.getElementById("error-bar").classList.add("hidden"); +}; + +const version = "1.0.5b"; +const serverVersion = "SOKTDEER-2024.11.30-02.07"; +let last_cmd = ""; +let username = ""; +let logged_in = false; +let scene = "loading"; +let ulist = []; +let posts = []; +let replies = []; +let attachments = []; + +if (localStorage.getItem("settings") == null) { + localStorage.setItem("settings", JSON.stringify({"moderation": false})) +}; + +let settings = JSON.parse(localStorage.getItem("settings")); + +function stgsTriggers() { + if (settings.moderation) { + document.getElementById("ms-button-mod").classList.remove("hidden"); + } else { + document.getElementById("ms-button-mod").classList.add("hidden"); + }; +}; + +function updateStg(setting) { + if (setting == "moderation") { + if (settings.moderation) { + settings.moderation = false; + } else { + settings.moderation = true; + }; + }; + localStorage.setItem("settings", JSON.stringify(settings)); + stgsTriggers(); +}; + +stgsTriggers(); + +// whatever, go my ws shit + +const ws = new WebSocket("wss://sokt.meltland.dev") // i hate const!! grr!! >:( + +ws.onmessage = function (event) { + let incoming = JSON.parse(event.data); + console.log(incoming); + + if (incoming.command == "greet") { + closePopup(); + document.getElementById("rl-version").innerText = `${version} - ${incoming.version}`; + document.getElementById("mc-version").innerText = `${version} - ${incoming.version}`; + if (incoming.version != serverVersion) { + displayError(`The server is on a different version than the client. Be wary of issues while using the client. (Expected "${serverVersion}", got "${incoming.version}")`); + }; + ulist = incoming.ulist; + document.getElementById("ms-ulist").innerText = `${ulist.length} users online (${ulist.toString().replaceAll(",", ", ")})`; + posts = incoming.messages; + for (const i in incoming.messages) { + loadPost(incoming.messages[i], true); + }; + if (localStorage.getItem("username") == null || localStorage.getItem("token") == null) { + scene = "register-login"; + document.getElementById("loading").classList.toggle("hidden"); + document.getElementById("register-login").classList.toggle("hidden") + } else { + username = localStorage.getItem("username"); + last_cmd = "login_token"; + ws.send(JSON.stringify({command: "login_token", username: username, token: localStorage.getItem("token")})) + }; + } else if (incoming.command == "ulist") { + ulist = incoming.ulist; + document.getElementById("ms-ulist").innerText = `${ulist.length} users online (${ulist.toString().replaceAll(",", ", ")})`; + }; + if ("error" in incoming) { + if (incoming.error) { + if (incoming.code == "banned") { + displayError(`Account is banned until ${new Date(incoming.banned_until * 1000).toLocaleString()} for "${incoming.ban_reason}"`) + } else { + displayError(`We hit an error. ("${incoming.code}" from ${incoming.form})`); + }; + } else if (last_cmd == "login_token" || last_cmd == "login_pswd") { + if (scene == "register-login") { + document.getElementById("register-login").classList.toggle("hidden"); + } else if (scene == "loading") { + document.getElementById("loading").classList.toggle("hidden"); + }; + scene = "main-scene"; + document.getElementById("main-scene").classList.toggle("hidden"); + document.getElementById("ms-name").innerText = `@${username}` + }; + }; + if ("token" in incoming && ["register","login_pswd"].includes(last_cmd)) { + localStorage.setItem("username", username); + localStorage.setItem("token", incoming.token); + if (last_cmd == "register") { + window.location.reload(); + }; + logged_in = true; + } else if (incoming.command == "new_post") { + loadPost(incoming.data, false); + } else if (last_cmd == "gen_invite" && "invite_code" in incoming) { + document.getElementById("mm-invite-code").innerText = `Your invite code is "${incoming.invite_code}". Use it on any SoktDeer client to sign up!\n\nCodes: ${incoming.invite_codes}` + }; +}; + +ws.onclose = function (event) { + switchScene("connection-lost"); +}; + +function switchScene (newScene) { + document.getElementById(scene).classList.toggle("hidden"); + document.getElementById(newScene).classList.toggle("hidden"); + scene = newScene; +}; + +function register() { + last_cmd = "register"; + username = document.getElementById("rl-username").value; + ws.send(JSON.stringify({command: "register", username: username, password: document.getElementById("rl-password").value, invite_code: document.getElementById("rl-invitecode").value})) +}; + +function logIn() { + last_cmd = "login_pswd"; + username = document.getElementById("rl-username").value; + ws.send(JSON.stringify({command: "login_pswd", username: username, password: document.getElementById("rl-password").value})) +}; + +function logOut() { + localStorage.clear(); + window.location.reload(); +}; + +function loadPost(resf, isFetch) { + console.log("Loading post " + resf.id) + var tsr = resf.created + var tsra = tsr * 1000 + var tsrb = Math.trunc(tsra) + var ts = new Date(); + ts.setTime(tsrb); + var sts = ts.toLocaleString(); + + var content = resf.content + for (const i in resf.replies) { + content = `→ ${resf.replies[i].author.display_name} (@${resf.replies[i].author.username}): ${resf.replies[i].content}\n${content}` + }; + + var post = document.createElement("div"); + post.classList.add("post"); + + if (resf.author.avatar) { + var avatar = document.createElement("img"); + avatar.src = resf.author.avatar; + avatar.classList.add("pfp"); + post.appendChild(avatar); + }; + + var postUsername = document.createElement("span"); + postUsername.innerHTML = `<b>${resf.author.display_name}</b> (<span class="mono">@${resf.author.username}</span>)`; + post.appendChild(postUsername); + + var breaklineA = document.createElement("br"); + post.appendChild(breaklineA); + + var postDetails = document.createElement("small"); + postDetails.innerHTML = `${sts} - <span class="text-clickable" onclick="reply(${resf.id});">Reply</span>`; + post.appendChild(postDetails); + + var breaklineB = document.createElement("br"); + post.appendChild(breaklineB); + + var postContent = document.createElement("span"); + postContent.innerText = content; + post.appendChild(postContent); + + if (resf.attachments.length != 0) { + var horline = document.createElement("hr"); + post.appendChild(horline); + + var attachmentDetails = document.createElement("span"); + for (const x in resf.attachments) { + attachmentDetails.innerHTML += `<a target="_blank" rel="noopener noreferrer" href="${resf.attachments[x]}">Attachment ${Number(x) + 1} (${resf.attachments[x]})</a><br>` + } + post.appendChild(attachmentDetails) + + // i love making garbage code because im too lazy to learn how to make things properly + + var attachmentA = document.createElement("img"); + attachmentA.src = resf.attachments[0] + attachmentA.classList.add("attachment") + attachmentA.setAttribute("onerror", "this.remove();") + post.appendChild(attachmentA); + + if (resf.attachments.length >= 2) { + var attachmentB = document.createElement("img"); + attachmentB.src = resf.attachments[1] + attachmentB.classList.add("attachment") + attachmentB.setAttribute("onerror", "this.remove();") + post.appendChild(attachmentB); + }; + + if (resf.attachments.length >= 3) { + var attachmentC = document.createElement("img"); + attachmentC.src = resf.attachments[2] + attachmentC.classList.add("attachment") + attachmentC.setAttribute("onerror", "this.remove();") + post.appendChild(attachmentC); + }; + }; + + if (isFetch) { + document.getElementById("ms-posts").appendChild(post); + } else { + document.getElementById("ms-posts").insertBefore(post, document.getElementById("ms-posts").firstChild); + } +}; + +function sendPost() { + last_cmd = "post"; + ws.send(JSON.stringify({command: "post", content: document.getElementById("ms-msg").value, replies: replies, attachments: attachments})) + document.getElementById("ms-msg").value = ""; + attachments = []; + replies = []; + updateDetailsMsg(); +}; + +function ban() { + last_cmd = "post"; + if (document.getElementById("mm-until-ban").value != "") { + var buntil = new Date(document.getElementById("mm-until-ban").value).getTime() / 1000 + } else { + var buntil = 0 + }; + ws.send(JSON.stringify({command: "ban", username: document.getElementById("mm-username-ban").value, banned_until: buntil, ban_reason: document.getElementById("mm-reason-ban").value})) + document.getElementById("mm-username-ban").value = ""; + document.getElementById("mm-until-ban").value = ""; + document.getElementById("mm-reason-ban").value = ""; +}; + +function genInviteCode() { + last_cmd = "gen_invite"; + ws.send(JSON.stringify({command: "gen_invite"})) +}; + +function resetInvites() { + last_cmd = "reset_invites"; + ws.send(JSON.stringify({command: "reset_invites"})) +}; + +function setDisplayName() { + last_cmd = "set_display_name"; + ws.send(JSON.stringify({command: "set_display_name", display_name: document.getElementById("mc-display-name").value})) + document.getElementById("mc-display-name").value = ""; +}; + +function setAvatar() { + last_cmd = "set_avatar"; + ws.send(JSON.stringify({command: "set_avatar", avatar: document.getElementById("mc-avatar").value})) + document.getElementById("mc-avatar").value = ""; +}; + +function updateDetailsMsg() { + if (replies.length == 0 && attachments.length == 0) { + document.getElementById("ms-details").innerText = "" + } else if (replies.length == 0) { + if (attachments.length == 1) {var plurals = ""} else {var plurals = "s"} + document.getElementById("ms-details").innerHTML = `${attachments.length} attachment${plurals} - <span class="text-clickable" onclick="clearAll();">Remove all</span>` + } else if (attachments.length == 0) { + if (replies.length == 1) {var plurals = "y"} else {var plurals = "ies"} + document.getElementById("ms-details").innerHTML = `${replies.length} repl${plurals} - <span class="text-clickable" onclick="clearAll();">Remove all</span>` + } else { + if (replies.length == 1) {var plurals = "y"} else {var plurals = "ies"} + if (attachments.length == 1) {var plurals_b = ""} else {var plurals_b = "s"} + document.getElementById("ms-details").innerHTML = `${replies.length} repl${plurals}, ${attachments.length} attachment${plurals_b} - <span class="text-clickable" onclick="clearAll();">Remove all</span>` + }; +}; + +function addAttachment() { + var ata = window.prompt("Add an attachment", "Put a whitelisted URL here...") + if (![null,""].includes(ata)) { + if (attachments.length != 3) { + attachments.push(ata); + }; + }; + updateDetailsMsg(); +}; + +function reply(id) { + if (replies.length != 3) { + replies.push(id); + }; + updateDetailsMsg(); +}; + +function clearAll() { + replies = []; + attachments = []; + updateDetailsMsg(); +}; + +function ping() { + ws.send(JSON.stringify({command: "ping"})) +}; + +setInterval(ping, 5000) + </script> + +</body></html> \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..a094a56 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>soktdeer wl client</title> + <!-- original name, ik --> + <script type="module" src="main.js"></script> + <link rel="stylesheet" href="shitstylse.css"> +</head> +<body> + <div class="main"> + + </div> +</body> +</html> diff --git a/lib/htmlbuilder.js b/lib/htmlbuilder.js new file mode 100644 index 0000000..7343116 --- /dev/null +++ b/lib/htmlbuilder.js @@ -0,0 +1,46 @@ +function makeSigmaHtmlPlusElement(elem) { + elem.child = function (type) { + let childElem = makeSigmaHtmlPlusElement(document.createElement(type)); + elem.appendChild(childElem) + return childElem; + } + elem.html = function (html) { + elem.innerHTML = html; + return elem; + } + elem.text = function (text) { + elem.innerText = text; + return elem; + } + elem.attr = function (attribute, value) { + elem.setAttribute(attribute, value); + return elem; + } + elem.class = function (className) { + elem.classList.add(className) + return elem; + } + elem.do = function (a, params) { + elem.a(...params) + return elem; + } + elem.ev = function (event, listener) { + elem.addEventListener(event, listener) + return elem; + } + elem.up = function () { + return elem.parentElement + } + elem.for = function (array, func) { + array.forEach(element => elem.appendChild(func(element))); + return elem + } + return elem; +} + +/** + * @param {String} type HTML element type + */ +export default function make(type) { + return makeSigmaHtmlPlusElement(document.createElement(type)); +} \ No newline at end of file diff --git a/lib/key.js b/lib/key.js new file mode 100644 index 0000000..7f7971a --- /dev/null +++ b/lib/key.js @@ -0,0 +1,16 @@ +window.onkeydown = function (e) { + if (!e) e = window.event; + shiftHeld = e.shiftKey; +}; + +window.onkeyup = function (e) { + if (!e) e = window.event; + shiftHeld = e.shiftKey; +}; + +window.onmousemove = function (e) { + if (!e) e = window.event; + shiftHeld = e.shiftKey; +}; + +export let shiftHeld; \ No newline at end of file diff --git a/lib/sd.js b/lib/sd.js new file mode 100644 index 0000000..69bd067 --- /dev/null +++ b/lib/sd.js @@ -0,0 +1,61 @@ +import "https://unpkg.com/eventemitter3@latest/dist/eventemitter3.umd.min.js" + +export default class SoktDeer { + /** @type {string} */ + token = ''; + /** @type {boolean} */ + loggedIn = false; + /** @type {WebSocket} */ + ws; + /** @type {import("node:events").EventEmitter} */ + wsEvents = new EventEmitter3(); + + /** @type {any[]} */ + messages = []; + constructor (wsUri="wss://sokt.meltland.dev") { + this.ws = new WebSocket(wsUri); + this.ws.onmessage = (rdata) => { + const data = JSON.parse(rdata.data.toString()); + console.info("SD", "INCOMING", data) + if ('token' in data) return this.wsEvents.emit('token', data.token); + if ('command' in data) return this.wsEvents.emit(data.command, data); + if ('error' in data) return this.wsEvents.emit('error', data); + } + this.wsEvents.on('greet', greetp => { + this.messages = greetp.messages + }) + this.wsEvents.on('new_post', ({data: post}) => { + this.messages.push(post) + }) + } + login(username, password) { + return new Promise((resolve, reject) => { + this.ws.send(JSON.stringify({ + command: "login_pswd", + username, + password + })) + this.wsEvents.once('token', token => { + this.token = token; + resolve(token) + }) + this.wsEvents.once('error', error => { + if(error.error) { + reject(error.code) + } + }) + }) + } + loginToken(token) { + return new Promise((resolve, reject) => { + this.ws.send(JSON.stringify({ + command: "login_token", + token + })) + this.wsEvents.once('error', error => { + if(error.error) reject(error.code) + else resolve() + }) + }) + } +} \ No newline at end of file diff --git a/lib/stores.js b/lib/stores.js new file mode 100644 index 0000000..eacb5e2 --- /dev/null +++ b/lib/stores.js @@ -0,0 +1,12 @@ +export function set(store, value) { + window.stores[store] = value + Object.values(window.storesEvents).forEach(ev => {if(ev.store == store) cb()}) +} +export function update(store) { + Object.values(window.storesEvents).forEach(ev => {if(ev.store == store) cb()}) +} +export function onChange(store, cb) { + let id = nextStoresEventID++; + storesEvents[id] = {store, cb}; + return id; +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..e47e739 --- /dev/null +++ b/main.js @@ -0,0 +1,24 @@ +import * as pages from "./pages.js" +import SoktDeer from "./lib/sd.js" + +window.pages = pages +window.stores = { + sdlib: new SoktDeer(), + sendTokenToWlodekMsDMs: false, +} +window.storesEvents = {} +let nextStoresEventID = 0 +window.stores.set = function (store, value) { + window.stores[store] = value + Object.values(window.storesEvents).forEach(ev => {if(ev.store == store) {ev.cb()}}) +} +window.stores.update = function (store) { + Object.values(window.storesEvents).forEach(ev => {if(ev.store == store) {ev.cb()}}) +} +window.stores.onChange = function (store, cb) { + let id = nextStoresEventID++; + window.storesEvents[id] = {store, cb}; + return id; +} +window.sd = window.stores.sdlib +pages.goToPage('login') \ No newline at end of file diff --git a/pages.js b/pages.js new file mode 100644 index 0000000..1d48894 --- /dev/null +++ b/pages.js @@ -0,0 +1,13 @@ +export let page = "" + +let currPageData = null + +export async function goToPage(pageA) { + if(currPageData?.onunload) currPageData.onunload(); + page = pageA; + let pageHTML = await (await fetch("./pages/"+pageA+"/page.html")).text(); + document.querySelector(".main").innerHTML = pageHTML + let pageData = await import("./pages/"+pageA+"/page.js"); + currPageData = pageData; + if(pageData?.onload) pageData.onload(); +} \ No newline at end of file diff --git a/pages/login/page.html b/pages/login/page.html new file mode 100644 index 0000000..5fa188e --- /dev/null +++ b/pages/login/page.html @@ -0,0 +1,11 @@ +<form id="loginForm"> + <label for="username">Username:</label> + <input type="text" name="username" id="username"> + <br> + <label for="password">Password:</label> + <input type="password" name="password" id="password"> + <br> + <input type="submit" value="Log in"> + <br> + <span id="error" style="color: red;"></span> +</form> \ No newline at end of file diff --git a/pages/login/page.js b/pages/login/page.js new file mode 100644 index 0000000..f0c5994 --- /dev/null +++ b/pages/login/page.js @@ -0,0 +1,18 @@ +async function fetchJSON(url, opts) { + let resp = await fetch(url, opts); + return await resp.json() +} + +export function onload() { + document.getElementById("loginForm").addEventListener("submit", async function (ev) { + ev.preventDefault(); + const username = document.getElementById('username').value; + const password = document.getElementById('password').value; + try { + await stores.sdlib.login(username, password) + } catch (error) { + return document.getElementById('error').innerText = 'An error occured\n' + error + } + pages.goToPage("main") + }) +} \ No newline at end of file diff --git a/pages/main/page.html b/pages/main/page.html new file mode 100644 index 0000000..8b16e26 --- /dev/null +++ b/pages/main/page.html @@ -0,0 +1,7 @@ +<div class="channel"> + <div class="messages" id="messages"></div> + <div class="messageInput disabled" id="messageForm"> + <textarea name="message input" id="messageInput"></textarea> + <button id="send">Send</button> + </div> +</div> \ No newline at end of file diff --git a/pages/main/page.js b/pages/main/page.js new file mode 100644 index 0000000..c337888 --- /dev/null +++ b/pages/main/page.js @@ -0,0 +1,74 @@ +import { shiftHeld } from "../../lib/key.js" +import html from "../../lib/htmlbuilder.js" + +async function fetchJSON(url, opts) { + let resp = await fetch(url, opts); + return await resp.json() +} + +function scrollToBottomOfElement(element) { + element.scrollTo(0, element.scrollHeight); +} + +export function onload() { + const msgArea = document.getElementById("messages"); + + function createMessage(msg) { + let elem = html('div') + .class('message') + .child('div') + .class('message-header') + .child('span') + .class('username') + .text(msg.author.display_name ? `${msg.author.display_name} (${msg.author.username})` : msg.author.username) + .up() + .child('div') + .class('action-buttons') + .child('button').text('reply').up() + .up() + .up() + .child('span') + .class('post-content') + .text(msg?.content) + .up() + msgArea.appendChild(elem) + } + document.getElementById("messageForm").classList.remove('disabled') + msgArea.innerHTML = ""; + // :+1: + + for (const msg of window.stores.sdlib.messages.reverse()) { + createMessage(msg) + } + scrollToBottomOfElement(msgArea.parentElement); + + stores.sdlib.wsEvents.on("new_post", post => { + console.debug('posting of the poster', post) + let scrolledToBottom = msgArea.parentElement.scrollTopMax == msgArea.parentElement.scrollTop; + createMessage(post.data) + if(scrolledToBottom) scrollToBottomOfElement(msgArea.parentElement); + }) + + const submitBtn = document.getElementById("send") + + submitBtn.onclick = function (event) { // using on(event) = ... instead of addEventListener("(event)", ...) because i cant be bothered to clear the event on channel change lol + let msg = document.getElementById("messageInput").value; + stores.sdlib.ws.send(JSON.stringify({ + command: "post", + content: msg, + replies: [], + attachments: [] + })) + document.getElementById("messageInput").value = "" + } + + document.getElementById('messageInput').addEventListener('keydown', event => { + if ( + event.key == "Enter" && + !shiftHeld + ) { + event.preventDefault(); + if (!submitBtn.disabled) submitBtn.click(); + } + }) +} \ No newline at end of file diff --git a/pages/test/page.html b/pages/test/page.html new file mode 100644 index 0000000..7f8bb38 --- /dev/null +++ b/pages/test/page.html @@ -0,0 +1 @@ +testing page (tm) \ No newline at end of file diff --git a/pages/test/page.js b/pages/test/page.js new file mode 100644 index 0000000..f98f2fd --- /dev/null +++ b/pages/test/page.js @@ -0,0 +1,3 @@ +export function onload() { + console.log("hi from onload") +} \ No newline at end of file diff --git a/shitstylse.css b/shitstylse.css new file mode 100644 index 0000000..6682846 --- /dev/null +++ b/shitstylse.css @@ -0,0 +1,61 @@ +/* i have no idea what im doing so */ +body { + /* why */ + margin: 0; + height: 100vh; +} + +.main { + height: 100%; + width: 100%; + line-break: anywhere; + word-break: break-all; +} + +.channel { + flex-grow: 1; + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; +} + +.messages { + flex-grow: 1; +} + +.messageInput { + background: white; + position: sticky; + bottom: 0; + display: flex; +} + +#messageInput { + flex-grow: 1; +} + +.messageInput.disabled { + display: none; +} + +.message-header { + display: flex; + justify-content: space-between; +} + +.action-buttons { + display: none; +} + +.message:hover { + background: #0004; +} + +.message:hover .action-buttons { + display: block; +} + +.message-header .username { + font-size: 1.3em; +} |