From a2c89301d5c63f573ac6680f9c11acda69043a78 Mon Sep 17 00:00:00 2001
From: Tyler Murphy <=>
Date: Wed, 26 Jul 2023 01:04:39 -0400
Subject: start dms
---
public/js/chat.js | 37 +++++++++++++++++++++++++++++++++++++
public/js/components.js | 18 ++++++++++++++----
public/js/home.js | 21 ++++++---------------
public/js/main.js | 8 ++++++--
public/js/people.js | 15 ++-------------
public/js/profile.js | 6 +++---
6 files changed, 68 insertions(+), 37 deletions(-)
create mode 100644 public/js/chat.js
(limited to 'public/js')
diff --git a/public/js/chat.js b/public/js/chat.js
new file mode 100644
index 0000000..41ba2ba
--- /dev/null
+++ b/public/js/chat.js
@@ -0,0 +1,37 @@
+import { div, pfp, p, parse, button, body, a, textarea, span, crawl } from './main.js'
+import { loadself, loadpostspage, createpost, loadusers } from './api.js'
+import { parsePost, header } from './components.js'
+
+function render() {
+
+ let new_body =
+ body({},
+ ...header(false, false, true, data.self.user_id),
+ )
+
+ document.body.replaceWith(new_body)
+
+}
+
+const data = {
+ self: {},
+ users: []
+}
+
+async function init() {
+
+ let request = (await loadself());
+ data.self = request.json
+
+ if (request.json == undefined) {
+ location.href = '/login'
+ return
+ }
+
+ data.users[data.self.user_id] = data.self
+
+ render()
+}
+
+
+init()
diff --git a/public/js/components.js b/public/js/components.js
index 7e2a268..f247875 100644
--- a/public/js/components.js
+++ b/public/js/components.js
@@ -1,9 +1,9 @@
-import { div, a, pfp, span, i, parse, parseDate, p, form, input, svg, path, parseMonth } from './main.js'
+import { div, a, pfp, span, i, parse, parseDate, p, form, input, svg, path, parseMonth, g } from './main.js'
import { postlike, postcomment, loadcommentspage } from './api.js';
window.parse = parse;
-export function header(home, people, user_id) {
+export function header(home, people, chat, user_id) {
return [
div({id: 'header'},
span({class: 'logo'},
@@ -21,6 +21,16 @@ export function header(home, people, user_id) {
svg({viewBox: '0 0 28 28', fill: 'currentColor', height: '28', width: '28'},
path({d: "M10.5 4.5c-2.272 0-2.75 1.768-2.75 3.25C7.75 9.542 8.983 11 10.5 11s2.75-1.458 2.75-3.25c0-1.482-.478-3.25-2.75-3.25zm0 8c-2.344 0-4.25-2.131-4.25-4.75C6.25 4.776 7.839 3 10.5 3s4.25 1.776 4.25 4.75c0 2.619-1.906 4.75-4.25 4.75zm9.5-6c-1.41 0-2.125.841-2.125 2.5 0 1.378.953 2.5 2.125 2.5 1.172 0 2.125-1.122 2.125-2.5 0-1.659-.715-2.5-2.125-2.5zm0 6.5c-1.999 0-3.625-1.794-3.625-4 0-2.467 1.389-4 3.625-4 2.236 0 3.625 1.533 3.625 4 0 2.206-1.626 4-3.625 4zm4.622 8a.887.887 0 00.878-.894c0-2.54-2.043-4.606-4.555-4.606h-1.86c-.643 0-1.265.148-1.844.413a6.226 6.226 0 011.76 4.336V21h5.621zm-7.122.562v-1.313a4.755 4.755 0 00-4.749-4.749H8.25A4.755 4.755 0 003.5 20.249v1.313c0 .518.421.938.937.938h12.125c.517 0 .938-.42.938-.938zM20.945 14C24.285 14 27 16.739 27 20.106a2.388 2.388 0 01-2.378 2.394h-5.81a2.44 2.44 0 01-2.25 1.5H4.437A2.44 2.44 0 012 21.562v-1.313A6.256 6.256 0 018.25 14h4.501a6.2 6.2 0 013.218.902A5.932 5.932 0 0119.084 14h1.861z"})
)
+ ),
+ a({id: 'chat', class: chat ? 'selected' : '', href: '/chat', 'aria-label': 'xssbook chat page'},
+ svg({viewBox: '0 0 512 512', fill: 'currentColor', height: '28', width: '28'},
+ g({transform: "translate(0.000000,512.000000) scale(0.100000,-0.100000)", fill: "#fffffff", stroke: "none"},
+ path({d: "M1731 4799 c-240 -27 -467 -93 -687 -199 -992 -481 -1340 -1619 -768 -2512 l43 -66 -150 -469 c-82 -257 -149 -481 -149 -496 0 -73 75 -147 150 -147 31 0 215 89 725 350 l230 118 90 -35 c109 -42 279 -87 395 -104 83 -12 86 -14 147 -70 172 -159 313 -256 514 -354 507 -245 1103 -270 1644 -68 l81 30 449 -229 c291 -148 464 -232 491 -235 80 -10 164 63 164 143 0 15 -67 238 -149 496 l-150 469 43 67 c330 511 364 1151 90 1689 -268 524 -818 913 -1421 1003 -43 7 -83 15 -89 18 -7 4 -54 45 -106 92 -143 128 -266 212 -443 299 -215 107 -352 152 -580 191 -139 25 -430 34 -564 19z m407 -300 c123 -13 261 -43 377 -80 100 -33 300 -127 385 -182 l54 -35 -39 -7 c-273 -43 -442 -94 -645 -191 -911 -439 -1295 -1442 -887 -2317 25 -53 41 -97 36 -97 -6 0 -67 22 -136 50 -78 31 -141 50 -166 50 -32 0 -104 -33 -363 -165 -178 -91 -325 -165 -327 -165 -3 0 42 145 100 323 57 177 104 340 105 362 1 47 -6 63 -84 178 -107 157 -180 326 -220 510 -29 135 -31 396 -5 530 119 596 612 1070 1253 1206 186 40 380 50 562 30z m1220 -600 c223 -24 404 -78 607 -179 436 -217 742 -607 832 -1059 24 -119 24 -384 0 -504 -39 -194 -130 -405 -244 -563 -31 -43 -60 -94 -64 -112 -9 -42 -4 -61 114 -429 52 -161 93 -293 91 -293 -2 0 -149 74 -327 165 -263 134 -331 165 -365 165 -26 0 -82 -17 -149 -44 -528 -216 -1130 -170 -1608 124 -163 100 -335 258 -452 417 -115 155 -211 374 -250 570 -24 122 -24 384 0 506 106 530 514 974 1062 1155 239 79 508 108 753 81z"}),
+ path({d: "M2488 2539 c-43 -22 -78 -81 -78 -129 0 -50 35 -107 80 -130 75 -38 157 -14 198 58 27 49 28 91 2 142 -37 73 -127 99 -202 59z"}),
+ path({d: "M3088 2539 c-43 -22 -78 -81 -78 -129 0 -50 35 -107 80 -130 75 -38 157 -14 198 58 27 49 28 91 2 142 -37 73 -127 99 -202 59z"}),
+ path({d: "M3688 2539 c-43 -22 -78 -81 -78 -129 0 -50 35 -107 80 -130 49 -25 90 -25 138 -1 43 22 82 84 82 131 0 47 -39 109 -82 131 -47 24 -93 24 -140 -2z"})
+ )
+ )
)
),
a({class: 'pfp', id: 'profile', href: '/profile', 'aria-label': 'your xssbook profile'},
@@ -246,7 +256,7 @@ export function parseUser(user) {
parse('Joined ' + parseDate(new Date(user.date)))
),
span({class: 'gtext'},
- parse('Gender :' + user.gender)
+ parse('Gender:' + user.gender)
),
span({class: 'gtext'},
parse('Birthday: ' + parseMonth(user.month) + ' ' + user.day + ', ' + user.year)
@@ -257,4 +267,4 @@ export function parseUser(user) {
)
)
)
-}
\ No newline at end of file
+}
diff --git a/public/js/home.js b/public/js/home.js
index 9f0398f..7d64eab 100644
--- a/public/js/home.js
+++ b/public/js/home.js
@@ -36,7 +36,7 @@ function render() {
let new_body =
body({},
- ...header(true, false, data.self.user_id),
+ ...header(true, false, false, data.self.user_id),
div({id: 'create'},
div({class: 'create'},
a({class: 'pfp', href: '/profile'},
@@ -44,7 +44,7 @@ function render() {
),
button({class: 'pfp'},
p({class: 'gtext', onclick: () => document.getElementById('popup').classList.remove('hidden')},
- parse(`What' on your mind, ${data.self.firstname}`)
+ parse(`What's on your mind, ${data.self.firstname}`)
)
)
)
@@ -113,7 +113,9 @@ async function load() {
const posts = (await loadpostspage(page)).json
if (posts.length === 0) {
- document.getElementById('load').remove()
+ let load = document.getElementById('load')
+ if (load)
+ load.remove()
return []
} else {
page++
@@ -134,17 +136,6 @@ async function load() {
async function init() {
let request = (await loadself());
-
- if (request.status === 429) {
- let new_body =
- body({},
- ...header(true, false)
- )
-
- document.body.replaceWith(new_body)
- throw new Error("Rate limited");
- }
-
data.self = request.json
if (request.json == undefined) {
@@ -161,4 +152,4 @@ async function init() {
}
-init()
\ No newline at end of file
+init()
diff --git a/public/js/main.js b/public/js/main.js
index b49474c..80ee48b 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -83,6 +83,10 @@ export function path(attrs, ...children) {
return createElementNS("path", attrs, ...children)
}
+export function g(attrs, ...children) {
+ return createElementNS("g", attrs, ...children)
+}
+
export function svg(attrs, ...children) {
return createElementNS("svg", attrs, ...children)
}
@@ -129,7 +133,7 @@ export function parseMonth(month) {
}
export function parseDate(date) {
- return parseMonth(date.getUTCMonth()) + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
+ return parseMonth(date.getUTCMonth()) + ' ' + (date.getUTCDate()-1) + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
}
export function crawl(key, object) {
@@ -142,4 +146,4 @@ export function crawl(key, object) {
}
}
return data
-}
\ No newline at end of file
+}
diff --git a/public/js/people.js b/public/js/people.js
index 99890d9..431359c 100644
--- a/public/js/people.js
+++ b/public/js/people.js
@@ -6,7 +6,7 @@ function render() {
let new_body =
body({},
- ...header(false, true, data.self.user_id),
+ ...header(false, true, false, data.self.user_id),
div({id: 'users'},
...data.users.map(u => parseUser(u))
),
@@ -49,17 +49,6 @@ async function load() {
async function init() {
let request = (await loadself());
-
- if (request.status === 429) {
- let new_body =
- body({},
- ...header(true, false, data.self.user_id)
- )
-
- document.body.replaceWith(new_body)
- throw new Error("Rate limited");
- }
-
if (request.json == undefined) {
location.href = '/login'
return
@@ -73,4 +62,4 @@ async function init() {
render()
}
-init()
\ No newline at end of file
+init()
diff --git a/public/js/profile.js b/public/js/profile.js
index a9990fb..b000425 100644
--- a/public/js/profile.js
+++ b/public/js/profile.js
@@ -107,7 +107,7 @@ async function render() {
let new_body =
body({},
- ...header(false, false, data.self.user_id),
+ ...header(false, false, false, data.self.user_id),
div({id: 'top'},
div({id: 'banner'},
div({class: 'bg'},
@@ -223,7 +223,7 @@ async function render() {
parse('Email: ' + data.user.email)
),
span({class: 'gtext bold'},
- parse('Gender ' + data.user.gender)
+ parse('Gender: ' + data.user.gender)
),
span({class: 'gtext bold'},
parse('Birthday: ' + parseMonth(data.user.month) + ' ' + data.user.day + ', ' + data.user.year)
@@ -362,4 +362,4 @@ async function init() {
render()
}
-init()
\ No newline at end of file
+init()
--
cgit v1.2.3-freya
From c4c26f42b6b06dbc75578314a483db66bf7ddb4c Mon Sep 17 00:00:00 2001
From: Tyler Murphy <=>
Date: Mon, 21 Aug 2023 23:19:53 -0400
Subject: dms
---
public/chat.html | 1 +
public/css/chat.css | 172 ++++++++++++++++++++++++++++++++++++++++
public/css/home.css | 2 +-
public/css/main.css | 6 +-
public/js/api.js | 26 +++++-
public/js/chat.js | 207 ++++++++++++++++++++++++++++++++++++++++++++++--
public/js/components.js | 163 +++++++++++++++++++++++++++++++++++++-
public/js/main.js | 6 +-
src/api/chat.rs | 17 ++--
src/database/chat.rs | 30 +++----
src/main.rs | 2 +-
src/types/chat.rs | 4 +-
12 files changed, 598 insertions(+), 38 deletions(-)
create mode 100644 public/css/chat.css
(limited to 'public/js')
diff --git a/public/chat.html b/public/chat.html
index e293210..ecd2f5c 100644
--- a/public/chat.html
+++ b/public/chat.html
@@ -17,6 +17,7 @@
+
diff --git a/public/css/chat.css b/public/css/chat.css
new file mode 100644
index 0000000..1092a39
--- /dev/null
+++ b/public/css/chat.css
@@ -0,0 +1,172 @@
+.spacer {
+ margin-bottom: 3.5em !important;
+}
+
+#cent {
+ display: flex;
+ width: 100%;
+ height: calc(100vh - 3.5em);
+ flex-direction: row;
+}
+
+#sidebar {
+ height: 100%;
+ width: 20%;
+ min-width: 25em;
+ display: flex;
+ flex-direction: column;
+}
+
+#sidebar>span {
+ display: block;
+ width: 100%;
+ text-align: center;
+ font-size: 1.5em;
+ margin: .5em 0;
+}
+
+#center {
+ height: 100%;
+ width: calc(100vw - 20%);
+ max-width: calc(100vw - 25em);
+}
+
+.room {
+ width: calc (100% - 1rem);
+ height: 3rem;
+ display: flex;
+ position: relative;
+ flex-direction: row;
+ padding: .5rem 1rem;
+}
+
+.room:hover, .current {
+ background-color: var(--hover);
+}
+
+.room-icon {
+ border-radius: 3rem;
+ height: 3rem;
+ width: 3rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: 1000;
+ font-size: 1.5rem;
+}
+
+.room-name {
+ display: flex;
+ height: 3rem;
+ margin-left: 1rem;
+ align-items: center;
+}
+
+.roomDisplay {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ position: relative;
+}
+
+.roomDisplayCenter {
+ display: flex;
+ width: calc(100% - 25em);
+ height: 100%;
+ flex-direction: column;
+}
+
+.roomDisplayPeople {
+ width: 25em;
+ height: 100%;
+ overflow-y: scroll;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+}
+
+.roomDisplayPeople>span {
+ display: block;
+ width: 100%;
+ text-align: center;
+ font-size: 1.5em;
+ margin: .5em 0em;
+}
+
+.person {
+ width: 20em;
+}
+
+.person img, .person .profile {
+ height: 7em;
+ width: 7em;
+}
+
+.person .info {
+ margin: 5px;
+}
+
+.person .ltext {
+ font-size: 18px;
+}
+
+.person .gtext {
+ font-size: 12px;
+}
+
+.roomDisplay .messages {
+ flex: 1;
+ margin-left: 1rem;
+ display: flex;
+ flex-direction: column-reverse;
+ overflow-y: scroll;
+}
+
+.roomDisplay .messageContent {
+ flex-grow: 0;
+ width: auto;
+ margin-bottom: .5rem;
+ display: block;
+ height: fit-content;
+ overflow: none;
+}
+
+.roomDisplay .messageContent[contenteditable]:empty::before {
+ content: "Send an unencrypted message";
+ color: gray;
+}
+
+.addUser[contenteditable]:empty::before {
+ content: "Type email to add user";
+ color: gray;
+}
+
+.addRoom[contenteditable]:empty::before {
+ content: "Type name to create room";
+ color: gray;
+}
+
+.message {
+ display: flex;
+ flex-direction: row;
+ height: fit-content;
+ padding: .5rem;
+}
+
+.message-pfp {
+ flex-grow: 0;
+ height: 3rem;
+ width: 3rem;
+ margin-right: 1rem;
+}
+
+.message-pfp img {
+ width: 100%;
+ height: 100%;
+ border-radius: 3rem;
+}
+
+.message-content {
+ flex-grow: 0;
+}
diff --git a/public/css/home.css b/public/css/home.css
index f467aba..f21d201 100644
--- a/public/css/home.css
+++ b/public/css/home.css
@@ -184,4 +184,4 @@ body {
width: calc(100% - 20px);
background-color: var(--secondary);
font-family: sfpro;
-}
\ No newline at end of file
+}
diff --git a/public/css/main.css b/public/css/main.css
index 352a3b4..c8ff1d7 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -14,6 +14,10 @@ body {
--popup: #ffffffcc;
}
+textarea {
+ resize: none
+}
+
body {
background-color: var(--primary);
width: 100%;
@@ -130,7 +134,7 @@ footer {
background-color: var(--primary);
}
-input {
+input, .input {
flex: 1;
font-family: sfpro;
background-color: var(--primary);
diff --git a/public/js/api.js b/public/js/api.js
index 5a55460..9ef5e4c 100644
--- a/public/js/api.js
+++ b/public/js/api.js
@@ -141,4 +141,28 @@ export const updateavatar = async (file) => {
export const updatebanner = async (file) => {
return await fileRequest('/users/banner', file, 'PUT')
-}
\ No newline at end of file
+}
+
+export const chatlist = async () => {
+ return await request('/chat/list', {})
+}
+
+export const chatcreate = async (name) => {
+ return await request('/chat/create', {name})
+}
+
+export const chatadd = async (email, room_id) => {
+ return await request('/chat/add', {email, room_id}, 'PATCH')
+}
+
+export const chatleave = async (room_id) => {
+ return await request('/chat/leave', {room_id}, 'DELETE')
+}
+
+export const chatsend = async (content, room_id) => {
+ return await request('/chat/send', {content, room_id})
+}
+
+export const chatload = async (newest_msg, page, room_id) => {
+ return await request('/chat/load', {newest_msg, page, room_id})
+}
diff --git a/public/js/chat.js b/public/js/chat.js
index 41ba2ba..33f21cb 100644
--- a/public/js/chat.js
+++ b/public/js/chat.js
@@ -1,21 +1,202 @@
-import { div, pfp, p, parse, button, body, a, textarea, span, crawl } from './main.js'
-import { loadself, loadpostspage, createpost, loadusers } from './api.js'
-import { parsePost, header } from './components.js'
+import { body, div, span, p, parse } from './main.js'
+import { loadself, chatlist, chatload, loadusers, chatcreate } from './api.js'
+import { createRoomDisplay, header, parseMessage, parseRoom, parseUser, createSingleLineInput } from './components.js'
-function render() {
+async function getUser(user_id) {
+ if (data.users[user_id]) {
+ return data.users[user_id]
+ } else {
+ let request = (await loadusers([user_id]))
+ if (request.status != 200) {
+ location.href = 'login'
+ } else {
+ data.users[user_id] = request.json[0]
+ return request.json[0]
+ }
+ }
+}
+
+async function parseMessageImpl(message) {
+ let user = await getUser(message.user_id)
+ return parseMessage(message, user)
+}
+
+async function onRoomClick(room) {
+ for (const room of Object.values(data.rooms)) {
+ room.display.style.display = 'none'
+ room.button.classList.remove('current')
+ }
+ room.display.style.display = ''
+ room.button.classList.add('current')
+}
+async function render() {
let new_body =
body({},
...header(false, false, true, data.self.user_id),
+ div({id: 'cent'},
+ div({id: 'sidebar'},
+ span({class: 'ltext'},
+ parse("Rooms")
+ ),
+ createSingleLineInput(
+ {
+ type: 'text',
+ name: 'addRoom',
+ class: 'addRoom input',
+ style: 'flex-grow: 0; width: 80%; margin-left: auto; margin-right: auto;'
+ },
+ async (text) => {
+ let result = (await chatcreate(text))
+ if (result.status != 201) {
+ alert(result.msg)
+ return false
+ } else {
+ return true
+ }
+ }
+ ),
+ ),
+ div({id: 'center'})
+ )
)
document.body.replaceWith(new_body)
-
}
const data = {
self: {},
- users: []
+ users: [],
+ rooms: {},
+}
+
+async function loadRoomPage(room_id) {
+ let room = data.rooms[room_id]
+ let request = (await chatload (
+ room.newest_msg,
+ room.page,
+ room_id
+ ))
+
+ if (request.json == undefined) {
+ alert(request.msg)
+ return
+ }
+
+ for (const msg of request.json) {
+ room.messages.push(msg)
+ }
+
+ room.page++
+}
+
+async function loadRoom(room_id) {
+ let room = data.rooms[room_id]
+
+ let request = (await loadusers(room.users))
+ if (request.status != 200) {
+ location.href = '/login'
+ } else {
+ for (const user of request.json) {
+ data.users[user.user_id] = user
+ }
+ }
+
+ room.page = 0
+ room.messages = []
+ if (room.newest_msg == undefined || room.newest_msg < 0)
+ room.newest_msg = Number.MAX_SAFE_INTEGER
+ await loadRoomPage(room_id)
+ room.newest_msg = Math.max(
+ ...room.messages.map(m => m.message_id)
+ )
+ room.page = 0
+
+ room.display = createRoomDisplay(room)
+
+ let displays = document.getElementById("center")
+ displays.appendChild(room.display)
+
+ let button = parseRoom(room, onRoomClick)
+
+ if (displays.children.length > 1) {
+ room.display.style.display = 'none'
+ } else {
+ button.classList.add('current')
+ }
+
+ let sidebar = document.getElementById("sidebar")
+ sidebar.appendChild(button)
+ room.button = button
+
+ let messages = room.display.getElementsByClassName('messages')[0]
+ for (const message of room.messages) {
+ messages.appendChild(await parseMessageImpl(message))
+ }
+
+ if (!room.people) room.people = room.people = {}
+
+ let people = room.display.getElementsByClassName("roomDisplayPeople")[0]
+ for (const user_id of room.users) {
+ if (room.people[user_id]) continue
+ let user = await getUser(user_id)
+ let el = parseUser(user)
+ people.appendChild(el)
+ room.people[user_id] = el
+ }
+
+}
+
+async function onMessage(message) {
+ let event = JSON.parse(message.data)
+ switch (event.type) {
+ case "message": {
+ let room = data.rooms[event.room_id]
+ if (!room) return
+ let display = room.display
+ let messages = display.getElementsByClassName('messages')[0]
+ messages.prepend(await parseMessageImpl(event))
+ break;
+ }
+ case "add": {
+ let room = data.rooms[event.room.room_id]
+ if (!room) {
+ // we are the user being added
+ data.rooms[event.room.room_id] = event.room
+ loadRoom(event.room.room_id)
+ } else {
+ let display = room.display
+ let people = display.getElementsByClassName('roomDisplayPeople')[0]
+ if (!room.people[event.user_id]) {
+ let user = await getUser(event.user_id)
+ let el = parseUser(user)
+ people.appendChild(el)
+ room.people[event.user_id] = el
+ }
+ }
+ break;
+ }
+ case "leave": {
+ let room = data.rooms[event.room_id]
+ if (!room) return
+ if (room.people[event.user_id]) {
+ room.people[event.user_id].remove()
+ delete room.people[event.user_id]
+ }
+ if (event.user_id == data.self.user_id) {
+ room.display.remove()
+ room.button.remove()
+ }
+ break;
+ }
+ case "typing": {
+ break;
+ }
+ default: {
+ console.warn("unhandled event: " + message.data)
+ break;
+ }
+ }
}
async function init() {
@@ -31,6 +212,20 @@ async function init() {
data.users[data.self.user_id] = data.self
render()
+
+ let rooms = (await chatlist());
+ if (rooms.json === undefined) {
+ alert(rooms.msg)
+ } else {
+ for (const room of rooms.json) {
+ data.rooms[room.room_id] = room
+ loadRoom(room.room_id)
+ }
+ }
+
+ let socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + location.host + "/api/chat/connect")
+ socket.addEventListener("message", onMessage);
+
}
diff --git a/public/js/components.js b/public/js/components.js
index f247875..38af787 100644
--- a/public/js/components.js
+++ b/public/js/components.js
@@ -1,5 +1,5 @@
import { div, a, pfp, span, i, parse, parseDate, p, form, input, svg, path, parseMonth, g } from './main.js'
-import { postlike, postcomment, loadcommentspage } from './api.js';
+import { postlike, postcomment, loadcommentspage, chatsend, chatadd, chatleave } from './api.js';
window.parse = parse;
@@ -268,3 +268,164 @@ export function parseUser(user) {
)
)
}
+
+const stringToColor = (str) => {
+ let hash = 0;
+ str.split('').forEach(char => {
+ hash = char.charCodeAt(0) + ((hash << 5) - hash)
+ })
+ let color = '#'
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xff
+ color += value.toString(16).padStart(2, '0')
+ }
+ return color
+}
+
+export function parseRoom(room, callback) {
+
+ let dspName = room.name[0].toUpperCase()
+ let color = stringToColor(room.room_id + room.name + room.room_id)
+
+ return (
+ div({class: 'room', onclick: () => {
+ callback(room)
+ }},
+ div({class: 'room-icon ltext', style: `background-color: ${color}`},
+ span({}, parse(dspName))
+ ),
+ div({class: 'room-name ltext'},
+ span({}, parse(room.name))
+ ),
+ div({
+ class: 'close',
+ onclick: async () => {
+ let request = (await chatleave(room.room_id))
+
+ if (request.status != 200) {
+ alert(request.msg)
+ }
+ }
+ })
+ )
+ )
+}
+
+export function parseRooms(rooms, callback) {
+ let ret = []
+
+ for (const room of rooms) {
+ ret.push(parseRoom(room, callback))
+ }
+
+ return ret
+}
+
+export function createMultiLineInput(attributes, onSubmit) {
+ let area = span({
+ ...attributes,
+ role: 'textbox',
+ contenteditable: '',
+ onkeydown: async (event) => {
+ if (event.keyCode == 13 && !event.shiftKey) {
+ event.preventDefault()
+ let text = area.innerHTML.trim()
+ text = text.replaceAll("\n", "
")
+ if (text.length < 1) return
+ if (await onSubmit(text)) {
+ area.textContent = ''
+ }
+ }
+ },
+ })
+ return area
+}
+
+export function createSingleLineInput(attributes, onSubmit) {
+ let area = span({
+ ...attributes,
+ role: 'textbox',
+ contenteditable: '',
+ onkeydown: async (event) => {
+ if (event.keyCode == 13 && !event.shiftKey) {
+ event.preventDefault()
+ let text = area.innerHTML.trim()
+ text = text.replaceAll("\n", "
")
+ if (text.length < 1) return
+ if (await onSubmit(text)) {
+ area.textContent = ''
+ }
+ } else if (event.keyCode == 13) {
+ event.preventDefault()
+ }
+ },
+ })
+ return area
+}
+
+export function createRoomDisplay(room) {
+ return (
+ div({class: 'roomDisplay'},
+ div({class: 'roomDisplayCenter'},
+ div({class: 'messages'}),
+ createMultiLineInput(
+ {
+ type: 'text',
+ name: 'messageContent',
+ class: 'messageContent input',
+ },
+ async (text) => {
+ let result = (await chatsend(text, room.room_id))
+
+ if (result.status != 201) {
+ alert(result.msg)
+ return false
+ } else {
+ return true
+ }
+ }
+ ),
+ ),
+ div({class: 'roomDisplayPeople'},
+ span({class: 'ltext'},
+ parse("People"),
+ ),
+ createSingleLineInput(
+ {
+ type: 'text',
+ name: 'addUser',
+ class: 'addUser input',
+ style: 'flex-grow: 0; width: 80%'
+ },
+ async (text) => {
+ let result = (await chatadd(text, room.room_id))
+ if (result.status != 200) {
+ alert(result.msg)
+ return false
+ } else {
+ return true
+ }
+ }
+ )
+ )
+ )
+ )
+}
+
+export function parseMessage(message, user) {
+ return (
+ div({class: 'message'},
+ a({class: 'message-pfp', href: `/profile?id=${message.user_id}`},
+ pfp(message.user_id)
+ ),
+ div({class: 'message-content'},
+ span({class: 'message-name ltext'},
+ parse(user.firstname + ' ' + user.lastname)
+ ),
+ p({class: 'message-text ltext'},
+ parse(message.content)
+ )
+ )
+ )
+ )
+}
diff --git a/public/js/main.js b/public/js/main.js
index 80ee48b..a20ef10 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -103,10 +103,6 @@ export function parse(html) {
return document.createRange().createContextualFragment(html);
}
-export function pfpl(id) {
-
-}
-
export function pfp(id) {
return img('pfp', {src: `/image/avatar?user_id=${id}`})
}
@@ -133,7 +129,7 @@ export function parseMonth(month) {
}
export function parseDate(date) {
- return parseMonth(date.getUTCMonth()) + ' ' + (date.getUTCDate()-1) + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
+ return parseMonth(date.getUTCMonth()) + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
}
export function crawl(key, object) {
diff --git a/src/api/chat.rs b/src/api/chat.rs
index 1e56c3e..02bdfbd 100644
--- a/src/api/chat.rs
+++ b/src/api/chat.rs
@@ -6,7 +6,7 @@ use tokio::sync::{Mutex, mpsc::{Sender, self}};
use crate::{
public::docs::{EndpointDocumentation, EndpointMethod},
types::{
- extract::{AuthorizedUser, Check, CheckResult, Database, Json},
+ extract::{AuthorizedUser, Check, CheckResult, Database, Json, Log},
http::ResponseCode,
chat::{ChatRoom, ChatEvent}, user::User,
},
@@ -75,7 +75,8 @@ pub const CHAT_LIST: EndpointDocumentation = EndpointDocumentation {
async fn list (
AuthorizedUser(user): AuthorizedUser,
- Database(db): Database
+ Database(db): Database,
+ _: Log
) -> Response {
let Ok(rooms) = ChatRoom::from_user_id(&db, user.user_id) else {
return ResponseCode::InternalServerError.text("Failed to retrieve rooms")
@@ -137,7 +138,7 @@ async fn create (
for user in &room.users {
send_event(ChatEvent::Add {
user_id: *user,
- room_id: room.room_id
+ room: room.clone()
}, &room).await;
}
@@ -191,10 +192,14 @@ async fn add (
return ResponseCode::BadRequest.text("User does not exist")
};
- let Ok(room) = ChatRoom::from_user_and_room_id(&db, user.user_id, body.room_id) else {
+ let Ok(mut room) = ChatRoom::from_user_and_room_id(&db, user.user_id, body.room_id) else {
return ResponseCode::BadRequest.text("Room doesnt exist or you are not in it")
};
+ if room.users.contains(&to_add.user_id) {
+ return ResponseCode::BadRequest.text("User is already in the room")
+ }
+
let Ok(success) = room.add_user(&db, to_add.user_id) else {
return ResponseCode::InternalServerError.text("Failed to add user")
};
@@ -202,10 +207,12 @@ async fn add (
if !success {
return ResponseCode::BadRequest.text("User is already in the room")
}
+
+ room.users.push(to_add.user_id);
send_event(ChatEvent::Add {
user_id: to_add.user_id,
- room_id: room.room_id
+ room: room.clone()
}, &room).await;
ResponseCode::Success.text("Successfully added user")
diff --git a/src/database/chat.rs b/src/database/chat.rs
index 7364211..99ec86c 100644
--- a/src/database/chat.rs
+++ b/src/database/chat.rs
@@ -61,9 +61,10 @@ impl Database {
);
",
)?;
+
let row = stmt.query_map([user_id], |row| {
- let room_id = row.get(0)?;
- let name = row.get(1)?;
+ let room_id: u64 = row.get(0)?;
+ let name: String = row.get(1)?;
let mut stmt2 = self.0.prepare(
"
@@ -72,20 +73,19 @@ impl Database {
"
)?;
- let mut users = Vec::new();
- let _ = stmt2.query_map([room_id], |row2| {
- let user_id = row2.get(0)?;
- users.push(user_id);
- Ok(())
- })?;
+ let users = stmt2.query_map([room_id], |row2| {
+ Ok(row2.get(0)?)
+ })?.into_iter().flatten().collect();
let room = ChatRoom {
room_id,
users,
name
};
+
Ok(room)
})?;
+
Ok(row.into_iter().flatten().collect())
}
@@ -158,8 +158,8 @@ impl Database {
let msg = stmt.query_row((user_id, room_id, date, content), |row| {
let message_id = row.get(0)?;
- let room_id = row.get(1)?;
- let user_id = row.get(2)?;
+ let user_id = row.get(1)?;
+ let room_id = row.get(2)?;
let date = row.get(3)?;
let content = row.get(4)?;
@@ -182,17 +182,17 @@ impl Database {
"
SELECT * FROM chat_messages
WHERE room_id = ?
- AND message_id < newest_message
- ORDER BY message_id ASC
+ AND message_id < ?
+ ORDER BY message_id DESC
LIMIT ?
OFFSET ?
"
)?;
- let messages = stmt.query_map((room_id, 20, 20 * page), |row| {
+ let messages = stmt.query_map((room_id, newest_message, 20, 20 * page), |row| {
let message_id = row.get(0)?;
- let room_id = row.get(1)?;
- let user_id = row.get(2)?;
+ let user_id = row.get(1)?;
+ let room_id = row.get(2)?;
let date = row.get(3)?;
let content = row.get(4)?;
diff --git a/src/main.rs b/src/main.rs
index 817f8ac..cc8a61e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -46,7 +46,7 @@ async fn main() {
tracing_subscriber::registry()
.with(
fmt_layer
- .with_filter(LevelFilter::TRACE)
+ .with_filter(LevelFilter::INFO)
.with_filter(filter_fn(|metadata| {
metadata.target().starts_with("xssbook")
})),
diff --git a/src/types/chat.rs b/src/types/chat.rs
index ab3390c..8413f77 100644
--- a/src/types/chat.rs
+++ b/src/types/chat.rs
@@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize};
use tracing::instrument;
use crate::{types::http::{ResponseCode, Result}, database::Database};
-#[derive(Serialize, Clone, Debug)]
+#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ChatRoom {
pub room_id: u64,
pub users: Vec,
@@ -33,7 +33,7 @@ pub enum ChatEvent {
#[serde(rename = "add")]
Add {
user_id: u64,
- room_id: u64
+ room: ChatRoom
},
#[serde(rename = "leave")]
--
cgit v1.2.3-freya
From 909d47f3316b3593a375bfb52535003c6bc9dd4b Mon Sep 17 00:00:00 2001
From: Tyler Murphy <=>
Date: Mon, 21 Aug 2023 23:43:01 -0400
Subject: fuck you you stupid browsers stop making thing safe >:(
---
public/js/components.js | 7 +++++++
1 file changed, 7 insertions(+)
(limited to 'public/js')
diff --git a/public/js/components.js b/public/js/components.js
index 38af787..0bf0ed1 100644
--- a/public/js/components.js
+++ b/public/js/components.js
@@ -330,6 +330,13 @@ export function createMultiLineInput(attributes, onSubmit) {
if (event.keyCode == 13 && !event.shiftKey) {
event.preventDefault()
let text = area.innerHTML.trim()
+ .replaceAll("&", '&')
+ .replaceAll("<", '<')
+ .replaceAll(">", '>')
+ .replaceAll(""", '"')
+ .replaceAll("'", "'")
+
+
text = text.replaceAll("\n", "
")
if (text.length < 1) return
if (await onSubmit(text)) {
--
cgit v1.2.3-freya
From fabc1e51cb6b21b19f00f155adb0ee329c9abdb2 Mon Sep 17 00:00:00 2001
From: Tyler Murphy <=>
Date: Tue, 22 Aug 2023 00:15:10 -0400
Subject: finish dms
---
public/css/chat.css | 10 ++++++++-
public/js/chat.js | 60 ++++++++++++++++++++++++++++---------------------
public/js/components.js | 17 ++++++++++++--
3 files changed, 58 insertions(+), 29 deletions(-)
(limited to 'public/js')
diff --git a/public/css/chat.css b/public/css/chat.css
index 1092a39..d654679 100644
--- a/public/css/chat.css
+++ b/public/css/chat.css
@@ -53,7 +53,8 @@
align-items: center;
font-weight: 1000;
font-size: 1.5rem;
-}
+
+right: 0;}
.room-name {
display: flex;
@@ -71,6 +72,7 @@
}
.roomDisplayCenter {
+ position: relative;
display: flex;
width: calc(100% - 25em);
height: 100%;
@@ -170,3 +172,9 @@
.message-content {
flex-grow: 0;
}
+
+.loadMessages {
+ position: absolute;
+ right: 0;
+ cursor: pointer;
+}
diff --git a/public/js/chat.js b/public/js/chat.js
index 33f21cb..ed92bb6 100644
--- a/public/js/chat.js
+++ b/public/js/chat.js
@@ -70,12 +70,12 @@ const data = {
rooms: {},
}
-async function loadRoomPage(room_id) {
- let room = data.rooms[room_id]
+async function loadRoomPage(room) {
+
let request = (await chatload (
room.newest_msg,
room.page,
- room_id
+ room.room_id
))
if (request.json == undefined) {
@@ -83,36 +83,38 @@ async function loadRoomPage(room_id) {
return
}
+ let messages = room.display.getElementsByClassName('messages')[0]
for (const msg of request.json) {
room.messages.push(msg)
+ messages.appendChild(await parseMessageImpl(msg))
}
room.page++
+
+ return request.json.length > 0
}
async function loadRoom(room_id) {
let room = data.rooms[room_id]
- let request = (await loadusers(room.users))
- if (request.status != 200) {
- location.href = '/login'
- } else {
- for (const user of request.json) {
- data.users[user.user_id] = user
+ let batch = []
+ for (const user_id of room.users) {
+ if (data.users[user_id]) continue
+ batch.push(user_id)
+ }
+
+ if (batch.length > 1) {
+ let request = (await loadusers(batch))
+ if (request.status != 200) {
+ location.href = '/login'
+ } else {
+ for (const user of request.json) {
+ data.users[user.user_id] = user
+ }
}
}
- room.page = 0
- room.messages = []
- if (room.newest_msg == undefined || room.newest_msg < 0)
- room.newest_msg = Number.MAX_SAFE_INTEGER
- await loadRoomPage(room_id)
- room.newest_msg = Math.max(
- ...room.messages.map(m => m.message_id)
- )
- room.page = 0
-
- room.display = createRoomDisplay(room)
+ room.display = createRoomDisplay(room, loadRoomPage)
let displays = document.getElementById("center")
displays.appendChild(room.display)
@@ -125,15 +127,20 @@ async function loadRoom(room_id) {
button.classList.add('current')
}
+ room.page = 0
+ room.messages = []
+ if (room.newest_msg == undefined || room.newest_msg < 0)
+ room.newest_msg = Number.MAX_SAFE_INTEGER
+ await loadRoomPage(room)
+ room.newest_msg = Math.min(
+ ...room.messages.map(m => m.message_id)
+ )
+ room.page = 0
+
let sidebar = document.getElementById("sidebar")
sidebar.appendChild(button)
room.button = button
- let messages = room.display.getElementsByClassName('messages')[0]
- for (const message of room.messages) {
- messages.appendChild(await parseMessageImpl(message))
- }
-
if (!room.people) room.people = room.people = {}
let people = room.display.getElementsByClassName("roomDisplayPeople")[0]
@@ -186,6 +193,7 @@ async function onMessage(message) {
if (event.user_id == data.self.user_id) {
room.display.remove()
room.button.remove()
+ delete data.rooms[event.room_id]
}
break;
}
@@ -202,13 +210,13 @@ async function onMessage(message) {
async function init() {
let request = (await loadself());
- data.self = request.json
if (request.json == undefined) {
location.href = '/login'
return
}
+ data.self = request.json
data.users[data.self.user_id] = data.self
render()
diff --git a/public/js/components.js b/public/js/components.js
index 0bf0ed1..88895c1 100644
--- a/public/js/components.js
+++ b/public/js/components.js
@@ -1,4 +1,4 @@
-import { div, a, pfp, span, i, parse, parseDate, p, form, input, svg, path, parseMonth, g } from './main.js'
+import { div, a, pfp, span, i, parse, parseDate, p, form, input, svg, path, parseMonth, g, button } from './main.js'
import { postlike, postcomment, loadcommentspage, chatsend, chatadd, chatleave } from './api.js';
window.parse = parse;
@@ -370,10 +370,23 @@ export function createSingleLineInput(attributes, onSubmit) {
return area
}
-export function createRoomDisplay(room) {
+export function createRoomDisplay(room, loadMessageCallback) {
+ let buttonEl = button({
+ class: 'loadMessages input',
+ style: 'flex-grow: 0',
+ onclick: async () => {
+ if (!await loadMessageCallback(room)) {
+ buttonEl.remove()
+ }
+ }
+ },
+ parse('Load Previous')
+ )
+
return (
div({class: 'roomDisplay'},
div({class: 'roomDisplayCenter'},
+ buttonEl,
div({class: 'messages'}),
createMultiLineInput(
{
--
cgit v1.2.3-freya