mirror of https://github.com/abpframework/abp.git
22 changed files with 616 additions and 1 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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,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<Comment>> GetListAsync( |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
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<Comment>> GetListAsync( |
|||
string entityType, |
|||
string entityId) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); |
|||
Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); |
|||
|
|||
return await DbSet |
|||
.Where(x => |
|||
x.EntityType == entityType && |
|||
x.EntityId == entityId) |
|||
.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) |
|||
{ |
|||
await base.DeleteAsync(reply.Id, autoSave, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
await base.DeleteAsync(id, autoSave, GetCancellationToken(cancellationToken)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
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; } |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public class CommentWithRepliesDto |
|||
{ |
|||
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; } |
|||
} |
|||
} |
|||
@ -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,19 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
public interface ICommentPublicAppService : IApplicationService |
|||
{ |
|||
Task<List<CommentWithRepliesDto>> 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,83 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.CmsKit.Comments |
|||
{ |
|||
[Authorize] |
|||
public class CommentPublicAppService : ApplicationService, ICommentPublicAppService |
|||
{ |
|||
protected ICommentRepository CommentRepository { get; } |
|||
|
|||
public CommentPublicAppService(ICommentRepository commentRepository) |
|||
{ |
|||
CommentRepository = commentRepository; |
|||
} |
|||
|
|||
public async Task<List<CommentWithRepliesDto>> GetAllForEntityAsync(string entityType, string entityId) |
|||
{ |
|||
var comments = await CommentRepository.GetListAsync(entityType, entityId); |
|||
|
|||
return ConvertCommentsToNestedStructure(comments); |
|||
} |
|||
|
|||
public async Task<CommentDto> CreateAsync(CreateCommentInput input) |
|||
{ |
|||
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); |
|||
} |
|||
|
|||
public async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input) |
|||
{ |
|||
var comment = await CommentRepository.GetAsync(id); |
|||
|
|||
comment.SetText(input.Text); |
|||
|
|||
var updatedComment = await CommentRepository.UpdateAsync(comment); |
|||
|
|||
return ObjectMapper.Map<Comment, CommentDto>(updatedComment); |
|||
} |
|||
|
|||
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<CommentWithRepliesDto> ConvertCommentsToNestedStructure(List<Comment> comments) |
|||
{ |
|||
var parentComments = comments |
|||
.Where(c=> c.RepliedCommentId == null) |
|||
.Select(c=> ObjectMapper.Map<Comment, CommentWithRepliesDto>(c)) |
|||
.ToList(); |
|||
|
|||
foreach (var parentComment in parentComments) |
|||
{ |
|||
parentComment.Replies = comments |
|||
.Where(c => c.RepliedCommentId == parentComment.Id) |
|||
.Select(c => ObjectMapper.Map<Comment, CommentDto>(c)) |
|||
.ToList(); |
|||
} |
|||
|
|||
return parentComments; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
|
|||
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<List<CommentWithRepliesDto>> 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,92 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
using Volo.CmsKit.Reactions; |
|||
|
|||
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 |
|||
{ |
|||
protected IReactionPublicAppService ReactionPublicAppService { get; } |
|||
|
|||
protected CmsKitUiOptions Options { get; } |
|||
|
|||
public CommentingViewComponent( |
|||
IReactionPublicAppService reactionPublicAppService, |
|||
IOptions<CmsKitUiOptions> options) |
|||
{ |
|||
} |
|||
|
|||
public virtual async Task<IViewComponentResult> InvokeAsync( |
|||
string entityType, |
|||
string entityId) |
|||
{ |
|||
|
|||
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", new CommentingViewModel |
|||
{ |
|||
EntityType = entityType, |
|||
EntityId = entityId, |
|||
Reactions = new List<CommentViewModel>() |
|||
}); |
|||
|
|||
var result = await ReactionPublicAppService.GetForSelectionAsync(entityType, entityId); |
|||
|
|||
var viewModel = new CommentingViewModel |
|||
{ |
|||
EntityType = entityType, |
|||
EntityId = entityId, |
|||
Reactions = new List<CommentViewModel>() |
|||
}; |
|||
|
|||
foreach (var reactionDto in result.Items) |
|||
{ |
|||
viewModel.Reactions.Add( |
|||
new CommentViewModel //TODO: AutoMap
|
|||
{ |
|||
Name = reactionDto.Reaction.Name, |
|||
DisplayName = reactionDto.Reaction.DisplayName, |
|||
Icon = Options.ReactionIcons.GetLocalizedIcon(reactionDto.Reaction.Name), |
|||
Count = reactionDto.Count, |
|||
IsSelectedByCurrentUser = reactionDto.IsSelectedByCurrentUser |
|||
}); |
|||
} |
|||
|
|||
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel); |
|||
} |
|||
|
|||
public class CommentingViewModel |
|||
{ |
|||
public string EntityType { get; set; } |
|||
|
|||
public string EntityId { get; set; } |
|||
|
|||
public List<CommentViewModel> Reactions { get; set; } |
|||
} |
|||
|
|||
public class CommentViewModel |
|||
{ |
|||
[NotNull] |
|||
public string Name { get; set; } |
|||
|
|||
[CanBeNull] |
|||
public string DisplayName { get; set; } |
|||
|
|||
[NotNull] |
|||
public string Icon { get; set; } |
|||
|
|||
public int Count { get; set; } |
|||
|
|||
public bool IsSelectedByCurrentUser { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
@model Volo.CmsKit.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent.CommentingViewModel |
|||
<div class="cms-comment-area" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId"> |
|||
<div class="comment p-3 pl-5"> |
|||
<h5> <i class="fa fa-comment-o"></i> Yunus Emre Kalkan </h5> |
|||
<p> |
|||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. |
|||
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, |
|||
when an unknown printer took a galley of type and scrambled it to make a type specimen book. |
|||
It has survived not only five centuries, but also the leap into electronic typesetting. |
|||
</p> |
|||
</div> |
|||
<hr/> |
|||
<div class="comment p-3 pl-5"> |
|||
<h5> <i class="fa fa-comment-o"></i> Yunus Emre Kalkan </h5> |
|||
<p> |
|||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. |
|||
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, |
|||
when an unknown printer took a galley of type and scrambled it to make a type specimen book. |
|||
It has survived not only five centuries, but also the leap into electronic typesetting. |
|||
</p> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,13 @@ |
|||
.cms-reaction-select-icon |
|||
{ |
|||
cursor: pointer; |
|||
} |
|||
.cms-reaction-icon |
|||
{ |
|||
cursor: pointer; |
|||
padding: 3px 5px 5px; |
|||
} |
|||
.cms-reaction-icon-selected |
|||
{ |
|||
background-color: #eef; |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
(function ($) { |
|||
|
|||
/* |
|||
var l = abp.localization.getResource('CmsKit'); |
|||
|
|||
var myDefaultWhiteList = $.fn.tooltip.Constructor.Default.whiteList; |
|||
|
|||
if (myDefaultWhiteList.span.indexOf('data-reaction-name') < 0) { |
|||
myDefaultWhiteList.span.push('data-reaction-name'); |
|||
} |
|||
|
|||
$(document).ready(function () { |
|||
|
|||
abp.widgets.CmsReactionSelection = function ($widget) { |
|||
var widgetManager = $widget.data('abp-widget-manager'); |
|||
var $reactionArea = $widget.find('.cms-reaction-area'); |
|||
var $selectIcon = $widget.find('.cms-reaction-select-icon'); |
|||
var $popoverContent = $widget.find('.cms-reaction-selection-popover-content'); |
|||
|
|||
function getFilters() { |
|||
return { |
|||
entityType: $reactionArea.attr('data-entity-type'), |
|||
entityId: $reactionArea.attr('data-entity-id') |
|||
}; |
|||
} |
|||
|
|||
function registerClickOfReactionIcons($container) { |
|||
$container.find('.cms-reaction-icon').each(function () { |
|||
var $icon = $(this); |
|||
$icon.click(function () { |
|||
var methodName = $icon.hasClass('cms-reaction-icon-selected') ? 'delete' : 'create'; |
|||
volo.cmsKit.reactions.reactionPublic[methodName]( |
|||
$.extend(getFilters(), { |
|||
reactionName: $icon.attr('data-reaction-name') |
|||
}) |
|||
).then(function () { |
|||
$selectIcon.popover('hide'); |
|||
widgetManager.refresh($widget); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function init() { |
|||
|
|||
$selectIcon.popover({ |
|||
placement: 'right', |
|||
html: true, |
|||
trigger: 'focus', |
|||
title: l('PickYourReaction'), |
|||
content: $popoverContent.html() |
|||
}).on('shown.bs.popover', function () { |
|||
var $popover = $('#' + $selectIcon.attr('aria-describedby')); |
|||
registerClickOfReactionIcons($popover); |
|||
}); |
|||
|
|||
registerClickOfReactionIcons($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