From e7cd397e0fe98074833e08880d915516718adaf5 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 9 Feb 2025 12:35:38 +0100 Subject: [PATCH] Update. --- src/snek/service/channel_message.py | 28 ++++++++- src/snek/static/app.js | 26 +++++++- src/snek/static/base.css | 15 ++++- src/snek/static/chat-window.js | 3 +- src/snek/static/push.js | 2 +- src/snek/static/upload-button.js | 4 +- src/snek/system/template.py | 3 +- src/snek/templates/app.html | 5 -- src/snek/templates/message.html | 2 +- src/snek/templates/web.html | 97 ++++++++++++++++++++++++++++- src/snek/view/rpc.py | 23 ++----- src/snek/view/web.py | 10 ++- 12 files changed, 183 insertions(+), 35 deletions(-) diff --git a/src/snek/service/channel_message.py b/src/snek/service/channel_message.py index dcc12d5..309e959 100644 --- a/src/snek/service/channel_message.py +++ b/src/snek/service/channel_message.py @@ -22,7 +22,8 @@ class ChannelMessageService(BaseService): context.update(dict( user_uid=user['uid'], username=user['username'], - user_nick=user['nick'] + user_nick=user['nick'], + color=user['color'] )) try: template = self.app.jinja2_env.get_template("message.html") @@ -34,4 +35,27 @@ class ChannelMessageService(BaseService): return model raise Exception(f"Failed to create channel message: {model.errors}.") - + async def to_extended_dict(self, message): + user = await self.services.user.get(uid=message["user_uid"]) + if not user: + print("User not found!", flush=True) + return {} + return { + "uid": message["uid"], + "color": user['color'], + "user_uid": message["user_uid"], + "channel_uid": message["channel_uid"], + "user_nick": user['nick'], + "message": message["message"], + "created_at": message["created_at"], + "html": message['html'], + "username": user['username'] + } + + async def offset(self, channel_uid, offset=0): + results = [] + + async for model in self.query("SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 60 OFFSET :offset",dict(channel_uid=channel_uid, offset=offset)): + results.append(model) + results.sort(key=lambda x: x['created_at']) + return results diff --git a/src/snek/static/app.js b/src/snek/static/app.js index 2f3e610..a0f83c2 100644 --- a/src/snek/static/app.js +++ b/src/snek/static/app.js @@ -289,7 +289,7 @@ class App extends EventHandler { this.audio = new NotificationAudio(500); const me = this this.ws.addEventListener("channel-message", (data) => { - me.emit(data.channel_uid, data); + me.emit("channel-message", data); }); this.rpc.getUser(null).then(user => { @@ -300,7 +300,31 @@ class App extends EventHandler { playSound(index) { this.audio.play(index); } + timeDescription(isoDate) { + const date = new Date(isoDate); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + let timeStr = `${hours}:${minutes}, ${this.timeAgo(new Date(isoDate), Date.now())}`; + return timeStr; + } + timeAgo(date1, date2) { + const diffMs = Math.abs(date2 - date1); + const days = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diffMs % (1000 * 60)) / 1000); + if (days) { + return `${days} ${days > 1 ? 'days' : 'day'} ago`; + } + if (hours) { + return `${hours} ${hours > 1 ? 'hours' : 'hour'} ago`; + } + if (minutes) { + return `${minutes} ${minutes > 1 ? 'minutes' : 'minute'} ago`; + } + return 'just now'; + } async benchMark(times = 100, message = "Benchmark Message") { const promises = []; const me = this; diff --git a/src/snek/static/base.css b/src/snek/static/base.css index e57b944..e069ff7 100644 --- a/src/snek/static/base.css +++ b/src/snek/static/base.css @@ -161,7 +161,7 @@ message-list { } .chat-messages { flex: 1; - + overflow-y: auto; padding: 10px; height: 200px; background: #1a1a1a; @@ -211,9 +211,20 @@ message-list { color: #e6e6e6; word-break: break-word; overflow-wrap: break-word; + display: block; } - +.message-content { + width: 100%; + display:block; + p { + display: block; + width:100%; + } +} +.message-content img { + max-width: 100%; +} .chat-messages .message .message-content .time { font-size: 0.8em; color: #aaa; diff --git a/src/snek/static/chat-window.js b/src/snek/static/chat-window.js index 1e6f9fb..62ed2ee 100644 --- a/src/snek/static/chat-window.js +++ b/src/snek/static/chat-window.js @@ -73,7 +73,8 @@ class ChatWindowElement extends HTMLElement { const me = this; channelElement.addEventListener("message", (message) => { if (me.user.uid !== message.detail.user_uid) app.playSound(0); - message.detail.element.scrollIntoView(); + + message.detail.element.scrollIntoView({"block": "end"}); }); } } diff --git a/src/snek/static/push.js b/src/snek/static/push.js index 806a51e..ca6a8a3 100644 --- a/src/snek/static/push.js +++ b/src/snek/static/push.js @@ -5,7 +5,7 @@ this.onpush = (event) => { }; navigator.serviceWorker - .register("service-worker.js") + .register("/service-worker.js") .then((serviceWorkerRegistration) => { serviceWorkerRegistration.pushManager.subscribe().then( (pushSubscription) => { diff --git a/src/snek/static/upload-button.js b/src/snek/static/upload-button.js index 6e3052f..c399d2b 100644 --- a/src/snek/static/upload-button.js +++ b/src/snek/static/upload-button.js @@ -21,7 +21,7 @@ class UploadButtonElement extends HTMLElement { const files = fileInput.files; const formData = new FormData(); - formData.append('channel_uid', this.chatInput.channelUid); + formData.append('channel_uid', this.channelUid); for (let i = 0; i < files.length; i++) { formData.append('files[]', files[i]); } @@ -105,7 +105,7 @@ class UploadButtonElement extends HTMLElement { `; this.shadowRoot.appendChild(this.container); - + this.channelUid = this.getAttribute('channel'); this.uploadButton = this.container.querySelector('.upload-button'); this.fileInput = this.container.querySelector('.hidden-input'); this.uploadButton.addEventListener('click', () => { diff --git a/src/snek/system/template.py b/src/snek/system/template.py index 89496a0..a543dd7 100644 --- a/src/snek/system/template.py +++ b/src/snek/system/template.py @@ -16,6 +16,7 @@ def set_link_target_blank(text): element.attrs['target'] = '_blank' element.attrs['rel'] = 'noopener noreferrer' element.attrs['referrerpolicy'] = 'no-referrer' + element.attrs['href'] = element.attrs['href'].strip(".") return str(soup) @@ -23,7 +24,7 @@ def set_link_target_blank(text): def linkify_https(text): if not "https://" in text: return text - url_pattern = r'(?()]+' + url_pattern = r'(?()]+(? - - - - - diff --git a/src/snek/templates/message.html b/src/snek/templates/message.html index c271db3..8c43c49 100644 --- a/src/snek/templates/message.html +++ b/src/snek/templates/message.html @@ -1 +1 @@ -{#
{{user_nick[0]}}
{{user_nick}}
#}{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}{#
{{created_at}}
#} +
{{user_nick[0]}}
{{user_nick}}
{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}
diff --git a/src/snek/templates/web.html b/src/snek/templates/web.html index 0225965..4078d92 100644 --- a/src/snek/templates/web.html +++ b/src/snek/templates/web.html @@ -1,4 +1,99 @@ {% extends "app.html" %} {% block main %} - +
+

{{ channel.label.value }}

+
+ + {% for message in messages %} + {% autoescape false %} + {{message.html}} + {% endautoescape %} + + {% endfor %} +
+ +
+ + +
+
+ {% endblock %} diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py index 53e5aab..9aa89f5 100644 --- a/src/snek/view/rpc.py +++ b/src/snek/view/rpc.py @@ -73,22 +73,11 @@ class RPCView(BaseView): async def get_messages(self, channel_uid, offset=0): self._require_login() messages = [] - async for message in self.services.channel_message.query("SELECT * FROM channel_message ORDER BY created_at DESC LIMIT 60"): - user = await self.services.user.get(uid=message["user_uid"]) - if not user: - print("User not found!", flush=True) - continue - messages.insert(0, { - "uid": message["uid"], - "color": user['color'], - "user_uid": message["user_uid"], - "channel_uid": message["channel_uid"], - "user_nick": user['nick'], - "message": message["message"], - "created_at": message["created_at"], - "html": message['html'], - "username": user['username'] - }) + print("Channel uid:", channel_uid, flush=True) + for message in await self.services.channel_message.offset(channel_uid, offset): + print(message, flush=True) + extended_dict = await self.services.channel_message.to_extended_dict(message) + messages.append(extended_dict) return messages async def get_channels(self): @@ -167,4 +156,4 @@ class RPCView(BaseView): pass elif msg.type == web.WSMsgType.CLOSE: pass - return ws \ No newline at end of file + return ws diff --git a/src/snek/view/web.py b/src/snek/view/web.py index 4923183..e02d8b3 100644 --- a/src/snek/view/web.py +++ b/src/snek/view/web.py @@ -70,4 +70,12 @@ class WebView(BaseView): if not user: return web.HTTPNotFound() - return await self.render_template("web.html", {"channel": channel_member,"user": user}) + if self.request.path.endswith(".json"): + return await super().get() + + messages = [await self.app.services.channel_message.to_extended_dict(message) for message in await self.app.services.channel_message.offset( + channel["uid"] + )] + + + return await self.render_template("web.html", {"channel": channel_member,"user": user,"messages": messages})