Update.
This commit is contained in:
		
							parent
							
								
									78f9679f30
								
							
						
					
					
						commit
						e7cd397e0f
					
				| @ -22,7 +22,8 @@ class ChannelMessageService(BaseService): | |||||||
|         context.update(dict( |         context.update(dict( | ||||||
|             user_uid=user['uid'], |             user_uid=user['uid'], | ||||||
|             username=user['username'], |             username=user['username'], | ||||||
|             user_nick=user['nick'] |             user_nick=user['nick'], | ||||||
|  |             color=user['color'] | ||||||
|         )) |         )) | ||||||
|         try: |         try: | ||||||
|             template = self.app.jinja2_env.get_template("message.html") |             template = self.app.jinja2_env.get_template("message.html") | ||||||
| @ -34,4 +35,27 @@ class ChannelMessageService(BaseService): | |||||||
|             return model |             return model | ||||||
|         raise Exception(f"Failed to create channel message: {model.errors}.") |         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); |         this.audio = new NotificationAudio(500); | ||||||
|         const me = this  |         const me = this  | ||||||
|         this.ws.addEventListener("channel-message", (data) => { |         this.ws.addEventListener("channel-message", (data) => { | ||||||
|             me.emit(data.channel_uid, data); |             me.emit("channel-message", data); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.rpc.getUser(null).then(user => { |         this.rpc.getUser(null).then(user => { | ||||||
| @ -300,7 +300,31 @@ class App extends EventHandler { | |||||||
|     playSound(index) { |     playSound(index) { | ||||||
|         this.audio.play(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") { |     async benchMark(times = 100, message = "Benchmark Message") { | ||||||
|         const promises = []; |         const promises = []; | ||||||
|         const me = this;  |         const me = this;  | ||||||
|  | |||||||
| @ -161,7 +161,7 @@ message-list { | |||||||
| } | } | ||||||
| .chat-messages { | .chat-messages { | ||||||
|   flex: 1; |   flex: 1; | ||||||
|    |   overflow-y: auto; | ||||||
|   padding: 10px; |   padding: 10px; | ||||||
|   height: 200px; |   height: 200px; | ||||||
|   background: #1a1a1a; |   background: #1a1a1a; | ||||||
| @ -211,9 +211,20 @@ message-list { | |||||||
|   color: #e6e6e6; |   color: #e6e6e6; | ||||||
|   word-break: break-word; |   word-break: break-word; | ||||||
|   overflow-wrap: 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 { | .chat-messages .message .message-content .time { | ||||||
|   font-size: 0.8em; |   font-size: 0.8em; | ||||||
|   color: #aaa; |   color: #aaa; | ||||||
|  | |||||||
| @ -73,7 +73,8 @@ class ChatWindowElement extends HTMLElement { | |||||||
|         const me = this; |         const me = this; | ||||||
|         channelElement.addEventListener("message", (message) => { |         channelElement.addEventListener("message", (message) => { | ||||||
|             if (me.user.uid !== message.detail.user_uid) app.playSound(0); |             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 |         navigator.serviceWorker | ||||||
|             .register("service-worker.js") |             .register("/service-worker.js") | ||||||
|             .then((serviceWorkerRegistration) => { |             .then((serviceWorkerRegistration) => { | ||||||
|                 serviceWorkerRegistration.pushManager.subscribe().then( |                 serviceWorkerRegistration.pushManager.subscribe().then( | ||||||
|                     (pushSubscription) => { |                     (pushSubscription) => { | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ class UploadButtonElement extends HTMLElement { | |||||||
| 
 | 
 | ||||||
|         const files = fileInput.files; |         const files = fileInput.files; | ||||||
|         const formData = new FormData(); |         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++) { |         for (let i = 0; i < files.length; i++) { | ||||||
|             formData.append('files[]', files[i]); |             formData.append('files[]', files[i]); | ||||||
|         } |         } | ||||||
| @ -105,7 +105,7 @@ class UploadButtonElement extends HTMLElement { | |||||||
|             </div> |             </div> | ||||||
|         `;
 |         `;
 | ||||||
|         this.shadowRoot.appendChild(this.container); |         this.shadowRoot.appendChild(this.container); | ||||||
| 
 |         this.channelUid = this.getAttribute('channel'); | ||||||
|         this.uploadButton = this.container.querySelector('.upload-button'); |         this.uploadButton = this.container.querySelector('.upload-button'); | ||||||
|         this.fileInput = this.container.querySelector('.hidden-input'); |         this.fileInput = this.container.querySelector('.hidden-input'); | ||||||
|         this.uploadButton.addEventListener('click', () => { |         this.uploadButton.addEventListener('click', () => { | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ def set_link_target_blank(text): | |||||||
|         element.attrs['target'] = '_blank' |         element.attrs['target'] = '_blank' | ||||||
|         element.attrs['rel'] = 'noopener noreferrer' |         element.attrs['rel'] = 'noopener noreferrer' | ||||||
|         element.attrs['referrerpolicy'] = 'no-referrer' |         element.attrs['referrerpolicy'] = 'no-referrer' | ||||||
|  |         element.attrs['href'] = element.attrs['href'].strip(".") | ||||||
| 
 | 
 | ||||||
|     return str(soup) |     return str(soup) | ||||||
|      |      | ||||||
| @ -23,7 +24,7 @@ def set_link_target_blank(text): | |||||||
| def linkify_https(text): | def linkify_https(text): | ||||||
|     if not "https://" in text: |     if not "https://" in text: | ||||||
|         return text  |         return text  | ||||||
|     url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+' |     url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+(?<!\.)' | ||||||
| 
 | 
 | ||||||
|     soup = BeautifulSoup(text, 'html.parser') |     soup = BeautifulSoup(text, 'html.parser') | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,11 +12,6 @@ | |||||||
|   <script src="/html-frame.js"></script> |   <script src="/html-frame.js"></script> | ||||||
|   <script src="/schedule.js"></script> |   <script src="/schedule.js"></script> | ||||||
|   <script src="/app.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="stylesheet" href="/base.css"> | ||||||
|   <link rel="manifest" href="/manifest.json" /> |   <link rel="manifest" href="/manifest.json" /> | ||||||
|   <link rel="icon" type="image/png" href="/image/snek1.png" sizes="32x32"> |   <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" %}  | {% extends "app.html" %}  | ||||||
| {% block main %} | {% 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 %} | {% endblock %} | ||||||
|  | |||||||
| @ -73,22 +73,11 @@ class RPCView(BaseView): | |||||||
|         async def get_messages(self, channel_uid, offset=0): |         async def get_messages(self, channel_uid, offset=0): | ||||||
|             self._require_login() |             self._require_login() | ||||||
|             messages = [] |             messages = [] | ||||||
|             async for message in self.services.channel_message.query("SELECT * FROM channel_message ORDER BY created_at DESC LIMIT 60"): |             print("Channel uid:", channel_uid, flush=True) | ||||||
|                 user = await self.services.user.get(uid=message["user_uid"]) |             for message in await self.services.channel_message.offset(channel_uid, offset): | ||||||
|                 if not user: |                 print(message, flush=True) | ||||||
|                     print("User not found!", flush=True) |                 extended_dict = await self.services.channel_message.to_extended_dict(message) | ||||||
|                     continue |                 messages.append(extended_dict) | ||||||
|                 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']  |  | ||||||
|                 }) |  | ||||||
|             return messages |             return messages | ||||||
| 
 | 
 | ||||||
|         async def get_channels(self): |         async def get_channels(self): | ||||||
| @ -167,4 +156,4 @@ class RPCView(BaseView): | |||||||
|                 pass  |                 pass  | ||||||
|             elif msg.type == web.WSMsgType.CLOSE: |             elif msg.type == web.WSMsgType.CLOSE: | ||||||
|                 pass  |                 pass  | ||||||
|         return ws |         return ws | ||||||
|  | |||||||
| @ -70,4 +70,12 @@ class WebView(BaseView): | |||||||
|         if not user: |         if not user: | ||||||
|             return web.HTTPNotFound() |             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