# Global Brython helpers from browser import document, console, window def _append_cache_bust(src: str) -> str: try: sep = "&" if "?" in src else "?" return f"{src}{sep}v={int(window.Date.new().getTime())}" except Exception as e: console.log(f"[brython] cache-bust error: {e}") return src def _fallback_avatar(ev): img = document.getElementById("nav-avatar") if img is None: return img.attrs["src"] = "/static/img/avatar_default.svg" console.log("[brython] avatar fallback applied") def _bind_copy_embed(): # Optional: present on pages with embed box btn = document.getElementById("copy-embed") area = document.getElementById("embed-code") if not btn or not area: return def _copy(ev): txt = area.value nav = window.navigator if hasattr(nav, "clipboard") and nav.clipboard: nav.clipboard.writeText(txt) console.log("[brython] embed copied (clipboard API)") else: area.select() ok = document.execCommand("copy") console.log(f"[brython] embed copied (execCommand={ok})") btn.bind("click", _copy) def _bind_user_menu_dismiss(): """ Close any
when clicking outside of it. Uses 'open' property and removes attribute as a safety. """ try: menus = list(document.select("details.user-menu")) except Exception: menus = [] if not menus: return def contains(root, el): cur = el while cur is not None: if cur == root: return True try: cur = cur.parentElement except Exception: cur = None return False def in_path(root, ev): try: path = ev.composedPath() except Exception: path = None if path: for el in path: if el == root: return True return False def close_menu(menu): try: if getattr(menu, "open", False): menu.open = False try: menu.removeAttribute("open") except Exception: pass except Exception as e: console.log(f"[brython] user-menu close error: {e}") def on_any_down(ev): try: tgt = getattr(ev, "target", None) for m in menus: # Skip if click is inside menu (via composedPath or DOM parent walk) if getattr(m, "open", False) and not ((tgt and contains(m, tgt)) or in_path(m, ev)): close_menu(m) except Exception as e: console.log(f"[brython] dismiss handler error: {e}") def on_keydown(ev): try: key = (getattr(ev, "key", "") or "").lower() code = getattr(ev, "code", "") if key == "escape" or code == "Escape": for m in menus: if getattr(m, "open", False): close_menu(m) except Exception as e: console.log(f"[brython] dismiss key handler error: {e}") # Use early-phase pointer/mouse/touch events to ensure we catch outside clicks document.bind("mousedown", on_any_down) document.bind("pointerdown", on_any_down) document.bind("touchstart", on_any_down) document.bind("keydown", on_keydown) def init(): img = document.getElementById("nav-avatar") if img is not None: img.bind("error", _fallback_avatar) if img.attrs.get("data-cache-bust") == "1": img.attrs["src"] = _append_cache_bust(img.attrs["src"]) console.log("[brython] nav avatar cache-busted") _bind_copy_embed() _bind_user_menu_dismiss() console.log("[brython] init.bry loaded") init()