Update.
This commit is contained in:
		
							parent
							
								
									78f9679f30
								
							
						
					
					
						commit
						e7cd397e0f
					
				| @ -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  | ||||
|  | ||||
| @ -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;  | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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"}); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -5,7 +5,7 @@ this.onpush = (event) => { | ||||
|         }; | ||||
| 
 | ||||
|         navigator.serviceWorker | ||||
|             .register("service-worker.js") | ||||
|             .register("/service-worker.js") | ||||
|             .then((serviceWorkerRegistration) => { | ||||
|                 serviceWorkerRegistration.pushManager.subscribe().then( | ||||
|                     (pushSubscription) => { | ||||
|  | ||||
| @ -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 { | ||||
|             </div> | ||||
|         `;
 | ||||
|         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', () => { | ||||
|  | ||||
| @ -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'(?<!["\'])\bhttps://[^\s<>()]+' | ||||
|     url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+(?<!\.)' | ||||
| 
 | ||||
|     soup = BeautifulSoup(text, 'html.parser') | ||||
| 
 | ||||
|  | ||||
| @ -12,11 +12,6 @@ | ||||
|   <script src="/html-frame.js"></script> | ||||
|   <script src="/schedule.js"></script> | ||||
|   <script src="/app.js"></script> | ||||
|   <script src="/models.js"></script> | ||||
|   <script src="/message-list.js"></script> | ||||
|   <script src="/message-list-manager.js"></script> | ||||
|   <script src="/chat-input.js"></script> | ||||
|   <script src="/chat-window.js"></script> | ||||
|   <link rel="stylesheet" href="/base.css"> | ||||
|   <link rel="manifest" href="/manifest.json" /> | ||||
|   <link rel="icon" type="image/png" href="/image/snek1.png" sizes="32x32"> | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| <link rel="stylesheet" href="/highlight.css">{#<div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" data-message="{{message}}" class="message"><div class="avatar" style="background-color: {{color}}; color: black;">{{user_nick[0]}}</div><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">#}{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}{#</div><div class="time">{{created_at}}</div></div></div>#} | ||||
| <div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" data-message="{{message}}" class="message"><div class="avatar" style="background-color: {{color}}; color: black;">{{user_nick[0]}}</div><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}</div><div class="time no-select" data-created_at="{{created_at}}"></div></div></div> | ||||
|  | ||||
| @ -1,4 +1,99 @@ | ||||
| {% extends "app.html" %}  | ||||
| {% block main %} | ||||
|     <chat-window class="chat-area"></chat-window> | ||||
|     <section class="chat-area" id="chat"> | ||||
|     <div class="chat-header"><h2>{{ channel.label.value }}</h2></div> | ||||
|     <div class="chat-messages"> | ||||
|          | ||||
|         {% for message in messages %} | ||||
|         {% autoescape false %} | ||||
|         {{message.html}} | ||||
|         {% endautoescape %} | ||||
|         <div style="display:none" class="message {% if loop.first or message.username != messages[loop.index0 - 1].username %}switch-user{% endif %}" data-uid="{{message.uid}}" data-color="{{message.color}}" data-user_nick="{{message.user_nick}}" data-created_at="{{message.created_at}}"> | ||||
|                 <div class="avatar no-select" style="background-color: {{message.color}}; color: black;">{{message.user_nick[0]}}</div>  | ||||
|             <div class="message-content"> | ||||
|         {% autoescape false %} | ||||
|                 {{ message.html }} | ||||
|              | ||||
|         {% endautoescape %} | ||||
|             <div class="time no-select" data-created_at="{{message.created_at}}">{{message.created_at}}</div> | ||||
|             </div> | ||||
|         </div> | ||||
|         {% endfor %} | ||||
|     </div> | ||||
|     <chat-window style="display:none" class="chat-area"></chat-window> | ||||
|     <div class="chat-input"> | ||||
|       <textarea placeholder="Type a message..." rows="2"></textarea> | ||||
|       <upload-button channel="{{channel.channel_uid.value}}"></upload-button> | ||||
|     </div> | ||||
|     </section> | ||||
|     <script> | ||||
|         const channelUid = "{{channel.channel_uid.value}}" | ||||
| 
 | ||||
|         function initInputField(textBox){ | ||||
| 
 | ||||
|             textBox.addEventListener('change', (e) => { | ||||
|       e.preventDefault(); | ||||
|       this.dispatchEvent(new CustomEvent('change', { detail: e.target.value, bubbles: true })); | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     textBox.addEventListener('keydown', (e) => { | ||||
|       if (e.key === 'Enter' && !e.shiftKey) { | ||||
|         e.preventDefault(); | ||||
|         const message = e.target.value.trim(); | ||||
|         if (!message) return; | ||||
|             app.rpc.sendMessage(channelUid, e.target.value) | ||||
|           e.target.value = ''; | ||||
|       } | ||||
|     }); | ||||
|         } | ||||
|         initInputField(document.querySelector("textarea")) | ||||
|         function updateTimes(){ | ||||
|         document.querySelectorAll(".time").forEach((time) => { | ||||
|                 time.innerText = app.timeDescription(time.dataset.created_at) | ||||
|         }) | ||||
|         } | ||||
|         function updateLayout() { | ||||
| 
 | ||||
|              document.querySelectorAll(".chat-messages").forEach((messages) => messages.scrollTop = messages.scrollHeight + 1000) | ||||
|             //document.querySelectorAll(".chat-messages .message:last-child").forEach((message) => message.scrollIntoView({block: "end"}))       | ||||
|             updateTimes() | ||||
|             let previousUser = null; | ||||
|             document.querySelectorAll(".message").forEach((message) => { | ||||
|                 if(previousUser != message.dataset.user_uid) { | ||||
|                     message.classList.add("switch-user") | ||||
|                     previousUser = message.dataset.user_uid | ||||
|                 }else{ | ||||
|                     message.classList.remove("switch-user") | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|          setInterval(() => { | ||||
|                  | ||||
|              | ||||
|                 updateTimes() | ||||
| 
 | ||||
|             }, 1000) | ||||
| 
 | ||||
|         app.addEventListener("channel-message", (data) => { | ||||
|             if(data.channel_uid != channelUid) return | ||||
|             if(data.username != "{{user.username.value}}"){ | ||||
|                 app.playSound(0) | ||||
|             } | ||||
|             const message = document.createElement("div") | ||||
|             //message.classList.add("message") | ||||
|             message.dataset.color = data.color | ||||
|             message.dataset.created_at = data.created_at | ||||
|             message.dataset.user_nick = data.user_nick | ||||
|             message.dataset.uid = data.uid | ||||
|             message.innerHTML = data.html | ||||
|             document.querySelector(".chat-messages").appendChild(message.firstChild) | ||||
|             setTimeout(()=>{ | ||||
|             updateLayout() | ||||
|             },50) | ||||
|         }) | ||||
|         updateLayout() | ||||
|                 //.scrollTop = document.querySelector(".chat-messages").scrollHeight + 5000 | ||||
|        // }) | ||||
|     </script> | ||||
| {% endblock %} | ||||
|  | ||||
| @ -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 | ||||
|         return ws | ||||
|  | ||||
| @ -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}) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user