From 11ccc76d93e04da06ddbefbf3cf9013d75986679 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 29 Dec 2025 22:21:51 +0100 Subject: [PATCH] Added partial-updating links --- nomadnet/ui/textui/Browser.py | 65 +++++++++++++++++------------- nomadnet/ui/textui/Guide.py | 48 +++++++++++++--------- nomadnet/ui/textui/MicronParser.py | 13 ++++-- nomadnet/util.py | 2 +- 4 files changed, 79 insertions(+), 49 deletions(-) diff --git a/nomadnet/ui/textui/Browser.py b/nomadnet/ui/textui/Browser.py index 545a826..b0885b7 100644 --- a/nomadnet/ui/textui/Browser.py +++ b/nomadnet/ui/textui/Browser.py @@ -185,6 +185,7 @@ class Browser: return destination_type def handle_link(self, link_target, link_data = None): + partial_ids = None request_data = None if link_data != None: link_fields = [] @@ -244,6 +245,10 @@ class Browser: if len(components) == 2: destination_type = self.expand_shorthands(components[0]) link_target = components[1] + elif link_target.startswith("p:"): + comps = link_target.split(":") + if len(comps) > 1: partial_ids = comps[1:] + destination_type = "partial" else: destination_type = "nomadnetwork.node" link_target = components[0] @@ -264,6 +269,9 @@ class Browser: RNS.log("Passing LXMF link to handler", RNS.LOG_DEBUG) self.handle_lxmf_link(link_target) + elif destination_type == "partial": + if partial_ids != None and len(partial_ids) > 0: self.handle_partial_updates(partial_ids) + else: RNS.log("No known handler for destination type "+str(destination_type), RNS.LOG_DEBUG) self.browser_footer = urwid.Text("Could not open link: "+"No known handler for destination type "+str(destination_type)) @@ -483,13 +491,13 @@ class Browser: def detect_partials(self): for w in self.attr_maps: o = w._original_widget - if hasattr(o, "partial_id"): - RNS.log(f"Found partial: {o.partial_id} / {o.partial_url} / {o.partial_refresh}") - partial = {"id": o.partial_id, "url": o.partial_url, "fields": o.partial_fields, "refresh": o.partial_refresh, - "content": None, "updated": None, "update_requested": None, "request_id": None, "destination": None, - "link": None, "pile": o, "attr_maps": None, "failed": False, "pr_throttle": 0} + if hasattr(o, "partial_hash"): + RNS.log(f"Found partial: {o.partial_hash} / {o.partial_url} / {o.partial_refresh}") + partial = {"hash": o.partial_hash, "id": o.partial_id, "url": o.partial_url, "fields": o.partial_fields, + "refresh": o.partial_refresh, "content": None, "updated": None, "update_requested": None, "request_id": None, + "destination": None, "link": None, "pile": o, "attr_maps": None, "failed": False, "pr_throttle": 0} - self.page_partials[o.partial_id] = partial + self.page_partials[o.partial_hash] = partial if len(self.page_partials) > 0: self.start_partial_updater() @@ -560,7 +568,7 @@ class Browser: partial["link"] = existing_link break - if not partial["link"]: + if not partial["link"] or partial["link"].status == RNS.Link.CLOSED: RNS.log(f"Establishing link for partial: {partial_destination_hash} / {path}", RNS.LOG_EXTREME) identity = RNS.Identity.recall(partial_destination_hash) destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.app_name, self.aspects) @@ -644,6 +652,19 @@ class Browser: def start_partial_updater(self): if not self.updater_running: self.update_partials() + def handle_partial_updates(self, partial_ids): + RNS.log(f"Update partials: {partial_ids}") + def job(): + for pid in self.page_partials: + try: + partial = self.page_partials[pid] + if partial["id"] in partial_ids: + partial["update_requested"] = time.time() + self.__load_partial(partial) + except Exception as e: RNS.log(f"Error updating page partial: {e}", RNS.LOG_ERROR) + + threading.Thread(target=job, daemon=True).start() + def update_partials(self, loop=None, user_data=None): with self.partial_updater_lock: def job(): @@ -702,35 +723,25 @@ class Browser: components = url.split(":") if len(components) == 1: if len(components[0]) == (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2: - try: - destination_hash = bytes.fromhex(components[0]) - except Exception as e: - raise ValueError("Malformed URL") + try: destination_hash = bytes.fromhex(components[0]) + except Exception as e: raise ValueError("Malformed URL") path = Browser.DEFAULT_PATH - else: - raise ValueError("Malformed URL") + else: raise ValueError("Malformed URL") elif len(components) == 2: if len(components[0]) == (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2: - try: - destination_hash = bytes.fromhex(components[0]) - except Exception as e: - raise ValueError("Malformed URL") + try: destination_hash = bytes.fromhex(components[0]) + except Exception as e: raise ValueError("Malformed URL") path = components[1] - if len(path) == 0: - path = Browser.DEFAULT_PATH + if len(path) == 0: path = Browser.DEFAULT_PATH else: if len(components[0]) == 0: if self.destination_hash != None: destination_hash = self.destination_hash path = components[1] - if len(path) == 0: - path = Browser.DEFAULT_PATH - else: - raise ValueError("Malformed URL") - else: - raise ValueError("Malformed URL") - else: - raise ValueError("Malformed URL") + if len(path) == 0: path = Browser.DEFAULT_PATH + else: raise ValueError("Malformed URL") + else: raise ValueError("Malformed URL") + else: raise ValueError("Malformed URL") if destination_hash != None and path != None: if path.startswith("/file/"): diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py index e245fb3..ddb2cb3 100644 --- a/nomadnet/ui/textui/Guide.py +++ b/nomadnet/ui/textui/Guide.py @@ -1302,24 +1302,6 @@ Here is `F00f`_`[a more visible link`72914442a3689add83a09a767963f57c:/page/inde When links like these are displayed in the built-in browser, clicking on them or activating them using the keyboard will cause the browser to load the specified URL. ->Partials - -You can include partials in pages, which will load asynchronously once the page itself has loaded. - -`Faaa -`= -`{f64a846313b874ee4a357040807f8c77:/page/partial_1.mu} -`= -`` - -It's also possible to set an auto-refresh interval for partials. The following partial will update every 10 seconds. - -`Faaa -`= -`{f64a846313b874ee4a357040807f8c77:/page/refreshing_partial.mu`10} -`= -`` - >Fields & Requests Nomad Network let's you use simple input fields for submitting data to node-side applications. Submitted data, along with other session variables will be available to the node-side script / program as environment variables. @@ -1473,6 +1455,36 @@ This line will `` +>Partials + +You can include partials in pages, which will load asynchronously once the page itself has loaded. + +`Faaa +`= +`{f64a846313b874ee4a357040807f8c77:/page/partial_1.mu} +`= +`` + +It's also possible to set an auto-refresh interval for partials. Omit or set to 0 to disable. The following partial will update every 10 seconds. + +`Faaa +`= +`{f64a846313b874ee4a357040807f8c77:/page/refreshing_partial.mu`10} +`= +`` + +You can include field values and variables in partial updates, and by setting the `!pid`! variable, you can create links that update one or more specific partials. + +`Faaa +`= +Name: `B444``b + +`F38a`[Say hello`p:32]`f + +`{f64a846313b874e84a357039807f8c77:/page/hello_partial.mu`0`pid=32|user_name} +`= +`` + >Literals To display literal content, for example source-code, or blocks of text that should not be interpreted by micron, you can use literal blocks, specified by the \\`= tag. Below is the source code of this entire document, presented as a literal block. diff --git a/nomadnet/ui/textui/MicronParser.py b/nomadnet/ui/textui/MicronParser.py index 7ca715e..0599216 100644 --- a/nomadnet/ui/textui/MicronParser.py +++ b/nomadnet/ui/textui/MicronParser.py @@ -93,6 +93,7 @@ def parse_partial(line): else: partial_data = line[0:endpos] + partial_id = None partial_components = partial_data.split("`") if len(partial_components) == 1: partial_url = partial_components[0] @@ -111,15 +112,21 @@ def parse_partial(line): partial_fields = "" partial_refresh = None - if partial_refresh and partial_refresh < 1: partial_refresh = None + if partial_refresh != None and partial_refresh < 1: partial_refresh = None pf = partial_fields.split("|") - if len(pf) > 0: partial_fields = pf + if len(pf) > 0: + partial_fields = pf + for f in pf: + if f.startswith("pid="): + pcs = f.split("=") + partial_id = pcs[1] if len(partial_url): pile = urwid.Pile([urwid.Text(f"⧖")]) partial_descriptor = "|".join(partial_components) - pile.partial_id = RNS.hexrep(RNS.Identity.full_hash(partial_descriptor.encode("utf-8")), delimit=False) + pile.partial_id = partial_id + pile.partial_hash = RNS.hexrep(RNS.Identity.full_hash(partial_descriptor.encode("utf-8")), delimit=False) pile.partial_url = partial_url pile.partial_fields = partial_fields pile.partial_refresh = partial_refresh diff --git a/nomadnet/util.py b/nomadnet/util.py index 1a31304..0d8c79f 100644 --- a/nomadnet/util.py +++ b/nomadnet/util.py @@ -1,7 +1,7 @@ import re import unicodedata -invalid_rendering = ["🕵️"] +invalid_rendering = ["🕵️", "☝"] def strip_modifiers(text): def process_characters(text):