Browse Source

Merge pull request #21236 from suhaib-mousa/features/favorites-blog-posts

favorites filtering blog posts
pull/21793/head
Enis Necipoglu 2 years ago
committed by GitHub
parent
commit
643356db85
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs
  2. 4
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json
  3. 4
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  4. 1
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs
  5. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs
  6. 19
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs
  7. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs
  8. 36
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs
  9. 46
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs
  10. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs
  11. 13
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  12. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css
  13. 8
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml
  14. 43
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml
  15. 24
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs
  16. 27
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js
  17. 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 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, 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); var count = await BlogPostRepository.GetCountAsync(input.Filter, input.BlogId, input.AuthorId, tagId: input.TagId);

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

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

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

@ -15,6 +15,7 @@
"CmsKit.Ratings": "Ratings", "CmsKit.Ratings": "Ratings",
"CmsKit.Reactions": "Reactions", "CmsKit.Reactions": "Reactions",
"CmsKit.Tags": "Tags", "CmsKit.Tags": "Tags",
"CmsKit.MarkedItems": "Marked Items",
"CmsKit:0002": "Content already exists!", "CmsKit:0002": "Content already exists!",
"CmsKit:0003": "The entity {0} is not taggable.", "CmsKit:0003": "The entity {0} is not taggable.",
"CmsKit:Blog:0001": "The given slug ({Slug}) already exists!", "CmsKit:Blog:0001": "The given slug ({Slug}) already exists!",
@ -27,6 +28,9 @@
"CmsKit:Reaction:0001": "The entity {EntityType} can't have reactions.", "CmsKit:Reaction:0001": "The entity {EntityType} can't have reactions.",
"CmsKit:Tag:0002": "The entity is not taggable!", "CmsKit:Tag:0002": "The entity is not taggable!",
"CmsKit:MarkedItem:ToggleConfirmation": "Are you sure you want to toggle the marked item?", "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.", "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CmsKit:Modals:Login": "Login", "CmsKit:Modals:Login": "Login",
"CmsKit:Modals:LoginModalDefaultMessage": "Please login to continue!", "CmsKit:Modals:LoginModalDefaultMessage": "Please login to continue!",

1
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs

@ -16,6 +16,7 @@ public class DefaultBlogFeatureProvider : IDefaultBlogFeatureProvider, ITransien
new BlogFeature(blogId, ReactionsFeature.Name), new BlogFeature(blogId, ReactionsFeature.Name),
new BlogFeature(blogId, RatingsFeature.Name), new BlogFeature(blogId, RatingsFeature.Name),
new BlogFeature(blogId, TagsFeature.Name), new BlogFeature(blogId, TagsFeature.Name),
new BlogFeature(blogId, MarkedItemsFeature.Name),
new BlogFeature(blogId, BlogPostScrollIndexFeature.Name), new BlogFeature(blogId, BlogPostScrollIndexFeature.Name),
new BlogFeature(blogId, BlogConsts.PreventXssFeatureName) new BlogFeature(blogId, BlogConsts.PreventXssFeatureName)
}); });

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? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
@ -22,6 +23,7 @@ public interface IBlogPostRepository : IBasicRepository<BlogPost, Guid>
Guid? blogId = null, Guid? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue, int maxResultCount = int.MaxValue,
int skipCount = 0, 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.Comments;
using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Localization; using Volo.CmsKit.Localization;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.MediaDescriptors; using Volo.CmsKit.MediaDescriptors;
using Volo.CmsKit.Menus; using Volo.CmsKit.Menus;
using Volo.CmsKit.Pages; using Volo.CmsKit.Pages;
@ -91,6 +92,24 @@ public class CmsKitDomainModule : AbpModule
{ {
// TODO: Configure TagEntityTypes here... // 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) 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 JetBrains.Annotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using System.Collections.Generic;
using System.Threading;
namespace Volo.CmsKit.MarkedItems; namespace Volo.CmsKit.MarkedItems;
public class MarkedItemManager : CmsKitDomainServiceBase public class MarkedItemManager : CmsKitDomainServiceBase
@ -20,7 +22,7 @@ public class MarkedItemManager : CmsKitDomainServiceBase
public virtual async Task<bool> ToggleUserMarkedItemAsync( public virtual async Task<bool> ToggleUserMarkedItemAsync(
Guid creatorId, Guid creatorId,
[NotNull] string entityType, [NotNull] string entityType,
[NotNull] string entityId) [NotNull] string entityId)
{ {
Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
@ -49,4 +51,12 @@ public class MarkedItemManager : CmsKitDomainServiceBase
); );
return true; 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.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.CmsKit.EntityFrameworkCore; using Volo.CmsKit.EntityFrameworkCore;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.Tags; using Volo.CmsKit.Tags;
using Volo.CmsKit.Users; using Volo.CmsKit.Users;
@ -18,12 +19,15 @@ namespace Volo.CmsKit.Blogs;
public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogPost, Guid>, IBlogPostRepository public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogPost, Guid>, IBlogPostRepository
{ {
private readonly MarkedItemManager _markedItemManager;
private EntityTagManager _entityTagManager; private EntityTagManager _entityTagManager;
public EfCoreBlogPostRepository( public EfCoreBlogPostRepository(
IDbContextProvider<ICmsKitDbContext> dbContextProvider, IDbContextProvider<ICmsKitDbContext> dbContextProvider,
MarkedItemManager markedItemManager,
EntityTagManager entityTagManager) : base(dbContextProvider) EntityTagManager entityTagManager) : base(dbContextProvider)
{ {
_markedItemManager = markedItemManager;
_entityTagManager = entityTagManager; _entityTagManager = entityTagManager;
} }
@ -50,17 +54,21 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
Guid? blogId = null, Guid? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
List<string> entityIdFilters = null; var tagFilteredEntityIds = tagId.HasValue
if (tagId.HasValue) ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)
{ : null;
entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken);
} var favoriteUserFilteredEntityIds = favoriteUserId.HasValue
? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType)
: null;
var queryable = (await GetDbSetAsync()) 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(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId)
.WhereIf(statusFilter.HasValue, x => x.Status == statusFilter) .WhereIf(statusFilter.HasValue, x => x.Status == statusFilter)
@ -75,6 +83,7 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
Guid? blogId = null, Guid? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue, int maxResultCount = int.MaxValue,
int skipCount = 0, int skipCount = 0,
@ -86,14 +95,17 @@ public class EfCoreBlogPostRepository : EfCoreRepository<ICmsKitDbContext, BlogP
var blogPostsDbSet = dbContext.Set<BlogPost>(); var blogPostsDbSet = dbContext.Set<BlogPost>();
var usersDbSet = dbContext.Set<CmsUser>(); var usersDbSet = dbContext.Set<CmsUser>();
List<string> entityIdFilters = null; var tagFilteredEntityIds = tagId.HasValue
if (tagId.HasValue) ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken)
{ : null;
entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken);
} var favoriteUserFilteredEntityIds = favoriteUserId.HasValue
? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType)
: null;
var queryable = (await GetDbSetAsync()) 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(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter))
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId) .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.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB; using Volo.Abp.MongoDB;
using Volo.CmsKit.Blogs; using Volo.CmsKit.Blogs;
using Volo.CmsKit.MarkedItems;
using Volo.CmsKit.Tags; using Volo.CmsKit.Tags;
using Volo.CmsKit.Users; using Volo.CmsKit.Users;
@ -19,10 +20,15 @@ namespace Volo.CmsKit.MongoDB.Blogs;
public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, BlogPost, Guid>, IBlogPostRepository public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, BlogPost, Guid>, IBlogPostRepository
{ {
private readonly MarkedItemManager _markedItemManager;
private EntityTagManager _entityTagManager; private EntityTagManager _entityTagManager;
public MongoBlogPostRepository(IMongoDbContextProvider<CmsKitMongoDbContext> dbContextProvider, EntityTagManager entityTagManager) : base( public MongoBlogPostRepository(
IMongoDbContextProvider<CmsKitMongoDbContext> dbContextProvider,
MarkedItemManager markedItemManager,
EntityTagManager entityTagManager) : base(
dbContextProvider) dbContextProvider)
{ {
_markedItemManager = markedItemManager;
_entityTagManager = entityTagManager; _entityTagManager = entityTagManager;
} }
@ -48,15 +54,19 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
Guid? blogId = null, Guid? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
cancellationToken = GetCancellationToken(cancellationToken); 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)) 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>>(!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>>(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf<BlogPost, IMongoQueryable<BlogPost>>(authorId.HasValue, x => x.AuthorId == authorId) .WhereIf<BlogPost, IMongoQueryable<BlogPost>>(authorId.HasValue, x => x.AuthorId == authorId)
@ -69,6 +79,7 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
Guid? blogId = null, Guid? blogId = null,
Guid? authorId = null, Guid? authorId = null,
Guid? tagId = null, Guid? tagId = null,
Guid? favoriteUserId = null,
BlogPostStatus? statusFilter = null, BlogPostStatus? statusFilter = null,
int maxResultCount = int.MaxValue, int maxResultCount = int.MaxValue,
int skipCount = 0, int skipCount = 0,
@ -79,12 +90,15 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
var dbContext = await GetDbContextAsync(cancellationToken); var dbContext = await GetDbContextAsync(cancellationToken);
var blogPostQueryable = await GetQueryableAsync(); 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 usersQueryable = dbContext.Collection<CmsUser>().AsQueryable();
var queryable = blogPostQueryable 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(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter))
.WhereIf(authorId.HasValue, x => x.AuthorId == authorId) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId)
@ -132,6 +146,28 @@ public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, B
return entityIdFilters; 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, public virtual async Task<bool> SlugExistsAsync(Guid blogId, [NotNull] string slug,
CancellationToken cancellationToken = default) 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? AuthorId { get; set; }
public Guid? TagId { 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); var blog = await BlogRepository.GetBySlugAsync(blogSlug);
Guid? favoriteUserId = await GetFavoriteUserIdAsync(input.FilterOnFavorites);
var blogPosts = await BlogPostRepository.GetListAsync(null, blog.Id, input.AuthorId, input.TagId, var blogPosts = await BlogPostRepository.GetListAsync(null, blog.Id, input.AuthorId, input.TagId,
favoriteUserId,
BlogPostStatus.Published, input.MaxResultCount, BlogPostStatus.Published, input.MaxResultCount,
input.SkipCount, input.Sorting); input.SkipCount, input.Sorting);
@ -100,4 +103,14 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
return tag.Name; return tag.Name;
} }
protected virtual async Task<Guid?> GetFavoriteUserIdAsync(bool? filterOnFavorites)
{
if (!filterOnFavorites.GetValueOrDefault() || !CurrentUser.IsAuthenticated)
{
return null;
}
return CurrentUser.GetId();
}
} }

2
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css

@ -2,6 +2,6 @@
} }
.cms-markedItem-area i { .cms-markedItem-area i {
font-size: 1.5em; font-size: 1.05rem;
cursor: pointer; cursor: pointer;
} }

8
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml

@ -5,7 +5,8 @@
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Modals.Login.LoginModalModel @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Modals.Login.LoginModalModel
@{ @{
Layout = null; Layout = null;
var message = String.IsNullOrEmpty(Model.ViewModel.Message) ? L["CmsKit:Modals:LoginModalDefaultMessage"] : Model.ViewModel.Message; var message = String.IsNullOrEmpty(Model.ViewModel.Message) ? L["CmsKit:Modals:LoginModalDefaultMessage"] :
Model.ViewModel.Message;
} }
<abp-modal centered="true"> <abp-modal centered="true">
<abp-modal-header title="@L["CmsKit:Modals:YouAreNotAuthenticated"]"> <abp-modal-header title="@L["CmsKit:Modals:YouAreNotAuthenticated"]">
@ -15,8 +16,9 @@
<h4> <h4>
@message @message
</h4> </h4>
<br />
<a href="@Model.ViewModel.LoginUrl" class="btn btn-secondary">@L["CmsKit:Modals:Login"]</a>
</div> </div>
</abp-modal-body> </abp-modal-body>
<abp-modal-footer class="d-flex justify-content-center">
<a href="@Model.ViewModel.LoginUrl" class="btn btn-secondary">@L["CmsKit:Modals:Login"]</a>
</abp-modal-footer>
</abp-modal> </abp-modal>

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

@ -1,6 +1,10 @@
@page @page
@using Volo.CmsKit.Blogs
@using Volo.CmsKit.Public.Web.Pages @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.Public.Web.Pages.Public.CmsKit.Blogs
@using Volo.CmsKit.Web @using Volo.CmsKit.Web
@ -8,6 +12,11 @@
@model IndexModel @model IndexModel
@{
const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png";
var isMarkedItemFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled<MarkedItemsFeature>() && Model.MarkedItemsFeature?.IsEnabled == true;
}
@section styles { @section styles {
<abp-style src="/Pages/Public/CmsKit/Blogs/index.css" /> <abp-style src="/Pages/Public/CmsKit/Blogs/index.css" />
} }
@ -15,13 +24,13 @@
@section scripts { @section scripts {
<abp-script-bundle> <abp-script-bundle>
<abp-script src="/Pages/Public/CmsKit/Blogs/index.js" /> <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> </abp-script-bundle>
} }
@{
const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png";
}
@if (Model.AuthorId.HasValue) @if (Model.AuthorId.HasValue)
{ {
<abp-row id="blogs-filter-area"> <abp-row id="blogs-filter-area">
@ -61,11 +70,37 @@
@if (Model.Blogs.TotalCount > 0) @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"> <abp-row id="blogs-container">
@foreach (var blog in Model.Blogs.Items) @foreach (var blog in Model.Blogs.Items)
{ {
<abp-column size="_12" size-md="_6" size-lg="_4"> <abp-column size="_12" size-md="_6" size-lg="_4">
<abp-card> <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 d-flex align-items-center justify-content-center"
abp-tooltip="@L["ToggleFavorite"]">
@await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new
{
entityId = blog.Id.ToString(),
entityType = BlogPostConsts.EntityType
})
</abp-button>
</div>
}
@if (blog.CoverImageMediaId != null) @if (blog.CoverImageMediaId != null)
{ {
<img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" /> <img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />

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

@ -1,9 +1,13 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination;
using Volo.Abp.GlobalFeatures;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents; using Volo.CmsKit.Contents;
using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Public.Blogs; using Volo.CmsKit.Public.Blogs;
using Volo.CmsKit.Users; using Volo.CmsKit.Users;
@ -22,6 +26,9 @@ public class IndexModel : CmsKitPublicPageModelBase
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public Guid? AuthorId { get; set; } public Guid? AuthorId { get; set; }
[BindProperty(SupportsGet = true)]
public bool? FilterOnFavorites { get; set; }
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public Guid? TagId { get; set; } public Guid? TagId { get; set; }
@ -32,12 +39,17 @@ public class IndexModel : CmsKitPublicPageModelBase
public CmsUserDto SelectedAuthor { get; protected set; } public CmsUserDto SelectedAuthor { get; protected set; }
public string FilteredTagName { get; protected set; } public string FilteredTagName { get; protected set; }
public BlogFeatureDto MarkedItemsFeature { get; private set; }
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } protected IBlogPostPublicAppService BlogPostPublicAppService { get; }
public IBlogFeatureAppService BlogFeatureAppService { get; }
public IndexModel(IBlogPostPublicAppService blogPostPublicAppService) public IndexModel(
IBlogPostPublicAppService blogPostPublicAppService,
IBlogFeatureAppService blogFeatureAppService)
{ {
BlogPostPublicAppService = blogPostPublicAppService; BlogPostPublicAppService = blogPostPublicAppService;
BlogFeatureAppService = blogFeatureAppService;
} }
public virtual async Task<IActionResult> OnGetAsync() public virtual async Task<IActionResult> OnGetAsync()
@ -49,7 +61,8 @@ public class IndexModel : CmsKitPublicPageModelBase
SkipCount = PageSize * (CurrentPage - 1), SkipCount = PageSize * (CurrentPage - 1),
MaxResultCount = PageSize, MaxResultCount = PageSize,
AuthorId = AuthorId, AuthorId = AuthorId,
TagId = TagId TagId = TagId,
FilterOnFavorites = FilterOnFavorites
}); });
if (AuthorId != null) if (AuthorId != null)
@ -62,6 +75,13 @@ public class IndexModel : CmsKitPublicPageModelBase
FilteredTagName = await BlogPostPublicAppService.GetTagNameAsync(TagId.Value); FilteredTagName = await BlogPostPublicAppService.GetTagNameAsync(TagId.Value);
} }
if (GlobalFeatureManager.Instance.IsEnabled<MarkedItemsFeature>() &&
Blogs.Items.Any())
{
var blogId = Blogs.Items.First().BlogId;
MarkedItemsFeature = await BlogFeatureAppService.GetOrDefaultAsync(blogId, GlobalFeatures.MarkedItemsFeature.Name);
}
return Page(); return Page();
} }
} }

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; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
}
.favorite-toggle {
width: 40px;
height: 40px;
} }
Loading…
Cancel
Save