summaryrefslogtreecommitdiff
path: root/public/js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js')
-rw-r--r--public/js/api.js26
-rw-r--r--public/js/chat.js207
-rw-r--r--public/js/components.js163
-rw-r--r--public/js/main.js6
4 files changed, 389 insertions, 13 deletions
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", "<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)
+ )
+ )
+ )
+ )
+}
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) {