From 874b73f8938fc2d0e70ad7cbed1d84d68a575c75 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:01:41 -0700 Subject: [PATCH 1/2] fix(messages): keep preview links from intercepting navigation --- Meshtastic/Views/Messages/ChannelList.swift | 2 +- .../Views/Messages/MessagePreviewText.swift | 31 +++++++++++++++++++ Meshtastic/Views/Messages/UserList.swift | 2 +- MeshtasticTests/MessagePreviewTextTests.swift | 27 ++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Views/Messages/MessagePreviewText.swift create mode 100644 MeshtasticTests/MessagePreviewTextTests.swift diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index 274d5a913..2abed1e4e 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -113,7 +113,7 @@ struct ChannelList: View { if hasMessages { HStack(alignment: .top) { - Text(LocalizedStringKey(mostRecent?.messagePayload ?? " ")) + MessagePreviewText(mostRecent?.messagePayload ?? " ") .font(.footnote) .foregroundColor(.onSurfaceVariant) } diff --git a/Meshtastic/Views/Messages/MessagePreviewText.swift b/Meshtastic/Views/Messages/MessagePreviewText.swift new file mode 100644 index 000000000..e8a6e8d8f --- /dev/null +++ b/Meshtastic/Views/Messages/MessagePreviewText.swift @@ -0,0 +1,31 @@ +import SwiftUI + +struct MessagePreviewText: View { + + private let text: AttributedString + + init(_ source: String) { + text = Self.attributedString(for: source) + } + + var body: some View { + Text(text) + } + + static func attributedString(for source: String) -> AttributedString { + guard var result = try? AttributedString( + markdown: source, + options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) + ) else { + return AttributedString(source) + } + + let linkRanges = result.runs.compactMap { run in + run.link == nil ? nil : run.range + } + for range in linkRanges { + result[range].link = nil + } + return result + } +} diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index f7769dd6e..db7f153f1 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -315,7 +315,7 @@ private struct DirectMessageUserRow: View { if let summary { HStack(alignment: .top) { - Text(LocalizedStringKey(summary.payload)) + MessagePreviewText(summary.payload) .font(.footnote) .foregroundColor(.onSurfaceVariant) } diff --git a/MeshtasticTests/MessagePreviewTextTests.swift b/MeshtasticTests/MessagePreviewTextTests.swift new file mode 100644 index 000000000..1c59dd329 --- /dev/null +++ b/MeshtasticTests/MessagePreviewTextTests.swift @@ -0,0 +1,27 @@ +import Foundation +import Testing + +@testable import Meshtastic + +@Suite("Message preview text") +struct MessagePreviewTextTests { + + @Test("Bare URLs are not interactive in conversation previews") + func bareURLIsNotInteractive() { + let source = "See https://meshtastic.org/docs for details" + let preview = MessagePreviewText.attributedString(for: source) + + #expect(String(preview.characters) == source) + #expect(preview.runs.allSatisfy { $0.link == nil }) + } + + @Test("Markdown links keep their label without remaining interactive") + func markdownLinkIsNotInteractive() { + let preview = MessagePreviewText.attributedString( + for: "Read the [Meshtastic docs](https://meshtastic.org/docs)" + ) + + #expect(String(preview.characters) == "Read the Meshtastic docs") + #expect(preview.runs.allSatisfy { $0.link == nil }) + } +} From b54c632684b810046434398a637432bf56dfd1a2 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:15:29 -0700 Subject: [PATCH 2/2] docs(messages): clarify preview link behavior --- Meshtastic/Resources/docs/index.json | 8 ++++---- Meshtastic/Resources/docs/markdown/user/messages.md | 2 +- Meshtastic/Resources/docs/user/messages.html | 2 +- Meshtastic/Views/Messages/MessagePreviewText.swift | 2 ++ docs/user/messages.md | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Resources/docs/index.json b/Meshtastic/Resources/docs/index.json index a93255a2b..729ea5224 100644 --- a/Meshtastic/Resources/docs/index.json +++ b/Meshtastic/Resources/docs/index.json @@ -292,24 +292,24 @@ "orange", "markdown", "delimiters", + "conversation", + "tap", "retry", "exclamationmark", - "tap", "recipient", "node", "meshtastic", - "conversation", "after", "selection", + "links", "bold", "radio", "public", - "links", "field", "button", "symbol" ], - "charCount": 11883 + "charCount": 12005 }, { "id": "mqtt", diff --git a/Meshtastic/Resources/docs/markdown/user/messages.md b/Meshtastic/Resources/docs/markdown/user/messages.md index c4e4633e2..ba3955c49 100644 --- a/Meshtastic/Resources/docs/markdown/user/messages.md +++ b/Meshtastic/Resources/docs/markdown/user/messages.md @@ -182,7 +182,7 @@ On iOS 18 and later, formatting buttons appear in the compact toolbar below the When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden. -Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. +Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble. | Example | Description | |---------|-------------| diff --git a/Meshtastic/Resources/docs/user/messages.html b/Meshtastic/Resources/docs/user/messages.html index b05ec3d62..cadb54bdb 100644 --- a/Meshtastic/Resources/docs/user/messages.html +++ b/Meshtastic/Resources/docs/user/messages.html @@ -317,7 +317,7 @@

How to Format Text

Live Preview

When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden.

-

Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance.

+

Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble.

diff --git a/Meshtastic/Views/Messages/MessagePreviewText.swift b/Meshtastic/Views/Messages/MessagePreviewText.swift index e8a6e8d8f..bccd88359 100644 --- a/Meshtastic/Views/Messages/MessagePreviewText.swift +++ b/Meshtastic/Views/Messages/MessagePreviewText.swift @@ -1,3 +1,5 @@ +// MARK: MessagePreviewText + import SwiftUI struct MessagePreviewText: View { diff --git a/docs/user/messages.md b/docs/user/messages.md index c4e4633e2..ba3955c49 100644 --- a/docs/user/messages.md +++ b/docs/user/messages.md @@ -182,7 +182,7 @@ On iOS 18 and later, formatting buttons appear in the compact toolbar below the When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden. -Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. +Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble. | Example | Description | |---------|-------------|