Skip to content

Commit

Permalink
feat: Feat/timeline with HTML render (#8)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
lcandy2 authored Jan 8, 2025
1 parent 7970a60 commit d1d30be
Show file tree
Hide file tree
Showing 9 changed files with 569 additions and 6 deletions.
34 changes: 34 additions & 0 deletions NeoDB/NeoDB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -145,6 +149,8 @@
245FD2122D2A817A005B55B3 /* KeychainSwift */,
2426883F2D2A9E0D00DFAAC1 /* InjectionNext */,
24D1BE402D2A9F340063B530 /* Inject */,
249B951C2D2EF5C800FE3D95 /* MarkdownUI */,
2403E7652D2EF7CC00ED4CB5 /* HTML2Markdown */,
);
productName = NeoDB;
productReference = 24CEACEF2D0DA6200083D4BA /* NeoDB.app */;
Expand Down Expand Up @@ -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 */;
Expand Down Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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" */;
Expand All @@ -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" */;
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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
Expand Down
32 changes: 30 additions & 2 deletions NeoDB/NeoDB/References/Cursor/timeline_local.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
# 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:
- Endpoint: GET `/api/v1/timelines/public`
- 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
<p>看過 <a href="https://neodb.social/movie/xxx" rel="nofollow">电影名称</a> 🌕🌕🌕🌕🌕 </p>
```

### 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
- Added HTML content rendering support
- Updated documentation
- Enhanced StatusView with rich text support
41 changes: 41 additions & 0 deletions NeoDB/NeoDB/References/Packages/HTML2Markdown.md
Original file line number Diff line number Diff line change
@@ -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 = "<p>This is a <em>terrible</em> idea.<br/>I must be daft.</p>"

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?

* `<strong>` and `<em>` for highlighting text
* ordered and unordered lists (`<ol>` and `<ul>`)
* paragraphs (`<p>`) and line breaks (`<br>`)
* hyperlinks (`<a href="...">`)

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)
20 changes: 20 additions & 0 deletions NeoDB/NeoDB/References/Packages/SwiftHTMLToMarkdown.md
Original file line number Diff line number Diff line change
@@ -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)
```
Loading

0 comments on commit d1d30be

Please sign in to comment.