Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Mactrix.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -109,6 +111,7 @@
34913F6F2EC0F59F003034CB /* Models */,
345E77E72ED9F309002E5B9A /* Utils */,
348E99872F40ABC3009F57A9 /* AsyncAlgorithms */,
349B041B2F5D77E80023FBF4 /* MessageFormatting */,
);
productName = Mactrix;
productReference = 343858422EB394590010922A /* Mactrix.app */;
Expand Down Expand Up @@ -483,6 +486,10 @@
isa = XCSwiftPackageProductDependency;
productName = Models;
};
349B041B2F5D77E80023FBF4 /* MessageFormatting */ = {
isa = XCSwiftPackageProductDependency;
productName = MessageFormatting;
};
34F7225E2EB531F40007B2A4 /* KeychainAccess */ = {
isa = XCSwiftPackageProductDependency;
package = 34F7225D2EB531F40007B2A4 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions Mactrix/Extensions/MatrixRustSDK+MessageContent.swift
Original file line number Diff line number Diff line change
@@ -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 {}
5 changes: 2 additions & 3 deletions Mactrix/Views/ChatView/ChatMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
31 changes: 31 additions & 0 deletions Mactrix/Views/ChatView/FormattedBodyView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import MatrixRustSDK
import MessageFormatting
import SwiftUI

struct FormattedBodyView: View {
@AppStorage("fontSize") private var fontSize = 13

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, baseFontSize: CGFloat(fontSize)))
.fixedSize(horizontal: false, vertical: true)
} else {
Text(rawBody)
.textSelection(.enabled)
.fixedSize(horizontal: false, vertical: true)
}
}
}
14 changes: 11 additions & 3 deletions MactrixLibrary/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -37,5 +39,11 @@ let package = Package(
.target(
name: "Models"
),
.target(
name: "MessageFormatting",
dependencies: [
"ZMarkupParser",
]
),
]
)
36 changes: 36 additions & 0 deletions MactrixLibrary/Sources/MessageFormatting/AttributedTextView.swift
Original file line number Diff line number Diff line change
@@ -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)

textField.isEditable = false
textField.isSelectable = true
textField.allowsEditingTextAttributes = true

textField.lineBreakStrategy = .standard
textField.lineBreakMode = .byWordWrapping
textField.usesSingleLineMode = false

return textField
}

public func updateNSView(_ textField: NSTextField, context: Context) {
if textField.attributedStringValue != attributedString {
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))
}
}
76 changes: 76 additions & 0 deletions MactrixLibrary/Sources/MessageFormatting/Parser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import AppKit
import Foundation
import ZMarkupParser

@MainActor
public func parseFormattedBody(_ body: String, baseFontSize: CGFloat = 13) -> NSAttributedString {
let headingParagraphSpacing = MarkupStyleParagraphStyle(
// paragraphSpacing: 5,
paragraphSpacingBefore: baseFontSize * 0.8
)

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: baseFontSize * 1.4),
paragraphStyle: headingParagraphSpacing
)
)
.add(
H3_HTMLTagName(),
withCustomStyle: MarkupStyle(
font: MarkupStyleFont(size: baseFontSize * 1.2),
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: baseFontSize, 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: baseFontSize * 0.4,
paragraphSpacingBefore: baseFontSize * 0.4
)
)
)
.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()

return parser.render(body)
}
72 changes: 72 additions & 0 deletions MactrixLibrary/Sources/MessageFormatting/Preview.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import SwiftUI

#Preview {
let sample = """
<h1>This is a title</h1>

<h1>Header 1</h1>
<h2>Header 2</h2>
<ul>
<li>Bullet one</li>
<li>Bullet two</li>
<li>Bullet three</li>
</ul>
<pre><code>This is code
Another code line
</code></pre>
<p><em>this was all rendered from Element X</em></p>


<h2>This is header 2</h2>
<h3>This is header 3</h3>
<h4>This is header 4</h4>
<h5>This is header 5</h5>
<h6>This is header 6</h6>

<p>
This <em>is</em> <b>bold</b>, <u>underline</u>, <s>strikethrough</s>.
</p>

<h2>Rendering lists</h2>

<p>Here is a list of bullets.</p>

<ul>
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ul>

<p>We can also make ordered lists.</p>

<ol>
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ol>

<h2>Code</h2>

<p>
This is how a code block looks like.
</p>

<code>
This is code
Another line
</code>

<hr />

<p>
An example of <code>inline</code> code.
</p>

<h2>Links</h2>

This is a link to <a href="https://google.com">google.com</a>
"""

AttributedTextView(attributedString: parseFormattedBody(sample))
.padding()
}