Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Bookmarks #403

Merged
merged 29 commits into from
Mar 7, 2025
Merged

Conversation

levinoeninckx
Copy link
Contributor

No description provided.

@linkdotnet
Copy link
Owner

linkdotnet commented Feb 26, 2025

That is already a good start - of course the simple button is a bit rough, but I get your idea.
A small detail: The "Bookmarks" shouldn't be part of the RSS Feed:
image

I showed cases that because adding another icon on the navbar might be just too much. Maybe we can merge the Booksmarks with the Archive into dropdown. I am really open to new ideas here.

For the icon. We can do one of those two approaches. Number 1: The simple one:

  1. Use a website like https://heroicons.com/outline and copy&paste the SVG into a button and we are done. Or better: https://flowbite.com/icons/ (they have more)
  2. What I outlined earlier: https://github.com/linkdotnet/Blog/blob/master/src/LinkDotNet.Blog.Web/wwwroot/css/fonts/README.md
    Go to https://icomoon.io/app/#/select, upload the "blog.json" and add the two new icons and download the the new woff files and so on.

The further I think of it, probably the more I want to replace everything with approach 1 as it is just way too annoying with approach 2.

@levinoeninckx
Copy link
Contributor Author

levinoeninckx commented Feb 26, 2025

That is already a good start - of course the simple button is a bit rough, but I get your idea. A small detail: The "Bookmarks" shouldn't be part of the RSS Feed: image

I showed cases that because adding another icon on the navbar might be just too much. Maybe we can merge the Booksmarks with the Archive into dropdown. I am really open to new ideas here.

For the icon. We can do one of those two approaches. Number 1: The simple one:

1. Use a website like https://heroicons.com/outline and copy&paste the SVG into a button and we are done. Or better: https://flowbite.com/icons/ (they have more)

2. What I outlined earlier: https://github.com/linkdotnet/Blog/blob/master/src/LinkDotNet.Blog.Web/wwwroot/css/fonts/README.md
   Go to https://icomoon.io/app/#/select, upload the "blog.json" and add the two new icons and download the the new woff files and so on.

The further I think of it, probably the more I want to replace everything with approach 1 as it is just way too annoying with approach 2.

Okay, I can create another collapsable navbar item (maybe 'collection'). I'll fix the icon and replace it with the button
ps: my bad, already thought it was weird to put it together with RSS :p

@linkdotnet
Copy link
Owner

Nah don't worry - just a detail. If you have other options/opinions let me know. I don't want to overload the navigation bar. I already feel there is too much stuff inside.

@levinoeninckx
Copy link
Contributor Author

I currently have no clue, tbh I feel like the navbar isn't overloaded at all (yet) so personally think it is fine to combine it with archive.
But it could be easily chanaged, maybe if a sidebar of some sort is added in the future.
Maybe some sort of side panel that slides in, showing the titles...

@linkdotnet
Copy link
Owner

I currently have no clue, tbh I feel like the navbar isn't overloaded at all (yet) so personally think it is fine to combine it with archive. But it could be easily chanaged, maybe if a sidebar of some sort is added in the future. Maybe some sort of side panel that slides in, showing the titles...

Maybe I am overreacting :D In any case - we can try without moving it into a category with the archive. Grouping it later on is done in no time

<button @onclick="OnClickHandler">
@if (!IsBookmarked)
{
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
Copy link
Owner

@linkdotnet linkdotnet Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start - as it is from Flowbite, it still has a lot of tailwindcss stuff inside.

Also it needs a bit more styling with tailwindcss (adding some styling on the button class)

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed the button background as well as the border and added a hover effect for the icon

return bookmarks.Contains(postId);
}

public async Task<IReadOnlyList<BlogPost>> GetBookmarkedPosts()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would refactor that method or the general approach a bit better.
The Bookmark service should only take care of the LocalStorage.

The Bookmark-Page is responsible to load all the data via the repository. There should be no need of a DbContextFactory - keep in mind that this blog also works with RavenDb, MongoDb, ...

YOu can have a look on the ArchivePage or SearchPage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reimplemented the GetBookmarkedPosts method, apologies did not consider that it can have different databases, im using the Repository in the bookmarks page now

@if (Configuration.Value.IsAboutMeEnabled)
<li><a class="nav-link" href="/"><i class="home"></i> Home</a></li>
<li><a class="nav-link" href="/archive"><i class="books"></i> Archive</a></li>
<li><a class="dropdown-item" href="/bookmarks">Bookmarks</a></li>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can try to add an icon here instead as well. It also needs the "nav-link" class, otherwise it is misaligned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, i'll add it and whoops overlooked that from copy pasting it

@levinoeninckx
Copy link
Contributor Author

I also added a bit of padding to the Bookmarks page


public async Task<bool> IsBookMarked(string postId)
{
if(string.IsNullOrEmpty(postId)) throw new ArgumentException(nameof(postId));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(string.IsNullOrEmpty(postId)) throw new ArgumentException(nameof(postId));
ArgumentException.ThrowIfNullOrEmpty(postId);

public async Task ToggleBookmark(string postId)
{
await InitializeIfNotExists();
if(string.IsNullOrEmpty(postId)) throw new ArgumentException(nameof(postId));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same here - plus: The sanity check should be the first line in this code. No need to call InitializeIfNotExists when postId is not set.

@@ -14,6 +15,7 @@
@inject IOptions<Introduction> Introduction
@inject IOptions<ApplicationConfiguration> AppConfiguration
@inject NavigationManager NavigationManager
@inject IBookmarkService BookmarkService
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems not needed - can be removed


public interface IBookmarkService
{
public Task<bool> IsBookMarked(string postId);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also write IsBookmarked as you did not use capital M for the other methods.

Copy link
Owner

@linkdotnet linkdotnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with me. There are some smaller things I would like to address.

}
</button>

<style>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We either could move this into BookmarkButton.razor.css or on the global css file.

@inject IBookmarkService BookmarkService
@inject IRepository<BlogPost> BlogPostRepository;

<section>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we align this a bit better to other pages like the archive?

<div class="container">
	<h3 class="pb-3 fw-bold">Bookmarks</h3>
    ...

}
</section>
<style>
section {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the idea from above, this could be deleted

protected override async Task OnAfterRenderAsync(bool firstRender)
{
var ids = await BookmarkService.GetBookmarkedPostIds();
bookmarkedPosts = await BlogPostRepository.GetAllByProjectionAsync(post => post, post => ids.Contains(post.Id));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: We could make a small sanity check if there are any ids. No need to go to the database if ids.Count == 0

ArgumentException.ThrowIfNullOrEmpty(postId);
await InitializeIfNotExists();

if (await IsBookmarked(postId))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two branches do a lot in common. Only the add/remove is different. Therefore the getItemand SetItem are common

<h2></h2>
<div class="header">
<h1>@BlogPost.Title</h1>
<BookmarkButton IsBookmarked="isBookmarked" OnClick="async () => { await BookmarkService.ToggleBookmark(BlogPost.Id); StateHasChanged(); }"></BookmarkButton>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this does more than one thing, it might be nice to have it inside a function rather than a lambda here.

@@ -129,6 +134,10 @@ else if (BlogPost is not null)
{
await JsRuntime.InvokeVoidAsync("hljs.highlightAll");
_ = UserRecordService.StoreUserRecordAsync();

ArgumentNullException.ThrowIfNull(BlogPost);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this isn't an argument, the ArgumentNullException doesn't really apply.
Rather an if(BlogPost is not null) - if the user navigates to a page with a non-existing ID then we get an exception

@levinoeninckx
Copy link
Contributor Author

Thanks for sticking with me. There are some smaller things I would like to address.

Np It's fun to work on something else aside from my job :D. I'll adress the last few things when I get the chance

@linkdotnet
Copy link
Owner

Some tests are failing (the Blazor Unit testing) mainly because of new dependencies that aren't mocked or provided in the respective test suite

@levinoeninckx
Copy link
Contributor Author

Some tests are failing (the Blazor Unit testing) mainly because of new dependencies that aren't mocked or provided in the respective test suite

Yes, I'm not familiar with the bUnit library (yet), I'm gonna try look into it later today and then try fixing those tests, if that is fine

@linkdotnet
Copy link
Owner

Some tests are failing (the Blazor Unit testing) mainly because of new dependencies that aren't mocked or provided in the respective test suite

Yes, I'm not familiar with the bUnit library (yet), I'm gonna try look into it later today and then try fixing those tests, if that is fine

Don't worry - I can also take over that part if you want. But of course, I don't want to push myself on top of your good work!

@levinoeninckx
Copy link
Contributor Author

@linkdotnet I have been messing around with bUnit to fix the failing tests but I'm having difficulties.
For some reason when I add the IBookmarkService to the services collection on a test and mock the ILocalStorage service I get some sort of error from Xunit.
I'm not really sure what to do about this so I'm asking for some help on this
PS. The error returned is not even readable since it is even over 100 lines so no real usefull information

@linkdotnet
Copy link
Owner

No worries - I can take over that part.

@linkdotnet
Copy link
Owner

PS. The error returned is not even readable since it is even over 100 lines so no real usefull information

That might be from bUnit itself. Could that I am maintainer of that library as well. Glad to have more work :D

@levinoeninckx
Copy link
Contributor Author

PS. The error returned is not even readable since it is even over 100 lines so no real usefull information

That might be from bUnit itself. Could that I am maintainer of that library as well. Glad to have more work :D

No clue, tried to read the entire log file that got spewed out and it starts at xUnit in the stacktrace and goes all the way down to the testrunner so not really sure 🤷‍♂️

@linkdotnet
Copy link
Owner

PS. The error returned is not even readable since it is even over 100 lines so no real usefull information

That might be from bUnit itself. Could that I am maintainer of that library as well. Glad to have more work :D

No clue, tried to read the entire log file that got spewed out and it starts at xUnit in the stacktrace and goes all the way down to the testrunner so not really sure 🤷‍♂️

Looks like an StackOverflow due to StateHasChanged inside OnAfterRender. Something we might fix on our site (bUnit)

@linkdotnet
Copy link
Owner

Caused by: bUnit-dev/bUnit#1682

@linkdotnet
Copy link
Owner

I will also fine-tune a bit the icon:
image
The picture is taken from my current blog posts.

@linkdotnet
Copy link
Owner

@levinoeninckx the issue with stackoverflow was mainly because StateHasChanged was called inside OnAfterRenderAsync which lead to another cycle of StateHasChanged inside OnAfterRenderAsync.... ;)

@linkdotnet
Copy link
Owner

So I added some more tests, fixed some behavior and did some small restyling.

Is there anything open from your side, otherwise that goes to master!

Thanks for the hard work! @levinoeninckx

@levinoeninckx
Copy link
Contributor Author

Nope all good! @linkdotnet

@linkdotnet
Copy link
Owner

Awesome - then merge it is!

@linkdotnet linkdotnet merged commit bbd1891 into linkdotnet:master Mar 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants