diff options
author | Tyler Murphy <=> | 2023-08-21 23:19:53 -0400 |
---|---|---|
committer | Tyler Murphy <=> | 2023-08-21 23:19:53 -0400 |
commit | c4c26f42b6b06dbc75578314a483db66bf7ddb4c (patch) | |
tree | fba137f6fe2e8470b0abf6d3c139fa9a4fc9b4e8 /public/js | |
parent | websocket (diff) | |
download | xssbook-c4c26f42b6b06dbc75578314a483db66bf7ddb4c.tar.gz xssbook-c4c26f42b6b06dbc75578314a483db66bf7ddb4c.tar.bz2 xssbook-c4c26f42b6b06dbc75578314a483db66bf7ddb4c.zip |
dms
Diffstat (limited to 'public/js')
-rw-r--r-- | public/js/api.js | 26 | ||||
-rw-r--r-- | public/js/chat.js | 207 | ||||
-rw-r--r-- | public/js/components.js | 163 | ||||
-rw-r--r-- | public/js/main.js | 6 |
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) { |