Mercurial > libervia-web
comparison libervia/web/pages/chat/_browser/__init__.py @ 1541:3c384b51b2a1
browser (chat): URL previews implementation
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 28 Jun 2023 10:09:14 +0200 |
parents | b4342176fa0a |
children | fb31d3dba0c3 |
comparison
equal
deleted
inserted
replaced
1540:b4342176fa0a | 1541:3c384b51b2a1 |
---|---|
1 import json | 1 import json |
2 | 2 import re |
3 | |
4 from bridge import AsyncBridge as Bridge | |
5 from browser import DOMNode, aio, bind, console as log, document, window, html | |
6 from cache import cache, identities | |
3 import dialog | 7 import dialog |
8 from file_uploader import FileUploader | |
4 import jid | 9 import jid |
5 from bridge import AsyncBridge as Bridge | |
6 from browser import aio, console as log, document, DOMNode, window, bind | |
7 from template import Template, safe | 10 from template import Template, safe |
8 from file_uploader import FileUploader | |
9 from cache import cache, identities | |
10 | 11 |
11 log.warning = log.warn | 12 log.warning = log.warn |
12 profile = window.profile or "" | 13 profile = window.profile or "" |
13 own_jid = jid.JID(window.own_jid) | 14 own_jid = jid.JID(window.own_jid) |
14 target_jid = jid.JID(window.target_jid) | 15 target_jid = jid.JID(window.target_jid) |
19 | 20 |
20 | 21 |
21 class LiberviaWebChat: | 22 class LiberviaWebChat: |
22 def __init__(self): | 23 def __init__(self): |
23 self.message_tpl = Template("chat/message.html") | 24 self.message_tpl = Template("chat/message.html") |
25 self.url_preview_control_tpl = Template("components/url_preview_control.html") | |
26 self.url_preview_tpl = Template("components/url_preview.html") | |
27 | |
24 self.messages_elt = document["messages"] | 28 self.messages_elt = document["messages"] |
25 | 29 |
26 # attachments | 30 # attachments |
27 self.file_uploader = FileUploader( | 31 self.file_uploader = FileUploader( |
28 "", "chat/attachment_preview.html", on_delete_cb=self.on_attachment_delete | 32 "", "chat/attachment_preview.html", on_delete_cb=self.on_attachment_delete |
164 # Check if user is viewing older messages or is at the bottom | 168 # Check if user is viewing older messages or is at the bottom |
165 is_at_bottom = self.is_at_bottom | 169 is_at_bottom = self.is_at_bottom |
166 | 170 |
167 self.messages_elt <= message_elt | 171 self.messages_elt <= message_elt |
168 self.make_attachments_dynamic(message_elt) | 172 self.make_attachments_dynamic(message_elt) |
173 # we add preview in parallel on purpose, as they can be slow to get | |
174 self.handle_url_previews(message_elt) | |
169 | 175 |
170 # If user was at the bottom, keep the scroll at the bottom | 176 # If user was at the bottom, keep the scroll at the bottom |
171 if is_at_bottom: | 177 if is_at_bottom: |
172 self.messages_elt.scrollTop = self.messages_elt.scrollHeight | 178 self.messages_elt.scrollTop = self.messages_elt.scrollHeight |
173 | 179 |
245 img_elt.style.cursor = "pointer" | 251 img_elt.style.cursor = "pointer" |
246 | 252 |
247 close_button = document.select_one(".modal-close") | 253 close_button = document.select_one(".modal-close") |
248 close_button.bind("click", self.close_modal) | 254 close_button.bind("click", self.close_modal) |
249 | 255 |
256 def find_links(self, message_elt): | |
257 """Find all http and https links within the body of a message.""" | |
258 msg_body_elt = message_elt.select_one(".msg_body") | |
259 if not msg_body_elt: | |
260 return | |
261 | |
262 # Extracting links from text content | |
263 text = msg_body_elt.text | |
264 raw_urls = re.findall( | |
265 r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F])|#)+", | |
266 text, | |
267 ) | |
268 | |
269 # Extracting links from <a> elements | |
270 a_elements = msg_body_elt.select("a") | |
271 for a_elt in a_elements: | |
272 href = a_elt.attrs.get("href", "") | |
273 if href.startswith("http://") or href.startswith("https://"): | |
274 raw_urls.append(href) | |
275 | |
276 # we remove duplicates | |
277 urls = list(dict.fromkeys(raw_urls)) | |
278 | |
279 return urls | |
280 | |
281 async def add_url_previews(self, url_previews_elt, urls) -> None: | |
282 """Add URL previews to the .url-previews element of a message.""" | |
283 url_previews_elt.clear() | |
284 for url in urls: | |
285 try: | |
286 url_preview_data_s = await bridge.url_preview_get(url, "") | |
287 except Exception as e: | |
288 log.warning(f"Couldn't get URL preview for {url}: {e}") | |
289 continue | |
290 | |
291 if not url_preview_data_s: | |
292 log.warning(f"No preview could be found for URL: {url}") | |
293 continue | |
294 | |
295 url_preview_data = json.loads(url_preview_data_s) | |
296 | |
297 url_preview_elt = self.url_preview_tpl.get_elt( | |
298 {"url_preview": url_preview_data} | |
299 ) | |
300 url_previews_elt <= url_preview_elt | |
301 | |
302 | |
303 def handle_url_previews(self, parent_elt=None): | |
304 """Check if URL are presents in a message and show appropriate element | |
305 | |
306 According to settings, either a preview control panel will be shown to wait for | |
307 user click, or directly the previews, or nothing at all. | |
308 """ | |
309 | |
310 if parent_elt is None: | |
311 parent_elt = document | |
312 chat_message_elts = parent_elt.select(".is-chat-message") | |
313 else: | |
314 chat_message_elts = [parent_elt] | |
315 for message_elt in chat_message_elts: | |
316 urls = self.find_links(message_elt) | |
317 if urls: | |
318 url_previews_elt = message_elt.select_one(".url-previews") | |
319 url_previews_elt.classList.remove("is-hidden") | |
320 preview_control_elt = self.url_preview_control_tpl.get_elt() | |
321 fetch_preview_btn = preview_control_elt.select_one(".action_fetch_preview") | |
322 fetch_preview_btn.bind( | |
323 "click", | |
324 lambda __, previews_elt=url_previews_elt, preview_urls=urls: aio.run( | |
325 self.add_url_previews(previews_elt, preview_urls) | |
326 ) | |
327 ) | |
328 url_previews_elt <= preview_control_elt | |
329 | |
250 def open_modal(self, evt): | 330 def open_modal(self, evt): |
251 modal_image = document.select_one("#modal-image") | 331 modal_image = document.select_one("#modal-image") |
252 modal_image.src = evt.target.src | 332 modal_image.src = evt.target.src |
253 modal_image.alt = evt.target.alt | 333 modal_image.alt = evt.target.alt |
254 modal = document.select_one("#modal") | 334 modal = document.select_one("#modal") |
269 document["attach_button"].bind("click", libervia_web_chat.on_attach_button_click) | 349 document["attach_button"].bind("click", libervia_web_chat.on_attach_button_click) |
270 document["file_input"].bind("change", libervia_web_chat.on_file_selected) | 350 document["file_input"].bind("change", libervia_web_chat.on_file_selected) |
271 bridge.register_signal("message_new", libervia_web_chat._on_message_new) | 351 bridge.register_signal("message_new", libervia_web_chat._on_message_new) |
272 | 352 |
273 libervia_web_chat.make_attachments_dynamic() | 353 libervia_web_chat.make_attachments_dynamic() |
354 libervia_web_chat.handle_url_previews() |