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 `