Commit 753f773650
Unsigned
Layout: unified · split
Sources/OrgSwift/Links.swift +22 −8
| @@ -74,6 +74,19 @@ func normalizeOrgLinkTarget(_ target: String) -> String { | ||
| 74 | 74 | target.hasPrefix("file:") ? String(target.dropFirst(5)) : target |
| 75 | 75 | } |
| 76 | 76 | |
| 77 | /// If a link *description* is itself an image reference — `[[img]]`, a `file:` image, or an | |
| 78 | /// image URL — return its source, so the image becomes the link's content. A bare relative | |
| 79 | /// string (`img.png`) is a plain text description, not an image. | |
| 80 | private func descriptionImageSource(_ label: String) -> String? { | |
| 81 | if label.hasPrefix("[["), label.hasSuffix("]]") { | |
| 82 | return String(label.dropFirst(2).dropLast(2)) | |
| 83 | } | |
| 84 | if label.hasPrefix("file:") || label.hasPrefix("http://") || label.hasPrefix("https://") { | |
| 85 | return label | |
| 86 | } | |
| 87 | return nil | |
| 88 | } | |
| 89 | ||
| 77 | 90 | private func renderOrgLink( |
| 78 | 91 | destination rawDestination: String, |
| 79 | 92 | label: String?, |
| @@ -90,15 +103,16 @@ private func renderOrgLink( | ||
| 90 | 103 | return ##"<a href="#\##(escapeHTMLAttribute(id))">\##(text)</a>"## |
| 91 | 104 | } |
| 92 | 105 | let destination = normalizeOrgLinkTarget(rawDestination) |
| 93 | if let label, label.hasPrefix("[["), label.hasSuffix("]]") { | |
| 94 | let source = String(label.dropFirst(2).dropLast(2)) | |
| 95 | if let imageHTML = makeOrgImageHTML(source: source, alt: nil, imageURLResolver: imageURLResolver) { | |
| 96 | let resolvedDestination = linkURLResolver?(destination) ?? destination | |
| 97 | guard let sanitizedURL = sanitizedReadmeLinkURLString(resolvedDestination) else { | |
| 98 | return imageHTML | |
| 99 | } | |
| 100 | return #"<a href="\#(sanitizedURL)">\#(imageHTML)</a>"# | |
| 106 | // A description that is itself an image link (`[[url][file:badge.svg]]`, the common | |
| 107 | // build-badge form, or the double-bracketed `[[url][[badge.svg]]]`) makes the image the | |
| 108 | // clickable content of the link. A plain-text description like `img.png` stays text. | |
| 109 | if let label, let source = descriptionImageSource(label), | |
| 110 | let imageHTML = makeOrgImageHTML(source: source, alt: nil, imageURLResolver: imageURLResolver) { | |
| 111 | let resolvedDestination = linkURLResolver?(destination) ?? destination | |
| 112 | guard let sanitizedURL = sanitizedReadmeLinkURLString(resolvedDestination) else { | |
| 113 | return imageHTML | |
| 101 | 114 | } |
| 115 | return #"<a href="\#(sanitizedURL)">\#(imageHTML)</a>"# | |
| 102 | 116 | } |
| 103 | 117 | |
| 104 | 118 | // A bare `[[image]]` with no description is an inline image. `[[image][text]]` is a |
Tests/OrgSwiftTests/OrgRendererTests.swift +16
| @@ -325,6 +325,22 @@ struct OrgRendererTests { | ||
| 325 | 325 | #expect(!html.contains("<img")) |
| 326 | 326 | } |
| 327 | 327 | |
| 328 | @Test | |
| 329 | func imageDescriptionBecomesClickableImage() { | |
| 330 | // A build-badge: the description is itself an image, so it is the link's content. | |
| 331 | let badge = render("[[https://example.com/builds][file:https://example.com/badge.svg]]") | |
| 332 | #expect(badge.contains(#"<a href="https://example.com/builds"><img src="https://example.com/badge.svg" alt=""></a>"#)) | |
| 333 | ||
| 334 | // The double-bracketed form works the same. | |
| 335 | let bracketed = render("[[https://example.com/builds][[https://example.com/badge.svg]]]") | |
| 336 | #expect(bracketed.contains(#"<a href="https://example.com/builds"><img"#)) | |
| 337 | ||
| 338 | // A plain-text description that merely looks path-like stays text. | |
| 339 | let plain = render("[[https://example.com][see diagram.png]]") | |
| 340 | #expect(plain.contains(#"<a href="https://example.com">see diagram.png</a>"#)) | |
| 341 | #expect(!plain.contains("<img")) | |
| 342 | } | |
| 343 | ||
| 328 | 344 | @Test |
| 329 | 345 | func captionPromotesImageToFigure() { |
| 330 | 346 | let html = render(""" |