Commit 505bd429eb
Unsigned
Layout: unified · split
Hutch.xcodeproj/project.pbxproj +2 −6
| @@ -11,7 +11,6 @@ | ||
| 11 | 11 | 8B2F89672F69DEB900FC0253 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89642F69DEB900FC0253 /* README.md */; }; |
| 12 | 12 | 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89652F69DEB900FC0253 /* SECURITY.md */; }; |
| 13 | 13 | 8BE082012F80000100000001 /* HutchWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8BE081F62F80000100000001 /* HutchWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; |
| 14 | 8BE09E792F7B20CB00F88693 /* TEST_README.md in Resources */ = {isa = PBXBuildFile; fileRef = 8BE09E782F7B20CB00F88693 /* TEST_README.md */; }; | |
| 15 | 14 | 8BF100032F9A000100000001 /* Markdown in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF100022F9A000100000001 /* Markdown */; }; |
| 16 | 15 | /* End PBXBuildFile section */ |
| 17 | 16 | |
| @@ -55,7 +54,6 @@ | ||
| 55 | 54 | 8B8182C22F6B742B000AE049 /* HutchTests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HutchTests; sourceTree = "<group>"; }; |
| 56 | 55 | 8BDCA5272F6B76B20066AA29 /* HutchTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = HutchTests.xctestplan; sourceTree = "<group>"; }; |
| 57 | 56 | 8BE081F62F80000100000001 /* HutchWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HutchWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 58 | 8BE09E782F7B20CB00F88693 /* TEST_README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TEST_README.md; sourceTree = "<group>"; }; | |
| 59 | 57 | /* End PBXFileReference section */ |
| 60 | 58 | |
| 61 | 59 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ |
| @@ -143,7 +141,6 @@ | ||
| 143 | 141 | 8BE081F52F80000100000001 /* HutchWidgetExtension */, |
| 144 | 142 | 8B8182B82F6B73F3000AE049 /* HutchTests */, |
| 145 | 143 | 8B4B28D22F6704280045FA19 /* Products */, |
| 146 | 8BE09E782F7B20CB00F88693 /* TEST_README.md */, | |
| 147 | 144 | ); |
| 148 | 145 | sourceTree = "<group>"; |
| 149 | 146 | }; |
| @@ -281,7 +278,6 @@ | ||
| 281 | 278 | buildActionMask = 2147483647; |
| 282 | 279 | files = ( |
| 283 | 280 | 8B2F89662F69DEB900FC0253 /* LICENSE in Resources */, |
| 284 | 8BE09E792F7B20CB00F88693 /* TEST_README.md in Resources */, | |
| 285 | 281 | 8B2F89672F69DEB900FC0253 /* README.md in Resources */, |
| 286 | 282 | 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */, |
| 287 | 283 | ); |
| @@ -486,7 +482,7 @@ | ||
| 486 | 482 | "$(inherited)", |
| 487 | 483 | "@executable_path/Frameworks", |
| 488 | 484 | ); |
| 489 | MARKETING_VERSION = 2.4.2; | |
| 485 | MARKETING_VERSION = 2.5.0; | |
| 490 | 486 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 491 | 487 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 492 | 488 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -523,7 +519,7 @@ | ||
| 523 | 519 | "$(inherited)", |
| 524 | 520 | "@executable_path/Frameworks", |
| 525 | 521 | ); |
| 526 | MARKETING_VERSION = 2.4.2; | |
| 522 | MARKETING_VERSION = 2.5.0; | |
| 527 | 523 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 528 | 524 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 529 | 525 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
Hutch/Views/Repositories/MarkdownHTMLRenderer.swift +38 −4
| @@ -10,7 +10,9 @@ private struct MarkdownHTMLRenderer: MarkupVisitor { | ||
| 10 | 10 | typealias Result = String |
| 11 | 11 | |
| 12 | 12 | nonisolated(unsafe) let imageURLResolver: ((String) -> String?)? |
| 13 | private var isRenderingTableHead = false | |
| 13 | nonisolated(unsafe) private var isRenderingTableHead = false | |
| 14 | nonisolated(unsafe) private var currentTableAlignments: [Markdown.Table.ColumnAlignment?] = [] | |
| 15 | nonisolated(unsafe) private var currentTableColumnIndex = 0 | |
| 14 | 16 | |
| 15 | 17 | nonisolated init(imageURLResolver: ((String) -> String?)?) { |
| 16 | 18 | self.imageURLResolver = imageURLResolver |
| @@ -140,7 +142,14 @@ private struct MarkdownHTMLRenderer: MarkupVisitor { | ||
| 140 | 142 | } |
| 141 | 143 | |
| 142 | 144 | nonisolated mutating func visitTable(_ table: Markdown.Table) -> String { |
| 143 | "<table>\n\(visitChildren(of: table))</table>\n" | |
| 145 | let previousAlignments = currentTableAlignments | |
| 146 | let previousColumnIndex = currentTableColumnIndex | |
| 147 | currentTableAlignments = table.columnAlignments | |
| 148 | currentTableColumnIndex = 0 | |
| 149 | let content = visitChildren(of: table) | |
| 150 | currentTableAlignments = previousAlignments | |
| 151 | currentTableColumnIndex = previousColumnIndex | |
| 152 | return "<table>\n\(content)</table>\n" | |
| 144 | 153 | } |
| 145 | 154 | |
| 146 | 155 | nonisolated mutating func visitTableHead(_ tableHead: Markdown.Table.Head) -> String { |
| @@ -160,12 +169,18 @@ private struct MarkdownHTMLRenderer: MarkupVisitor { | ||
| 160 | 169 | } |
| 161 | 170 | |
| 162 | 171 | nonisolated mutating func visitTableRow(_ tableRow: Markdown.Table.Row) -> String { |
| 163 | "<tr>\(visitChildren(of: tableRow))</tr>\n" | |
| 172 | let previousColumnIndex = currentTableColumnIndex | |
| 173 | currentTableColumnIndex = 0 | |
| 174 | let content = visitChildren(of: tableRow) | |
| 175 | currentTableColumnIndex = previousColumnIndex | |
| 176 | return "<tr>\(content)</tr>\n" | |
| 164 | 177 | } |
| 165 | 178 | |
| 166 | 179 | nonisolated mutating func visitTableCell(_ tableCell: Markdown.Table.Cell) -> String { |
| 167 | 180 | let tagName = isRenderingTableHead ? "th" : "td" |
| 168 | return "<\(tagName)>\(visitChildren(of: tableCell))</\(tagName)>" | |
| 181 | let styleAttribute = alignmentStyleAttribute(forColumn: currentTableColumnIndex) | |
| 182 | currentTableColumnIndex += 1 | |
| 183 | return "<\(tagName)\(styleAttribute)>\(visitChildren(of: tableCell))</\(tagName)>" | |
| 169 | 184 | } |
| 170 | 185 | |
| 171 | 186 | nonisolated private mutating func visitChildren(of markup: Markup) -> String { |
| @@ -195,6 +210,25 @@ private struct MarkdownHTMLRenderer: MarkupVisitor { | ||
| 195 | 210 | } |
| 196 | 211 | } |
| 197 | 212 | |
| 213 | nonisolated private func alignmentStyleAttribute(forColumn column: Int) -> String { | |
| 214 | guard column < currentTableAlignments.count, | |
| 215 | let alignment = currentTableAlignments[column] else { | |
| 216 | return "" | |
| 217 | } | |
| 218 | ||
| 219 | let textAlignment: String | |
| 220 | switch alignment { | |
| 221 | case .left: | |
| 222 | textAlignment = "left" | |
| 223 | case .center: | |
| 224 | textAlignment = "center" | |
| 225 | case .right: | |
| 226 | textAlignment = "right" | |
| 227 | } | |
| 228 | ||
| 229 | return " style=\"text-align: \(textAlignment);\"" | |
| 230 | } | |
| 231 | ||
| 198 | 232 | nonisolated private func checkboxHTML(for checkbox: Checkbox) -> String { |
| 199 | 233 | switch checkbox { |
| 200 | 234 | case .checked: |
HutchTests/ReadmeViewTests.swift +10
| @@ -54,6 +54,16 @@ struct MarkdownRenderingTests { | ||
| 54 | 54 | #expect(html.contains("<th>")) |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | @Test | |
| 58 | func markdownTableAlignment() { | |
| 59 | let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| a | b | c |" | |
| 60 | let html = markdownToHTML(input) | |
| 61 | ||
| 62 | #expect(html.contains("text-align: left;")) | |
| 63 | #expect(html.contains("text-align: center;")) | |
| 64 | #expect(html.contains("text-align: right;")) | |
| 65 | } | |
| 66 | ||
| 57 | 67 | @Test |
| 58 | 68 | func markdownStrikethrough() { |
| 59 | 69 | let html = markdownToHTML("~~deleted~~") |