changeset 1638:4670dbf49909

browser (blog): Add initial browser implementation.
author Goffi <goffi@goffi.org>
date Fri, 04 Jul 2025 18:22:44 +0200
parents 29dd52585984
children 301fe2f1a34a
files libervia/web/pages/blog/_browser/__init__.py
diffstat 1 files changed, 144 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/web/pages/blog/_browser/__init__.py	Fri Jul 04 18:22:44 2025 +0200
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+
+import json
+
+import alt_media_player
+from bridge import AsyncBridge as Bridge
+from browser import aio, document, console as log, window
+from cache import identities, roster
+from template import Template
+from javascript import pyobj2jsobj
+from js_modules.tippy_js import tippy as tippy_ori
+
+
+log.warning = log.warn
+bridge = Bridge()
+alt_media_player.install_if_needed()
+
+own_jid = window.own_jid
+blog_url = window.blog_url
+service = window.service
+node = window.node
+
+
+# FIXME: workaround for https://github.com/brython-dev/brython/issues/2542
+def tippy(target, data):
+    return tippy_ori(target, pyobj2jsobj(data))
+
+
+class LiberviaWebBlog:
+
+    def __init__(self) -> None:
+        # Templates
+        self.reactions_tpl = Template("components/reactions.html")
+        self.reactions_details_tpl = Template("components/reactions_details.html")
+
+        # panels and their toggle buttons
+        self.left_panel = document["left_panel"]
+        self.left_toggle = document["left_panel-toggle"]
+        self.left_toggle.bind("click", self.on_left_panel_toggle_click)
+        self.main_panel = document["main_panel"]
+        self.contacts_links_tpl = Template("components/contacts_links.html")
+        contacts_links_elt = self.contacts_links_tpl.get_elt(
+            {
+                "roster": roster,
+                "identities": identities,
+                "base_url": blog_url
+            }
+        )
+        document["contact-blogs"] <= contacts_links_elt
+        self.post_init_blog_item()
+
+    def on_left_panel_toggle_click(self, evt) -> None:
+        """Show/Hide side bar."""
+        self.left_panel.classList.toggle("is-collapsed")
+        # self.main_panel.classList.toggle("is-expanded-left")
+
+    async def on_reaction_click(self, evt, blog_item_elt):
+        item_id = blog_item_elt["id"]
+        window.evt = evt
+        reaction = evt.detail["unicode"]
+        attachments_data = {
+            "service": service,
+            "node": node,
+            "id": item_id,
+            "extra": {"reactions": {"add": [reaction]}}
+        }
+        await bridge.ps_attachments_set(json.dumps(attachments_data))
+
+    def get_reaction_panel(self, source_elt):
+        emoji_picker_elt = document.createElement("emoji-picker")
+        blog_item_elt = source_elt.closest("article.blog-item")
+        emoji_picker_elt.bind(
+            "emoji-click", lambda evt: aio.run(self.on_reaction_click(evt, blog_item_elt))
+        )
+
+        return emoji_picker_elt
+
+    async def add_attachments(self, blog_item_elt) -> None:
+        try:
+            attachments_s, metadata_s = await bridge.ps_attachments_get(
+                service, node, blog_item_elt["id"], [], ""
+            )
+        except Exception as e:
+            log.warning(
+                f"Can't find attachments for {blog_item_elt['id']}: {e}"
+            )
+        else:
+            attachments = json.loads(attachments_s)
+            metadata = json.loads(metadata_s)
+            reactions: dict[str, list[str]] = {}
+            for attachment in attachments:
+                if "reactions" in attachment:
+                    for reaction in attachment["reactions"]["reactions"]:
+                        reactions.setdefault(reaction, []).append(attachment["from"])
+            if reactions:
+                reactions_elt = self.reactions_tpl.get_elt(
+                    {
+                        "own_jid": str(own_jid),
+                        "reactions": reactions
+                    })
+                footer = blog_item_elt.select_one("div.blog-item-footer")
+                if footer is not None:
+                    footer <= reactions_elt
+
+
+    def post_init_blog_item(self, parent_elt = None) -> None:
+        """Make the dynamic elements clickabled and add attachments to blog item.
+
+        @parent_elt: all ``blog_item`` elements of this element (or ``parent_elt`` itself
+            if it's a blog_item) well be handled.
+            If None, all blog items of the whole document will be handled.
+        """
+        if parent_elt is None:
+            blog_items = document.select(".blog-item")
+        elif parent_elt.classList.contains(".blog-item"):
+            blog_items= [parent_elt]
+        else:
+            blog_items = parent_elt.select(".blog-item")
+
+        for blog_item_elt in blog_items:
+            aio.run(self.add_attachments(blog_item_elt))
+            reaction_button_elt =  blog_item_elt.select_one("button.reaction-button")
+            if reaction_button_elt:
+                tippy(
+                    reaction_button_elt,
+                    {
+                        "trigger": "click",
+                        "content": self.get_reaction_panel,
+                        "appendTo": document.body,
+                        "placement": "bottom",
+                        "interactive": True,
+                        "theme": "light",
+                        "onShow": lambda __, blog_item_elt=blog_item_elt: (
+                            blog_item_elt.classList.add("has-popup-focus")
+                        ),
+                        "onHide": lambda __, blog_item_elt=blog_item_elt: (
+                            blog_item_elt.classList.remove("has-popup-focus")
+                        ),
+                    }
+                )
+
+
+
+libervia_web_blog = LiberviaWebBlog()