Fix message spacing and remove markdown link support

- Reduce VStack spacing from 4 to 2 and nested VStack from 2 to 0
- Set list row insets top/bottom to 0 to minimize spacing on iPhone
- Remove all markdown link parsing and formatting
- Share plain URLs instead of markdown when using share extension
- Keep only plain text URL detection and preview functionality
This commit is contained in:
jack
2025-07-24 12:47:49 +02:00
parent f45c52e9d3
commit b480a4836c
4 changed files with 19 additions and 89 deletions
+4 -6
View File
@@ -98,14 +98,12 @@ struct BitchatApp: App {
// Try to parse as JSON first // Try to parse as JSON first
if let data = sharedContent.data(using: .utf8), if let data = sharedContent.data(using: .utf8),
let urlData = try? JSONSerialization.jsonObject(with: data) as? [String: String], let urlData = try? JSONSerialization.jsonObject(with: data) as? [String: String],
let url = urlData["url"], let url = urlData["url"] {
let title = urlData["title"] { // Send plain URL
// Send just emoji with hidden markdown link self.chatViewModel.sendMessage(url)
let markdownLink = "👇 [\(title)](\(url))"
self.chatViewModel.sendMessage(markdownLink)
} else { } else {
// Fallback to simple URL // Fallback to simple URL
self.chatViewModel.sendMessage("Shared link: \(sharedContent)") self.chatViewModel.sendMessage(sharedContent)
} }
} else { } else {
self.chatViewModel.sendMessage(sharedContent) self.chatViewModel.sendMessage(sharedContent)
+2 -27
View File
@@ -979,33 +979,8 @@ class ChatViewModel: ObservableObject {
senderStyle.font = .system(size: 14, weight: .medium, design: .monospaced) senderStyle.font = .system(size: 14, weight: .medium, design: .monospaced)
result.append(sender.mergingAttributes(senderStyle)) result.append(sender.mergingAttributes(senderStyle))
// Process content with hashtags, mentions, and markdown links // Process content with hashtags and mentions
var content = message.content let content = message.content
// First, check if content starts with 👇 followed by markdown link
if content.hasPrefix("👇 [") {
// This is a URL share - remove everything after the emoji
if let linkStart = content.firstIndex(of: "[") {
let indexBeforeLink = content.index(before: linkStart)
content = String(content[..<indexBeforeLink])
}
} else {
// Handle normal markdown links
let markdownLinkPattern = #"\[([^\]]+)\]\(([^)]+)\)"#
if let markdownRegex = try? NSRegularExpression(pattern: markdownLinkPattern, options: []) {
let markdownMatches = markdownRegex.matches(in: content, options: [], range: NSRange(location: 0, length: content.count))
// Process matches in reverse order to maintain string indices
for match in markdownMatches.reversed() {
if let fullRange = Range(match.range, in: content),
let titleRange = Range(match.range(at: 1), in: content) {
// Normal markdown link - replace with just the title
let linkTitle = String(content[titleRange])
content.replaceSubrange(fullRange, with: linkTitle)
}
}
}
}
let hashtagPattern = "#([a-zA-Z0-9_]+)" let hashtagPattern = "#([a-zA-Z0-9_]+)"
let mentionPattern = "@([a-zA-Z0-9_]+)" let mentionPattern = "@([a-zA-Z0-9_]+)"
+3 -16
View File
@@ -177,7 +177,7 @@ struct ContentView: View {
let windowedMessages = messages.suffix(100) let windowedMessages = messages.suffix(100)
ForEach(Array(windowedMessages), id: \.id) { message in ForEach(Array(windowedMessages), id: \.id) { message in
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 2) {
// Check if current user is mentioned // Check if current user is mentioned
let _ = message.mentions?.contains(viewModel.nickname) ?? false let _ = message.mentions?.contains(viewModel.nickname) ?? false
@@ -189,7 +189,7 @@ struct ContentView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} else { } else {
// Regular messages with natural text wrapping // Regular messages with natural text wrapping
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
// Single text view for natural wrapping // Single text view for natural wrapping
Text(viewModel.formatMessageAsText(message, colorScheme: colorScheme)) Text(viewModel.formatMessageAsText(message, colorScheme: colorScheme))
@@ -205,18 +205,6 @@ struct ContentView: View {
} }
} }
// Check for links and show preview
if let markdownLink = message.content.extractMarkdownLink() {
// Don't show link preview if the message is just the emoji
let cleanContent = message.content.trimmingCharacters(in: .whitespacesAndNewlines)
if cleanContent.hasPrefix("👇") {
LazyLinkPreviewView(
url: markdownLink.url,
title: markdownLink.title,
id: "\(message.id)-\(markdownLink.url.absoluteString)"
)
}
} else {
// Check for plain URLs // Check for plain URLs
let urls = message.content.extractURLs() let urls = message.content.extractURLs()
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
@@ -229,7 +217,6 @@ struct ContentView: View {
} }
} }
} }
}
.id(message.id) .id(message.id)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { .onTapGesture {
@@ -240,7 +227,7 @@ struct ContentView: View {
showMessageActions = true showMessageActions = true
} }
} }
.listRowInsets(EdgeInsets(top: 2, leading: 12, bottom: 2, trailing: 12)) .listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowBackground(backgroundColor) .listRowBackground(backgroundColor)
} }
+1 -31
View File
@@ -410,50 +410,20 @@ extension String {
func extractURLs() -> [(url: URL, range: Range<String.Index>)] { func extractURLs() -> [(url: URL, range: Range<String.Index>)] {
var urls: [(URL, Range<String.Index>)] = [] var urls: [(URL, Range<String.Index>)] = []
// Check for markdown-style links [title](url) // Check for plain URLs
let markdownPattern = #"\[([^\]]+)\]\(([^)]+)\)"#
if let regex = try? NSRegularExpression(pattern: markdownPattern) {
let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
if let urlRange = Range(match.range(at: 2), in: self),
let url = URL(string: String(self[urlRange])),
let fullRange = Range(match.range, in: self) {
urls.append((url, fullRange))
}
}
}
// Also check for plain URLs
let types: NSTextCheckingResult.CheckingType = .link let types: NSTextCheckingResult.CheckingType = .link
if let detector = try? NSDataDetector(types: types.rawValue) { if let detector = try? NSDataDetector(types: types.rawValue) {
let matches = detector.matches(in: self, range: NSRange(location: 0, length: self.utf16.count)) let matches = detector.matches(in: self, range: NSRange(location: 0, length: self.utf16.count))
for match in matches { for match in matches {
if let range = Range(match.range, in: self), if let range = Range(match.range, in: self),
let url = match.url { let url = match.url {
// Don't add if this URL is already part of a markdown link
let isPartOfMarkdown = urls.contains { $0.1.overlaps(range) }
if !isPartOfMarkdown {
urls.append((url, range)) urls.append((url, range))
} }
} }
} }
}
return urls return urls
} }
func extractMarkdownLink() -> (title: String, url: URL)? {
let pattern = #"\[([^\]]+)\]\(([^)]+)\)"#
if let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: self, range: NSRange(location: 0, length: self.utf16.count)) {
if let titleRange = Range(match.range(at: 1), in: self),
let urlRange = Range(match.range(at: 2), in: self),
let url = URL(string: String(self[urlRange])) {
return (String(self[titleRange]), url)
}
}
return nil
}
} }
#Preview { #Preview {