diff --git a/nomadnet/Conversation.py b/nomadnet/Conversation.py index d7039dc..26a5089 100644 --- a/nomadnet/Conversation.py +++ b/nomadnet/Conversation.py @@ -417,6 +417,7 @@ class ConversationMessage: self.timestamp = self.lxm.timestamp self.sort_timestamp = os.path.getmtime(self.file_path) + if self.lxm.state > LXMF.LXMessage.GENERATING and self.lxm.state < LXMF.LXMessage.SENT: found = False @@ -597,6 +598,18 @@ class ConversationMessage: return {} + def content_renderer(self): + if not self.loaded: self.load() + RNS.log(self.lxm) + if self.lxm and hasattr(self.lxm, "get_fields"): + fields = self.lxm.get_fields() + RNS.log(f"has fields: {fields}") + if fields and isinstance(fields, dict): + if LXMF.FIELD_RENDERER in fields: + return fields[LXMF.FIELD_RENDERER] + + return None + def has_attachments(self): if self._cached_has_attachments is not None: return self._cached_has_attachments diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py index f3afd73..e578bbd 100644 --- a/nomadnet/NomadNetworkApp.py +++ b/nomadnet/NomadNetworkApp.py @@ -138,6 +138,7 @@ class NomadNetworkApp: self.try_propagation_on_fail = True self.disable_propagation = True self.notify_on_new_message = True + self.compose_markdown = True self.lxmf_max_propagation_size = None self.lxmf_max_sync_size = None @@ -836,6 +837,10 @@ class NomadNetworkApp: value = self.config["client"].as_bool(option) self.notify_on_new_message = value + if option == "compose_in_markdown": + value = self.config["client"].as_bool(option) + self.compose_markdown = value + if option == "user_interface": value = value.lower() if value == "none": @@ -1146,7 +1151,6 @@ downloads_path = ~/Downloads # Where to save received attachments. If not set, # attachments will be saved to the downloads path. # attachment_save_path = ~/Downloads - notify_on_new_message = yes # By default, the peer is announced at startup @@ -1210,6 +1214,10 @@ max_accepted_size = 500 # been received, for every destination. compact_announce_stream = yes +# You can choose whether or not to compose +# messages in markdown format. +compose_in_markdown = yes + [textui] # Amount of time to show intro screen diff --git a/nomadnet/ui/TextUI.py b/nomadnet/ui/TextUI.py index f05ae81..3cf4a3c 100644 --- a/nomadnet/ui/TextUI.py +++ b/nomadnet/ui/TextUI.py @@ -5,6 +5,7 @@ import os import platform import nomadnet +from nomadnet.ui import THEME_DARK, THEME_LIGHT from nomadnet.ui.textui import * from nomadnet import NomadNetworkApp @@ -13,8 +14,6 @@ COLORMODE_16 = 16 COLORMODE_88 = 88 COLORMODE_256 = 256 COLORMODE_TRUE = 2**24 -THEME_DARK = 0x01 -THEME_LIGHT = 0x02 THEMES = { THEME_DARK: { diff --git a/nomadnet/ui/__init__.py b/nomadnet/ui/__init__.py index b11aadb..cb06cf8 100644 --- a/nomadnet/ui/__init__.py +++ b/nomadnet/ui/__init__.py @@ -8,6 +8,8 @@ pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc") modules = py_modules+pyc_modules __all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))])) +THEME_DARK = 0x01 +THEME_LIGHT = 0x02 UI_NONE = 0x00 UI_MENU = 0x01 diff --git a/nomadnet/ui/textui/Channels.py b/nomadnet/ui/textui/Channels.py index ea119d8..be26dee 100644 --- a/nomadnet/ui/textui/Channels.py +++ b/nomadnet/ui/textui/Channels.py @@ -13,10 +13,11 @@ from nomadnet.ui.textui.MicronParser import LinkableText, LinkSpec from RNS.Utilities.rngit.util import MarkdownToMicron from RNS.Utilities.rngit.highlight import SyntaxHighlighter from .MicronParser import markup_to_attrmaps -from nomadnet.util import strip_modifiers +from nomadnet.util import strip_modifiers, strip_micron, strip_escaped_micron, unescape_micron, strip_non_formatting_tags -theme_dark = { "ts": "888", +theme_dark = { "text": "ddd", + "ts": "888", "nick_self": "6c5", "nick_peer": "3cd", "notice": "fd3", @@ -25,7 +26,8 @@ theme_dark = { "ts": "888", "mention": "fb4", "link": "79d", } -theme_light = { "ts": "888", +theme_light = { "text": "111", + "ts": "888", "nick_self": "3a0", "nick_peer": "077", "notice": "a70", @@ -899,47 +901,6 @@ class _ChatLinkDelegate: except Exception as e: RNS.log("Could not open page link: "+str(e), RNS.LOG_ERROR) -def strip_micron(text): - text = re.sub(r'`[FB][0-9a-fA-F]{3}', '', text) - text = re.sub(r'`[FB]T[0-9a-fA-F]{6}', '', text) - text = re.sub(r'`[!*_=]', '', text) - text = re.sub(r'`f`b', '', text) - text = re.sub(r'`f', '', text) - text = re.sub(r'`b', '', text) - text = re.sub(r'`<', '', text) - text = re.sub(r'`>', '', text) - text = re.sub(r'`{', '', text) - return text - -def strip_escaped_micron(text): - text = re.sub(r'¦[FB][0-9a-fA-F]{3}', '', text) - text = re.sub(r'¦[FB]T[0-9a-fA-F]{6}', '', text) - text = re.sub(r'¦[!*_=]', '', text) - text = re.sub(r'¦f`b', '', text) - text = re.sub(r'¦f', '', text) - text = re.sub(r'¦b', '', text) - text = re.sub(r'¦<', '', text) - text = re.sub(r'¦>', '', text) - text = re.sub(r'¦{', '', text) - return text - -def unescape_micron(text): - text = re.sub(r'¦([FB][0-9a-fA-F]{3})', r'`\1', text) - text = re.sub(r'¦([FB]T[0-9a-fA-F]{6})', r'`\1', text) - text = re.sub(r'¦([!*_=])', r'`\1', text) - text = re.sub(r'¦(f`b)', r'`\1', text) - text = re.sub(r'¦(f)', r'`\1', text) - text = re.sub(r'¦(b)', r'`\1', text) - text = re.sub(r'¦(<)', r'`\1', text) - text = re.sub(r'¦(>)', r'`\1', text) - text = re.sub(r'¦({)', r'`\1', text) - return text - -def strip_non_formatting_tags(text): - text = re.sub(r'`<', '', text) - text = re.sub(r'`>', '', text) - text = re.sub(r'`{', '', text) - return text mdc = MarkdownToMicron(max_width=80, syntax_highlighter=SyntaxHighlighter(), url_scope=None) def _message_widget(app, hub, m, link_delegate=None): @@ -1023,22 +984,22 @@ def _message_widget(app, hub, m, link_delegate=None): prefix_micron = f"{irc_ts}{_ts_prefix_raw(m.ts)}" nick_micron = f"`f{nick_attr}<{sender}>`f " if app.rrc_ui_justify_msgs: - prefix_rendered = _render_body(f"{prefix_micron}") - body_rendered = _render_body(f"{nick_micron}{message_body}") + prefix_rendered = _render_body(f"{prefix_micron}", fg=t["text"]) + body_rendered = _render_body(f"{nick_micron}{message_body}", fg=t["text"]) if app.rrc_ui_space_msgs: body_rendered.append(urwid.Text("")) columns = urwid.Columns([(urwid.PACK, urwid.Pile(prefix_rendered)), urwid.Pile(body_rendered)], dividechars=1) final_widget = urwid.Padding(columns, left=1) else: - rendered = _render_body(f"{prefix_micron}{nick_micron}{message_body}", link_delegate=ld) + rendered = _render_body(f"{prefix_micron}{nick_micron}{message_body}", link_delegate=ld, fg=t["text"]) if app.rrc_ui_space_msgs: rendered.append(urwid.Text("")) final_widget = urwid.Padding(urwid.Pile(rendered), left=1) final_widget.msg = m return final_widget -def _render_body(markup, link_delegate=None): - try: return markup_to_attrmaps(strip_modifiers(markup), url_delegate=link_delegate, link_class=_ChatLinkableText) +def _render_body(markup, link_delegate=None, fg="bbb"): + try: return markup_to_attrmaps(strip_modifiers(markup), url_delegate=link_delegate, link_class=_ChatLinkableText, fg_color=fg) except Exception as e: RNS.trace_exception(e) return [] diff --git a/nomadnet/ui/textui/Conversations.py b/nomadnet/ui/textui/Conversations.py index 57521e3..872b49b 100644 --- a/nomadnet/ui/textui/Conversations.py +++ b/nomadnet/ui/textui/Conversations.py @@ -15,6 +15,11 @@ from nomadnet.Conversation import ConversationMessage from nomadnet.util import strip_modifiers from nomadnet.util import sanitize_name +from RNS.Utilities.rngit.util import MarkdownToMicron +from RNS.Utilities.rngit.highlight import SyntaxHighlighter +from .MicronParser import markup_to_attrmaps +from nomadnet.util import strip_modifiers, strip_micron, strip_escaped_micron, unescape_micron, strip_non_formatting_tags +from nomadnet.ui import THEME_DARK, THEME_LIGHT def relative_time(timestamp): now = time.time() @@ -1302,7 +1307,7 @@ class ConversationWidget(urwid.WidgetWrap): if not message_hash in added_hashes: added_hashes.append(message_hash) was_loaded = message.loaded - message_widget = LXMessageWidget(message) + message_widget = LXMessageWidget(message, theme=self.app.config["textui"]["theme"]) self.message_widgets.append(message_widget) if not was_loaded and message.loaded: needs_index.append(message) @@ -1467,6 +1472,10 @@ class ConversationWidget(urwid.WidgetWrap): if file_attachments: fields = {LXMF.FIELD_FILE_ATTACHMENTS: file_attachments} + if self.app.compose_markdown: + if not fields: fields = {} + fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_MARKDOWN + if self.conversation.send(content, title, fields=fields): self.clear_editor() @@ -1609,7 +1618,9 @@ class ConversationWidget(urwid.WidgetWrap): class LXMessageWidget(urwid.WidgetWrap): - def __init__(self, message): + mdc = MarkdownToMicron(max_width=80, syntax_highlighter=SyntaxHighlighter(), url_scope=None) + + def __init__(self, message, theme=THEME_DARK): app = nomadnet.NomadNetworkApp.get_shared_instance() g = app.ui.glyphs self.timestamp = message.get_timestamp() @@ -1623,6 +1634,7 @@ class LXMessageWidget(urwid.WidgetWrap): msg_method = message._cached_method time_format = app.time_format message_time = datetime.fromtimestamp(self.timestamp) + renderer = message.content_renderer() encryption_string = "" if message.get_transport_encrypted(): encryption_string = " "+g["encrypted"] @@ -1685,7 +1697,16 @@ class LXMessageWidget(urwid.WidgetWrap): content_text = message.get_content() content_lines = content_text.split("\n") - indented = "\n".join(" "+line for line in content_lines) + markdown = renderer == LXMF.RENDERER_MARKDOWN + + default_fg = "bbb" if theme == THEME_DARK else "444" + if markdown: + formatted = self.mdc.format_block(content_text) + message_body = strip_non_formatting_tags(formatted) + rendered = markup_to_attrmaps(strip_modifiers(message_body), url_delegate=None, fg_color=default_fg, bg_color=None) + content_pile = urwid.Padding(urwid.Pile(rendered), left=2, right=2) + + else: indented = "\n".join(" "+line for line in content_lines) pile_widgets = [title] @@ -1711,7 +1732,8 @@ class LXMessageWidget(urwid.WidgetWrap): pile_widgets.append(self.progress_attr) self._start_progress_poll() - pile_widgets.append(urwid.Text(indented)) + if markdown: pile_widgets.append(content_pile) + else: pile_widgets.append(urwid.Text(indented)) if has_attachments and cached_names: att_file_idx = 0 diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py index 840dcc8..f80e870 100644 --- a/nomadnet/ui/textui/Guide.py +++ b/nomadnet/ui/textui/Guide.py @@ -771,6 +771,12 @@ The maximum accepted unpacked size for messages received directly from other pee With this option enabled, Nomad Network will only display one entry in the announce stream per destination. Older announces are culled when a new one arrives. < +>>> +`!compose_in_markdown = yes`! +>>>> +Configures whether sent messages are composed in markdown format. +< + >> RRC Section This section hold configuration directives related to the RRC client behaviour. It is delimited by the `![rrc]`! header in the configuration file. Available directives, along with their default values, are as follows: diff --git a/nomadnet/util.py b/nomadnet/util.py index 9f2562a..9b3427c 100644 --- a/nomadnet/util.py +++ b/nomadnet/util.py @@ -141,3 +141,45 @@ def sanitize_name(name): name = name.strip() return name + +def strip_micron(text): + text = re.sub(r'`[FB][0-9a-fA-F]{3}', '', text) + text = re.sub(r'`[FB]T[0-9a-fA-F]{6}', '', text) + text = re.sub(r'`[!*_=]', '', text) + text = re.sub(r'`f`b', '', text) + text = re.sub(r'`f', '', text) + text = re.sub(r'`b', '', text) + text = re.sub(r'`<', '', text) + text = re.sub(r'`>', '', text) + text = re.sub(r'`{', '', text) + return text + +def strip_escaped_micron(text): + text = re.sub(r'¦[FB][0-9a-fA-F]{3}', '', text) + text = re.sub(r'¦[FB]T[0-9a-fA-F]{6}', '', text) + text = re.sub(r'¦[!*_=]', '', text) + text = re.sub(r'¦f`b', '', text) + text = re.sub(r'¦f', '', text) + text = re.sub(r'¦b', '', text) + text = re.sub(r'¦<', '', text) + text = re.sub(r'¦>', '', text) + text = re.sub(r'¦{', '', text) + return text + +def unescape_micron(text): + text = re.sub(r'¦([FB][0-9a-fA-F]{3})', r'`\1', text) + text = re.sub(r'¦([FB]T[0-9a-fA-F]{6})', r'`\1', text) + text = re.sub(r'¦([!*_=])', r'`\1', text) + text = re.sub(r'¦(f`b)', r'`\1', text) + text = re.sub(r'¦(f)', r'`\1', text) + text = re.sub(r'¦(b)', r'`\1', text) + text = re.sub(r'¦(<)', r'`\1', text) + text = re.sub(r'¦(>)', r'`\1', text) + text = re.sub(r'¦({)', r'`\1', text) + return text + +def strip_non_formatting_tags(text): + text = re.sub(r'`<', '', text) + text = re.sub(r'`>', '', text) + text = re.sub(r'`{', '', text) + return text \ No newline at end of file