mirror of https://github.com/abpframework/abp.git
committed by
GitHub
45 changed files with 1470 additions and 55 deletions
@ -1,43 +0,0 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace Volo.CmsKit.Migrations |
|||
{ |
|||
public partial class Added_Reactions : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.CreateTable( |
|||
name: "CmsUserReactions", |
|||
columns: table => new |
|||
{ |
|||
Id = table.Column<Guid>(nullable: false), |
|||
EntityType = table.Column<string>(maxLength: 64, nullable: false), |
|||
EntityId = table.Column<string>(maxLength: 64, nullable: false), |
|||
ReactionName = table.Column<string>(maxLength: 32, nullable: false), |
|||
CreatorId = table.Column<Guid>(nullable: false), |
|||
CreationTime = table.Column<DateTime>(nullable: false) |
|||
}, |
|||
constraints: table => |
|||
{ |
|||
table.PrimaryKey("PK_CmsUserReactions", x => x.Id); |
|||
}); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_CmsUserReactions_EntityType_EntityId", |
|||
table: "CmsUserReactions", |
|||
columns: new[] { "EntityType", "EntityId" }); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_CmsUserReactions_CreatorId_EntityType_EntityId_ReactionName", |
|||
table: "CmsUserReactions", |
|||
columns: new[] { "CreatorId", "EntityType", "EntityId", "ReactionName" }); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropTable( |
|||
name: "CmsUserReactions"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
@page |
|||
@using Localization.Resources.AbpUi |
|||
@using Microsoft.Extensions.Localization |
|||
@using Volo.CmsKit.Pages |
|||
@using Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting |
|||
@model PublicModel |
|||
@inject IStringLocalizer<AbpUiResource> Localizer |
|||
<h1>CMS Kit DEMO - Public</h1> |
|||
|
|||
<abp-card class="mb-3"> |
|||
<abp-card-body> |
|||
<abp-blockquote> |
|||
<p> |
|||
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. |
|||
</p> |
|||
<footer> |
|||
Martin Fowler |
|||
</footer> |
|||
</abp-blockquote> |
|||
</abp-card-body> |
|||
@await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "publicQuote", entityId = "1", loginUrl="Account/Login?ReturnUrl=%2Fpublic" }) |
|||
</abp-card> |
|||
|
|||
<abp-card class="mb-3"> |
|||
<abp-card-body> |
|||
<abp-blockquote> |
|||
<p> |
|||
Writing code is very simple, but writing simple code is the hardest thing there is! |
|||
</p> |
|||
<footer> |
|||
Halil ibrahim Kalkan <small>(inspired from Johan Cruyff)</small> |
|||
</footer> |
|||
</abp-blockquote> |
|||
</abp-card-body> |
|||
@await Component.InvokeAsync(typeof(CommentingViewComponent), new { entityType = "publicQuote", entityId = "2", loginUrl="Account/Login?ReturnUrl=%2Fpublic" }) |
|||
</abp-card> |
|||
@ -0,0 +1,11 @@ |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
|
|||
namespace Volo.CmsKit.Pages |
|||
{ |
|||
public class PublicModel : PageModel |
|||
{ |
|||
public void OnGet() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public static class CommentConsts |
|||
{ |
|||
public static int EntityTypeLength { get; set; } = 64; |
|||
|
|||
public static int EntityIdLength { get; set; } = 64; |
|||
|
|||
public static int MaxTextLength { get; set; } = 512; |
|||
} |
|||
} |
|||
@ -1,6 +1,18 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"PickYourReaction": "Pick your reaction" |
|||
"PickYourReaction": "Pick your reaction", |
|||
"YourComment": "Your comment", |
|||
"YourReply": "Your reply", |
|||
"Comments": "Comments", |
|||
"Send": "Send", |
|||
"Delete": "Delete", |
|||
"Reply": "Reply", |
|||
"Update": "Update", |
|||
"Edit": "Edit", |
|||
"LoginToAddComment": "Login to add comment", |
|||
"LoginToReply": "Login to reply", |
|||
"MessageDeletionConfirmationMessage": "This comment will be deleted completely.", |
|||
"CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display." |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class Comment: Entity<Guid>, IAggregateRoot<Guid>, IHasCreationTime, IMustHaveCreator |
|||
{ |
|||
public virtual string EntityType { get; protected set; } |
|||
|
|||
public virtual string EntityId { get; protected set; } |
|||
|
|||
public virtual string Text { get; protected set; } |
|||
|
|||
public virtual Guid? RepliedCommentId { get; protected set; } |
|||
|
|||
public virtual Guid CreatorId { get; set; } |
|||
|
|||
public virtual DateTime CreationTime { get; set; } |
|||
|
|||
protected Comment() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Comment( |
|||
Guid id, |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId, |
|||
[NotNull] string text, |
|||
Guid? repliedCommentId, |
|||
Guid creatorId) |
|||
: base(id) |
|||
{ |
|||
EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType), CommentConsts.EntityTypeLength); |
|||
EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId), CommentConsts.EntityIdLength); |
|||
RepliedCommentId = repliedCommentId; |
|||
CreatorId = creatorId; |
|||
|
|||
SetText(text); |
|||
} |
|||
|
|||
public virtual void SetText(string text) |
|||
{ |
|||
Text = Check.NotNullOrWhiteSpace(text, nameof(text), CommentConsts.MaxTextLength); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.CmsKit.Users; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CommentWithAuthor |
|||
{ |
|||
public Comment Comment { get; set; } |
|||
|
|||
public CmsUser Author { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public interface ICommentRepository : IBasicRepository<Comment, Guid> |
|||
{ |
|||
Task<List<CommentWithAuthor>> GetListWithAuthorsAsync( |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public class CmsUser : AggregateRoot<Guid>, IUser, IUpdateUserData |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
|
|||
public virtual string UserName { get; protected set; } |
|||
|
|||
public virtual string Email { get; protected set; } |
|||
|
|||
public virtual string Name { get; set; } |
|||
|
|||
public virtual string Surname { get; set; } |
|||
|
|||
public virtual bool EmailConfirmed { get; protected set; } |
|||
|
|||
public virtual string PhoneNumber { get; protected set; } |
|||
|
|||
public virtual bool PhoneNumberConfirmed { get; protected set; } |
|||
|
|||
protected CmsUser() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public CmsUser(IUserData user) |
|||
: base(user.Id) |
|||
{ |
|||
TenantId = user.TenantId; |
|||
UpdateInternal(user); |
|||
} |
|||
|
|||
public virtual bool Update(IUserData user) |
|||
{ |
|||
if (Id != user.Id) |
|||
{ |
|||
throw new ArgumentException($"Given User's Id '{user.Id}' does not match to this User's Id '{Id}'"); |
|||
} |
|||
|
|||
if (TenantId != user.TenantId) |
|||
{ |
|||
throw new ArgumentException($"Given User's TenantId '{user.TenantId}' does not match to this User's TenantId '{TenantId}'"); |
|||
} |
|||
|
|||
if (Equals(user)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
UpdateInternal(user); |
|||
return true; |
|||
} |
|||
|
|||
protected virtual bool Equals(IUserData user) |
|||
{ |
|||
return Id == user.Id && |
|||
TenantId == user.TenantId && |
|||
UserName == user.UserName && |
|||
Name == user.Name && |
|||
Surname == user.Surname && |
|||
Email == user.Email && |
|||
EmailConfirmed == user.EmailConfirmed && |
|||
PhoneNumber == user.PhoneNumber && |
|||
PhoneNumberConfirmed == user.PhoneNumberConfirmed; |
|||
} |
|||
|
|||
protected virtual void UpdateInternal(IUserData user) |
|||
{ |
|||
Email = user.Email; |
|||
Name = user.Name; |
|||
Surname = user.Surname; |
|||
EmailConfirmed = user.EmailConfirmed; |
|||
PhoneNumber = user.PhoneNumber; |
|||
PhoneNumberConfirmed = user.PhoneNumberConfirmed; |
|||
UserName = user.UserName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Volo.Abp.Uow; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public class CmsUserLookupService: UserLookupService<CmsUser, ICmsUserRepository>, ICmsUserLookupService |
|||
{ |
|||
public CmsUserLookupService( |
|||
ICmsUserRepository userRepository, |
|||
IUnitOfWorkManager unitOfWorkManager) |
|||
: base( |
|||
userRepository, |
|||
unitOfWorkManager) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override CmsUser CreateUser(IUserData externalUser) |
|||
{ |
|||
return new CmsUser(externalUser); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public class CmsUserSynchronizer : |
|||
IDistributedEventHandler<EntityUpdatedEto<UserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
protected ICmsUserRepository UserRepository { get; } |
|||
|
|||
protected ICmsUserLookupService UserLookupService { get; } |
|||
|
|||
public CmsUserSynchronizer( |
|||
ICmsUserRepository userRepository, |
|||
ICmsUserLookupService userLookupService) |
|||
{ |
|||
UserRepository = userRepository; |
|||
UserLookupService = userLookupService; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityUpdatedEto<UserEto> eventData) |
|||
{ |
|||
var user = await UserRepository.FindAsync(eventData.Entity.Id); |
|||
if (user == null) |
|||
{ |
|||
user = await UserLookupService.FindByIdAsync(eventData.Entity.Id); |
|||
if (user == null) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (user.Update(eventData.Entity)) |
|||
{ |
|||
await UserRepository.UpdateAsync(user); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public interface ICmsUserLookupService : IUserLookupService<CmsUser> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public interface ICmsUserRepository: IUserRepository<CmsUser> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.CmsKit.EntityFrameworkCore; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class EfCoreCommentRepository : EfCoreRepository<ICmsKitDbContext, Comment, Guid>, |
|||
ICommentRepository |
|||
{ |
|||
public EfCoreCommentRepository(IDbContextProvider<ICmsKitDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async Task<List<CommentWithAuthor>> GetListWithAuthorsAsync( |
|||
string entityType, |
|||
string entityId) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); |
|||
Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); |
|||
|
|||
var query = from comment in DbSet |
|||
join user in DbContext.CmsUsers on comment.CreatorId equals user.Id |
|||
where entityType == comment.EntityType && entityId == comment.EntityId |
|||
orderby comment.CreationTime |
|||
select new CommentWithAuthor |
|||
{ |
|||
Comment = comment, |
|||
Author = user |
|||
}; |
|||
|
|||
return await query.ToListAsync(); |
|||
} |
|||
|
|||
public override async Task DeleteAsync(Guid id, bool autoSave = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
var replies = await DbSet |
|||
.Where(x => x.RepliedCommentId == id) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
|
|||
foreach (var reply in replies) |
|||
{ |
|||
//TODO: Discuss if it is better to mark it as deleted and show in the ui as "This is deleted" instead of deleting it and replies completely
|
|||
await base.DeleteAsync(reply.Id, autoSave, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
await base.DeleteAsync(id, autoSave, GetCancellationToken(cancellationToken)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.Users.EntityFrameworkCore; |
|||
using Volo.CmsKit.EntityFrameworkCore; |
|||
|
|||
namespace Volo.CmsKit.Users |
|||
{ |
|||
public class EfCoreCmsUserRepository: EfCoreUserRepositoryBase<ICmsKitDbContext, CmsUser>, ICmsUserRepository |
|||
{ |
|||
public EfCoreCmsUserRepository(IDbContextProvider<ICmsKitDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CmsUserDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string UserName { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string Surname { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CommentDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string EntityType { get; set; } |
|||
|
|||
public string EntityId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
|
|||
public Guid? RepliedCommentId { get; set; } |
|||
|
|||
public Guid CreatorId { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public CmsUserDto Author { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CommentWithDetailsDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string EntityType { get; set; } |
|||
|
|||
public string EntityId { get; set; } |
|||
|
|||
public string Text { get; set; } |
|||
|
|||
public Guid CreatorId { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public List<CommentDto> Replies { get; set; } |
|||
|
|||
public CmsUserDto Author { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CreateCommentInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.EntityTypeLength))] |
|||
public string EntityType { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.EntityIdLength))] |
|||
public string EntityId { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] |
|||
public string Text { get; set; } |
|||
|
|||
public Guid? RepliedCommentId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public interface ICommentPublicAppService : IApplicationService |
|||
{ |
|||
Task<ListResultDto<CommentWithDetailsDto>> GetAllForEntityAsync(string entityType, string entityId); |
|||
|
|||
Task<CommentDto> CreateAsync(CreateCommentInput input); |
|||
|
|||
Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input); |
|||
|
|||
Task DeleteAsync(Guid id); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class UpdateCommentInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] |
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Authorization; |
|||
using Volo.CmsKit.Users; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CommentPublicAppService : ApplicationService, ICommentPublicAppService |
|||
{ |
|||
protected CmsKitOptions CmsKitOptions { get; } |
|||
protected ICommentRepository CommentRepository { get; } |
|||
protected ICmsUserLookupService CmsUserLookupService { get; } |
|||
|
|||
public CommentPublicAppService( |
|||
ICommentRepository commentRepository, |
|||
ICmsUserLookupService cmsUserLookupService, |
|||
IOptions<CmsKitOptions> cmsKitOptions) |
|||
{ |
|||
CmsKitOptions = cmsKitOptions.Value; |
|||
CommentRepository = commentRepository; |
|||
CmsUserLookupService = cmsUserLookupService; |
|||
} |
|||
|
|||
public async Task<ListResultDto<CommentWithDetailsDto>> GetAllForEntityAsync(string entityType, string entityId) |
|||
{ |
|||
CheckAuthorizationAsync(entityType); |
|||
|
|||
var commentsWithAuthor = await CommentRepository.GetListWithAuthorsAsync(entityType, entityId); |
|||
|
|||
return new ListResultDto<CommentWithDetailsDto>( |
|||
ConvertCommentsToNestedStructure(commentsWithAuthor) |
|||
); |
|||
} |
|||
|
|||
[Authorize] |
|||
public async Task<CommentDto> CreateAsync(CreateCommentInput input) |
|||
{ |
|||
var user = await CmsUserLookupService.FindByIdAsync(CurrentUser.Id.Value); |
|||
|
|||
if (user == null) |
|||
{ |
|||
throw new BusinessException(message: "User Not found!"); |
|||
} |
|||
|
|||
var comment = await CommentRepository.InsertAsync(new Comment( |
|||
GuidGenerator.Create(), |
|||
input.EntityType, |
|||
input.EntityId, |
|||
input.Text, |
|||
input.RepliedCommentId, |
|||
CurrentUser.Id.Value |
|||
)); |
|||
|
|||
return ObjectMapper.Map<Comment, CommentDto>(comment); |
|||
} |
|||
|
|||
[Authorize] |
|||
public async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input) |
|||
{ |
|||
var comment = await CommentRepository.GetAsync(id); |
|||
|
|||
if (comment.CreatorId != CurrentUser.Id) |
|||
{ |
|||
throw new BusinessException(); |
|||
} |
|||
|
|||
comment.SetText(input.Text); |
|||
|
|||
var updatedComment = await CommentRepository.UpdateAsync(comment); |
|||
|
|||
return ObjectMapper.Map<Comment, CommentDto>(updatedComment); |
|||
} |
|||
|
|||
[Authorize] |
|||
public async Task DeleteAsync(Guid id) |
|||
{ |
|||
var comment = await CommentRepository.GetAsync(id); |
|||
|
|||
if (comment.CreatorId != CurrentUser.Id) |
|||
{ |
|||
throw new BusinessException(); |
|||
} |
|||
|
|||
await CommentRepository.DeleteAsync(id); |
|||
} |
|||
|
|||
private List<CommentWithDetailsDto> ConvertCommentsToNestedStructure(List<CommentWithAuthor> comments) |
|||
{ |
|||
var parentComments = comments |
|||
.Where(c=> c.Comment.RepliedCommentId == null) |
|||
.Select(c=> ObjectMapper.Map<Comment, CommentWithDetailsDto>(c.Comment)) |
|||
.ToList(); |
|||
|
|||
foreach (var parentComment in parentComments) |
|||
{ |
|||
parentComment.Author = GetAuthorAsDtoFromCommentList(comments, parentComment.Id); |
|||
|
|||
parentComment.Replies = comments |
|||
.Where(c => c.Comment.RepliedCommentId == parentComment.Id) |
|||
.Select(c => ObjectMapper.Map<Comment, CommentDto>(c.Comment)) |
|||
.ToList(); |
|||
|
|||
foreach (var reply in parentComment.Replies) |
|||
{ |
|||
reply.Author = GetAuthorAsDtoFromCommentList(comments, reply.Id); |
|||
} |
|||
} |
|||
|
|||
return parentComments; |
|||
} |
|||
|
|||
private async Task CheckAuthorizationAsync(string entityType) |
|||
{ |
|||
if (await IsPublicEntity(entityType)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!CurrentUser.IsAuthenticated) |
|||
{ |
|||
throw new AbpAuthorizationException(L["CommentAuthorizationExceptionMessage"]); |
|||
} |
|||
} |
|||
|
|||
private async Task<bool> IsPublicEntity(string entityType) |
|||
{ |
|||
return CmsKitOptions.PublicCommentEntities.Contains(entityType); |
|||
} |
|||
|
|||
private CmsUserDto GetAuthorAsDtoFromCommentList(List<CommentWithAuthor> comments, Guid commentId) |
|||
{ |
|||
return ObjectMapper.Map<CmsUser, CmsUserDto>(comments.Single(c => c.Comment.Id == commentId).Author); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
[RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] |
|||
[Area("cms-kit")] |
|||
[Route("api/cms-kit-public/comments")] |
|||
public class CommentPublicController : CmsKitPublicControllerBase, ICommentPublicAppService |
|||
{ |
|||
public ICommentPublicAppService CommentPublicAppService { get; } |
|||
|
|||
public CommentPublicController(ICommentPublicAppService commentPublicAppService) |
|||
{ |
|||
CommentPublicAppService = commentPublicAppService; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{entityType}/{entityId}")] |
|||
public Task<ListResultDto<CommentWithDetailsDto>> GetAllForEntityAsync(string entityType, string entityId) |
|||
{ |
|||
return CommentPublicAppService.GetAllForEntityAsync(entityType, entityId); |
|||
} |
|||
|
|||
[HttpPost] |
|||
public Task<CommentDto> CreateAsync(CreateCommentInput input) |
|||
{ |
|||
return CommentPublicAppService.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("{id}")] |
|||
public Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input) |
|||
{ |
|||
return CommentPublicAppService.UpdateAsync(id, input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("update")] |
|||
public Task DeleteAsync(Guid id) |
|||
{ |
|||
return CommentPublicAppService.DeleteAsync(id); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting |
|||
{ |
|||
public class CommentingScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Commenting/default.js"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting |
|||
{ |
|||
public class CommentingStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Commenting/default.css"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
using Volo.CmsKit.Comments; |
|||
|
|||
namespace Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting |
|||
{ |
|||
[ViewComponent(Name = "CmsCommenting")] |
|||
[Widget( |
|||
ScriptTypes = new[] {typeof(CommentingScriptBundleContributor)}, |
|||
StyleTypes = new[] {typeof(CommentingStyleBundleContributor)}, |
|||
RefreshUrl = "/CmsKitPublicWidgets/Commenting" |
|||
)] |
|||
public class CommentingViewComponent : AbpViewComponent |
|||
{ |
|||
public ICommentPublicAppService CommentPublicAppService { get; } |
|||
|
|||
public CommentingViewComponent( |
|||
ICommentPublicAppService commentPublicAppService) |
|||
{ |
|||
CommentPublicAppService = commentPublicAppService; |
|||
} |
|||
|
|||
public virtual async Task<IViewComponentResult> InvokeAsync(string entityType, string entityId, string loginUrl = null) |
|||
{ |
|||
var result = await CommentPublicAppService.GetAllForEntityAsync(entityType, entityId); |
|||
|
|||
var viewModel = new CommentingViewModel |
|||
{ |
|||
EntityId = entityId, |
|||
EntityType = entityType, |
|||
LoginUrl = loginUrl, |
|||
Comments = result.Items.ToList() |
|||
}; |
|||
|
|||
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel); |
|||
} |
|||
|
|||
public class CommentingViewModel |
|||
{ |
|||
public string EntityType { get; set; } |
|||
|
|||
public string EntityId { get; set; } |
|||
|
|||
public string LoginUrl { get; set; } |
|||
|
|||
public List<CommentWithDetailsDto> Comments { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
@using Microsoft.AspNetCore.Html |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.Abp.Users |
|||
@using Volo.CmsKit.Comments |
|||
@using Volo.CmsKit.Localization |
|||
@inject ICurrentUser CurrentUser |
|||
@inject IHtmlLocalizer<CmsKitResource> L |
|||
@model Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent.CommentingViewModel |
|||
|
|||
@{ |
|||
Func<dynamic, IHtmlContent> GetCommentTitle(CmsUserDto author, DateTime creationTime) => |
|||
@<span> |
|||
@((string.IsNullOrWhiteSpace(author.Name) |
|||
? author.UserName |
|||
: author.Name + " " + author.Surname).Trim()) |
|||
<small>@creationTime.ToString()</small> |
|||
</span>; |
|||
} |
|||
@{ |
|||
Func<dynamic, IHtmlContent> GetCommentArea(Guid? repliedCommentId, bool cancelButton = false) => |
|||
@<div class="cms-comment-form-area" data-reply-id="@(repliedCommentId?.ToString() ?? "")" |
|||
style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")"> |
|||
<form class="cms-comment-form"> |
|||
<input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId"/> |
|||
<div class="form-group"> |
|||
<label> @(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? L["YourComment"] : L["YourReply"]) </label> |
|||
<textarea class="form-control" name="commentText" rows="3"></textarea> |
|||
</div> |
|||
@if (cancelButton) |
|||
{ |
|||
<abp-button type="button" button-type="Secondary" class="reply-cancel-button" data-reply-id="@(repliedCommentId?.ToString() ?? "")"> @L["Cancel"] </abp-button> |
|||
} |
|||
<abp-button type="submit" button-type="Primary"> @L["Send"] </abp-button> |
|||
</form> |
|||
</div>; |
|||
} |
|||
@{ |
|||
Func<dynamic, IHtmlContent> GetCommentContentArea(Guid id, Guid authorId, bool isReply, string text) => |
|||
@<div> |
|||
<div class="cms-comment-content-area" data-id="@id.ToString()"> |
|||
<p> |
|||
@text |
|||
</p> |
|||
</div> |
|||
|
|||
@if (!isReply) |
|||
{ |
|||
@if (CurrentUser.IsAuthenticated) |
|||
{ |
|||
<a href="#" class="comment-reply-link" data-reply-id="@id.ToString()"> |
|||
<i class="fa fa-reply"></i> @L["Reply"] |
|||
</a> |
|||
} |
|||
else |
|||
{ |
|||
<a href="@Model.LoginUrl"> @L["LoginToReply"]</a> |
|||
} |
|||
} |
|||
@if (authorId == CurrentUser.Id) |
|||
{ |
|||
@if (!isReply) |
|||
{ |
|||
<span> | </span> |
|||
} |
|||
|
|||
<a href="#" class="comment-delete-link" data-id="@id.ToString()"> |
|||
<i class="fa fa-trash"></i> @L["Delete"] |
|||
</a> |
|||
|
|||
<span> | </span> |
|||
|
|||
<a href="#" class="comment-edit-link" data-id="@id.ToString()"> |
|||
<i class="fa fa-pencil"></i> @L["Edit"] |
|||
</a> |
|||
} |
|||
<div class="cms-comment-edit-area" data-id="@id.ToString()" style="display:none"> |
|||
<form class="cms-comment-update-form"> |
|||
<input hidden value="@id.ToString()" name="id"/> |
|||
<div class="form-group"> |
|||
<textarea class="form-control" name="commentText" rows="3">@text</textarea> |
|||
</div> |
|||
<abp-button type="button" button-type="Secondary" class="comment-edit-cancel-button" data-id="@id.ToString()"> @L["Cancel"] </abp-button> |
|||
<abp-button type="submit" button-type="Primary"> @L["Update"] </abp-button> |
|||
</form> |
|||
</div> |
|||
</div>; |
|||
} |
|||
|
|||
<div class="cms-comment-area" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId"> |
|||
|
|||
<div class="pl-5 pt-3"> |
|||
<h4> |
|||
<i class="fa fa-comment-o"></i> |
|||
@L["Comments"] |
|||
</h4> |
|||
@foreach (var comment in Model.Comments) |
|||
{ |
|||
<div class="comment pt-2 mt-1 mb-1"> |
|||
<div class="border-bottom"> |
|||
<h5> |
|||
@GetCommentTitle(comment.Author, comment.CreationTime).Invoke(null) |
|||
</h5> |
|||
|
|||
@GetCommentContentArea(comment.Id, comment.Author.Id, false, comment.Text).Invoke(null) |
|||
|
|||
@if (CurrentUser.IsAuthenticated) |
|||
{ |
|||
@GetCommentArea(comment.Id, true).Invoke(null) |
|||
} |
|||
|
|||
@if (comment.Replies.Any()) |
|||
{ |
|||
<div class="pl-5"> |
|||
@foreach (var reply in comment.Replies) |
|||
{ |
|||
<div class="comment pt-3"> |
|||
<div class="border-bottom"> |
|||
|
|||
<h6> |
|||
@GetCommentTitle(reply.Author, reply.CreationTime).Invoke(null) |
|||
</h6> |
|||
|
|||
@GetCommentContentArea(reply.Id, reply.Author.Id, true, reply.Text).Invoke(null) |
|||
</div> |
|||
</div> |
|||
} |
|||
</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
} |
|||
<div class="p-3 pl-5"> |
|||
@if (CurrentUser.IsAuthenticated) |
|||
{ |
|||
@GetCommentArea(null).Invoke(null) |
|||
} |
|||
else if (!string.IsNullOrWhiteSpace(Model.LoginUrl)) |
|||
{ |
|||
<div class="float-right mb-2"> |
|||
<a href="@Model.LoginUrl" class="btn btn-primary"> @L["LoginToAddComment"]</a> |
|||
</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1 @@ |
|||
|
|||
@ -0,0 +1,162 @@ |
|||
(function ($) { |
|||
|
|||
var l = abp.localization.getResource('CmsKit'); |
|||
|
|||
$(document).ready(function () { |
|||
|
|||
abp.widgets.CmsCommenting = function ($widget) { |
|||
var widgetManager = $widget.data('abp-widget-manager'); |
|||
var $commentArea = $widget.find('.cms-comment-area'); |
|||
|
|||
function getFilters() { |
|||
return { |
|||
entityType: $commentArea.attr('data-entity-type'), |
|||
entityId: $commentArea.attr('data-entity-id') |
|||
}; |
|||
} |
|||
|
|||
function registerEditLinks($container) { |
|||
$container.find('.comment-edit-link').each(function () { |
|||
var $link = $(this); |
|||
$link.on('click', function (e) { |
|||
e.preventDefault(); |
|||
|
|||
var commentId = $link.data('id'); |
|||
|
|||
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id='+ commentId +']'); |
|||
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id='+ commentId +']'); |
|||
|
|||
$relatedCommentContentArea.hide(); |
|||
$relatedCommentEditFormArea.show(); |
|||
$link.removeAttr('href'); |
|||
}); |
|||
}); |
|||
$container.find('.comment-edit-cancel-button').each(function () { |
|||
var $button = $(this); |
|||
$button.on('click', function (e) { |
|||
e.preventDefault(); |
|||
|
|||
var commentId = $button.data('id'); |
|||
|
|||
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id='+ commentId +']'); |
|||
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id='+ commentId +']'); |
|||
var $link = $container.find('.comment-edit-link[data-id='+ commentId +']'); |
|||
|
|||
$relatedCommentContentArea.show(); |
|||
$relatedCommentEditFormArea.hide(); |
|||
$link.attr('href','#'); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function registerReplyLinks($container) { |
|||
$container.find('.comment-reply-link').each(function () { |
|||
var $link = $(this); |
|||
$link.on('click', function (e) { |
|||
e.preventDefault(); |
|||
|
|||
var replyCommentId = $link.data('reply-id'); |
|||
|
|||
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id='+ replyCommentId +']'); |
|||
|
|||
$relatedCommentArea.show(); |
|||
$link.removeAttr('href'); |
|||
}); |
|||
}); |
|||
$container.find('.reply-cancel-button').each(function () { |
|||
var $button = $(this); |
|||
$button.on('click', function (e) { |
|||
e.preventDefault(); |
|||
|
|||
var replyCommentId = $button.data('reply-id'); |
|||
|
|||
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id='+ replyCommentId +']'); |
|||
var $replyLink = $container.find('.comment-reply-link[data-reply-id='+ replyCommentId +']'); |
|||
|
|||
$relatedCommentArea.hide(); |
|||
$replyLink.attr('href','#'); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function registerDeleteLinks($container) { |
|||
$container.find('.comment-delete-link').each(function () { |
|||
var $link = $(this); |
|||
$link.on('click', '', function (e) { |
|||
e.preventDefault(); |
|||
|
|||
abp.message.confirm(l("MessageDeletionConfirmationMessage"), function (ok) { |
|||
if (ok){ |
|||
volo.cmsKit.comments.commentPublic.delete($link.data('id') |
|||
).then(function () { |
|||
widgetManager.refresh($widget); |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function registerUpdateOfNewComment($container) { |
|||
$container.find('.cms-comment-update-form').each(function () { |
|||
var $form = $(this); |
|||
$form.submit(function (e) { |
|||
e.preventDefault(); |
|||
var formAsObject = $form.serializeFormToObject(); |
|||
volo.cmsKit.comments.commentPublic.update( |
|||
formAsObject.id, |
|||
{ |
|||
text: formAsObject.commentText |
|||
} |
|||
).then(function () { |
|||
widgetManager.refresh($widget); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function registerSubmissionOfNewComment($container) { |
|||
$container.find('.cms-comment-form').each(function () { |
|||
var $form = $(this); |
|||
$form.submit(function (e) { |
|||
e.preventDefault(); |
|||
var formAsObject = $form.serializeFormToObject(); |
|||
console.log(formAsObject); |
|||
volo.cmsKit.comments.commentPublic.create( |
|||
$.extend(getFilters(), { |
|||
repliedCommentId: formAsObject.repliedCommentId, |
|||
text: formAsObject.commentText |
|||
}) |
|||
).then(function () { |
|||
widgetManager.refresh($widget); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function init() { |
|||
registerReplyLinks($widget); |
|||
registerEditLinks($widget); |
|||
registerDeleteLinks($widget); |
|||
|
|||
registerUpdateOfNewComment($widget); |
|||
registerSubmissionOfNewComment($widget); |
|||
} |
|||
|
|||
return { |
|||
init: init, |
|||
getFilters: getFilters |
|||
}; |
|||
}; |
|||
|
|||
$('.abp-widget-wrapper[data-widget-name="CmsCommenting"]') |
|||
.each(function () { |
|||
var widgetManager = new abp.WidgetManager({ |
|||
wrapper: $(this), |
|||
}); |
|||
|
|||
widgetManager.init(); |
|||
}); |
|||
}); |
|||
|
|||
})(jQuery); |
|||
Loading…
Reference in new issue