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()