From d1d30be82b8b491c68728307457ffb101497ea5f Mon Sep 17 00:00:00 2001
From: cirtron <45784494+lcandy2@users.noreply.github.com>
Date: Thu, 9 Jan 2025 02:19:43 +0800
Subject: [PATCH] feat: Feat/timeline with HTML render (#8)
* feat: basic login
* feat: successful login
* feat: save credentials with keychain-swift
* feat: adjust log
* feat: switch instances
* feat: lowercase server address
* fix: non-neodb.social instance cannot login
* feat: user api
* feat: make llm knows keychain-swift is already added
* feat: custom ContentUnavailableView
* feat: profile view
* feat: feat inject
* feat: new profile view design
* feat: injection
* feat: new profile design
* feat: injection next
* feat: redesigned profile
* refactor: replace @StateObject with @EnvironmentObject for AuthService in ContentView, LoginView, and NeoDBApp
* feat: implement user caching and refresh functionality in UserService and ProfileViewModel
- Added caching mechanism for user data in UserService to improve performance.
- Updated getCurrentUser method to accept a forceRefresh parameter for optional cache bypass.
- Implemented clearCache method to allow cache clearing on logout.
- Modified loadUserProfile method in ProfileViewModel to support force refresh.
- Enhanced ProfileView to show loading indicators and support pull-to-refresh for user profile loading.
* refactor: enhance ProfileView layout and loading state handling
- Introduced a new private variable for avatar size to standardize avatar dimensions.
- Refactored profile content display logic to improve readability and maintainability.
- Added a placeholder for the avatar while loading user data.
- Updated loading indicators and error handling for a better user experience.
- Ensured the logout button is disabled when no user is present.
* chore: remove outdated Project Structure documentation and update logout button in ProfileView
- Deleted the Project Structure.md file as it was no longer relevant.
- Updated the logout button in ProfileView to use a text label instead of an icon, enhancing clarity for users.
* refactor: update ContentView to use HomeView and remove unused code
- Replaced the static "Home Feed" text with the HomeView component for better functionality.
- Removed unnecessary commented-out code and debug-related properties to clean up the ContentView structure.
* refactor: update Status model and TimelineService for improved data handling
- Changed Status from a struct to a class, adding new properties for enhanced functionality, including uri, editedAt, and various flags (favourited, reblogged, etc.).
- Updated the TimelineService to modify the timeline fetching method and improve error logging with detailed messages for better debugging.
- Enhanced HomeViewModel to handle loading states and detailed error reporting, improving user experience during data fetch operations.
- Added support for displaying status statistics (replies, reblogs, favourites) in the HomeView.
* refactor: update TimelineService to local only
- Changed logger subsystem from "app.neodb" to "social.neodb.app" for better categorization.
- Updated getTimeline method to include a new 'local' parameter, defaulting to true, to filter statuses.
- Enhanced query item handling to always include 'local=true' in requests.
- Improved logging messages to specify when local statuses are successfully decoded.
* feat: enhance local timeline with HTML content rendering
- Updated TimelineService to display only local statuses and render HTML content properly.
- Introduced HTMLContentView for rendering status content with support for links, mentions, and emoji ratings.
- Added SwiftDown and MarkdownUI packages for HTML parsing and rendering.
- Improved documentation to reflect new features and implementation details.
- Enhanced StatusView with rich text support for better readability and interaction.
---
NeoDB/NeoDB.xcodeproj/project.pbxproj | 34 +++
.../xcshareddata/swiftpm/Package.resolved | 47 ++++-
.../NeoDB/References/Cursor/timeline_local.md | 32 ++-
.../References/Packages/HTML2Markdown.md | 41 ++++
.../Packages/SwiftHTMLToMarkdown.md | 20 ++
.../Articles/GettingStarted.md | 197 ++++++++++++++++++
.../Articles/MigratingToVersion2.md | 80 +++++++
.../Packages/swift-markdown-ui/MarkdownUI.md | 85 ++++++++
NeoDB/NeoDB/Views/Home/HomeView.swift | 39 +++-
9 files changed, 569 insertions(+), 6 deletions(-)
create mode 100644 NeoDB/NeoDB/References/Packages/HTML2Markdown.md
create mode 100644 NeoDB/NeoDB/References/Packages/SwiftHTMLToMarkdown.md
create mode 100755 NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/GettingStarted.md
create mode 100755 NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/MigratingToVersion2.md
create mode 100755 NeoDB/NeoDB/References/Packages/swift-markdown-ui/MarkdownUI.md
diff --git a/NeoDB/NeoDB.xcodeproj/project.pbxproj b/NeoDB/NeoDB.xcodeproj/project.pbxproj
index 7fe1246..e9742a6 100644
--- a/NeoDB/NeoDB.xcodeproj/project.pbxproj
+++ b/NeoDB/NeoDB.xcodeproj/project.pbxproj
@@ -7,8 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
+ 2403E7662D2EF7CC00ED4CB5 /* HTML2Markdown in Frameworks */ = {isa = PBXBuildFile; productRef = 2403E7652D2EF7CC00ED4CB5 /* HTML2Markdown */; };
242688402D2A9E0D00DFAAC1 /* InjectionNext in Frameworks */ = {isa = PBXBuildFile; productRef = 2426883F2D2A9E0D00DFAAC1 /* InjectionNext */; };
245FD2132D2A817A005B55B3 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 245FD2122D2A817A005B55B3 /* KeychainSwift */; };
+ 249B951D2D2EF5C800FE3D95 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 249B951C2D2EF5C800FE3D95 /* MarkdownUI */; };
24D1BE412D2A9F340063B530 /* Inject in Frameworks */ = {isa = PBXBuildFile; productRef = 24D1BE402D2A9F340063B530 /* Inject */; };
/* End PBXBuildFile section */
@@ -73,7 +75,9 @@
files = (
245FD2132D2A817A005B55B3 /* KeychainSwift in Frameworks */,
242688402D2A9E0D00DFAAC1 /* InjectionNext in Frameworks */,
+ 2403E7662D2EF7CC00ED4CB5 /* HTML2Markdown in Frameworks */,
24D1BE412D2A9F340063B530 /* Inject in Frameworks */,
+ 249B951D2D2EF5C800FE3D95 /* MarkdownUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -145,6 +149,8 @@
245FD2122D2A817A005B55B3 /* KeychainSwift */,
2426883F2D2A9E0D00DFAAC1 /* InjectionNext */,
24D1BE402D2A9F340063B530 /* Inject */,
+ 249B951C2D2EF5C800FE3D95 /* MarkdownUI */,
+ 2403E7652D2EF7CC00ED4CB5 /* HTML2Markdown */,
);
productName = NeoDB;
productReference = 24CEACEF2D0DA6200083D4BA /* NeoDB.app */;
@@ -232,6 +238,8 @@
245FD2112D2A817A005B55B3 /* XCRemoteSwiftPackageReference "keychain-swift" */,
2415DF2F2D2A9DCC00DAC07F /* XCRemoteSwiftPackageReference "InjectionNext" */,
24AFFC8E2D2A9F040085A6D0 /* XCRemoteSwiftPackageReference "Inject" */,
+ 249B95192D2EF5B500FE3D95 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
+ 2403E7622D2EF7AE00ED4CB5 /* XCRemoteSwiftPackageReference "HTML2Markdown" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 24CEACF02D0DA6200083D4BA /* Products */;
@@ -631,6 +639,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 2403E7622D2EF7AE00ED4CB5 /* XCRemoteSwiftPackageReference "HTML2Markdown" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/divadretlaw/HTML2Markdown";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 3.0.2;
+ };
+ };
2415DF2F2D2A9DCC00DAC07F /* XCRemoteSwiftPackageReference "InjectionNext" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/InjectionNext";
@@ -647,6 +663,14 @@
minimumVersion = 24.0.0;
};
};
+ 249B95192D2EF5B500FE3D95 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 2.4.1;
+ };
+ };
24AFFC8E2D2A9F040085A6D0 /* XCRemoteSwiftPackageReference "Inject" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/krzysztofzablocki/Inject.git";
@@ -658,6 +682,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 2403E7652D2EF7CC00ED4CB5 /* HTML2Markdown */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2403E7622D2EF7AE00ED4CB5 /* XCRemoteSwiftPackageReference "HTML2Markdown" */;
+ productName = HTML2Markdown;
+ };
2426883F2D2A9E0D00DFAAC1 /* InjectionNext */ = {
isa = XCSwiftPackageProductDependency;
package = 2415DF2F2D2A9DCC00DAC07F /* XCRemoteSwiftPackageReference "InjectionNext" */;
@@ -668,6 +697,11 @@
package = 245FD2112D2A817A005B55B3 /* XCRemoteSwiftPackageReference "keychain-swift" */;
productName = KeychainSwift;
};
+ 249B951C2D2EF5C800FE3D95 /* MarkdownUI */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 249B95192D2EF5B500FE3D95 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */;
+ productName = MarkdownUI;
+ };
24D1BE402D2A9F340063B530 /* Inject */ = {
isa = XCSwiftPackageProductDependency;
package = 24AFFC8E2D2A9F040085A6D0 /* XCRemoteSwiftPackageReference "Inject" */;
diff --git a/NeoDB/NeoDB.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NeoDB/NeoDB.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index de23583..2338c88 100644
--- a/NeoDB/NeoDB.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/NeoDB/NeoDB.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,6 +1,15 @@
{
- "originHash" : "657466b00a4cf16eda52a23bd963af27f6da71d69047ed11c12b2a367c93a6ba",
+ "originHash" : "3e7121260e0594c3a45cf5123fec5e95f05a024e1d9a426ddcda347f395a2ad2",
"pins" : [
+ {
+ "identity" : "html2markdown",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/divadretlaw/HTML2Markdown",
+ "state" : {
+ "revision" : "fe37711094b261d9fceeaf021143002ba37e563e",
+ "version" : "3.0.2"
+ }
+ },
{
"identity" : "inject",
"kind" : "remoteSourceControl",
@@ -27,6 +36,42 @@
"revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608",
"version" : "24.0.0"
}
+ },
+ {
+ "identity" : "networkimage",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/gonzalezreal/NetworkImage",
+ "state" : {
+ "revision" : "2849f5323265386e200484b0d0f896e73c3411b9",
+ "version" : "6.0.1"
+ }
+ },
+ {
+ "identity" : "swift-cmark",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swiftlang/swift-cmark",
+ "state" : {
+ "revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
+ "version" : "0.5.0"
+ }
+ },
+ {
+ "identity" : "swift-markdown-ui",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/gonzalezreal/swift-markdown-ui",
+ "state" : {
+ "revision" : "5f613358148239d0292c0cef674a3c2314737f9e",
+ "version" : "2.4.1"
+ }
+ },
+ {
+ "identity" : "swiftsoup",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/scinfu/SwiftSoup.git",
+ "state" : {
+ "revision" : "0837db354faf9c9deb710dc597046edaadf5360f",
+ "version" : "2.7.6"
+ }
}
],
"version" : 3
diff --git a/NeoDB/NeoDB/References/Cursor/timeline_local.md b/NeoDB/NeoDB/References/Cursor/timeline_local.md
index a12c333..6ecdc5a 100644
--- a/NeoDB/NeoDB/References/Cursor/timeline_local.md
+++ b/NeoDB/NeoDB/References/Cursor/timeline_local.md
@@ -1,7 +1,7 @@
# Timeline Local-Only Implementation
## Overview
-Modified TimelineService to show only local statuses from the instance.
+Modified TimelineService to show only local statuses from the instance and properly render HTML content.
## API Reference
From Mastodon API documentation:
@@ -9,18 +9,46 @@ From Mastodon API documentation:
- Parameter: `local=true` to show only local statuses
- Default: Shows both local and remote statuses (local=false)
+## Content Rendering
+Status content comes in HTML format with the following features:
+- Links with href attributes
+- Mentions with rel attributes
+- Emoji ratings (π for filled stars)
+- Paragraphs with spacing
+
+### HTML Example
+```html
+
ηι η΅ε½±εη§° πππππ
+```
+
+### Implementation Details
+1. Added SwiftDown package for HTML parsing
+2. Created custom AttributedString converter
+3. Added link handling support
+4. Implemented emoji rating display
+
+## Dependencies
+Required SPM packages:
+- SwiftDown: HTML and Markdown parsing
+- SwiftSoup (optional): Advanced HTML manipulation
+
## Implementation Details
1. Added local parameter to URL query
2. Set local=true by default to show only local statuses
3. Improved error logging for better debugging
+4. Added HTML content parsing and rendering
+5. Implemented link handling and styling
## Design Rationale
- Local-only timeline provides more relevant content for NeoDB users
- Reduces noise from remote instances
- Improves performance by reducing data load
- Better content moderation as all content is from the same instance
+- Rich text rendering enhances readability and interaction
## Code Changes
- Modified getTimeline method in TimelineService
- Added local parameter to URLComponents
-- Updated documentation
\ No newline at end of file
+- Added HTML content rendering support
+- Updated documentation
+- Enhanced StatusView with rich text support
\ No newline at end of file
diff --git a/NeoDB/NeoDB/References/Packages/HTML2Markdown.md b/NeoDB/NeoDB/References/Packages/HTML2Markdown.md
new file mode 100644
index 0000000..d73afbb
--- /dev/null
+++ b/NeoDB/NeoDB/References/Packages/HTML2Markdown.md
@@ -0,0 +1,41 @@
+# HTML2Markdown
+
+## What is this?
+
+It's a Swift Package which attempts to convert HTML into Markdown.
+
+## How do I use it?
+
+```swift
+let html = "This is a terrible idea.
I must be daft.
"
+
+do {
+ let dom = try HTMLParser().parse(html: html)
+ let markdown = dom.markdownFormatted(options: .unorderedListBullets)
+ print(markdown)
+} catch {
+ // parsing error
+}
+```
+
+This generates the following markdown string:
+
+```
+This is a *terrible* idea.\nI must be daft.
+```
+
+## What is supported?
+
+* `` and `` for highlighting text
+* ordered and unordered lists (`` and ``)
+* paragraphs (``) and line breaks (`
`)
+* hyperlinks (``)
+
+All other HTML tags are removed.
+
+> Note:
+> `SwiftUI.Text` currently cannot render Markdown lists therefore use the `MarkdownGenerator.Options.unorderedListBullets` option to generate nicer-looking bullets: `β’` instead of `*`.
+
+## License
+
+See [LICENSE](LICSNE)
diff --git a/NeoDB/NeoDB/References/Packages/SwiftHTMLToMarkdown.md b/NeoDB/NeoDB/References/Packages/SwiftHTMLToMarkdown.md
new file mode 100644
index 0000000..3ce8738
--- /dev/null
+++ b/NeoDB/NeoDB/References/Packages/SwiftHTMLToMarkdown.md
@@ -0,0 +1,20 @@
+# Swift HTML -> Markdown
+This package allows you to quickly convert HTML code into Markdown. Normally you want to go from Markdown -> HTML and there are a thousand packages and parsers to do that. However, what if the service you are using (like Mastodon) provides everything in HTML but you want to parse it in Markdown? Well you need to convert it. This package allows you to do just that!
+
+## Supported Flavors of HTML
+| Flavor | Support |
+| -------------------- | --------------------------------------------------- |
+| Mastodon Status HTML (MastodonHTML) | β
|
+| Basic HTML (BasicHTML) | β οΈ (Almost fully supported, tables are not) |
+
+## How to use
+Using the library is pretty simple. The first step is to pick which HTML flavor you are going to be converting. If you are converting a Basic HTML document you can use the `BasicHTML` class. For Mastodon statuses you can use the `MastodonHTML` class.
+
+Once you have picked out the class you will be using, the following code is all you need!
+```swift
+var document = BasicHTML(rawHTML: raw)
+try document.parse()
+
+let markdown = try document.asMarkdown()
+print(markdwon)
+```
diff --git a/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/GettingStarted.md b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/GettingStarted.md
new file mode 100755
index 0000000..d50a585
--- /dev/null
+++ b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/GettingStarted.md
@@ -0,0 +1,197 @@
+# Getting started
+
+Learn how to display and style Markdown text in SwiftUI.
+
+## Creating a Markdown view
+
+A `Markdown` view displays rich structured text using the Markdown syntax. It can display images,
+headings, lists (including task lists), blockquotes, code blocks, tables, and thematic breaks,
+besides styled text and links.
+
+The simplest way of creating a `Markdown` view is to pass a Markdown string to the
+``Markdown/init(_:baseURL:imageBaseURL:)-63py1`` initializer.
+
+```swift
+let markdownString = """
+ ## Try MarkdownUI
+
+ **MarkdownUI** is a native Markdown renderer for SwiftUI
+ compatible with the
+ [GitHub Flavored Markdown Spec](https://github.github.com/gfm/).
+ """
+
+var body: some View {
+ Markdown(markdownString)
+}
+```
+
+![](MarkdownString)
+
+A more convenient way to create a `Markdown` view is by using the
+``Markdown/init(baseURL:imageBaseURL:content:)`` initializer, which takes a Markdown content
+builder in which you can compose the view content, either by providing Markdown strings or by
+using an expressive domain-specific language.
+
+```swift
+var body: some View {
+ Markdown {
+ """
+ ## Using a Markdown Content Builder
+ Use Markdown strings or an expressive domain-specific language
+ to build the content.
+ """
+ Heading(.level2) {
+ "Try MarkdownUI"
+ }
+ Paragraph {
+ Strong("MarkdownUI")
+ " is a native Markdown renderer for SwiftUI"
+ " compatible with the "
+ InlineLink(
+ "GitHub Flavored Markdown Spec",
+ destination: URL(string: "https://github.github.com/gfm/")!
+ )
+ "."
+ }
+ }
+}
+```
+
+![](MarkdownContentBuilder)
+
+You can also create a ``MarkdownContent`` value in your model layer and later create a `Markdown`
+view by passing the content value to the ``Markdown/init(_:baseURL:imageBaseURL:)-42bru``
+initializer. The ``MarkdownContent`` value pre-parses the Markdown string preventing the view from
+doing this step.
+
+```swift
+// Somewhere in the model layer
+let content = MarkdownContent("You can try **CommonMark** [here](https://spec.commonmark.org/dingus/).")
+
+// Later in the view layer
+var body: some View {
+ Markdown(self.model.content)
+}
+```
+
+## Styling Markdown
+
+Markdown views use a basic default theme to display the contents. For more information, read about
+the ``Theme/basic`` theme.
+
+```swift
+Markdown {
+ """
+ You can quote text with a `>`.
+
+ > Outside of a dog, a book is man's best friend. Inside of a
+ > dog it's too dark to read.
+
+ β Groucho Marx
+ """
+}
+```
+
+![](BlockquoteContent)
+
+You can customize the appearance of Markdown content by applying different themes using the
+`markdownTheme(_:)` modifier. For example, you can apply one of the built-in themes, like
+``Theme/gitHub``, to either a Markdown view or a view hierarchy that contains Markdown views.
+
+```swift
+Markdown {
+ """
+ You can quote text with a `>`.
+
+ > Outside of a dog, a book is man's best friend. Inside of a
+ > dog it's too dark to read.
+
+ β Groucho Marx
+ """
+}
+.markdownTheme(.gitHub)
+```
+
+![](GitHubBlockquote)
+
+To override a specific text style from the current theme, use the `markdownTextStyle(_:textStyle:)`
+modifier. The following example shows how to override the ``Theme/code`` text style.
+
+```swift
+Markdown {
+ """
+ Use `git status` to list all new or modified files
+ that haven't yet been committed.
+ """
+}
+.markdownTextStyle(\.code) {
+ FontFamilyVariant(.monospaced)
+ FontSize(.em(0.85))
+ ForegroundColor(.purple)
+ BackgroundColor(.purple.opacity(0.25))
+}
+```
+
+![](CustomInlineCode)
+
+You can also use the `markdownBlockStyle(_:body:)` modifier to override a specific block style. For
+example, you can override only the ``Theme/blockquote`` block style, leaving other block styles
+untouched.
+
+```swift
+Markdown {
+ """
+ You can quote text with a `>`.
+
+ > Outside of a dog, a book is man's best friend. Inside of a
+ > dog it's too dark to read.
+
+ β Groucho Marx
+ """
+}
+.markdownBlockStyle(\.blockquote) { configuration in
+ configuration.label
+ .padding()
+ .markdownTextStyle {
+ FontCapsVariant(.lowercaseSmallCaps)
+ FontWeight(.semibold)
+ BackgroundColor(nil)
+ }
+ .overlay(alignment: .leading) {
+ Rectangle()
+ .fill(Color.teal)
+ .frame(width: 4)
+ }
+ .background(Color.teal.opacity(0.5))
+}
+```
+
+![](CustomBlockquote)
+
+Another way to customize the appearance of Markdown content is to create your own theme. To create
+a theme, start by instantiating an empty ``Theme`` and chain together the different text and block
+styles in a single expression.
+
+```swift
+extension Theme {
+ static let fancy = Theme()
+ .code {
+ FontFamilyVariant(.monospaced)
+ FontSize(.em(0.85))
+ }
+ .link {
+ ForegroundColor(.purple)
+ }
+ // More text styles...
+ .paragraph { configuration in
+ configuration.label
+ .relativeLineSpacing(.em(0.25))
+ .markdownMargin(top: 0, bottom: 16)
+ }
+ .listItem { configuration in
+ configuration.label
+ .markdownMargin(top: .em(0.25))
+ }
+ // More block styles...
+}
+```
diff --git a/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/MigratingToVersion2.md b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/MigratingToVersion2.md
new file mode 100755
index 0000000..393d150
--- /dev/null
+++ b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/Articles/MigratingToVersion2.md
@@ -0,0 +1,80 @@
+# Migrating to MarkdownUI 2
+
+Learn how to migrate existing apps using MarkdownUI 1.x to the latest version of the library.
+
+## Overview
+
+MarkdownUI 2 has been rewritten from scratch and brings a ton of new features and improvements
+like:
+
+- [GitHub Flavored Markdown](https://github.github.com/gfm/) (tables, task lists,
+ strikethrough text, and autolinks)
+- Native SwiftUI rendering
+- Customization support via themes, text styles, and block styles.
+
+These new features come with the cost of a few breaking changes that this guide will help you to
+address.
+
+## Minimum requirements
+
+You can use MarkdownUI 2 on the following platforms:
+
+- macOS 12.0+
+- iOS 15.0+
+- tvOS 15.0+
+- watchOS 8.0+
+
+Some features, like displaying tables or multi-image paragraphs, require macOS 13.0+, iOS 16.0+,
+tvOS 16.0+, and watchOS 9.0+.
+
+## Creating Markdown content
+
+MarkdownUI 2 introduces a new domain-specific language to create Markdown content and no longer
+depends on [gonzalezreal/SwiftCommonMark](https://github.com/gonzalezreal/SwiftCommonMark).
+
+One significant difference when using MarkdownUI 2 is that ``MarkdownContent`` replaces `Document`
+by providing similar functionality.
+
+Another thing to be aware of is the different naming of some of the types you use to compose
+Markdown content:
+
+- Use ``Blockquote`` instead of `BlockQuote`.
+- Use ``NumberedList`` instead of `OrderedList`.
+- Use ``BulletedList`` instead of `BulletList`.
+- Use ``InlineImage`` instead of `Image`.
+- Use ``InlineLink`` instead of `Link`.
+- Use ``Code`` instead of `InlineCode`.
+
+## Loading asset images
+
+MarkdownUI 2 introduces the ``ImageProvider`` protocol and its conforming types
+``DefaultImageProvider`` and ``AssetImageProvider``. These types and the new
+`markdownImageProvider(_:)` modifier replace the `MarkdownImageHandler` type and
+the `setImageHandler(_:forURLScheme:)` modifier.
+
+The following example shows how to configure the asset image provider to load images from the
+main bundle.
+
+```swift
+Markdown {
+ "![A dog](dog)"
+ "β Photo by AndrΓ© Spieker"
+}
+.markdownImageProvider(.asset)
+```
+
+## Customizing link behavior
+
+The `onOpenMarkdownLink(perform:)` modifier in MarkdownUI 1.x was provided to enable link behavior
+customization in macOS 11.0, iOS 14.0, and tvOS 14.0. This modifier is no longer available in
+MarkdownUI 2 since it does not support those platforms. However, you can customize the link
+behavior by setting the `openURL` environment value with a custom `OpenURLAction`.
+
+## Styling Markdown
+
+MarkdownUI 1.x offered a few options to customize the content appearance. In contrast, MarkdownUI 2
+brings the new ``Theme``, ``TextStyle``, and ``BlockStyle`` types that let you apply a custom
+appearance to blocks and text inlines in a Markdown view.
+
+Consequently, the `MarkdownStyle` type, all of its subtypes, and the `markdownStyle(_:)` modifier
+are no longer available in MarkdownUI 2.
diff --git a/NeoDB/NeoDB/References/Packages/swift-markdown-ui/MarkdownUI.md b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/MarkdownUI.md
new file mode 100755
index 0000000..385a3eb
--- /dev/null
+++ b/NeoDB/NeoDB/References/Packages/swift-markdown-ui/MarkdownUI.md
@@ -0,0 +1,85 @@
+# ``MarkdownUI``
+
+Display and customize Markdown text in SwiftUI.
+
+## Overview
+
+MarkdownUI is a powerful library for displaying and customizing Markdown text in SwiftUI. It is
+compatible with the [GitHub Flavored Markdown Spec](https://github.github.com/gfm/) and can
+display images, headings, lists (including task lists), blockquotes, code blocks, tables,
+and thematic breaks, besides styled text and links.
+
+MarkdownUI offers comprehensible theming features to customize how it displays Markdown text.
+You can use the built-in themes, create your own or override specific text and block styles.
+
+![A MarkdownUI view that displays a heading, paragraph, code block, and table using different themes](MarkdownUI)
+
+## Topics
+
+### Essentials
+
+-
+
+### Upgrade guides
+
+-
+
+### Displaying Markdown
+
+- ``Markdown``
+
+### Customizing Appearance
+
+- ``Theme``
+- ``TextStyle``
+- ``ForegroundColor``
+- ``BackgroundColor``
+- ``FontFamily``
+- ``FontSize``
+- ``FontStyle``
+- ``FontWeight``
+- ``FontWidth``
+- ``StrikethroughStyle``
+- ``UnderlineStyle``
+- ``FontFamilyVariant``
+- ``FontCapsVariant``
+- ``FontDigitVariant``
+- ``TextKerning``
+- ``TextTracking``
+- ``BlockStyle``
+- ``BlockConfiguration``
+- ``ListMarkerConfiguration``
+- ``TaskListMarkerConfiguration``
+- ``TableBackgroundStyle``
+- ``TableBorderStyle``
+- ``TableCellConfiguration``
+
+### Formatting content
+
+- ``MarkdownContent``
+- ``Paragraph``
+- ``Heading``
+- ``Blockquote``
+- ``CodeBlock``
+- ``BulletedList``
+- ``NumberedList``
+- ``ListItem``
+- ``TaskList``
+- ``TaskListItem``
+- ``TextTable``
+- ``ThematicBreak``
+- ``InlineContent``
+- ``Code``
+- ``Emphasis``
+- ``Strong``
+- ``Strikethrough``
+- ``InlineImage``
+- ``InlineLink``
+
+### Extensibility
+
+- ``ImageProvider``
+- ``DefaultImageProvider``
+- ``AssetImageProvider``
+- ``CodeSyntaxHighlighter``
+- ``PlainTextCodeSyntaxHighlighter``
diff --git a/NeoDB/NeoDB/Views/Home/HomeView.swift b/NeoDB/NeoDB/Views/Home/HomeView.swift
index adeab3d..acee98e 100644
--- a/NeoDB/NeoDB/Views/Home/HomeView.swift
+++ b/NeoDB/NeoDB/Views/Home/HomeView.swift
@@ -1,5 +1,7 @@
import SwiftUI
import OSLog
+import HTML2Markdown
+import MarkdownUI
@MainActor
class HomeViewModel: ObservableObject {
@@ -133,6 +135,7 @@ struct HomeView: View {
struct StatusView: View {
let status: Status
+ @Environment(\.openURL) private var openURL
var body: some View {
VStack(alignment: .leading, spacing: 12) {
@@ -176,8 +179,7 @@ struct StatusView: View {
}
// Content
- Text(status.content)
- .font(.body)
+ HTMLContentView(htmlContent: status.content)
.textSelection(.enabled)
// Media
@@ -213,7 +215,6 @@ struct StatusView: View {
}
.padding()
.background(Color(.systemBackground))
- .enableInjection()
}
#if DEBUG
@@ -254,4 +255,36 @@ struct StatusView: View {
}
}
}
+}
+
+struct HTMLContentView: View {
+ let htmlContent: String
+ @Environment(\.openURL) private var openURL
+
+ var body: some View {
+ if let markdown = convertHTMLToMarkdown(htmlContent) {
+ Markdown(markdown)
+ .textSelection(.enabled)
+ .padding(.vertical, 4)
+ } else {
+ Text(htmlContent)
+ .textSelection(.enabled)
+ }
+ }
+
+ private func convertHTMLToMarkdown(_ html: String) -> String? {
+ // Remove extra newlines and spaces
+ let cleanedHTML = html.replacingOccurrences(of: "\n", with: "")
+ .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression)
+
+ do {
+ let dom = try HTMLParser().parse(html: cleanedHTML)
+ // Use bullets for unordered lists for better SwiftUI Text compatibility
+ let markdown = dom.markdownFormatted(options: .unorderedListBullets)
+ return markdown
+ } catch {
+ print("Error parsing HTML: \(error)")
+ return nil
+ }
+ }
}
\ No newline at end of file