diff --git a/readeck/UI/Settings/MarkdownContentView.swift b/readeck/UI/Settings/MarkdownContentView.swift new file mode 100644 index 0000000..d105eb4 --- /dev/null +++ b/readeck/UI/Settings/MarkdownContentView.swift @@ -0,0 +1,35 @@ +import SwiftUI +import MarkdownUI + +/// A custom view that renders Markdown content using the MarkdownUI library. +/// This view encapsulates the Markdown rendering logic, making it easy to swap +/// the underlying Markdown library if needed in the future. +struct MarkdownContentView: View { + let content: String + + var body: some View { + Markdown(content) + .textSelection(.enabled) + } +} + +#Preview { + ScrollView { + MarkdownContentView(content: """ +# Heading 1 + +This is a paragraph with **bold** and *italic* text. + +## Heading 2 + +- List item 1 +- List item 2 +- List item 3 + +### Heading 3 + +Another paragraph with [a link](https://example.com). +""") + .padding() + } +} diff --git a/readeck/UI/Settings/ReleaseNotesView.swift b/readeck/UI/Settings/ReleaseNotesView.swift index 76724af..d91999b 100644 --- a/readeck/UI/Settings/ReleaseNotesView.swift +++ b/readeck/UI/Settings/ReleaseNotesView.swift @@ -1,56 +1,14 @@ import SwiftUI -extension AttributedString { - init(styledMarkdown markdownString: String) throws { - var output = try AttributedString( - markdown: markdownString, - options: .init( - allowsExtendedAttributes: true, - interpretedSyntax: .full, - failurePolicy: .returnPartiallyParsedIfPossible - ), - baseURL: nil - ) - - for (intentBlock, intentRange) in output.runs[AttributeScopes.FoundationAttributes.PresentationIntentAttribute.self].reversed() { - guard let intentBlock = intentBlock else { continue } - for intent in intentBlock.components { - switch intent.kind { - case .header(level: let level): - switch level { - case 1: - output[intentRange].font = .system(.title).bold() - case 2: - output[intentRange].font = .system(.title2).bold() - case 3: - output[intentRange].font = .system(.title3).bold() - default: - break - } - default: - break - } - } - - if intentRange.lowerBound != output.startIndex { - output.characters.insert(contentsOf: "\n\n", at: intentRange.lowerBound) - } - } - - self = output - } -} - struct ReleaseNotesView: View { @Environment(\.dismiss) private var dismiss var body: some View { NavigationView { ScrollView { - VStack(alignment: .leading, spacing: 16) { - if let attributedString = loadReleaseNotes() { - Text(attributedString) - .textSelection(.enabled) + VStack(alignment: .leading, spacing: 0) { + if let markdownContent = loadReleaseNotes() { + MarkdownContentView(content: markdownContent) .padding() } else { Text("Unable to load release notes") @@ -71,13 +29,12 @@ struct ReleaseNotesView: View { } } - private func loadReleaseNotes() -> AttributedString? { + private func loadReleaseNotes() -> String? { guard let url = Bundle.main.url(forResource: "RELEASE_NOTES", withExtension: "md"), - let markdownContent = try? String(contentsOf: url), - let attributedString = try? AttributedString(styledMarkdown: markdownContent) else { + let markdownContent = try? String(contentsOf: url) else { return nil } - return attributedString + return markdownContent } }