Browse Source

implement favorites filter for blog post listing

pull/21236/head
Suhaib 2 years ago
parent
commit
9f90cc27f2
  1. 10
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs
  2. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json
  3. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  4. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs
  5. 19
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs
  6. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs
  7. 36
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs
  8. 46
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs
  9. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs
  10. 13
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  11. 43
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml
  12. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs
  13. 27
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js
  14. 5
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css

10
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs

@ -111,9 +111,15 @@ public class BlogPostAdminAppService : CmsKitAppServiceBase, IBlogPostAdminAppSe
{
var blogs = (await BlogRepository.GetListAsync()).ToDictionary(x => x.Id);
var blogPosts = await BlogPostRepository.GetListAsync(input.Filter, input.BlogId, input.AuthorId, input.TagId,
var blogPosts = await BlogPostRepository.GetListAsync(
input.Filter,
input.BlogId,
input.AuthorId,
input.TagId,
statusFilter: input.Status,
input.MaxResultCount, input.SkipCount, input.Sorting);
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount,
sorting: input.Sorting);
var count = await BlogPostRepository.GetCountAsync(input.Filter, input.BlogId, input.AuthorId, tagId: input.TagId);

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json

@ -28,6 +28,9 @@
"CmsKit:MarkedItem:0001": "لا يمكن أن تمييز الكيان {EntityType}.",
"CmsKit:MarkedItem:0002": "لم يتم العثور على تعريف لنوع الكيان '{EntityType}'.",
"CmsKit:MarkedItem:0003": "يوجد بالفعل تعريف لنوع الكيان '{EntityType}'. يجب أن يكون لكل نوع كيان تعريف واحد فقط.",
"ToggleFavorite": "تفضيل/إلغاء",
"FavoritesFilterMessage": "يرجى تسجيل الدخول لتصفية المفضلات الخاصة بك",
"FilterOnFavorites": "تصفية المفضلات",
"CmsKit:Tag:0002": "الكيان غير قابل للوسم!",
"CommentAuthorizationExceptionMessage": "هذه التعليقات غير مسموح بها للعرض العام.",
"CommentDeletionConfirmationMessage": "سيتم حذف هذا التعليق وجميع الردود!",

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -27,6 +27,9 @@
"CmsKit:Reaction:0001": "The entity {EntityType} can't have reactions.",
"CmsKit:Tag:0002": "The entity is not taggable!",
"CmsKit:MarkedItem:ToggleConfirmation": "Are you sure you want to toggle the marked item?",
"ToggleFavorite": "Add/Remove Favorite",
"FavoritesFilterMessage": "Please log in to filter your favorites",
"FilterOnFavorites": "Filter On Favorites",
"CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CmsKit:Modals:Login": "Login",
"CmsKit:Modals:LoginModalDefaultMessage": "Please login to continue!",

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs

@ -14,6 +14,7 @@ public interface IBlogPostRepository : IBasicRepository<BlogPost, Guid>
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default);
@ -22,6 +23,7 @@ public interface IBlogPostRepository : IBasicRepository<BlogPost, Guid>
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,

19
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs

@ -12,6 +12,7 @@ using Volo.CmsKit.Blogs;
using Volo.CmsKit.Comments;
using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Localization;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.MediaDescriptors;
using Volo.CmsKit.Menus;
using Volo.CmsKit.Pages;
@ -91,6 +92,24 @@ public class CmsKitDomainModule : AbpModule
{
// TODO: Configure TagEntityTypes here...
}
if (GlobalFeatureManager.Instance.IsEnabled<MarkedItemsFeature>())
{
Configure<CmsKitMarkedItemOptions>(options =>
{
if (GlobalFeatureManager.Instance.IsEnabled<BlogsFeature>())
{
options.EntityTypes.Add(
new MarkedItemEntityTypeDefinition(
BlogPostConsts.EntityType,
StandardMarkedItems.Favorite
)
);
}
// TODO: Add more entities that can be marked.
});
}
}
public override void PostConfigureServices(ServiceConfigurationContext context)

12
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs

@ -2,6 +2,8 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using Volo.Abp;
using System.Collections.Generic;
using System.Threading;
namespace Volo.CmsKit.MarkedItems;
public class MarkedItemManager : CmsKitDomainServiceBase
@ -20,7 +22,7 @@ public class MarkedItemManager : CmsKitDomainServiceBase
public virtual async Task<bool> ToggleUserMarkedItemAsync(
Guid creatorId,
[NotNull] string entityType,
[NotNull] string entityType,
[NotNull] string entityId)
{
Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
@ -49,4 +51,12 @@ public class MarkedItemManager : CmsKitDomainServiceBase
);
return true;
}
public async Task<List<string>> GetEntityIdsFilteredByUserAsync(
[NotNull] Guid userId,
[NotNull] string entityType,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
{
return await UserMarkedItemRepository.GetEntityIdsFilteredByUserAsync(userId, entityType, tenantId, cancellationToken);
}
}

36
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs

@ -11,6 +11,7 @@ using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.CmsKit.EntityFrameworkCore;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.Tags;
using Volo.CmsKit.Users;
@ -18,12 +19,15 @@ namespace Volo.CmsKit.Blogs;
public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogPost, Guid>, IBlogPostRepository
{
private readonly MarkedItemManager _markedItemManager;
private EntityTagManager _entityTagManager;
public EfCoreBlogPostRepository(
IDbContextProvider<ICmsKitDbContext> dbContextProvider,
MarkedItemManager markedItemManager,
EntityTagManager entityTagManager) : base(dbContextProvider)
{
_markedItemManager = markedItemManager;
_entityTagManager = entityTagManager;
}
@ -50,17 +54,21 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default)
{
List<string> entityIdFilters = null;
if (tagId.HasValue)
{
entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken);
}
var tagFilteredEntityIds = tagId.HasValue
? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)
: null;
var favoriteUserFilteredEntityIds = favoriteUserId.HasValue
? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType)
: null;
var queryable = (await GetDbSetAsync())
.WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id.ToString()))
.WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString()))
.WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString()))
.WhereIf(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId)
.WhereIf(statusFilter.HasValue, x => x.Status == statusFilter)
@ -75,6 +83,7 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
@ -86,14 +95,17 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
var blogPostsDbSet = dbContext.Set<BlogPost>();
var usersDbSet = dbContext.Set<CmsUser>();
List<string> entityIdFilters = null;
if (tagId.HasValue)
{
entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken);
}
var tagFilteredEntityIds = tagId.HasValue
? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)
: null;
var favoriteUserFilteredEntityIds = favoriteUserId.HasValue
? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType)
: null;
var queryable = (await GetDbSetAsync())
.WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id.ToString()))
.WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString()))
.WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString()))
.WhereIf(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter))
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId)

46
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs

@ -12,6 +12,7 @@ using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.Tags;
using Volo.CmsKit.Users;
@ -19,10 +20,15 @@ namespace Volo.CmsKit.MongoDB.Blogs;
public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, BlogPost, Guid>, IBlogPostRepository
{
private readonly MarkedItemManager _markedItemManager;
private EntityTagManager _entityTagManager;
public MongoBlogPostRepository(IMongoDbContextProvider<CmsKitMongoDbContext> dbContextProvider, EntityTagManager entityTagManager) : base(
public MongoBlogPostRepository(
IMongoDbContextProvider<CmsKitMongoDbContext> dbContextProvider,
MarkedItemManager markedItemManager,
EntityTagManager entityTagManager) : base(
dbContextProvider)
{
_markedItemManager = markedItemManager;
_entityTagManager = entityTagManager;
}
@ -48,15 +54,19 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default)
{
cancellationToken = GetCancellationToken(cancellationToken);
var entityIdFilters = await GetEntityIdsByTagId(tagId, cancellationToken);
var tagFilteredEntityIds = await GetEntityIdsByTagId(tagId, cancellationToken);
var favoriteUserFilteredEntityIds = await GetFavoriteEntityIdsByUserId(favoriteUserId, cancellationToken);
return await (await GetMongoQueryableAsync(cancellationToken))
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(entityIdFilters.Any(), x => entityIdFilters.Contains(x.Id))
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id))
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id))
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter))
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(authorId.HasValue, x => x.AuthorId == authorId)
@ -69,6 +79,7 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
Guid? blogId = null,
Guid? authorId = null,
Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
@ -79,12 +90,15 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
var dbContext = await GetDbContextAsync(cancellationToken);
var blogPostQueryable = await GetQueryableAsync();
var entityIdFilters = await GetEntityIdsByTagId(tagId, cancellationToken);
var tagFilteredEntityIds = await GetEntityIdsByTagId(tagId, cancellationToken);
var favoriteUserFilteredEntityIds = await GetFavoriteEntityIdsByUserId(favoriteUserId, cancellationToken);
var usersQueryable = dbContext.Collection<CmsUser>().AsQueryable();
var queryable = blogPostQueryable
.WhereIf(entityIdFilters.Any(), x => entityIdFilters.Contains(x.Id))
.WhereIf(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id))
.WhereIf(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id))
.WhereIf(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter))
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId)
@ -132,6 +146,28 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
return entityIdFilters;
}
protected virtual async Task<List<Guid>> GetFavoriteEntityIdsByUserId(Guid? userId, CancellationToken cancellationToken)
{
var entityIdFilters = new List<Guid>();
if (!userId.HasValue)
{
return entityIdFilters;
}
var entityIds =
await _markedItemManager.GetEntityIdsFilteredByUserAsync(userId.Value, BlogPostConsts.EntityType, CurrentTenant.Id, cancellationToken);
foreach (var entityId in entityIds)
{
if (Guid.TryParse(entityId, out var parsedEntityId))
{
entityIdFilters.Add(parsedEntityId);
}
}
return entityIdFilters;
}
public virtual async Task<bool> SlugExistsAsync(Guid blogId, [NotNull] string slug,
CancellationToken cancellationToken = default)
{

1
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs

@ -8,4 +8,5 @@ public class BlogPostGetListInput : PagedAndSortedResultRequestDto
public Guid? AuthorId { get; set; }
public Guid? TagId { get; set; }
public bool? FilterOnFavorites { get; set; }
}

13
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs

@ -54,7 +54,10 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
{
var blog = await BlogRepository.GetBySlugAsync(blogSlug);
Guid? favoriteUserId = await GetFavoriteUserIdAsync(input.FilterOnFavorites);
var blogPosts = await BlogPostRepository.GetListAsync(null, blog.Id, input.AuthorId, input.TagId,
favoriteUserId,
BlogPostStatus.Published, input.MaxResultCount,
input.SkipCount, input.Sorting);
@ -100,4 +103,14 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
return tag.Name;
}
protected virtual async Task<Guid?> GetFavoriteUserIdAsync(bool? filterOnFavorites)
{
if (!filterOnFavorites.GetValueOrDefault() || !CurrentUser.IsAuthenticated)
{
return null;
}
return CurrentUser.GetId();
}
}

43
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml

@ -1,6 +1,10 @@
@page
@using Volo.CmsKit.Blogs
@using Volo.CmsKit.Public.Web.Pages
@using Volo.Abp.GlobalFeatures
@using Volo.CmsKit.GlobalFeatures
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.MarkedItemToggle
@using Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs
@using Volo.CmsKit.Web
@ -8,6 +12,11 @@
@model IndexModel
@{
const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png";
var isMarkedItemFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled<MarkedItemsFeature>();
}
@section styles {
<abp-style src="/Pages/Public/CmsKit/Blogs/index.css" />
}
@ -15,13 +24,13 @@
@section scripts {
<abp-script-bundle>
<abp-script src="/Pages/Public/CmsKit/Blogs/index.js" />
@if (isMarkedItemFeatureEnabled)
{
<abp-script src="/Pages/Public/CmsKit/Blogs/filter-on-favorites.js" />
}
</abp-script-bundle>
}
@{
const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png";
}
@if (Model.AuthorId.HasValue)
{
<abp-row id="blogs-filter-area">
@ -61,11 +70,37 @@
@if (Model.Blogs.TotalCount > 0)
{
@if (isMarkedItemFeatureEnabled)
{
var filterOnFavorties = Model.FilterOnFavorites.GetValueOrDefault();
string icon = filterOnFavorties ? "heart" : "heart-o";
<abp-button class="favorite-button badge text-bg-light my-2 border-light-subtle"
button-type="Light"
icon="@icon text-danger"
text="@L["FilterOnFavorites"]"
filter-on-favorites="@filterOnFavorties">
</abp-button>
}
<abp-row id="blogs-container">
@foreach (var blog in Model.Blogs.Items)
{
<abp-column size="_12" size-md="_6" size-lg="_4">
<abp-card>
@if (isMarkedItemFeatureEnabled)
{
<div class="position-absolute top-0 end-0 m-3 z-3">
<abp-button button-type="Light"
class="favorite-toggle rounded-circle shadow-sm p-2"
abp-tooltip="@L["ToggleFavorite"]">
@await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new
{
entityId = blog.Id.ToString(),
entityType = BlogPostConsts.EntityType
})
</abp-button>
</div>
}
@if (blog.CoverImageMediaId != null)
{
<img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs

@ -22,6 +22,9 @@ public class IndexModel : CmsKitPublicPageModelBase
[BindProperty(SupportsGet = true)]
public Guid? AuthorId { get; set; }
[BindProperty(SupportsGet = true)]
public bool? FilterOnFavorites { get; set; }
[BindProperty(SupportsGet = true)]
public Guid? TagId { get; set; }
@ -49,7 +52,8 @@ public class IndexModel : CmsKitPublicPageModelBase
SkipCount = PageSize * (CurrentPage - 1),
MaxResultCount = PageSize,
AuthorId = AuthorId,
TagId = TagId
TagId = TagId,
FilterOnFavorites = FilterOnFavorites
});
if (AuthorId != null)

27
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js

@ -0,0 +1,27 @@
$(function () {
var l = abp.localization.getResource('CmsKit');
let filterButton = $('.favorite-button');
let filterOnFavorites = filterButton.attr('filter-on-favorites');
const loginModal = new abp.ModalManager(abp.appPath + 'CmsKit/Shared/Modals/Login/LoginModal');
$('.favorite-button').on('click', function () {
if (!abp.currentUser.isAuthenticated) {
const currentPageRoute = window.location.pathname;
loginModal.open({ message: l("FavoritesFilterMessage"), returnUrl: currentPageRoute });
return;
}
let currentUrl = new URL(window.location.href);
let searchParams = currentUrl.searchParams;
// Toggle the 'filterOnFavorites' parameter
if (filterOnFavorites) {
searchParams.delete('filterOnFavorites');
} else {
searchParams.set('filterOnFavorites', 'true');
}
window.location.href = currentUrl.pathname + '?' + searchParams.toString();
});
});

5
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css

@ -17,4 +17,9 @@
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.favorite-toggle {
width: 40px;
height: 40px;
}
Loading…
Cancel
Save