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
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ HTML blocks may render as plain text or be ignored depending on parser support.

</details>

## Mermaid diagram fallback
## Mermaid diagram

Mermaid is intentionally included as an unimplemented markdown feature. Until a diagram renderer exists, this should remain readable as a fenced code block.
Mermaid fences render as interactive diagrams. Configure them via `MermaidConfig`; setting `.disabled` falls back to a readable fenced code block.

```mermaid
flowchart TD
Expand Down
18 changes: 18 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let package = Package(
targets: ["SwiftStreamingMarkdown"])
],
dependencies: [
.package(url: "https://github.com/lukilabs/beautiful-mermaid-swift", exact: "1.0.4"),
.package(url: "https://github.com/ordo-one/equatable", exact: "1.4.1"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", exact: "1.19.4"),
.package(url: "https://github.com/swiftlang/swift-markdown.git", exact: "0.7.3"),
Expand All @@ -28,7 +29,8 @@ let package = Package(
.product(name: "Markdown", package: "swift-markdown"),
.product(name: "HighlightSwift", package: "highlightswift"),
.product(name: "iosMath", package: "iosMath"),
.product(name: "Shimmer", package: "SwiftUI-Shimmer")
.product(name: "Shimmer", package: "SwiftUI-Shimmer"),
.product(name: "BeautifulMermaid", package: "beautiful-mermaid-swift")
],
path: "Sources/MarkdownText",
resources: [
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The renderer targets the subset of CommonMark + GitHub-flavored Markdown that LL
- [x] `Inline code`
- [x] Inline links
- [x] Fenced code blocks with language tag
- [x] Mermaid diagrams — rendered as interactive diagrams for `mermaid`-tagged fenced code blocks; theme and opt-out via `MermaidConfig` (`withMermaidConfig`, `.disabled`)
- [x] Block quotes (with nested inlines, lists, and citations)
- [x] Ordered lists
- [x] Unordered lists (with nesting)
Expand All @@ -121,7 +122,7 @@ The renderer targets the subset of CommonMark + GitHub-flavored Markdown that LL
- [ ] Raw HTML (`<details>`, `<kbd>`, `<aside>`, …) — kept inline as text
- [ ] GitHub alerts (`> [!NOTE]`) — rendered as plain block quotes
- [ ] Container directives (`::: warning … :::`) and admonitions (`!!! note`)
- [ ] Mermaid / PlantUML diagrams — rendered as fenced code
- [ ] PlantUML diagrams — rendered as fenced code

The bundled `Kitchen Sink` demonstration in the sample app exercises every item above so you can verify the fallback behavior on-device.

Expand Down
2 changes: 2 additions & 0 deletions Sources/MarkdownText/Block/CodeBlock+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ extension CodeBlock: BlockConvertible {
func convert(attributeContainer: NSAttributeContainer, config: MarkdownRenderConfig) -> MarkdownRenderable {
if self.language == LaTexPreProcessorImpl.customCodeType {
return .latex(id: self.id, content: self.code)
} else if self.language?.lowercased() == "mermaid" && config.mermaidConfig.isEnabled {
return .mermaidView(id: self.id, code: self.code)
} else {
return .codeBlock(id: self.id, language: self.language, code: self.code)
}
Expand Down
110 changes: 96 additions & 14 deletions Sources/MarkdownText/Models/MarkdownRenderConfig+Builders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -41,7 +44,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -60,7 +66,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -79,7 +88,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -98,7 +110,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -117,7 +132,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -136,7 +154,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -156,7 +177,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -175,7 +199,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: value,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -194,7 +221,32 @@ extension MarkdownRenderConfig {
codeBlockConfig: value,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

/// Returns a copy with `mermaidConfig` replaced.
public func withMermaidConfig(_ value: MermaidConfig) -> MarkdownRenderConfig {
MarkdownRenderConfig(
shouldAnimateText: shouldAnimateText,
blockQuoteStyle: blockQuoteStyle,
Comment thread
28pins marked this conversation as resolved.
headingStyle: headingStyle,
orderedListStyle: orderedListStyle,
paragraphStyle: paragraphStyle,
tableStyle: tableStyle,
inlineStyle: inlineStyle,
textContextMenu: textContextMenu,
citationConfig: citationConfig,
codeBlockConfig: codeBlockConfig,
mermaidConfig: value,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor,
imageConfig: imageConfig, : was missing
blockQuoteAlertStyle: blockQuoteAlertStyle : was missing
)
}

Expand All @@ -214,7 +266,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: value,
thematicBreakColor: thematicBreakColor
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -233,7 +288,10 @@ extension MarkdownRenderConfig {
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: value
thematicBreakColor: value,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: blockQuoteAlertStyle
)
}

Expand All @@ -253,7 +311,31 @@ extension MarkdownRenderConfig {
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor,
imageConfig: value
imageConfig: value,
mermaidConfig: mermaidConfig, : was missing
blockQuoteAlertStyle: blockQuoteAlertStyle : was missing
)
}

/// Returns a copy with `blockQuoteAlertStyle` replaced.
public func withBlockQuoteAlertStyle(_ value: BlockQuoteAlertStyle) -> MarkdownRenderConfig {
MarkdownRenderConfig(
shouldAnimateText: shouldAnimateText,
blockQuoteStyle: blockQuoteStyle,
headingStyle: headingStyle,
orderedListStyle: orderedListStyle,
paragraphStyle: paragraphStyle,
tableStyle: tableStyle,
inlineStyle: inlineStyle,
textContextMenu: textContextMenu,
citationConfig: citationConfig,
codeBlockConfig: codeBlockConfig,
blockSpacing: blockSpacing,
textSelectionConfig: textSelectionConfig,
thematicBreakColor: thematicBreakColor,
mermaidConfig: mermaidConfig,
imageConfig: imageConfig,
blockQuoteAlertStyle: value
)
}
}
}
4 changes: 4 additions & 0 deletions Sources/MarkdownText/Models/MarkdownRenderConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public struct MarkdownRenderConfig: Hashable, Sendable {
public let citationConfig: CitationConfig
/// Configuration that controls code-block syntax-highlighting styling.
public let codeBlockConfig: CodeBlockConfig
/// Configuration that controls Mermaid diagram styling.
public let mermaidConfig: MermaidConfig
/// Vertical spacing between adjacent blocks (paragraphs, headings,
/// code blocks, lists, etc.). Defaults to 30.
public let blockSpacing: CGFloat
Expand Down Expand Up @@ -280,6 +282,7 @@ public struct MarkdownRenderConfig: Hashable, Sendable {
textContextMenu: TextContextMenu? = nil,
citationConfig: CitationConfig = .default,
codeBlockConfig: CodeBlockConfig = .default,
mermaidConfig: MermaidConfig = .default,
blockSpacing: CGFloat = MarkdownRenderConfig.defaultBlockSpacing,
textSelectionConfig: TextSelectionConfig = .default,
thematicBreakColor: Color = MarkdownRenderConfig.defaultThematicBreakColor,
Expand All @@ -295,6 +298,7 @@ public struct MarkdownRenderConfig: Hashable, Sendable {
self.textContextMenu = textContextMenu
self.citationConfig = citationConfig
self.codeBlockConfig = codeBlockConfig
self.mermaidConfig = mermaidConfig
self.blockSpacing = blockSpacing
self.textSelectionConfig = textSelectionConfig
self.thematicBreakColor = thematicBreakColor
Expand Down
4 changes: 4 additions & 0 deletions Sources/MarkdownText/Models/MarkdownRenderable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ indirect enum MarkdownRenderable: Identifiable, Equatable, @unchecked Sendable {
/// To be rendered as a code block
case codeBlock(id: String, language: String?, code: String)

/// To be rendered as a Mermaid diagram (a code block tagged with `mermaid`)
case mermaidView(id: String, code: String)

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

Expand All @@ -54,6 +57,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 .mermaidView(let id, _): return id
case .table(let id, _, _, _): return id
case .thematicBreak(let id): return id
case .blockQuote(let id, _): return id
Expand Down
Loading