dms
This commit is contained in:
parent
2adbe0d999
commit
c4c26f42b6
12 changed files with 598 additions and 38 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
<link rel="stylesheet" href="/css/header.css">
|
<link rel="stylesheet" href="/css/header.css">
|
||||||
|
<link rel="stylesheet" href="/css/people.css">
|
||||||
<link rel="stylesheet" href="/css/chat.css">
|
<link rel="stylesheet" href="/css/chat.css">
|
||||||
|
|
||||||
<script type="module" src="/js/chat.js"></script>
|
<script type="module" src="/js/chat.js"></script>
|
||||||
|
|
172
public/css/chat.css
Normal file
172
public/css/chat.css
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -14,6 +14,10 @@ body {
|
||||||
--popup: #ffffffcc;
|
--popup: #ffffffcc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -130,7 +134,7 @@ footer {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input, .input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-family: sfpro;
|
font-family: sfpro;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
|
|
|
@ -142,3 +142,27 @@ export const updateavatar = async (file) => {
|
||||||
export const updatebanner = async (file) => {
|
export const updatebanner = async (file) => {
|
||||||
return await fileRequest('/users/banner', file, 'PUT')
|
return await fileRequest('/users/banner', file, 'PUT')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,202 @@
|
||||||
import { div, pfp, p, parse, button, body, a, textarea, span, crawl } from './main.js'
|
import { body, div, span, p, parse } from './main.js'
|
||||||
import { loadself, loadpostspage, createpost, loadusers } from './api.js'
|
import { loadself, chatlist, chatload, loadusers, chatcreate } from './api.js'
|
||||||
import { parsePost, header } from './components.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 =
|
let new_body =
|
||||||
body({},
|
body({},
|
||||||
...header(false, false, true, data.self.user_id),
|
...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)
|
document.body.replaceWith(new_body)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
self: {},
|
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() {
|
async function init() {
|
||||||
|
@ -31,6 +212,20 @@ async function init() {
|
||||||
data.users[data.self.user_id] = data.self
|
data.users[data.self.user_id] = data.self
|
||||||
|
|
||||||
render()
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
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 } from './main.js'
|
||||||
import { postlike, postcomment, loadcommentspage } from './api.js';
|
import { postlike, postcomment, loadcommentspage, chatsend, chatadd, chatleave } from './api.js';
|
||||||
|
|
||||||
window.parse = parse;
|
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", "<br>")
|
||||||
|
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", "<br>")
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -103,10 +103,6 @@ export function parse(html) {
|
||||||
return document.createRange().createContextualFragment(html);
|
return document.createRange().createContextualFragment(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pfpl(id) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pfp(id) {
|
export function pfp(id) {
|
||||||
return img('pfp', {src: `/image/avatar?user_id=${id}`})
|
return img('pfp', {src: `/image/avatar?user_id=${id}`})
|
||||||
}
|
}
|
||||||
|
@ -133,7 +129,7 @@ export function parseMonth(month) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseDate(date) {
|
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) {
|
export function crawl(key, object) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use tokio::sync::{Mutex, mpsc::{Sender, self}};
|
||||||
use crate::{
|
use crate::{
|
||||||
public::docs::{EndpointDocumentation, EndpointMethod},
|
public::docs::{EndpointDocumentation, EndpointMethod},
|
||||||
types::{
|
types::{
|
||||||
extract::{AuthorizedUser, Check, CheckResult, Database, Json},
|
extract::{AuthorizedUser, Check, CheckResult, Database, Json, Log},
|
||||||
http::ResponseCode,
|
http::ResponseCode,
|
||||||
chat::{ChatRoom, ChatEvent}, user::User,
|
chat::{ChatRoom, ChatEvent}, user::User,
|
||||||
},
|
},
|
||||||
|
@ -75,7 +75,8 @@ pub const CHAT_LIST: EndpointDocumentation = EndpointDocumentation {
|
||||||
|
|
||||||
async fn list (
|
async fn list (
|
||||||
AuthorizedUser(user): AuthorizedUser,
|
AuthorizedUser(user): AuthorizedUser,
|
||||||
Database(db): Database
|
Database(db): Database,
|
||||||
|
_: Log
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let Ok(rooms) = ChatRoom::from_user_id(&db, user.user_id) else {
|
let Ok(rooms) = ChatRoom::from_user_id(&db, user.user_id) else {
|
||||||
return ResponseCode::InternalServerError.text("Failed to retrieve rooms")
|
return ResponseCode::InternalServerError.text("Failed to retrieve rooms")
|
||||||
|
@ -137,7 +138,7 @@ async fn create (
|
||||||
for user in &room.users {
|
for user in &room.users {
|
||||||
send_event(ChatEvent::Add {
|
send_event(ChatEvent::Add {
|
||||||
user_id: *user,
|
user_id: *user,
|
||||||
room_id: room.room_id
|
room: room.clone()
|
||||||
}, &room).await;
|
}, &room).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,10 +192,14 @@ async fn add (
|
||||||
return ResponseCode::BadRequest.text("User does not exist")
|
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")
|
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 {
|
let Ok(success) = room.add_user(&db, to_add.user_id) else {
|
||||||
return ResponseCode::InternalServerError.text("Failed to add user")
|
return ResponseCode::InternalServerError.text("Failed to add user")
|
||||||
};
|
};
|
||||||
|
@ -203,9 +208,11 @@ async fn add (
|
||||||
return ResponseCode::BadRequest.text("User is already in the room")
|
return ResponseCode::BadRequest.text("User is already in the room")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
room.users.push(to_add.user_id);
|
||||||
|
|
||||||
send_event(ChatEvent::Add {
|
send_event(ChatEvent::Add {
|
||||||
user_id: to_add.user_id,
|
user_id: to_add.user_id,
|
||||||
room_id: room.room_id
|
room: room.clone()
|
||||||
}, &room).await;
|
}, &room).await;
|
||||||
|
|
||||||
ResponseCode::Success.text("Successfully added user")
|
ResponseCode::Success.text("Successfully added user")
|
||||||
|
|
|
@ -61,9 +61,10 @@ impl Database {
|
||||||
);
|
);
|
||||||
",
|
",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let row = stmt.query_map([user_id], |row| {
|
let row = stmt.query_map([user_id], |row| {
|
||||||
let room_id = row.get(0)?;
|
let room_id: u64 = row.get(0)?;
|
||||||
let name = row.get(1)?;
|
let name: String = row.get(1)?;
|
||||||
|
|
||||||
let mut stmt2 = self.0.prepare(
|
let mut stmt2 = self.0.prepare(
|
||||||
"
|
"
|
||||||
|
@ -72,20 +73,19 @@ impl Database {
|
||||||
"
|
"
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut users = Vec::new();
|
let users = stmt2.query_map([room_id], |row2| {
|
||||||
let _ = stmt2.query_map([room_id], |row2| {
|
Ok(row2.get(0)?)
|
||||||
let user_id = row2.get(0)?;
|
})?.into_iter().flatten().collect();
|
||||||
users.push(user_id);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let room = ChatRoom {
|
let room = ChatRoom {
|
||||||
room_id,
|
room_id,
|
||||||
users,
|
users,
|
||||||
name
|
name
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(room)
|
Ok(room)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(row.into_iter().flatten().collect())
|
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 msg = stmt.query_row((user_id, room_id, date, content), |row| {
|
||||||
let message_id = row.get(0)?;
|
let message_id = row.get(0)?;
|
||||||
let room_id = row.get(1)?;
|
let user_id = row.get(1)?;
|
||||||
let user_id = row.get(2)?;
|
let room_id = row.get(2)?;
|
||||||
let date = row.get(3)?;
|
let date = row.get(3)?;
|
||||||
let content = row.get(4)?;
|
let content = row.get(4)?;
|
||||||
|
|
||||||
|
@ -182,17 +182,17 @@ impl Database {
|
||||||
"
|
"
|
||||||
SELECT * FROM chat_messages
|
SELECT * FROM chat_messages
|
||||||
WHERE room_id = ?
|
WHERE room_id = ?
|
||||||
AND message_id < newest_message
|
AND message_id < ?
|
||||||
ORDER BY message_id ASC
|
ORDER BY message_id DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
OFFSET ?
|
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 message_id = row.get(0)?;
|
||||||
let room_id = row.get(1)?;
|
let user_id = row.get(1)?;
|
||||||
let user_id = row.get(2)?;
|
let room_id = row.get(2)?;
|
||||||
let date = row.get(3)?;
|
let date = row.get(3)?;
|
||||||
let content = row.get(4)?;
|
let content = row.get(4)?;
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ async fn main() {
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(
|
.with(
|
||||||
fmt_layer
|
fmt_layer
|
||||||
.with_filter(LevelFilter::TRACE)
|
.with_filter(LevelFilter::INFO)
|
||||||
.with_filter(filter_fn(|metadata| {
|
.with_filter(filter_fn(|metadata| {
|
||||||
metadata.target().starts_with("xssbook")
|
metadata.target().starts_with("xssbook")
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use crate::{types::http::{ResponseCode, Result}, database::Database};
|
use crate::{types::http::{ResponseCode, Result}, database::Database};
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct ChatRoom {
|
pub struct ChatRoom {
|
||||||
pub room_id: u64,
|
pub room_id: u64,
|
||||||
pub users: Vec<u64>,
|
pub users: Vec<u64>,
|
||||||
|
@ -33,7 +33,7 @@ pub enum ChatEvent {
|
||||||
#[serde(rename = "add")]
|
#[serde(rename = "add")]
|
||||||
Add {
|
Add {
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
room_id: u64
|
room: ChatRoom
|
||||||
},
|
},
|
||||||
|
|
||||||
#[serde(rename = "leave")]
|
#[serde(rename = "leave")]
|
||||||
|
|
Loading…
Reference in a new issue