WIP: add image and custom view rendering - #86
Conversation
|
sirily11 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
Pull request overview
Adds first-class rendering support for Markdown images and swift-markdown block directives, plus an app-provided SwiftUI hook (MarkdownCustomViewBuilder) to override rendering for those payloads. This extends the library’s renderable model (MarkdownRenderable) and updates conversion/rendering so these new elements flow through RenderableDocument → DocumentView.
Changes:
- Introduces
MarkdownImage/MarkdownCustomBlockmodels and adds.image/.customViewcases toMarkdownRenderable, including paragraph splitting around inline images. - Adds
MarkdownCustomViewBuildertoMarkdownRenderConfigand wires it into newMarkdownImageView/MarkdownCustomBlockViewSwiftUI renderers. - Adds unit + snapshot tests and updates the sample app (fixtures + demo builder) to showcase custom rendering.
Reviewed changes
Copilot reviewed 25 out of 34 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/MarkdownTextTests/ImageAndCustomViewTests.swift | Unit tests for image renderables, paragraph splitting, directive parsing, and config builders. |
| Tests/MarkdownTextTests/ImageAndCustomViewSnapshotTests.swift | Snapshot coverage for custom view builder rendering of images and directives. |
| Sources/MarkdownText/UI/MarkdownImageView.swift | New SwiftUI view to render .image (default rendering + custom builder hook). |
| Sources/MarkdownText/UI/MarkdownCustomBlockView.swift | New SwiftUI view to render .customView with fallback to directive content. |
| Sources/MarkdownText/UI/BlockView.swift | Wires .image and .customView into the main block rendering switch. |
| Sources/MarkdownText/Parser/MarkdownParserImpl.swift | Adds swift-markdown ParseOptions toggling for block directives. |
| Sources/MarkdownText/Parser/MarkdownParser.swift | Updates parse(text:config:) to enable directive parsing based on config. |
| Sources/MarkdownText/Parser/MarkdownParseOption.swift | Adds parseBlockDirectives to parsing options. |
| Sources/MarkdownText/Models/RenderableDocument.swift | Ensures .customView contributes fallback content to extracted attributed strings. |
| Sources/MarkdownText/Models/MarkdownRenderConfig+Builders.swift | Updates builder methods to preserve citationConfig + customViewBuilder; adds new builder helpers. |
| Sources/MarkdownText/Models/MarkdownRenderConfig.swift | Adds customViewBuilder to the public render config. |
| Sources/MarkdownText/Models/MarkdownRenderable.swift | Adds .image / .customView cases and IDs. |
| Sources/MarkdownText/Models/MarkdownImage.swift | New model representing parsed images. |
| Sources/MarkdownText/Models/MarkdownCustomView.swift | New custom view payload types and type-erased builder. |
| Sources/MarkdownText/Inline/Markdown+InlineConvertible.swift | Adds Markdown.Image inline conversion (fallback text) and MarkdownImage extraction. |
| Sources/MarkdownText/Block/UnorderedList+.swift | Uses blockRenderables to allow list items to contain split paragraphs/images/directives. |
| Sources/MarkdownText/Block/Paragraph+.swift | Splits paragraphs into multiple renderables when they contain inline images; refactors inline-appending helper. |
| Sources/MarkdownText/Block/OrderedList+.swift | Uses blockRenderables for list item children. |
| Sources/MarkdownText/Block/Document+.swift | Uses blockRenderables to support top-level images/directives in document conversion. |
| Sources/MarkdownText/Block/BlockDirective+.swift | Converts parsed block directives into .customView renderables with fallback content. |
| Sources/MarkdownText/Block/BlockConvertible.swift | Adds blockRenderables(...) to centralize paragraph/directive special-casing. |
| Examples/SwiftStreamingMarkdownSample/SwiftStreamingMarkdownSample/SampleMarkdownTheme.swift | Passes demonstration custom view builder into render config. |
| Examples/SwiftStreamingMarkdownSample/SwiftStreamingMarkdownSample/Resources/Fixtures/kitchen-sink.md | Updates fixture to include image + directive examples. |
| Examples/SwiftStreamingMarkdownSample/SwiftStreamingMarkdownSample/Resources/Assets.xcassets/StreamingMarkdownSample.imageset/streaming-markdown.svg | Adds sample SVG asset for image rendering demo. |
| Examples/SwiftStreamingMarkdownSample/SwiftStreamingMarkdownSample/Resources/Assets.xcassets/StreamingMarkdownSample.imageset/Contents.json | Asset catalog metadata for the sample SVG. |
| Examples/SwiftStreamingMarkdownSample/SwiftStreamingMarkdownSample/Demonstrations.swift | Adds sample customViewBuilder + demo views for image/callout rendering. |
| let parseOption = MarkdownParseOption( | ||
| speculativeRewrite: false, | ||
| parseBlockDirectives: config.customViewBuilder != nil | ||
| ) |
| @ViewBuilder | ||
| private var defaultImageView: some View { | ||
| if let uiImage { | ||
| renderedImage(Image(uiImage: uiImage)) | ||
| } else if let remoteURL { | ||
| AsyncImage(url: remoteURL) { phase in | ||
| switch phase { | ||
| case .empty: | ||
| placeholder(systemImage: "photo", text: image.alternativeText) | ||
| case .success(let loadedImage): | ||
| renderedImage(loadedImage) | ||
| case .failure: | ||
| placeholder(systemImage: "exclamationmark.triangle", text: fallbackText) | ||
| @unknown default: | ||
| placeholder(systemImage: "photo", text: fallbackText) | ||
| } | ||
| } | ||
| } else { | ||
| placeholder(systemImage: "photo", text: fallbackText) | ||
| } | ||
| } |
| import Foundation | ||
| import Markdown | ||
| import SwiftUI | ||
|
|
| if let fileURL, let image = UIImage(contentsOfFile: fileURL.path) { | ||
| return image | ||
| } | ||
|
|
||
| return nil |
|
sirily11 Thanks for contributing! Could you please create an issue to elaborate the ask and requirements? Thanks. |
Thanks. I have created one issue in the issues tab already |
|
thanks for tackling this, sirily11 — image support is something I'd really like to see land, and this is a strong start. a few notes from reading through, in case they're useful:
happy to help with the image piece if that's useful — e.g. a focused, cross-platform image-rendering PR with a safe default — whatever best fits how you and the maintainers want to sequence things. |
Yeah. I would love to split this PR into much more smaller one and only focus on the Image for now. Thanks for the suggestions! |
Summary
Validation
OSS readiness