From 73712c5d5a12f03d3b5ac5623f63e5e9eb6dafa9 Mon Sep 17 00:00:00 2001 From: mar77i Date: Tue, 5 Nov 2024 17:52:57 +0100 Subject: [PATCH] 'load more' button and add some structure to the js --- chat/static/chat/chat.css | 21 +- chat/static/chat/chat.html | 44 ---- chat/static/chat/chat.js | 384 ++++++++++++++++++++-------------- chat/templates/chat/base.html | 5 +- chat/templates/chat/chat.html | 22 +- chat/urls.py | 6 +- chat/views.py | 40 +--- chat/websocket.py | 4 + todo.txt | 18 +- 9 files changed, 286 insertions(+), 258 deletions(-) delete mode 100644 chat/static/chat/chat.html diff --git a/chat/static/chat/chat.css b/chat/static/chat/chat.css index b4dabaf..966a8d5 100644 --- a/chat/static/chat/chat.css +++ b/chat/static/chat/chat.css @@ -48,24 +48,23 @@ nav > div { border-bottom: 1px solid; } -.messages_header > ul { +.messages_header ul { position: fixed; width: calc(100% - 10em); border: 1px solid; } -.messages_header > button:first-of-type { - position: fixed; - top: .5em; - right: 1em; +.messages_header h1, .messages_header button { + margin: .5em 1em; } -.messages { - overflow-y: auto; +.messages_header h1 { + display: inline-block; + min-width: 10%; } -h1 { - margin: .5em 1em; +.logout_button { + float: right; } ul { @@ -76,6 +75,10 @@ li { padding: .25em; } +.messages { + overflow-y: auto; +} + .messages > p { padding: .25em; border-bottom: 1px solid #444; diff --git a/chat/static/chat/chat.html b/chat/static/chat/chat.html deleted file mode 100644 index e068dc4..0000000 --- a/chat/static/chat/chat.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - Chat - - - - - - - -
-
-

-
    -
- -
-
-
- -
- - diff --git a/chat/static/chat/chat.js b/chat/static/chat/chat.js index d7f93ac..a7a7a55 100644 --- a/chat/static/chat/chat.js +++ b/chat/static/chat/chat.js @@ -2,14 +2,7 @@ var current_channel = null; var private_manager = null; var channel_manager = null; - var bottom_callback; - var ws_receive_msg; - var ws_schemas = {"http:": "ws:", "https:": "wss:"}; - - function no_bottom_callback() { - bottom_callback = no_bottom_callback; - } - bottom_callback = no_bottom_callback; + var messages_manager = null; function maybe_redirect(data) { if (data.hasOwnProperty("Location")) { @@ -23,10 +16,10 @@ url, { "abort": function (msg) { - add_msg(null, "xhr_abort", msg); + messages_manager.add_msg(null, "xhr_abort", msg, null); }, "error": function (msg) { - add_msg(null, "xhr_error", msg); + messages_manager.add_msg(null, "xhr_error", msg, null); }, "load": function () { var response_data; @@ -59,20 +52,9 @@ return get_data_id(_.dgEBCN0("user").children[0]); } - function set_msg(p, sender, msg) { - _.clear_children(p); - if (typeof sender === "number") { - sender = private_manager.items_per_id[sender].username; - } - p.appendChild(_.dcTN(sender)); - p.appendChild(_.dcTN(": ")); - p.appendChild(_.dcTN(msg)); - return p; - } - function make_tag_with_data_id(tag_name, data_id) { var tag = document.createElement(tag_name); - if (data_id !== null) { + if (data_id) { tag.setAttribute("data-id", data_id); } return tag; @@ -85,29 +67,6 @@ return a; } - function find_existing_msg(msg_id, callback) { - var messages = _.dgEBCN0("messages"); - return _.foreach_arr( - messages.children, - function (item) { - if (get_data_id(item) === msg_id) { - callback(item); - return true; - } - } - ); - } - - function add_msg(id, user, text) { - _.dgEBCN0("messages").appendChild( - set_msg(make_tag_with_data_id("p", id), user, text) - ); - } - - function add_msg_callback(item) { - add_msg(item.id, item[current_channel.msg_user_field], item.text); - } - function clear_messages_header() { var messages_header = _.dgEBCN0("messages_header"); _.clear_children(messages_header.children[0]); @@ -116,17 +75,19 @@ } function clear_channel() { - _.clear_children(_.dgEBCN0("messages")); + var messages_footer = _.dgEBCN0("messages_footer"); + messages_manager.clear(); clear_messages_header(); _.dgEBCN0("messages_header").children[0].appendChild(_.dcTN("Chat")); - _.dgEBCN0("messages_footer").children[0].disabled = true; + messages_footer.children[0].value = ""; + messages_footer.children[0].disabled = true; } function set_channel(field_value) { clear_channel(); if (this === window) { current_channel = null; - _.dgEBCN0("messages").appendChild(_.dcTN("No channel selected!")); + messages_manager.add_msg(null, null, "No channel selected!", null); return; } current_channel = this; @@ -141,12 +102,8 @@ + "=" + field_value.toString() ), function (msgs) { - var messages = _.dgEBCN0("messages"); - // add a "load previous" button here - _.foreach_arr(msgs.result, add_msg_callback); - messages.scrollTop = ( - messages.scrollHeight - messages.clientHeight - ); + messages_manager.load_msgs_callback(msgs); + messages_manager.scroll_to_bottom(); } ); } @@ -163,14 +120,14 @@ } function items_reload_callback(item) { - var t = this; + var outer = this; var li; var a; this.items_per_id[item.id] = item; li = make_tag_with_data_id("li", item.id); a = make_callback_a( function () { - t.set_channel(item.id); + outer.set_channel(item.id); } ); a.appendChild(this.item_to_tn(item)); @@ -179,7 +136,7 @@ } function PrivateMessageManager() { - var t = this; + var outer = this; this.msg_url = "/api/privatemessage/"; this.msg_table = "chat_privatemessage"; this.msg_user_field = "sender_id"; @@ -208,7 +165,7 @@ data.result, function (item) { var p; - items_reload_callback.call(t, item); + items_reload_callback.call(outer, item); if (!item.is_authenticated) { return; } @@ -218,7 +175,7 @@ _.dgEBCN0("user").appendChild(p); } ); - update_channel_header.call(t); + update_channel_header.call(outer); } ); }; @@ -245,7 +202,7 @@ } function ChannelManager() { - var t = this; + var outer = this; this.msg_url = "/api/channelmessage/"; this.msg_table = "chat_channelmessage"; this.msg_user_field = "user_id"; @@ -295,7 +252,6 @@ }); messages_header.appendChild(btn); messages_header.children[1].style.display = "none"; - // messages_header.style.display = "block"; }; this.items_url = "/api/channel/"; this.items_classname = "channels"; @@ -307,8 +263,11 @@ "get", this.items_url, function (data) { - _.foreach_arr(data.result, items_reload_callback.bind(t)); - update_channel_header.call(t); + _.foreach_arr( + data.result, + items_reload_callback.bind(outer) + ); + update_channel_header.call(outer); } ); }; @@ -324,97 +283,226 @@ this.items_reload(); } - function stick_to_bottom(element) { - if (element.clientHeight + element.scrollTop === element.scrollHeight) { - bottom_callback = function () { - element.scrollTop = element.scrollHeight - element.clientHeight; - no_bottom_callback(); - }; - } else { - no_bottom_callback(); - } + function Message(id, user, text) { + this.id = id; + this.p = make_tag_with_data_id("p", id); + this.update = function (user, text) { + this.user = user || this.user; + this.text = text || this.text; + _.clear_children(this.p); + if (this.user) { + if (typeof this.user === "number") { + user = private_manager.items_per_id[this.user].username; + } else { + user = this.user; + } + this.p.appendChild(_.dcTN(user)); + this.p.appendChild(_.dcTN(": ")); + } + this.p.appendChild(_.dcTN(this.text)); + }; + this.update(user, text); } - ws_receive_msg = { - DELETE: function (data) { - find_existing_msg( - data.obj.id, - function (tag) { - tag.parentElement.removeChild(tag); - } - ); - }, - INSERT: function (data) { - stick_to_bottom(_.dgEBCN0("messages")); - xhr( - "get", - current_channel.msg_url + data.obj.id + "/", - function (item) { - add_msg_callback(item); - bottom_callback(); - } - ); - }, - TRUNCATE: function () { - _.clear_children(_.dgEBCN0("messages")); - }, - UPDATE: function (data) { - var messages = _.dgEBCN0("messages"); - stick_to_bottom(messages); - find_existing_msg( - data.obj.id, - function (tag) { - stick_to_bottom(messages); - xhr( - "get", - ( - current_channel.msg_url - + get_data_id(tag).toString() - + "/" - ), - function (msg) { - set_msg( - tag, - msg[current_channel.msg_user_field], - msg.text - ); - bottom_callback(); - } - ); - } - ); + function MessagesManager() { + var messages = _.dgEBCN0("messages"); + this.msgs_by_id = {}; + this.add_msg = function (id, user, text, next) { + var msg = new Message(id, user, text); + messages.insertBefore(msg.p, next || null); + if (id) { + this.msgs_by_id[id.toString(36)] = msg; + } + }; + this.by_id = function (msg_id) { + return this.msgs_by_id[msg_id.toString(36)] || null; + }; + this.clear = function () { + _.clear_attributes(this.msgs_by_id); + _.clear_children(messages); + }; + this.remove = function (id) { + var msg = this.by_id(id); + if (msg) { + msg.p.parentElement.removeChild(msg.p); + delete this.msgs_by_id[msg.id.toString(36)]; + } + }; + this.previous_button = function (url) { + var p; + var button; + if (!url) { + return; + } + p = document.createElement("p"); + button = document.createElement("button"); + button.appendChild(_.dcTN("Load more")); + button.addEventListener("click", function () { + xhr( + "get", + url, + function (msgs) { + p.parentElement.removeChild(p); + messages_manager.load_msgs_callback( + msgs, + messages.firstElementChild + ); + } + ); + }); + messages.insertBefore(p, messages.firstElementChild); + p.appendChild(button); + }; + this.load_msgs_callback = function (msgs, next) { + this.previous_button(msgs.previous); + _.foreach_arr(msgs.result, function (item) { + messages_manager.add_msg( + item.id, + item[current_channel.msg_user_field], + item.text, + next + ); + }); + }; + function no_bottom_callback() { + messages_manager.bottom_callback = no_bottom_callback; } - }; + this.bottom_callback = no_bottom_callback; + this.stick_to_bottom = function () { + if ( + messages.clientHeight + messages.scrollTop + === messages.scrollHeight + ) { + messages_manager.bottom_callback = function () { + messages_manager.scroll_to_bottom(); + no_bottom_callback(); + }; + } else { + no_bottom_callback(); + } + }; + this.scroll_to_bottom = function () { + messages.scrollTop = ( + messages.scrollHeight - messages.clientHeight + ); + }; + } - function ws_receive(msg) { - var data = JSON.parse(msg.data); - maybe_redirect(data); - // here we could add indication for the pm or channel being modified - if ( - data.table === "chat_channel" || data.table === "chat_channeluser" - ) { - channel_manager.items_reload(); - } else if (data.table === "chat_user") { - private_manager.items_reload(); - } - if (current_channel !== null && current_channel.match_channel(data)) { - ws_receive_msg[data.op](data); + function WebSocketManager() { + var ws_schemas = {"http:": "ws:", "https:": "wss:"}; + var ws_receive_msg = { + DELETE: function (data) { + messages_manager.remove(data.obj.id); + }, + INSERT: function (data) { + messages_manager.stick_to_bottom(); + xhr( + "get", + current_channel.msg_url + data.obj.id + "/", + function (item) { + messages_manager.add_msg( + item.id, + item[current_channel.msg_user_field], + item.text, + null + ); + messages_manager.bottom_callback(); + } + ); + }, + TRUNCATE: function () { + messages_manager.clear(); + }, + UPDATE: function (data) { + var msg = messages_manager.by_id(data.obj.id); + if (!msg) { + return; + } + messages_manager.stick_to_bottom(); + xhr( + "get", + ( + current_channel.msg_url + + msg.id.toString() + + "/" + ), + function (data) { + msg.update( + data[current_channel.msg_user_field], + data.text + ); + messages_manager.bottom_callback(); + } + ); + } + }; + var ws = new WebSocket( + ws_schemas[window.location.protocol] + + "//" + + window.location.host + + "/" + ); + function ws_receive(msg) { + var data; + try { + data = JSON.parse(msg.data); + } catch { + data = msg.data; + } + maybe_redirect(data); + if (typeof data === "string") { + console.log(data); + return; + } + // here we could add indication for the pm or channel being modified + if ( + data.table === "chat_channel" + || data.table === "chat_channeluser" + ) { + channel_manager.items_reload(); + } else if (data.table === "chat_user") { + private_manager.items_reload(); + } + if (current_channel && current_channel.match_channel(data)) { + ws_receive_msg[data.op](data); + } } + ws.addEventListener("message", ws_receive); + ws.addEventListener("close", function (event) { + event = event || window.event; + messages_manager.add_msg(null, "ws_close", "Closed", null); + // display reconnect button here + }); + ws.addEventListener("error", function (event) { + event = event || window.event; + messages_manager.add_msg(null, "ws_error", event, null); + // display reconnect button here + }); } function send_msg() { var ta = _.dgEBCN0("messages_footer").children[0]; - var data = {"text": ta.value}; + var data; + if (!current_channel || ta.value === "") { + return; + } + data = {"text": ta.value}; _.foreach_obj( current_channel.msg_data, function (name, value) { data[name] = value; } ); - if (current_channel) { - xhr("post", current_channel.msg_url, null, JSON.stringify(data)); - ta.value = ""; - } + ta.disabled = true; + xhr( + "post", + current_channel.msg_url, + function () { + ta.value = ""; + ta.disabled = false; + }, + JSON.stringify(data) + ); } function input_onkeydown(event) { @@ -429,15 +517,11 @@ } } - function logout() { - window.location.href = "/logout/"; - } - window.addEventListener( "load", function (event) { var messages_footer = _.dgEBCN0("messages_footer"); - var ws; + messages_manager = new MessagesManager(); set_channel.call(window); event = event || window.event; if (event.target.readyState !== "complete") { @@ -445,23 +529,13 @@ } private_manager = new PrivateMessageManager(); channel_manager = new ChannelManager(); - ws = new WebSocket( - ws_schemas[window.location.protocol] - + "//" - + window.location.host - + "/" - ); - ws.addEventListener("message", ws_receive); + new WebSocketManager(); messages_footer.children[0].addEventListener( "keydown", input_onkeydown ); messages_footer.children[0].value = ""; messages_footer.children[1].addEventListener("click", send_msg); - _.dgEBCN0("messages_header").children[2].addEventListener( - "click", - logout - ); } ); }()); diff --git a/chat/templates/chat/base.html b/chat/templates/chat/base.html index 94075d3..152655e 100644 --- a/chat/templates/chat/base.html +++ b/chat/templates/chat/base.html @@ -1,10 +1,11 @@ -{% load static %} +{% load static %} + - {% block title %}Title{% endblock title %} + Chat — {{ view.title }} {% block head %}{% endblock head %} diff --git a/chat/templates/chat/chat.html b/chat/templates/chat/chat.html index 7c601fa..d830515 100644 --- a/chat/templates/chat/chat.html +++ b/chat/templates/chat/chat.html @@ -2,12 +2,16 @@ {% load static %} {% block head %} - + + + + {% endblock head %} {% block header %}