mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:25:19 +00:00
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:
@@ -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)
|
||||||
|
|||||||
@@ -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_]+)"
|
||||||
|
|||||||
@@ -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,27 +205,14 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for links and show preview
|
// Check for plain URLs
|
||||||
if let markdownLink = message.content.extractMarkdownLink() {
|
let urls = message.content.extractURLs()
|
||||||
// Don't show link preview if the message is just the emoji
|
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
|
||||||
let cleanContent = message.content.trimmingCharacters(in: .whitespacesAndNewlines)
|
LazyLinkPreviewView(
|
||||||
if cleanContent.hasPrefix("👇") {
|
url: urlInfo.url,
|
||||||
LazyLinkPreviewView(
|
title: nil,
|
||||||
url: markdownLink.url,
|
id: "\(message.id)-\(urlInfo.url.absoluteString)"
|
||||||
title: markdownLink.title,
|
)
|
||||||
id: "\(message.id)-\(markdownLink.url.absoluteString)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for plain URLs
|
|
||||||
let urls = message.content.extractURLs()
|
|
||||||
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
|
|
||||||
LazyLinkPreviewView(
|
|
||||||
url: urlInfo.url,
|
|
||||||
title: nil,
|
|
||||||
id: "\(message.id)-\(urlInfo.url.absoluteString)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
urls.append((url, range))
|
||||||
let isPartOfMarkdown = urls.contains { $0.1.overlaps(range) }
|
|
||||||
if !isPartOfMarkdown {
|
|
||||||
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user