# HG changeset patch # User Goffi # Date 1687939754 -7200 # Node ID 3c384b51b2a1bb9612fe41cf76bb901a8cf6d144 # Parent b4342176fa0aac05bc5755cab7228f0b3ba91f5e browser (chat): URL previews implementation diff -r b4342176fa0a -r 3c384b51b2a1 libervia/web/pages/chat/_browser/__init__.py --- a/libervia/web/pages/chat/_browser/__init__.py Wed Jun 28 10:06:54 2023 +0200 +++ b/libervia/web/pages/chat/_browser/__init__.py Wed Jun 28 10:09:14 2023 +0200 @@ -1,12 +1,13 @@ import json +import re -import dialog -import jid from bridge import AsyncBridge as Bridge -from browser import aio, console as log, document, DOMNode, window, bind +from browser import DOMNode, aio, bind, console as log, document, window, html +from cache import cache, identities +import dialog +from file_uploader import FileUploader +import jid from template import Template, safe -from file_uploader import FileUploader -from cache import cache, identities log.warning = log.warn profile = window.profile or "" @@ -21,6 +22,9 @@ class LiberviaWebChat: def __init__(self): self.message_tpl = Template("chat/message.html") + self.url_preview_control_tpl = Template("components/url_preview_control.html") + self.url_preview_tpl = Template("components/url_preview.html") + self.messages_elt = document["messages"] # attachments @@ -166,6 +170,8 @@ self.messages_elt <= message_elt self.make_attachments_dynamic(message_elt) + # we add preview in parallel on purpose, as they can be slow to get + self.handle_url_previews(message_elt) # If user was at the bottom, keep the scroll at the bottom if is_at_bottom: @@ -247,6 +253,80 @@ close_button = document.select_one(".modal-close") close_button.bind("click", self.close_modal) + def find_links(self, message_elt): + """Find all http and https links within the body of a message.""" + msg_body_elt = message_elt.select_one(".msg_body") + if not msg_body_elt: + return + + # Extracting links from text content + text = msg_body_elt.text + raw_urls = re.findall( + r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F])|#)+", + text, + ) + + # Extracting links from elements + a_elements = msg_body_elt.select("a") + for a_elt in a_elements: + href = a_elt.attrs.get("href", "") + if href.startswith("http://") or href.startswith("https://"): + raw_urls.append(href) + + # we remove duplicates + urls = list(dict.fromkeys(raw_urls)) + + return urls + + async def add_url_previews(self, url_previews_elt, urls) -> None: + """Add URL previews to the .url-previews element of a message.""" + url_previews_elt.clear() + for url in urls: + try: + url_preview_data_s = await bridge.url_preview_get(url, "") + except Exception as e: + log.warning(f"Couldn't get URL preview for {url}: {e}") + continue + + if not url_preview_data_s: + log.warning(f"No preview could be found for URL: {url}") + continue + + url_preview_data = json.loads(url_preview_data_s) + + url_preview_elt = self.url_preview_tpl.get_elt( + {"url_preview": url_preview_data} + ) + url_previews_elt <= url_preview_elt + + + def handle_url_previews(self, parent_elt=None): + """Check if URL are presents in a message and show appropriate element + + According to settings, either a preview control panel will be shown to wait for + user click, or directly the previews, or nothing at all. + """ + + if parent_elt is None: + parent_elt = document + chat_message_elts = parent_elt.select(".is-chat-message") + else: + chat_message_elts = [parent_elt] + for message_elt in chat_message_elts: + urls = self.find_links(message_elt) + if urls: + url_previews_elt = message_elt.select_one(".url-previews") + url_previews_elt.classList.remove("is-hidden") + preview_control_elt = self.url_preview_control_tpl.get_elt() + fetch_preview_btn = preview_control_elt.select_one(".action_fetch_preview") + fetch_preview_btn.bind( + "click", + lambda __, previews_elt=url_previews_elt, preview_urls=urls: aio.run( + self.add_url_previews(previews_elt, preview_urls) + ) + ) + url_previews_elt <= preview_control_elt + def open_modal(self, evt): modal_image = document.select_one("#modal-image") modal_image.src = evt.target.src @@ -271,3 +351,4 @@ bridge.register_signal("message_new", libervia_web_chat._on_message_new) libervia_web_chat.make_attachments_dynamic() +libervia_web_chat.handle_url_previews()