From f142e6950d5621a1200811fe99a7f150385aec47 Mon Sep 17 00:00:00 2001 From: mar77i Date: Wed, 6 Nov 2024 14:32:50 +0100 Subject: [PATCH] display the ts time on messages. --- chat/serializers.py | 2 +- chat/static/chat/chat.css | 9 ++- chat/static/chat/chat.js | 142 +++++++++++++++++++-------------- chat/static/chat/underscore.js | 134 +++++++++++++++++++++++++++++++ chat/templates/chat/chat.html | 1 - todo.txt | 3 +- 6 files changed, 228 insertions(+), 63 deletions(-) diff --git a/chat/serializers.py b/chat/serializers.py index f542b31..a843b38 100644 --- a/chat/serializers.py +++ b/chat/serializers.py @@ -34,7 +34,7 @@ class ModelSerializer: field = self.model._meta.get_field(field_name) value = getattr(instance, field_name) if isinstance(field, DateTimeField): - value = value.isoformat(" ") + value = value.isoformat(timespec="milliseconds") elif isinstance(field, (ManyToManyField, ManyToManyRel)): value = [v.pk for v in value.all()] return value diff --git a/chat/static/chat/chat.css b/chat/static/chat/chat.css index 966a8d5..70de80d 100644 --- a/chat/static/chat/chat.css +++ b/chat/static/chat/chat.css @@ -80,6 +80,13 @@ li { } .messages > p { - padding: .25em; + padding: .5em; border-bottom: 1px solid #444; } + +.date_span { + padding-right: 1em; + display: inline-block; + text-align: right; + width: 4em; +} diff --git a/chat/static/chat/chat.js b/chat/static/chat/chat.js index a7a7a55..a119822 100644 --- a/chat/static/chat/chat.js +++ b/chat/static/chat/chat.js @@ -16,10 +16,12 @@ url, { "abort": function (msg) { - messages_manager.add_msg(null, "xhr_abort", msg, null); + console.log(msg); + new Message({"text": msg, "user": "xhr_abort"}, null); }, "error": function (msg) { - messages_manager.add_msg(null, "xhr_error", msg, null); + console.log(msg); + new Message({"text": msg, "user": "xhr_error"}, null); }, "load": function () { var response_data; @@ -87,7 +89,7 @@ clear_channel(); if (this === window) { current_channel = null; - messages_manager.add_msg(null, null, "No channel selected!", null); + new Message({"text": "No channel selected!"}, null); return; } current_channel = this; @@ -283,37 +285,72 @@ this.items_reload(); } - 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; + function Message(init_msg, next) { + if (typeof init_msg.id === "number") { + this.id = init_msg.id; + messages_manager.msgs_by_id[this.id.toString(36)] = this; + } else { + this.id = null; + } + this.msg = init_msg; + this.p = make_tag_with_data_id("p", init_msg.id); + _.dgEBCN0("messages").insertBefore(this.p, next || null); + this.remove = function () { + this.p.parentElement.removeChild(this.p); + if (this.id !== null) { + delete messages_manager.msgs_by_id[this.id.toString(36)]; + } + }; + function get_date_span(ts) { + var d = new Date(ts); + var date_span = document.createElement("span"); + date_span.appendChild(_.dcTN(_.strftime(d, "%H:%m"))); + date_span.setAttribute( + "title", + _.strftime(d, "%Y-%m-%d %H:%M:%S.%f") + ); + date_span.setAttribute("class", "date_span"); + return date_span; + } + this.update = function (msg) { + msg = msg || this.msg; + if ( + !msg.hasOwnProperty("user") + && current_channel !== null + && current_channel.hasOwnProperty("msg_user_field") + && msg.hasOwnProperty(current_channel.msg_user_field) + ) { + msg.user = msg[current_channel.msg_user_field]; + delete msg[current_channel.msg_user_field]; + } _.clear_children(this.p); - if (this.user) { - if (typeof this.user === "number") { - user = private_manager.items_per_id[this.user].username; + if (msg.ts) { + this.p.appendChild(get_date_span(msg.ts)); + this.p.appendChild(_.dcTN(" ")); + } + if (msg.user) { + if (typeof msg.user === "number") { + this.p.appendChild( + _.dcTN( + private_manager.items_per_id[ + msg.user + ].username + ) + ); } else { - user = this.user; + this.p.appendChild(_.dcTN(msg.user)); } - this.p.appendChild(_.dcTN(user)); this.p.appendChild(_.dcTN(": ")); } - this.p.appendChild(_.dcTN(this.text)); + this.p.appendChild(_.dcTN(msg.text)); + this.msg = msg; }; - this.update(user, text); + this.update(); } 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; }; @@ -321,13 +358,6 @@ _.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; @@ -355,13 +385,8 @@ }; 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 - ); + _.foreach_arr(msgs.result, function (msg) { + new Message(msg, next); }); }; function no_bottom_callback() { @@ -392,30 +417,24 @@ var ws_schemas = {"http:": "ws:", "https:": "wss:"}; var ws_receive_msg = { DELETE: function (data) { - messages_manager.remove(data.obj.id); + var msg = messages_manager.get_by(data.obj.id); + if (msg) { + msg.remove(); + } }, 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(); - } + ws_receive_msg.insert_callback ); }, TRUNCATE: function () { messages_manager.clear(); }, UPDATE: function (data) { - var msg = messages_manager.by_id(data.obj.id); - if (!msg) { + if (!messages_manager.by_id(data.obj.id)) { return; } messages_manager.stick_to_bottom(); @@ -423,17 +442,20 @@ "get", ( current_channel.msg_url - + msg.id.toString() + + data.obj.id.toString() + "/" ), - function (data) { - msg.update( - data[current_channel.msg_user_field], - data.text - ); - messages_manager.bottom_callback(); - } + ws_receive_msg.update_callback ); + }, + insert_callback: function (item) { + new Message(item, null); + messages_manager.bottom_callback(); + }, + update_callback: function (data) { + console.log(messages_manager.by_id(data.id)); + messages_manager.by_id(data.id).update(data); + messages_manager.bottom_callback(); } }; var ws = new WebSocket( @@ -470,12 +492,13 @@ ws.addEventListener("message", ws_receive); ws.addEventListener("close", function (event) { event = event || window.event; - messages_manager.add_msg(null, "ws_close", "Closed", null); + new Message({"text": "Closed", "user": "ws_close"}, null); // display reconnect button here }); ws.addEventListener("error", function (event) { event = event || window.event; - messages_manager.add_msg(null, "ws_error", event, null); + console.log(event); + new Message({"text": event, "user": "ws_error"}, null); // display reconnect button here }); } @@ -500,6 +523,7 @@ function () { ta.value = ""; ta.disabled = false; + ta.focus(); }, JSON.stringify(data) ); diff --git a/chat/static/chat/underscore.js b/chat/static/chat/underscore.js index 3c074aa..88ef749 100644 --- a/chat/static/chat/underscore.js +++ b/chat/static/chat/underscore.js @@ -40,6 +40,139 @@ ); } + function left_pad(s, length, fill) { + var i; + if (typeof s === "number") { + s = s.toString(); + } + for (i = 0; s.length < length; i += 1) { + s = fill[i % fill.length] + s; + } + return s; + } + + function intl_dateformat(date, locale, obj) { + return new Intl.DateTimeFormat(locale || "en-US", obj).format(date); + } + + function get_timezone_offset(date) { + var tzo = date.getTimezoneOffset(); + var sign; + if (tzo < 0) { + sign = "-"; + tzo *= -1; + } else { + sign = "+"; + } + return [ + sign, + left_pad(Math.floor(tzo / 60), 2, "0"), + left_pad(Math.floor(tzo % 60), 2, "0") + ].join(""); + } + + function get_timezone(date, locale) { + var s = intl_dateformat(date, locale, {"timeZoneName": "short"}); + var pos = s.indexOf(","); + if (pos !== -1) { + s = s.substring(pos + 1).trim(); + } + return s; + } + + function strftime(date, s, locale) { + var pos = s.indexOf("%"); + var out = []; + while (true) { + pos = s.indexOf("%"); + if (pos === -1) { + break; + } + out.push(s.substring(0, pos)); + switch (s[pos + 1]) { + case "a": + out.push(intl_dateformat(date, locale, {"weekday": "short"})); + break; + case "A": + out.push(intl_dateformat(date, locale, {"weekday": "long"})); + break; + case "b": + out.push(intl_dateformat(date, locale, {"month": "short"})); + break; + case "B": + out.push(intl_dateformat(date, locale, {"month": "long"})); + break; + case "c": + out.push(date.toLocaleString(date)); + break; + case "d": + out.push(left_pad(date.getDate(), 2, "0")); + break; + case "f": + out.push(left_pad(date.getMilliseconds(), 3, "0")); + break; + case "H": + out.push(left_pad(date.getHours(), 2, "0")); + break; + case "I": + if (date.getHours() % 12 === 0) { + out.push("12"); + } else { + out.push(left_pad(date.getHours() % 12, 2, "0")); + } + break; + // missing: "j" + case "m": + out.push(left_pad(date.getMonth() + 1, 2, "0")); + break; + case "M": + out.push(left_pad(date.getMinutes(), 2, "0")); + break; + case "p": + if (date.getHours() < 12) { + out.push("AM"); + } else { + out.push("PM"); + } + break; + case "S": + out.push(left_pad(date.getSeconds(), 2, "0")); + break; + // missing: "U" + case "w": + out.push(date.getDay().toString()); + break; + // missing: "W" + case "x": + out.push(date.toLocaleDateString()); + break; + case "X": + out.push(date.toLocaleTimeString()); + break; + case "y": + out.push(left_pad(date.getFullYear() % 100, 2, "0")); + break; + case "Y": + out.push(left_pad(date.getFullYear(), 4, "0")); + break; + case "z": + out.push(get_timezone_offset(date)); + break; + case "Z": + out.push(get_timezone(date, locale)); + break; + case "%": + out.push(s[pos + 1]); + break; + default: + out.push(s.substring(pos, pos + 2)); + } + s = s.substring(pos + 2); + } + out.push(s); + return out.join(""); + } + function xhr(method, url, callbacks, data) { var request = new XMLHttpRequest(); if (!callbacks.hasOwnProperty("error")) { @@ -85,6 +218,7 @@ }, "foreach_arr": foreach_arr, "foreach_obj": foreach_obj, + "strftime": strftime, "xhr": xhr }; }()); diff --git a/chat/templates/chat/chat.html b/chat/templates/chat/chat.html index d830515..251beca 100644 --- a/chat/templates/chat/chat.html +++ b/chat/templates/chat/chat.html @@ -2,7 +2,6 @@ {% load static %} {% block head %} - diff --git a/todo.txt b/todo.txt index 9be9c0a..a822541 100644 --- a/todo.txt +++ b/todo.txt @@ -1,9 +1,10 @@ [ ] display a reconnect button when ws is disconnected - if reconnect is successful, reload all loaded messages, channel list and user list -[ ] show message timestamps and message edit/delete menus +[ ] show message edit/delete menus - privileged users can edit / delete any message [ ] edit/delete existing messages [ ] new message(s) indicators in left panel + [ ] update address bar per channel #channel: / #user: for now? [ ] channel management: channel admin [ ] write email to user -- 2.47.0