diff --git a/Start/Client/Components/Bookmark.razor b/Start/Client/Components/Bookmark.razor
index 193daea..e2ae192 100644
--- a/Start/Client/Components/Bookmark.razor
+++ b/Start/Client/Components/Bookmark.razor
@@ -1,4 +1,4 @@
-
+
@if (!String.IsNullOrEmpty(this.Model.Notes))
{
diff --git a/Start/Client/Components/BookmarkGroup.razor b/Start/Client/Components/BookmarkGroup.razor
index e50c064..6f32d1a 100644
--- a/Start/Client/Components/BookmarkGroup.razor
+++ b/Start/Client/Components/BookmarkGroup.razor
@@ -2,6 +2,7 @@
@using Fluxor
@using Start.Client.Store.State
@using Start.Client.Store.Features.DeleteGroup
+@using Start.Client.Store.Features.CreateBookmark
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@@ -33,7 +34,7 @@
No Bookmarks
-
@@ -89,6 +90,7 @@
protected void OnCreateBookmarkClicked()
{
- // Placeholder
+ dispatch.Dispatch(new ShowCreateBookmarkFormAction(this.Group.BookmarkGroupId,
+ this.Group.Title));
}
}
diff --git a/Start/Client/Components/CreateBookmark.razor b/Start/Client/Components/CreateBookmark.razor
new file mode 100644
index 0000000..5c9ba90
--- /dev/null
+++ b/Start/Client/Components/CreateBookmark.razor
@@ -0,0 +1,91 @@
+@using Start.Client.Store.Features.CreateBookmark
+@using Fluxor
+
+@inherits Fluxor.Blazor.Web.Components.FluxorComponent
+
+@inject IActionSubscriber actionSubscriber
+@inject IDispatcher dispatch
+@inject IState state
+
+
+
+@code {
+ protected BookmarkDto Model { get; set; } = new BookmarkDto("", "", null, 0);
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+
+ this.Model = new BookmarkDto("", "", null, this.state.Value.GroupId);
+
+ actionSubscriber.SubscribeToAction(this,
+ a => this.Model.BookmarkGroupId = a.GroupId);
+ }
+
+ protected void OnSubmit()
+ {
+ dispatch.Dispatch(new SubmitCreateBookmarkAction(this.Model));
+ }
+
+ protected void OnDialogClose()
+ {
+ dispatch.Dispatch(new HideCreateBookmarkFormAction());
+ }
+}
diff --git a/Start/Client/Pages/Index.razor b/Start/Client/Pages/Index.razor
index c13ebbb..227fcd6 100644
--- a/Start/Client/Pages/Index.razor
+++ b/Start/Client/Pages/Index.razor
@@ -80,6 +80,8 @@ else
+
+
}
diff --git a/Start/Client/Sass/app.scss b/Start/Client/Sass/app.scss
index 9d520ed..08c1eb0 100644
--- a/Start/Client/Sass/app.scss
+++ b/Start/Client/Sass/app.scss
@@ -191,6 +191,32 @@
ul.bookmarks {
list-style: none;
margin: 0;
+ margin-block: 0;
+
+ .bookmark {
+ margin-top: 0;
+
+ a {
+ display: inline-block;
+ width: 100%;
+ padding: 0.5em 0.83em;
+
+ &:hover {
+ text-decoration: none;
+ background-color: $bg-color-dark;
+ }
+ }
+
+ &:last-child {
+ a {
+ border-radius: 0 0 0.4em 0.4em;
+ }
+ }
+
+ &:not(:last-child) {
+ border-bottom: solid 1px $border-color;
+ }
+ }
li.noBookmarksItem {
margin-top: 0;
diff --git a/Start/Client/Sass/dark-mode.scss b/Start/Client/Sass/dark-mode.scss
index 6e4d3ca..700dc81 100644
--- a/Start/Client/Sass/dark-mode.scss
+++ b/Start/Client/Sass/dark-mode.scss
@@ -74,6 +74,10 @@ $dark-border-color: darken($dark-color, 10%);
// Cards
.card {
border-color: $dark-border-color;
+
+ .card-body {
+ background-color: $dark-bg-color-dark;
+ }
}
@@ -124,4 +128,20 @@ $dark-border-color: darken($dark-color, 10%);
#sidebar .accountActions {
border-top-color: $dark-border-color;
}
+
+ ul.bookmarks {
+ .bookmark {
+ &:not(:last-child) {
+ border-bottom: solid 1px $dark-border-color;
+ }
+
+ a, a:visited {
+ color: $dark-primary-color;
+ }
+
+ a:hover {
+ background-color: $dark-bg-color;
+ }
+ }
+ }
}
diff --git a/Start/Client/Start.Client.csproj b/Start/Client/Start.Client.csproj
index 43324b0..56ef6d0 100644
--- a/Start/Client/Start.Client.csproj
+++ b/Start/Client/Start.Client.csproj
@@ -130,6 +130,7 @@
+
@@ -147,5 +148,6 @@
+
diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs
new file mode 100644
index 0000000..f82e5cc
--- /dev/null
+++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs
@@ -0,0 +1,35 @@
+using Start.Shared;
+
+namespace Start.Client.Store.Features.CreateBookmark {
+ public class ShowCreateBookmarkFormAction {
+ public int GroupId { get; init; }
+ public string GroupTitle { get; init; }
+
+ public ShowCreateBookmarkFormAction(int groupId, string groupTitle) {
+ this.GroupId = groupId;
+ this.GroupTitle = groupTitle;
+ }
+ }
+
+ public class HideCreateBookmarkFormAction { }
+
+ public class FetchCreateBookmarkAction { }
+
+ public class ReceivedCreateBookmarkAction { }
+
+ public class ErrorFetchingCreateBookmarkAction {
+ public string ErrorMessage { get; init; }
+
+ public ErrorFetchingCreateBookmarkAction(string errorMessage) {
+ this.ErrorMessage = errorMessage;
+ }
+ }
+
+ public class SubmitCreateBookmarkAction {
+ public BookmarkDto NewBookmark { get; init; }
+
+ public SubmitCreateBookmarkAction(BookmarkDto newBookmark) {
+ this.NewBookmark = newBookmark;
+ }
+ }
+}
diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs
new file mode 100644
index 0000000..b0436a8
--- /dev/null
+++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs
@@ -0,0 +1,45 @@
+using System.Threading.Tasks;
+using Fluxor;
+using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
+using Start.Shared.Api;
+using Start.Client.Store.Features.CurrentContainer;
+
+namespace Start.Client.Store.Features.CreateBookmark {
+ public class CreateBookmarkEffects {
+ public IBookmarksApi BookmarksApi { get; init; }
+
+ public CreateBookmarkEffects(IBookmarksApi bookmarksApi) {
+ this.BookmarksApi = bookmarksApi;
+ }
+
+ [EffectMethod]
+ public async Task SubmitCreateBookmark(SubmitCreateBookmarkAction action,
+ IDispatcher dispatch) {
+ dispatch.Dispatch(new FetchCreateBookmarkAction());
+
+ try {
+ Refit.ApiResponse? apiResponse = await this.BookmarksApi
+ .CreateBookmark(action.NewBookmark.Title, action.NewBookmark.Url,
+ action.NewBookmark.Notes, action.NewBookmark.BookmarkGroupId);
+
+ if (!apiResponse.IsSuccessStatusCode) {
+ dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction(
+ "Error creating bookmark group: Status code " + apiResponse.StatusCode.ToString()));
+ return;
+ }
+
+ if (apiResponse.Content == null) {
+ dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction(
+ "Error creating bookmark group"));
+ return;
+ }
+
+ dispatch.Dispatch(new AddBookmarkAction(apiResponse.Content));
+ dispatch.Dispatch(new ReceivedCreateBookmarkAction());
+ dispatch.Dispatch(new HideCreateBookmarkFormAction());
+ } catch (AccessTokenNotAvailableException e) {
+ e.Redirect();
+ }
+ }
+ }
+}
diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs
new file mode 100644
index 0000000..25f470f
--- /dev/null
+++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs
@@ -0,0 +1,11 @@
+using Fluxor;
+
+namespace Start.Client.Store.Features.CreateBookmark {
+ public class CreateBookmarkFeature : Feature {
+ public override string GetName() => "Create Bookmark";
+
+ protected override CreateBookmarkState GetInitialState() {
+ return new CreateBookmarkState();
+ }
+ }
+}
diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs
new file mode 100644
index 0000000..f6c6867
--- /dev/null
+++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs
@@ -0,0 +1,41 @@
+using Fluxor;
+
+namespace Start.Client.Store.Features.CreateBookmark {
+ public static class CreateBookmarkReducers {
+ [ReducerMethod]
+ public static CreateBookmarkState ShowCreateBookmarkForm(CreateBookmarkState state,
+ ShowCreateBookmarkFormAction action) {
+ return state with {
+ ShowCreateBookmarkForm = true,
+ GroupId = action.GroupId,
+ GroupTitle = action.GroupTitle,
+ IsLoadingCreateBookmark = false,
+ CreateBookmarkErrorMessage = null
+ };
+ }
+
+ [ReducerMethod(typeof(HideCreateBookmarkFormAction))]
+ public static CreateBookmarkState HideCreateBookmarkForm(CreateBookmarkState state) {
+ return state with {
+ ShowCreateBookmarkForm = false
+ };
+ }
+
+ [ReducerMethod(typeof(FetchCreateBookmarkAction))]
+ public static CreateBookmarkState FetchCreateBookmark(CreateBookmarkState state) {
+ return state with {
+ IsLoadingCreateBookmark = true,
+ CreateBookmarkErrorMessage = null
+ };
+ }
+
+ [ReducerMethod]
+ public static CreateBookmarkState ErrorFetchingCreateBookmark(CreateBookmarkState state,
+ ErrorFetchingCreateBookmarkAction action) {
+ return state with {
+ CreateBookmarkErrorMessage = action.ErrorMessage,
+ IsLoadingCreateBookmark = false
+ };
+ }
+ }
+}
diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs
new file mode 100644
index 0000000..929f454
--- /dev/null
+++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs
@@ -0,0 +1,15 @@
+using Start.Client.Store.State;
+
+namespace Start.Client.Store.Features.CreateBookmark {
+ public record CreateBookmarkState : RootState {
+ public bool ShowCreateBookmarkForm { get; init; }
+ public int GroupId { get; init; }
+ public string GroupTitle { get; init; }
+ public bool IsLoadingCreateBookmark { get; init; }
+ public string? CreateBookmarkErrorMessage { get; init; }
+
+ public CreateBookmarkState() {
+ this.GroupTitle = "";
+ }
+ }
+}
diff --git a/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs b/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs
index ed1ef00..d5329b7 100644
--- a/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs
+++ b/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs
@@ -33,7 +33,13 @@ namespace Start.Client.Store.Features.CreateGroup {
return;
}
- dispatch.Dispatch(new AddBookmarkGroupAction(action.NewGroup));
+ if (apiResponse.Content == null) {
+ dispatch.Dispatch(new ErrorFetchingCreateGroupAction(
+ "Error creating bookmark group"));
+ return;
+ }
+
+ dispatch.Dispatch(new AddBookmarkGroupAction(apiResponse.Content));
dispatch.Dispatch(new RecievedCreateGroupAction());
dispatch.Dispatch(new HideCreateGroupFormAction());
} catch (AccessTokenNotAvailableException e) {
diff --git a/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs b/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs
index a8a5724..e00d3a6 100644
--- a/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs
+++ b/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs
@@ -44,4 +44,20 @@ namespace Start.Client.Store.Features.CurrentContainer {
this.BookmarkGroupId = bookmarkGroupId;
}
}
+
+ public class AddBookmarkAction {
+ public BookmarkDto Bookmark { get; init; }
+
+ public AddBookmarkAction(BookmarkDto bookmark) {
+ this.Bookmark = bookmark;
+ }
+ }
+
+ public class RemoveBookmarkAction {
+ public int BookmarkId { get; init; }
+
+ public RemoveBookmarkAction(int bookmarkId) {
+ this.BookmarkId = bookmarkId;
+ }
+ }
}
diff --git a/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs b/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs
index f4341c5..23b05c5 100644
--- a/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs
+++ b/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs
@@ -78,5 +78,56 @@ namespace Start.Client.Store.Features.CurrentContainer {
}
};
}
+
+ [ReducerMethod]
+ public static RootState AddBookmark(RootState state, AddBookmarkAction action) {
+ BookmarkContainerDto? container = state.CurrentContainerState.Container;
+
+ if (container == null)
+ return state;
+
+ List? groups = container.BookmarkGroups
+ ?.Select(bg => {
+ if (bg.BookmarkGroupId == action.Bookmark.BookmarkGroupId) {
+ return new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color,
+ bg.BookmarkContainerId,
+ bg.Bookmarks?
+ .Concat(new List { action.Bookmark })
+ .ToList());
+ }
+
+ return bg;
+ })
+ .ToList();
+
+ return state with {
+ CurrentContainerState = state.CurrentContainerState with {
+ Container = new BookmarkContainerDto(container.BookmarkContainerId,
+ container.Title, groups)
+ }
+ };
+ }
+
+ [ReducerMethod]
+ public static RootState RemoveBookmark(RootState state, RemoveBookmarkAction action) {
+ BookmarkContainerDto? container = state.CurrentContainerState.Container;
+
+ if (container == null)
+ return state;
+
+ List? groups = container.BookmarkGroups
+ ?.Select(bg => new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color,
+ bg.BookmarkContainerId, bg.Bookmarks
+ ?.Where(b => b.BookmarkId != action.BookmarkId)
+ .ToList()))
+ .ToList();
+
+ return state with {
+ CurrentContainerState = state.CurrentContainerState with {
+ Container = new BookmarkContainerDto(container.BookmarkContainerId,
+ container.Title, groups)
+ }
+ };
+ }
}
}
diff --git a/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs b/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs
index 7dbd809..4aebb30 100644
--- a/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs
+++ b/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs
@@ -1,6 +1,7 @@
-using System;
+using Start.Client.Store.State;
+
namespace Start.Client.Store.Features.DeleteGroup {
- public record DeleteGroupState {
+ public record DeleteGroupState : RootState {
public bool ShowDeleteGroupForm { get; init; }
public int BookmarkGroupIdToDelete { get; init; }
public string BookmarkGroupTitleToDelete { get; init; }
diff --git a/Start/Server/Extensions/BookmarkMaps.cs b/Start/Server/Extensions/BookmarkMaps.cs
index 89860ac..63f771d 100644
--- a/Start/Server/Extensions/BookmarkMaps.cs
+++ b/Start/Server/Extensions/BookmarkMaps.cs
@@ -6,7 +6,7 @@ namespace Start.Server.Extensions {
public static class BookmarkMaps {
public static BookmarkDto MapToDto(this Bookmark bookmark) {
return new BookmarkDto(bookmark.BookmarkId, bookmark.Title, bookmark.Url,
- bookmark.Notes);
+ bookmark.Notes, bookmark.BookmarkGroupId);
}
public static BookmarkGroupDto MapToDto(this BookmarkGroup bookmarkGroup) {
diff --git a/Start/Shared/Api/IBookmarksApi.cs b/Start/Shared/Api/IBookmarksApi.cs
index a9d1387..352f684 100644
--- a/Start/Shared/Api/IBookmarksApi.cs
+++ b/Start/Shared/Api/IBookmarksApi.cs
@@ -4,7 +4,7 @@ using System.Net.Http;
namespace Start.Shared.Api {
public interface IBookmarksApi {
- [Get("{bookmarkId}")]
+ [Get("/{bookmarkId}")]
Task> GetBookmark(int bookmarkId);
[Post("/Create")]
diff --git a/Start/Shared/BookmarkDto.cs b/Start/Shared/BookmarkDto.cs
index 04ce10d..3468191 100644
--- a/Start/Shared/BookmarkDto.cs
+++ b/Start/Shared/BookmarkDto.cs
@@ -1,5 +1,5 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
namespace Start.Shared {
public class BookmarkDto {
@@ -11,14 +11,19 @@ namespace Start.Shared {
[StringLength(5000)]
public string? Notes { get; set; }
- public BookmarkDto(string title, string url, string? notes) {
+ public int BookmarkGroupId { get; set; }
+
+ public BookmarkDto(string title, string url, string? notes, int bookmarkGroupId) {
this.Title = title;
this.Url = url;
this.Notes = notes;
+ this.BookmarkGroupId = bookmarkGroupId;
}
- public BookmarkDto(int bookmarkId, string title, string url, string? notes)
- : this(title, url, notes) {
+ [JsonConstructor]
+ public BookmarkDto(int bookmarkId, string title, string url, string? notes,
+ int bookmarkGroupId)
+ : this(title, url, notes, bookmarkGroupId) {
this.BookmarkId = bookmarkId;
}
}