From 5205e3d819a073efe2714dfaa038c57bbfeb6be1 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 10 Mar 2026 09:06:19 +0100 Subject: [PATCH 1/4] Work on layout of AttributedTextView --- .../AttributedTextView.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift diff --git a/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift b/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift new file mode 100644 index 0000000..faeaa90 --- /dev/null +++ b/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift @@ -0,0 +1,36 @@ +import SwiftUI + +public struct AttributedTextView: NSViewRepresentable { + public let attributedString: NSAttributedString + + public init(attributedString: NSAttributedString) { + self.attributedString = attributedString + } + + public func makeNSView(context: Context) -> NSTextField { + let textField = NSTextField(labelWithAttributedString: attributedString) + + // Behavior settings + textField.isEditable = false + textField.isSelectable = true + textField.allowsEditingTextAttributes = true + + // Enable text wrapping + textField.cell?.wraps = true + textField.cell?.isScrollable = false + textField.lineBreakStrategy = .standard + textField.lineBreakMode = .byWordWrapping + + // Layout Priority, this helps SwiftUI understand it should stretch vertically + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.setContentCompressionResistancePriority(.required, for: .vertical) + + return textField + } + + public func updateNSView(_ textField: NSTextField, context: Context) { + if textField.attributedStringValue != attributedString { + textField.attributedStringValue = attributedString + } + } +} From cc78ccca2e270c8a228805988901157cbc581f9f Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 10 Mar 2026 09:38:16 +0100 Subject: [PATCH 2/4] Reapply "Start on timeline text formatting." This reverts commit 6bda5a1913e90858451e8c67b6598d13d6fb57ee. --- Mactrix.xcodeproj/project.pbxproj | 7 ++ .../xcshareddata/swiftpm/Package.resolved | 20 ++++- .../MatrixRustSDK+MessageContent.swift | 17 +++- Mactrix/Views/ChatView/ChatMessageView.swift | 5 +- .../Views/ChatView/FormattedBodyView.swift | 29 +++++++ MactrixLibrary/Package.swift | 14 +++- .../Sources/MessageFormatting/Parser.swift | 78 +++++++++++++++++++ .../Sources/MessageFormatting/Preview.swift | 59 ++++++++++++++ 8 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 Mactrix/Views/ChatView/FormattedBodyView.swift create mode 100644 MactrixLibrary/Sources/MessageFormatting/Parser.swift create mode 100644 MactrixLibrary/Sources/MessageFormatting/Preview.swift diff --git a/Mactrix.xcodeproj/project.pbxproj b/Mactrix.xcodeproj/project.pbxproj index d354a95..81b2a8d 100644 --- a/Mactrix.xcodeproj/project.pbxproj +++ b/Mactrix.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 348E99882F40ABC3009F57A9 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 348E99872F40ABC3009F57A9 /* AsyncAlgorithms */; }; 34913F6E2EC0F532003034CB /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 34913F6D2EC0F532003034CB /* MatrixRustSDK */; }; 34913F702EC0F59F003034CB /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 34913F6F2EC0F59F003034CB /* Models */; }; + 349B041C2F5D77E80023FBF4 /* MessageFormatting in Frameworks */ = {isa = PBXBuildFile; productRef = 349B041B2F5D77E80023FBF4 /* MessageFormatting */; }; 34F7225F2EB531F40007B2A4 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 34F7225E2EB531F40007B2A4 /* KeychainAccess */; }; /* End PBXBuildFile section */ @@ -46,6 +47,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 349B041C2F5D77E80023FBF4 /* MessageFormatting in Frameworks */, 3409F4EC2EBFD8D4009537B4 /* UI in Frameworks */, 34913F6E2EC0F532003034CB /* MatrixRustSDK in Frameworks */, 345E77E82ED9F309002E5B9A /* Utils in Frameworks */, @@ -109,6 +111,7 @@ 34913F6F2EC0F59F003034CB /* Models */, 345E77E72ED9F309002E5B9A /* Utils */, 348E99872F40ABC3009F57A9 /* AsyncAlgorithms */, + 349B041B2F5D77E80023FBF4 /* MessageFormatting */, ); productName = Mactrix; productReference = 343858422EB394590010922A /* Mactrix.app */; @@ -483,6 +486,10 @@ isa = XCSwiftPackageProductDependency; productName = Models; }; + 349B041B2F5D77E80023FBF4 /* MessageFormatting */ = { + isa = XCSwiftPackageProductDependency; + productName = MessageFormatting; + }; 34F7225E2EB531F40007B2A4 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = 34F7225D2EB531F40007B2A4 /* XCRemoteSwiftPackageReference "KeychainAccess" */; diff --git a/Mactrix.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mactrix.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c65f2ed..5f7bae5 100644 --- a/Mactrix.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mactrix.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "d084e0c9b1d3b8ac948908f0ca98de1cca938f342f749686b8062637737a5cf4", + "originHash" : "cef1eb81361acd9acea13764bdc16f4787935d4b09f5dcc6bd8e53e93b503c29", "pins" : [ { "identity" : "keychainaccess", @@ -36,6 +36,24 @@ "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", "version" : "1.3.0" } + }, + { + "identity" : "zmarkupparser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ZhgChgLi/ZMarkupParser.git", + "state" : { + "revision" : "f25c9838e9e77ae49cc3f434ee26e669cb4043c7", + "version" : "1.12.0" + } + }, + { + "identity" : "znstextattachment", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ZhgChgLi/ZNSTextAttachment", + "state" : { + "revision" : "8c6c34e82eb31ad373b209cb88c2a2aff76c6cdf", + "version" : "1.1.9" + } } ], "version" : 3 diff --git a/Mactrix/Extensions/MatrixRustSDK+MessageContent.swift b/Mactrix/Extensions/MatrixRustSDK+MessageContent.swift index fc13dd8..078c727 100644 --- a/Mactrix/Extensions/MatrixRustSDK+MessageContent.swift +++ b/Mactrix/Extensions/MatrixRustSDK+MessageContent.swift @@ -1,13 +1,22 @@ import MatrixRustSDK protocol MessageContent { + var body: String { get } + var formatted: FormattedBody? { get } +} + +protocol MediaMessageContent { var filename: String { get } var caption: String? { get } var formattedCaption: FormattedBody? { get } var source: MediaSource { get } } -extension FileMessageContent: MessageContent {} -extension AudioMessageContent: MessageContent {} -extension VideoMessageContent: MessageContent {} -extension ImageMessageContent: MessageContent {} +extension FileMessageContent: MediaMessageContent {} +extension AudioMessageContent: MediaMessageContent {} +extension VideoMessageContent: MediaMessageContent {} +extension ImageMessageContent: MediaMessageContent {} + +extension EmoteMessageContent: MessageContent {} +extension NoticeMessageContent: MessageContent {} +extension TextMessageContent: MessageContent {} diff --git a/Mactrix/Views/ChatView/ChatMessageView.swift b/Mactrix/Views/ChatView/ChatMessageView.swift index 879b790..34549a8 100644 --- a/Mactrix/Views/ChatView/ChatMessageView.swift +++ b/Mactrix/Views/ChatView/ChatMessageView.swift @@ -82,9 +82,8 @@ struct ChatMessageView: View, UI.MessageEventActions { .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) case let .text(content: content): - Text(content.body.formatAsMarkdown) - .textSelection(.enabled) - .fixedSize(horizontal: false, vertical: true) + FormattedBodyView(messageContent: content) + //Text(content.body.formatAsMarkdown) case let .location(content: content): Text("Location: \(content.body) \(content.geoUri)").textSelection(.enabled) case let .other(msgtype: msgtype, body: body): diff --git a/Mactrix/Views/ChatView/FormattedBodyView.swift b/Mactrix/Views/ChatView/FormattedBodyView.swift new file mode 100644 index 0000000..681979e --- /dev/null +++ b/Mactrix/Views/ChatView/FormattedBodyView.swift @@ -0,0 +1,29 @@ +import MatrixRustSDK +import MessageFormatting +import SwiftUI + +struct FormattedBodyView: View { + let rawBody: String + let htmlBody: String? + + init(messageContent: some MessageContent) { + self.rawBody = messageContent.body + + if let formatted = messageContent.formatted, formatted.format == .html { + self.htmlBody = formatted.body + } else { + self.htmlBody = nil + } + } + + var body: some View { + if let htmlBody { + AttributedTextView(attributedString: parseFormattedBody(htmlBody)) + .fixedSize(horizontal: false, vertical: true) + } else { + Text(rawBody) + .textSelection(.enabled) + .fixedSize(horizontal: false, vertical: true) + } + } +} diff --git a/MactrixLibrary/Package.swift b/MactrixLibrary/Package.swift index fcd001c..6f570c4 100644 --- a/MactrixLibrary/Package.swift +++ b/MactrixLibrary/Package.swift @@ -17,10 +17,12 @@ let package = Package( targets: ["Models"] ), .library(name: "Utils", targets: ["Utils"]), + .library(name: "MessageFormatting", targets: ["MessageFormatting"]), + ], + dependencies: [ + .package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.12.0"), + // .package(url: "https://github.com/matrix-org/matrix-rust-components-swift", from: "25.10.27"), ], - /* dependencies: [ - .package(url: "https://github.com/matrix-org/matrix-rust-components-swift", from: "25.10.27"), - ], */ targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. @@ -37,5 +39,11 @@ let package = Package( .target( name: "Models" ), + .target( + name: "MessageFormatting", + dependencies: [ + "ZMarkupParser", + ] + ), ] ) diff --git a/MactrixLibrary/Sources/MessageFormatting/Parser.swift b/MactrixLibrary/Sources/MessageFormatting/Parser.swift new file mode 100644 index 0000000..f916a15 --- /dev/null +++ b/MactrixLibrary/Sources/MessageFormatting/Parser.swift @@ -0,0 +1,78 @@ +import AppKit +import Foundation +import ZMarkupParser + +@MainActor +let headingParagraphSpacing = MarkupStyleParagraphStyle( + // paragraphSpacing: 5, + paragraphSpacingBefore: 10 +) + +@MainActor +let parser = ZHTMLParserBuilder + .initWithDefault() + .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 12))) + .add( + H1_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 24), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + H2_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 18), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + H3_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 16), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + H4_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 14, weight: .style(.medium)), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + H5_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 12, weight: .style(.semibold)), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + H6_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 12, weight: .style(.semibold)), + paragraphStyle: headingParagraphSpacing + ) + ) + .add( + P_HTMLTagName(), + withCustomStyle: MarkupStyle( + paragraphStyle: MarkupStyleParagraphStyle( + paragraphSpacing: 5, + paragraphSpacingBefore: 5 + ) + ) + ) + .add( + CODE_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: 12, familyName: .familyNames(["Menlo"])) + // backgroundColor: .init(color: NSColor(red: 0.8, green: 0.8, blue: 1, alpha: 0.5)) + ) + ) + .build() + +@MainActor +public func parseFormattedBody(_ body: String) -> NSAttributedString { + return parser.render(body) +} diff --git a/MactrixLibrary/Sources/MessageFormatting/Preview.swift b/MactrixLibrary/Sources/MessageFormatting/Preview.swift new file mode 100644 index 0000000..0f490a5 --- /dev/null +++ b/MactrixLibrary/Sources/MessageFormatting/Preview.swift @@ -0,0 +1,59 @@ +import SwiftUI + +#Preview { + let sample = """ +

This is a title

+ +

This is header 2

+

This is header 3

+

This is header 4

+
This is header 5
+
This is header 6
+ +

+ This is bold, underline, strikethrough. +

+ +

Rendering lists

+ +

Here is a list of bullets.

+ + + +

We can also make ordered lists.

+ +
    +
  1. Item one
  2. +
  3. Item two
  4. +
  5. Item three
  6. +
+ +

Code

+ +

+ This is how a code block looks like. +

+ + + This is code + Another line + + +
+ +

+ An example of inline code. +

+ +

Links

+ + This is a link to google.com + """ + + AttributedTextView(attributedString: parseFormattedBody(sample)) + .frame(height: 600) +} From dac7d3203bf1323ba5ab7c666f3c7a987b2dbeb6 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 10 Mar 2026 14:57:07 +0100 Subject: [PATCH 3/4] Fix layout calculations --- .../MessageFormatting/AttributedTextView.swift | 16 ++++++++-------- .../Sources/MessageFormatting/Parser.swift | 8 ++++---- .../Sources/MessageFormatting/Preview.swift | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift b/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift index faeaa90..10d8c61 100644 --- a/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift +++ b/MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift @@ -10,20 +10,13 @@ public struct AttributedTextView: NSViewRepresentable { public func makeNSView(context: Context) -> NSTextField { let textField = NSTextField(labelWithAttributedString: attributedString) - // Behavior settings textField.isEditable = false textField.isSelectable = true textField.allowsEditingTextAttributes = true - // Enable text wrapping - textField.cell?.wraps = true - textField.cell?.isScrollable = false textField.lineBreakStrategy = .standard textField.lineBreakMode = .byWordWrapping - - // Layout Priority, this helps SwiftUI understand it should stretch vertically - textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.usesSingleLineMode = false return textField } @@ -33,4 +26,11 @@ public struct AttributedTextView: NSViewRepresentable { textField.attributedStringValue = attributedString } } + + public func sizeThatFits(_ proposal: ProposedViewSize, nsView textField: NSTextField, context: Context) -> CGSize? { + guard let width = proposal.width, width > 0, width != .infinity else { return nil } + + textField.preferredMaxLayoutWidth = width + return textField.cell?.cellSize(forBounds: NSRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)) + } } diff --git a/MactrixLibrary/Sources/MessageFormatting/Parser.swift b/MactrixLibrary/Sources/MessageFormatting/Parser.swift index f916a15..8063059 100644 --- a/MactrixLibrary/Sources/MessageFormatting/Parser.swift +++ b/MactrixLibrary/Sources/MessageFormatting/Parser.swift @@ -11,7 +11,7 @@ let headingParagraphSpacing = MarkupStyleParagraphStyle( @MainActor let parser = ZHTMLParserBuilder .initWithDefault() - .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 12))) + .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))) .add( H1_HTMLTagName(), withCustomStyle: MarkupStyle( @@ -43,14 +43,14 @@ let parser = ZHTMLParserBuilder .add( H5_HTMLTagName(), withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 12, weight: .style(.semibold)), + font: MarkupStyleFont(size: 13, weight: .style(.semibold)), paragraphStyle: headingParagraphSpacing ) ) .add( H6_HTMLTagName(), withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 12, weight: .style(.semibold)), + font: MarkupStyleFont(size: 13, weight: .style(.semibold)), paragraphStyle: headingParagraphSpacing ) ) @@ -66,7 +66,7 @@ let parser = ZHTMLParserBuilder .add( CODE_HTMLTagName(), withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 12, familyName: .familyNames(["Menlo"])) + font: MarkupStyleFont(size: 13, familyName: .familyNames(["Menlo"])), // backgroundColor: .init(color: NSColor(red: 0.8, green: 0.8, blue: 1, alpha: 0.5)) ) ) diff --git a/MactrixLibrary/Sources/MessageFormatting/Preview.swift b/MactrixLibrary/Sources/MessageFormatting/Preview.swift index 0f490a5..38ea8ce 100644 --- a/MactrixLibrary/Sources/MessageFormatting/Preview.swift +++ b/MactrixLibrary/Sources/MessageFormatting/Preview.swift @@ -4,6 +4,19 @@ import SwiftUI let sample = """

This is a title

+

Header 1

+

Header 2

+
    +
  • Bullet one
  • +
  • Bullet two
  • +
  • Bullet three
  • +
+
This is code
+    Another code line
+    
+

this was all rendered from Element X

+ +

This is header 2

This is header 3

This is header 4

@@ -55,5 +68,5 @@ import SwiftUI """ AttributedTextView(attributedString: parseFormattedBody(sample)) - .frame(height: 600) + .padding() } From f199ac35526d49072695e3ecc455e25d25945b9e Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 10 Mar 2026 15:18:23 +0100 Subject: [PATCH 4/4] Make it work with custom font sizes --- .../Views/ChatView/FormattedBodyView.swift | 4 +- .../Sources/MessageFormatting/Parser.swift | 118 +++++++++--------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/Mactrix/Views/ChatView/FormattedBodyView.swift b/Mactrix/Views/ChatView/FormattedBodyView.swift index 681979e..d1c0f17 100644 --- a/Mactrix/Views/ChatView/FormattedBodyView.swift +++ b/Mactrix/Views/ChatView/FormattedBodyView.swift @@ -3,6 +3,8 @@ import MessageFormatting import SwiftUI struct FormattedBodyView: View { + @AppStorage("fontSize") private var fontSize = 13 + let rawBody: String let htmlBody: String? @@ -18,7 +20,7 @@ struct FormattedBodyView: View { var body: some View { if let htmlBody { - AttributedTextView(attributedString: parseFormattedBody(htmlBody)) + AttributedTextView(attributedString: parseFormattedBody(htmlBody, baseFontSize: CGFloat(fontSize))) .fixedSize(horizontal: false, vertical: true) } else { Text(rawBody) diff --git a/MactrixLibrary/Sources/MessageFormatting/Parser.swift b/MactrixLibrary/Sources/MessageFormatting/Parser.swift index 8063059..fb75a5b 100644 --- a/MactrixLibrary/Sources/MessageFormatting/Parser.swift +++ b/MactrixLibrary/Sources/MessageFormatting/Parser.swift @@ -3,76 +3,74 @@ import Foundation import ZMarkupParser @MainActor -let headingParagraphSpacing = MarkupStyleParagraphStyle( - // paragraphSpacing: 5, - paragraphSpacingBefore: 10 -) +public func parseFormattedBody(_ body: String, baseFontSize: CGFloat = 13) -> NSAttributedString { + let headingParagraphSpacing = MarkupStyleParagraphStyle( + // paragraphSpacing: 5, + paragraphSpacingBefore: baseFontSize * 0.8 + ) -@MainActor -let parser = ZHTMLParserBuilder - .initWithDefault() - .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))) - .add( - H1_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 24), - paragraphStyle: headingParagraphSpacing + let parser = ZHTMLParserBuilder + .initWithDefault() + .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: baseFontSize))) + .add( + H1_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize * 1.8), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - H2_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 18), - paragraphStyle: headingParagraphSpacing + .add( + H2_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize * 1.4), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - H3_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 16), - paragraphStyle: headingParagraphSpacing + .add( + H3_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize * 1.2), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - H4_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 14, weight: .style(.medium)), - paragraphStyle: headingParagraphSpacing + .add( + H4_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize * 1.1, weight: .style(.medium)), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - H5_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 13, weight: .style(.semibold)), - paragraphStyle: headingParagraphSpacing + .add( + H5_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize, weight: .style(.semibold)), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - H6_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 13, weight: .style(.semibold)), - paragraphStyle: headingParagraphSpacing + .add( + H6_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize, weight: .style(.semibold)), + paragraphStyle: headingParagraphSpacing + ) ) - ) - .add( - P_HTMLTagName(), - withCustomStyle: MarkupStyle( - paragraphStyle: MarkupStyleParagraphStyle( - paragraphSpacing: 5, - paragraphSpacingBefore: 5 + .add( + P_HTMLTagName(), + withCustomStyle: MarkupStyle( + paragraphStyle: MarkupStyleParagraphStyle( + paragraphSpacing: baseFontSize * 0.4, + paragraphSpacingBefore: baseFontSize * 0.4 + ) ) ) - ) - .add( - CODE_HTMLTagName(), - withCustomStyle: MarkupStyle( - font: MarkupStyleFont(size: 13, familyName: .familyNames(["Menlo"])), - // backgroundColor: .init(color: NSColor(red: 0.8, green: 0.8, blue: 1, alpha: 0.5)) + .add( + CODE_HTMLTagName(), + withCustomStyle: MarkupStyle( + font: MarkupStyleFont(size: baseFontSize, familyName: .familyNames(["Menlo"])) + // backgroundColor: .init(color: NSColor(red: 0.8, green: 0.8, blue: 1, alpha: 0.5)) + ) ) - ) - .build() + .build() -@MainActor -public func parseFormattedBody(_ body: String) -> NSAttributedString { return parser.render(body) }