aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Hotline/Hotline/HotlineProtocol.swift4
-rw-r--r--Hotline/Managers/ChatStore.swift26
-rw-r--r--Hotline/Models/ChatMessage.swift9
-rw-r--r--Hotline/Models/Hotline.swift8
-rw-r--r--Hotline/Utility/FoundationExtensions.swift34
-rw-r--r--Hotline/Utility/SwiftUIExtensions.swift26
-rw-r--r--Hotline/macOS/ChatView.swift49
-rw-r--r--Hotline/macOS/FilesView.swift3
8 files changed, 98 insertions, 61 deletions
diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift
index ff91672..9b3a812 100644
--- a/Hotline/Hotline/HotlineProtocol.swift
+++ b/Hotline/Hotline/HotlineProtocol.swift
@@ -241,11 +241,11 @@ struct HotlineAccount: Identifiable {
}
if fieldType == .userLogin {
- self.login = field.getObfuscatedString()!
+ self.login = field.getObfuscatedString() ?? ""
}
if fieldType == .userPassword {
- self.password = field.getObfuscatedString()!
+ self.password = field.getObfuscatedString() ?? ""
}
if fieldType == .userAccess, let opts = field.getUInt64(){
diff --git a/Hotline/Managers/ChatStore.swift b/Hotline/Managers/ChatStore.swift
index daf8ca0..59c9484 100644
--- a/Hotline/Managers/ChatStore.swift
+++ b/Hotline/Managers/ChatStore.swift
@@ -28,12 +28,23 @@ actor ChatStore {
}
}
+ struct EntryMetadata: Codable {
+ var images: [ImageMetadata]?
+
+ struct ImageMetadata: Codable {
+ let url: String
+ let width: CGFloat?
+ let height: CGFloat?
+ }
+ }
+
struct Entry: Codable {
let id: UUID
let body: String
let username: String?
let type: String
let date: Date
+ var metadata: EntryMetadata?
}
struct LoadResult {
@@ -81,6 +92,21 @@ actor ChatStore {
}
}
+ func updateMetadata(_ metadata: EntryMetadata, for entryID: UUID, key: SessionKey) async {
+ do {
+ guard var logFile = try loadLogFile(for: key) else { return }
+
+ if let index = logFile.entries.firstIndex(where: { $0.id == entryID }) {
+ logFile.entries[index].metadata = metadata
+ cache[key] = logFile
+ try persist(logFile, for: key)
+ }
+ }
+ catch {
+ print("ChatStore: failed to update metadata —", error)
+ }
+ }
+
func loadHistory(for key: SessionKey, limit: Int? = nil) async -> LoadResult {
do {
let logFile = try loadLogFile(for: key)
diff --git a/Hotline/Models/ChatMessage.swift b/Hotline/Models/ChatMessage.swift
index 744a5d2..1c210ce 100644
--- a/Hotline/Models/ChatMessage.swift
+++ b/Hotline/Models/ChatMessage.swift
@@ -48,19 +48,22 @@ extension ChatMessageType {
}
struct ChatMessage: Identifiable {
- let id = UUID()
-
+ let id: UUID
+
let text: String
let type: ChatMessageType
let date: Date
let username: String?
+ var metadata: ChatStore.EntryMetadata?
static let parser = /^\s*([^\:]+):\s*([\s\S]+)$/
init(text: String, type: ChatMessageType, date: Date) {
+ self.id = UUID()
self.type = type
self.date = date
-
+ self.metadata = nil
+
if
type == .message,
let match = text.firstMatch(of: ChatMessage.parser) {
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift
index 08b79a1..3783ba0 100644
--- a/Hotline/Models/Hotline.swift
+++ b/Hotline/Models/Hotline.swift
@@ -1445,7 +1445,9 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
else {
renderedText = entry.body
}
- return ChatMessage(text: renderedText, type: chatType, date: entry.date)
+ var message = ChatMessage(text: renderedText, type: chatType, date: entry.date)
+ message.metadata = entry.metadata
+ return message
}
self.chat = historyMessages + currentMessages
self.lastPersistedMessageType = historyMessages.last?.type
@@ -1530,12 +1532,12 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
guard let parent = self.findNews(in: self.news, at: path), !parent.children.isEmpty else {
return nil
}
-
+
return parent.children.first { child in
guard let childArticleID = child.articleID else {
return false
}
-
+
return child.type == .article && child.articleID == childArticleID
}
}
diff --git a/Hotline/Utility/FoundationExtensions.swift b/Hotline/Utility/FoundationExtensions.swift
index 9fc9ae7..90a3032 100644
--- a/Hotline/Utility/FoundationExtensions.swift
+++ b/Hotline/Utility/FoundationExtensions.swift
@@ -8,6 +8,13 @@ enum Endianness {
extension String {
+ func markdownToAttributedString() -> AttributedString {
+ let markdownText = self.convertingLinksToMarkdown()
+ let attr = (try? AttributedString(markdown: markdownText, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))) ?? AttributedString(self)
+
+ return attr
+ }
+
func convertToAttributedStringWithLinks() -> AttributedString {
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: self)
let matches = self.ranges(of: RegularExpressions.relaxedLink)
@@ -54,17 +61,28 @@ extension String {
}
func convertingLinksToMarkdown() -> String {
- var cp = String(self)
- cp.replace(RegularExpressions.relaxedLink) { match -> String in
+// var cp = String(self)
+
+ self.replacing(RegularExpressions.relaxedLink) { match in
let linkText = self[match.range]
- var injectedScheme = "https://"
- if let _ = try? RegularExpressions.supportedLinkScheme.prefixMatch(in: linkText) {
- injectedScheme = ""
- }
- return "[\(linkText)](\(injectedScheme)\(linkText))"
+ // Only add https:// if the link doesn't already have a scheme
+ let hasScheme = (try? RegularExpressions.supportedLinkScheme.prefixMatch(in: linkText)) != nil
+ let url = hasScheme ? String(linkText) : "https://\(linkText)"
+
+ return "[\(linkText)](\(url))"
}
- return cp
+
+// cp.replace(RegularExpressions.relaxedLink) { match -> String in
+// let linkText = self[match.range]
+// var injectedScheme = "https://"
+// if let _ = try? RegularExpressions.supportedLinkScheme.prefixMatch(in: linkText) {
+// injectedScheme = ""
+// }
+//
+// return "[\(linkText)](\(injectedScheme)\(linkText))"
+// }
+// return cp
}
}
diff --git a/Hotline/Utility/SwiftUIExtensions.swift b/Hotline/Utility/SwiftUIExtensions.swift
index 3b850e0..12217b2 100644
--- a/Hotline/Utility/SwiftUIExtensions.swift
+++ b/Hotline/Utility/SwiftUIExtensions.swift
@@ -1,7 +1,33 @@
import SwiftUI
+import Foundation
extension Color {
init(hex: Int, opacity: Double = 1.0) {
self.init(red: Double((hex >> 16) & 0xFF) / 255.0, green: Double((hex >> 8) & 0xFF) / 255.0, blue: Double(hex & 0xFF) / 255.0, opacity: opacity)
}
}
+
+extension AttributedString {
+ func setHangingIndent(firstLineHeadIndent: CGFloat = 0, otherLinesHeadIndent: CGFloat) -> AttributedString {
+// var blah = self
+
+// guard var paragraph = self.paragraphStyle else {
+// return
+// }
+
+ var p = self.paragraphStyle?.mutableCopy() as? NSMutableParagraphStyle
+ p?.headIndent = otherLinesHeadIndent
+ p?.firstLineHeadIndent = firstLineHeadIndent
+
+// paragraph.headIndent = otherLinesHeadIndent // indent for lines 2+
+// paragraph.firstLineHeadIndent = firstLineHeadIndent // usually 0
+
+ var blah = self
+
+
+ blah.paragraphStyle = p
+
+ return blah
+ }
+}
+
diff --git a/Hotline/macOS/ChatView.swift b/Hotline/macOS/ChatView.swift
index a45e7ed..2ad9816 100644
--- a/Hotline/macOS/ChatView.swift
+++ b/Hotline/macOS/ChatView.swift
@@ -69,58 +69,21 @@ struct ChatDisconnectedMessageView: View {
struct ChatMessageView: View {
let message: ChatMessage
-
+
var body: some View {
HStack(alignment: .firstTextBaseline) {
if let username = message.username {
- // if msg.text.isImageURL() {
- // HStack(alignment: .bottom) {
- // Text("**\(username):** ")
- //
- // let imageURL = URL(string: msg.text)!
- // AsyncImage(url: imageURL) { phase in
- // switch phase {
- // case .failure:
- // Text(LocalizedStringKey(msg.text.convertLinksToMarkdown()))
- // .lineSpacing(4)
- // .multilineTextAlignment(.leading)
- // .textSelection(.enabled)
- // .tint(Color("Link Color"))
- // case .success(let img):
- // Link(destination: imageURL) {
- // img
- // .resizable()
- // .scaledToFit()
- // .frame(maxWidth: 250, maxHeight: 150, alignment: .leading)
- // .onAppear {
- // reader.scrollTo(bottomID, anchor: .bottom)
- // }
- // }
- // default:
- // ProgressView().controlSize(.small)
- // }
- // }
- //
- // Spacer()
- // }
- // }
- // else {
- Text(LocalizedStringKey("**\(username):** \(message.text)".convertingLinksToMarkdown()))
- .lineSpacing(4)
- .multilineTextAlignment(.leading)
- .textSelection(.enabled)
- .tint(Color("Link Color"))
- // }
+ Text("\(username): ").fontWeight(.semibold) + Text(message.text.markdownToAttributedString())
}
else {
Text(message.text)
- .lineSpacing(4)
- .multilineTextAlignment(.leading)
- .textSelection(.enabled)
- .tint(Color("Link Color"))
}
Spacer()
}
+ .lineSpacing(4)
+ .multilineTextAlignment(.leading)
+ .textSelection(.enabled)
+ .tint(Color("Link Color"))
}
}
diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift
index 00a4e1e..42232af 100644
--- a/Hotline/macOS/FilesView.swift
+++ b/Hotline/macOS/FilesView.swift
@@ -244,11 +244,10 @@ struct FilesView: View {
return "No files found in \(processed) \(folderWord)"
}
return "\(count) file\(count == 1 ? "" : "s") found in \(processed) \(folderWord)"
- case .cancelled(let processed):
+ case .cancelled(_):
if model.fileSearchResults.isEmpty {
return nil
}
- let folderWord = processed == 1 ? "folder" : "folders"
return "Search cancelled"
case .failed(let message):
return "Search failed: \(message)"