Skip to content
Open
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
11 changes: 10 additions & 1 deletion Sources/MarkdownText/Block/Table+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ extension Markdown.Table: BlockConvertible {
let headerColumnCount = self.head.childCount
let isWellFormed = self.body.children.allSatisfy { $0.childCount == headerColumnCount }
let rawMarkdown = isWellFormed ? self.format() : ""
return .table(id: self.id, headers: headerCells, rows: rows, rawMarkdown: rawMarkdown)
let alignments = (0..<headerCells.count).map { index -> MarkdownColumnAlignment in
guard index < self.columnAlignments.count else { return .leading }
switch self.columnAlignments[index] {
case .left: return .leading
case .center: return .center
Comment on lines +41 to +45
case .right: return .trailing
case .none: return .leading
}
}
return .table(id: self.id, headers: headerCells, rows: rows, alignments: alignments, rawMarkdown: rawMarkdown)
}
}
13 changes: 11 additions & 2 deletions Sources/MarkdownText/Models/MarkdownRenderable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ import AppKit
/// Markdown element representation that is ready to be rendered by a SwiftUI View
/// The representation already have all the parsing and processing completed to minimize rendering overhead on UI thread.
/// This data structure is not thread safe due to the usage of `NSMutableAttributedString`, this needs to be addressed as a future improvement
/// Per-column text alignment declared by a GFM table's delimiter row
/// (`:---`, `:---:`, `---:`). `.leading` is the GFM default for a column that
/// declares nothing.
enum MarkdownColumnAlignment: Equatable, Sendable {
case leading
case center
case trailing
}

indirect enum MarkdownRenderable: Identifiable, Equatable, @unchecked Sendable {

/// To be rendered as a paragraph
Expand All @@ -34,7 +43,7 @@ indirect enum MarkdownRenderable: Identifiable, Equatable, @unchecked Sendable {
case codeBlock(id: String, language: String?, code: String)

/// To be rendered as a table
case table(id: String, headers: [NSMutableAttributedString], rows: [[NSMutableAttributedString]], rawMarkdown: String)
case table(id: String, headers: [NSMutableAttributedString], rows: [[NSMutableAttributedString]], alignments: [MarkdownColumnAlignment], rawMarkdown: String)

/// To be rendered as thematic break
case thematicBreak(id: String)
Expand All @@ -54,7 +63,7 @@ indirect enum MarkdownRenderable: Identifiable, Equatable, @unchecked Sendable {
case .orderedList(let id, _): return id
case .unorderedList(let id, _, _): return id
case .codeBlock(let id, _, _): return id
case .table(let id, _, _, _): return id
case .table(let id, _, _, _, _): return id
case .thematicBreak(let id): return id
case .blockQuote(let id, _): return id
case .image(let id, _): return id
Expand Down
4 changes: 2 additions & 2 deletions Sources/MarkdownText/Models/RenderableDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ extension MarkdownRenderable {
return items.plainText(separator: "\n")
case .codeBlock(_, _, let code):
return code
case .table(_, let headers, let rows, _):
case .table(_, let headers, let rows, _, _):
let headerLine = headers.map { $0.string }.joined(separator: "\t")
let rowLines = rows.map { row in row.map { $0.string }.joined(separator: "\t") }
return ([headerLine] + rowLines).joined(separator: "\n")
Expand Down Expand Up @@ -135,7 +135,7 @@ extension MarkdownRenderable {
return items.flatMap { $0.attributedStrings() }
case .unorderedList(_, let items, _):
return items.flatMap { $0.attributedStrings() }
case .table(_, let headers, let rows, _):
case .table(_, let headers, let rows, _, _):
return headers + rows.flatMap { $0 }
default:
return []
Expand Down
3 changes: 2 additions & 1 deletion Sources/MarkdownText/UI/BlockView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ struct SingleBlockView: View {
code: code)
case .thematicBreak:
ThematicBreakView()
case .table(_, let headers, let rows, let rawMarkdown):
case .table(_, let headers, let rows, let alignments, let rawMarkdown):
TableView(headings: headers,
rows: rows,
alignments: alignments,
rawMarkdown: rawMarkdown)
case .blockQuote(_, let item):
BlockQuoteView(item: item)
Expand Down
37 changes: 31 additions & 6 deletions Sources/MarkdownText/UI/TableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct TableView: View {

let headings: [AttributedString]
let rows: [[RowContent]]
let alignments: [MarkdownColumnAlignment]
let columnMaxWidths: [Int: CGFloat]

private let defaultMaxColumnWidth: CGFloat = 200
Expand All @@ -32,7 +33,8 @@ struct TableView: View {

private let rawMarkdown: String

init(headings: [NSMutableAttributedString], rows: [[NSMutableAttributedString]], columnMaxWidths: [Int: CGFloat] = [:], rawMarkdown: String = "") {
init(headings: [NSMutableAttributedString], rows: [[NSMutableAttributedString]], alignments: [MarkdownColumnAlignment] = [], columnMaxWidths: [Int: CGFloat] = [:], rawMarkdown: String = "") {
self.alignments = alignments
self.headings = headings.map { AttributedString($0) }
self.rows = rows.map { row in
row.map { content in
Expand All @@ -52,13 +54,36 @@ struct TableView: View {
return rows.count
}

/// The GFM delimiter row's alignment for a column, defaulting to `.leading`
/// for a table that declares none.
private func alignment(forColumn index: Int) -> MarkdownColumnAlignment {
guard index < alignments.count else { return .leading }
return alignments[index]
}

private func textAlignment(forColumn index: Int) -> TextAlignment {
switch alignment(forColumn: index) {
case .leading: return .leading
case .center: return .center
case .trailing: return .trailing
}
}

private func frameAlignment(forColumn index: Int) -> Alignment {
switch alignment(forColumn: index) {
case .leading: return .topLeading
case .center: return .top
case .trailing: return .topTrailing
}
}

private func headerView(colIdx: Int) -> some View {
HStack(spacing: 0) {
Text(headings[colIdx])
.foregroundStyle(config.tableStyle.headerTextColor)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.multilineTextAlignment(textAlignment(forColumn: colIdx))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: frameAlignment(forColumn: colIdx))
.if(config.shouldAnimateText) { view in
view.fadeInTextTransition(attributedString: headings[colIdx])
}
Expand Down Expand Up @@ -106,7 +131,7 @@ struct TableView: View {
case .containsAttachment(let nsAttributedString):
HStack(spacing: 0) {
ParagraphView(contents: applyTypographyThemingAndGetContent(nsAttributedString))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: frameAlignment(forColumn: colIdx))
.accessibilityValue(String.itemPositionInTable(rowIndex: rowIdx + 2, totalRow: numOfRows + 1, columnIndex: colIdx + 1, totalColumn: headings.count))
Comment on lines 131 to 135
Spacer()
}
Expand All @@ -119,8 +144,8 @@ struct TableView: View {
Text(attributedString)
.foregroundStyle(config.tableStyle.regularTextColor)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.multilineTextAlignment(textAlignment(forColumn: colIdx))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: frameAlignment(forColumn: colIdx))
.if(config.shouldAnimateText) { view in
view.fadeInTextTransition(attributedString: attributedString)
}
Expand Down