Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix markdown-formatter customization accessibility #3101

Merged
merged 8 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### ✅ Added
- Validates file size limit per attachment type defined in Stream's Dashboard [#3105](https://github.com/GetStream/stream-chat-swift/pull/3105)
- Make it easier to customize `ComposerVC.updateContent()` [#3112](https://github.com/GetStream/stream-chat-swift/pull/3112)
- Add support markdown font styling customization [#3101](https://github.com/GetStream/stream-chat-swift/pull/3101)

### 🐞 Fixed
- Fix support for markdown ordered list with all numbers [#3090](https://github.com/GetStream/stream-chat-swift/pull/3090)
- Fix support for markdown italic and bold styles inside snake-styled text [#3094](https://github.com/GetStream/stream-chat-swift/pull/3094)
Expand Down
12 changes: 12 additions & 0 deletions DemoApp/StreamChat/StreamChatWrapper+DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,17 @@ extension StreamChatWrapper {
Components.default.messageActionsVC = DemoChatMessageActionsVC.self
Components.default.reactionsSorting = { $0.type.position < $1.type.position }
Components.default.messageLayoutOptionsResolver = DemoChatMessageLayoutOptionsResolver()

// Customize MarkdownFormatter
let defaultFormatter = DefaultMarkdownFormatter()
defaultFormatter.styles.bodyFont.color = .systemOrange
defaultFormatter.styles.codeFont.color = .systemPurple
defaultFormatter.styles.h1Font.color = .systemBlue
defaultFormatter.styles.h2Font.color = .systemRed
defaultFormatter.styles.h3Font.color = .systemYellow
defaultFormatter.styles.h4Font.color = .systemGreen
defaultFormatter.styles.h5Font.color = .systemBrown
defaultFormatter.styles.h6Font.color = .systemPink
Appearance.default.formatters.markdownFormatter = defaultFormatter
}
}
2 changes: 1 addition & 1 deletion Scripts/updateDependency.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ if [[ $dependency_directory == *"DifferenceKit"* ]]; then
fi

if [[ $dependency_directory == *"SwiftyMarkdown"* ]]; then
# We currently use customized version of SwiftyLineProcessor.swift
# We currently use customized version of SwiftyMarkdown
git restore $output_directory/SwiftyMarkdown/SwiftyLineProcessor.swift || true
git restore $output_directory/SwiftyMarkdown/SwiftyTokeniser.swift || true
fi
109 changes: 101 additions & 8 deletions Sources/StreamChatUI/Appearance+Formatters/MarkdownFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ public protocol MarkdownFormatter {

/// Default implementation for the Markdown formatter
open class DefaultMarkdownFormatter: MarkdownFormatter {
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved
enum Attributes {
enum Code {
static let fontName: String = "CourierNewPSMT"
}
}
public var styles: MarkdownStyles
public var markdownRegexPattern: String

private let markdownRegexPattern: String =
"((?:\\`(.*?)\\`)|(?:\\*{1,2}(.*?)\\*{1,2})|(?:\\~{2}(.*?)\\~{2})|(?:\\_{1,2}(.*?)\\_{1,2})|^(>){1}|(#){1,6}|(=){3,10}|(-){1,3}|(\\d{1,3}\\.)|(?:\\[(.*?)\\])(?:\\((.*?)\\))|(?:\\[(.*?)\\])(?:\\[(.*?)\\])|(\\]\\:))+"
private let defaultMarkdownRegex = "((?:\\`(.*?)\\`)|(?:\\*{1,2}(.*?)\\*{1,2})|(?:\\~{2}(.*?)\\~{2})|(?:\\_{1,2}(.*?)\\_{1,2})|^(>){1}|(#){1,6}|(=){3,10}|(-){1,3}|(\\d{1,3}\\.)|(?:\\[(.*?)\\])(?:\\((.*?)\\))|(?:\\[(.*?)\\])(?:\\[(.*?)\\])|(\\]\\:))+"

public init() {
styles = MarkdownStyles()
markdownRegexPattern = defaultMarkdownRegex
}

private lazy var regex: NSRegularExpression? = {
guard let regex = try? NSRegularExpression(pattern: markdownRegexPattern, options: .anchorsMatchLines) else {
Expand All @@ -43,7 +44,99 @@ open class DefaultMarkdownFormatter: MarkdownFormatter {

open func format(_ string: String) -> NSAttributedString {
let markdownFormatter = SwiftyMarkdown(string: string)
markdownFormatter.code.fontName = Attributes.Code.fontName
modify(swiftyMarkdownFont: markdownFormatter.code, with: styles.codeFont)
modify(swiftyMarkdownFont: markdownFormatter.body, with: styles.bodyFont)
modify(swiftyMarkdownFont: markdownFormatter.link, with: styles.linkFont)
modify(swiftyMarkdownFont: markdownFormatter.h1, with: styles.h1Font)
modify(swiftyMarkdownFont: markdownFormatter.h2, with: styles.h2Font)
modify(swiftyMarkdownFont: markdownFormatter.h3, with: styles.h3Font)
modify(swiftyMarkdownFont: markdownFormatter.h4, with: styles.h4Font)
modify(swiftyMarkdownFont: markdownFormatter.h5, with: styles.h5Font)
modify(swiftyMarkdownFont: markdownFormatter.h6, with: styles.h6Font)
return markdownFormatter.attributedString()
}

private func modify(swiftyMarkdownFont: FontProperties, with font: MarkdownFont) {
if let fontName = font.name {
swiftyMarkdownFont.fontName = fontName
}
if let fontSize = font.size {
swiftyMarkdownFont.fontSize = fontSize
}
if let fontColor = font.color {
swiftyMarkdownFont.color = fontColor
}
if let fontStyle = font.styling?.asSwiftyMarkdownFontStyle() {
swiftyMarkdownFont.fontStyle = fontStyle
}
}
}

/// Configures the font style properties for base Markdown elements
public struct MarkdownStyles {
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved

/// The regular paragraph font.
public var bodyFont: MarkdownFont = .init()

/// The font used for coding blocks in markdown text.
public var codeFont: MarkdownFont = .init()

/// The font used for links found in markdown text.
public var linkFont: MarkdownFont = .init()

/// The font used for H1 headers in markdown text.
public var h1Font: MarkdownFont = .init()

/// The font used for H2 headers in markdown text.
public var h2Font: MarkdownFont = .init()

/// The font used for H3 headers in markdown text.
public var h3Font: MarkdownFont = .init()

/// The font used for H4 headers in markdown text.
public var h4Font: MarkdownFont = .init()

/// The font used for H5 headers in markdown text.
public var h5Font: MarkdownFont = .init()

/// The font used for H6 headers in markdown text.
public var h6Font: MarkdownFont = .init()

public init() {
codeFont.name = "CourierNewPSMT"
}
}

public struct MarkdownFont {
public var name: String?
public var size: Double?
public var color: UIColor?
public var styling: MarkdownFontStyle?

public init() {
name = nil
size = nil
color = nil
styling = nil
}
}

public enum MarkdownFontStyle: Int {
case normal
case bold
case italic
case boldItalic

func asSwiftyMarkdownFontStyle() -> FontStyle {
switch self {
case .normal:
return .normal
case .bold:
return .bold
case .italic:
return .italic
case .boldItalic:
return .boldItalic
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ final class DefaultMarkdownFormatter_Tests: XCTestCase {
XCTAssertEqual(expectedBoldAttributedSubstring, attributedString.attributedSubstring(from: range).string)
} else if let fontAttribute = fontAttribute,
let fontNameAttribute = fontAttribute.fontDescriptor.fontAttributes[.name] as? String,
fontNameAttribute == DefaultMarkdownFormatter.Attributes.Code.fontName {
fontNameAttribute == DefaultMarkdownFormatter().styles.codeFont.name {
XCTAssertEqual(expectedCodeAttributedSubstring, attributedString.attributedSubstring(from: range).string)
} else if let linkAttribute = attributes[.link] as? NSURL,
let url = linkAttribute.absoluteString {
Expand Down
Loading