Browse Source

Merge pull request #15039 from abpframework/Cmskit-deleting-blogpost-v12514

Cmskit deleting blogpost and adding captcha to the comment component
pull/15090/head
malik masis 4 years ago
committed by GitHub
parent
commit
ccc8281fcd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs
  2. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Comments/CommentWithAuthorDto.cs
  3. 6
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs
  4. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs
  5. 0
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/EntityTypeDefinition.cs
  6. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  7. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json
  8. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs
  9. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs
  10. 26
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParameteresInput.cs
  11. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj
  12. 17
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  13. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  14. 8
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/BlogPostPublicClientProxy.Generated.cs
  15. 37
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json
  16. 11
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs
  17. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs
  18. 42
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs
  19. 51
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs
  20. 55
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  21. 26
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css
  22. 83
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js
  23. 30
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.scss
  24. 61
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml
  25. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs
  26. 16
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/blogPost.js
  27. 8
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.js
  28. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs
  29. 66
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs
  30. 32
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs
  31. 7
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs
  32. 73
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs
  33. 175
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs
  34. 5
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj
  35. 8
      modules/cms-kit/src/Volo.CmsKit.Public.Web/wwwroot/client-proxies/cms-kit-proxy.js

1
modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

@ -167,6 +167,7 @@ public class CmsKitWebUnifiedModule : AbpModule
Configure<CmsKitCommentOptions>(options => Configure<CmsKitCommentOptions>(options =>
{ {
options.EntityTypes.Add(new CommentEntityTypeDefinition("quote")); options.EntityTypes.Add(new CommentEntityTypeDefinition("quote"));
options.IsRecaptchaEnabled = true;
}); });
Configure<CmsKitMediaOptions>(options => Configure<CmsKitMediaOptions>(options =>

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Comments/CommentWithAuthorDto.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Volo.CmsKit.Users;
namespace Volo.CmsKit.Admin.Comments; namespace Volo.CmsKit.Admin.Comments;

6
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CmsKitCommentOptions.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs

@ -7,4 +7,10 @@ public class CmsKitCommentOptions
{ {
[NotNull] [NotNull]
public List<CommentEntityTypeDefinition> EntityTypes { get; } = new List<CommentEntityTypeDefinition>(); public List<CommentEntityTypeDefinition> EntityTypes { get; } = new List<CommentEntityTypeDefinition>();
/// <summary>
/// Flag to enable/disable ReCaptcha for comment component.
/// Default: false
/// </summary>
public bool IsRecaptchaEnabled { get; set; }
} }

1
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs

@ -1,5 +1,4 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using System;
using Volo.Abp; using Volo.Abp;
namespace Volo.CmsKit.Comments; namespace Volo.CmsKit.Comments;

0
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/EntityTypeDefinition.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/EntityTypeDefinition.cs

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

@ -209,6 +209,9 @@
"Feature:ReactionEnable": "Reaction", "Feature:ReactionEnable": "Reaction",
"Feature:ReactionEnableDescription": "CMS Kit's reaction system that allows users to send reactions to entities such as BlogPost, Comments, etc.", "Feature:ReactionEnableDescription": "CMS Kit's reaction system that allows users to send reactions to entities such as BlogPost, Comments, etc.",
"Feature:TagEnable": "Taging", "Feature:TagEnable": "Taging",
"Feature:TagEnableDescription": "CMS Kit's tag system that allows tagging entities such as BlogPost." "Feature:TagEnableDescription": "CMS Kit's tag system that allows tagging entities such as BlogPost.",
"DeleteBlogPostMessage": "The blog will be deleted. Are you sure?",
"CaptchaCode": "Captcha code",
"CommentTextRequired": "Comment is required"
} }
} }

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json

@ -209,6 +209,9 @@
"Feature:ReactionEnable": "Reaksiyon", "Feature:ReactionEnable": "Reaksiyon",
"Feature:ReactionEnableDescription": "Cmskit reaksiyon sistemi", "Feature:ReactionEnableDescription": "Cmskit reaksiyon sistemi",
"Feature:TagEnable": "Etkiket", "Feature:TagEnable": "Etkiket",
"Feature:TagEnableDescription": "Cmskit etiket sistemi" "Feature:TagEnableDescription": "Cmskit etiket sistemi",
"DeleteBlogPostMessage": "Blog silinecek. Emin misiniz?",
"CaptchaCode": "Captcha kodu",
"CommentTextRequired": "Yorum zorunlu"
} }
} }

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

@ -17,4 +17,5 @@ public interface IBlogPostPublicAppService : IApplicationService
Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input); Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input);
Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id); Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id);
Task DeleteAsync(Guid id);
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs

@ -13,4 +13,8 @@ public class CreateCommentInput
public string Text { get; set; } public string Text { get; set; }
public Guid? RepliedCommentId { get; set; } public Guid? RepliedCommentId { get; set; }
public Guid? CaptchaToken { get; set; }
public int CaptchaAnswer { get; set; }
} }

26
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParameteresInput.cs

@ -0,0 +1,26 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
using Volo.CmsKit.Comments;
namespace Volo.CmsKit.Public.Comments;
[Serializable]
public class CreateCommentWithParameteresInput
{
[Required]
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))]
public string Text { get; set; }
[Required]
public string EntityType { get; set; }
[Required]
public string EntityId { get; set; }
public Guid? RepliedCommentId { get; set; }
public Guid? CaptchaToken { get; set; }
public int CaptchaAnswer { get; set; }
}

2
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" /> <Import Project="..\..\..\..\common.props" />
<Import Project="..\..\..\..\configureawait.props" /> <Import Project="..\..\..\..\configureawait.props" />

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

@ -2,11 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.Features; using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures; using Volo.Abp.GlobalFeatures;
using Volo.Abp.Users;
using Volo.CmsKit.Blogs; using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents; using Volo.CmsKit.Contents;
using Volo.CmsKit.Features; using Volo.CmsKit.Features;
using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.GlobalFeatures;
@ -70,4 +72,17 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
return ObjectMapper.Map<CmsUser, CmsUserDto>(author); return ObjectMapper.Map<CmsUser, CmsUserDto>(author);
} }
[Authorize]
public async Task DeleteAsync(Guid id)
{
var rating = await BlogPostRepository.GetAsync(id);
if (rating.CreatorId != CurrentUser.GetId())
{
throw new AbpAuthorizationException();
}
await BlogPostRepository.DeleteAsync(id);
}
} }

1
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs

@ -68,7 +68,6 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
) )
); );
await UnitOfWorkManager.Current.SaveChangesAsync(); await UnitOfWorkManager.Current.SaveChangesAsync();
await DistributedEventBus.PublishAsync(new CreatedCommentEvent await DistributedEventBus.PublishAsync(new CreatedCommentEvent

8
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/BlogPostPublicClientProxy.Generated.cs

@ -50,4 +50,12 @@ public partial class BlogPostPublicClientProxy : ClientProxyBase<IBlogPostPublic
{ typeof(Guid), id } { typeof(Guid), id }
}); });
} }
public virtual async Task DeleteAsync(Guid id)
{
await RequestAsync(nameof(DeleteAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), id }
});
}
} }

37
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json

@ -1169,6 +1169,43 @@
}, },
"allowAnonymous": null, "allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService" "implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
},
"DeleteAsyncById": {
"uniqueName": "DeleteAsyncById",
"name": "DeleteAsync",
"httpMethod": "DELETE",
"url": "api/cms-kit-public/blog-posts/{id}",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "id",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "id",
"name": "id",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
],
"returnValue": {
"type": "System.Void",
"typeSimple": "System.Void"
},
"allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
} }
} }
} }

11
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs

@ -42,15 +42,22 @@ public class BlogPostPublicController : CmsKitPublicControllerBase, IBlogPostPub
[HttpGet] [HttpGet]
[Route("authors")] [Route("authors")]
public Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input) public virtual Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input)
{ {
return BlogPostPublicAppService.GetAuthorsHasBlogPostsAsync(input); return BlogPostPublicAppService.GetAuthorsHasBlogPostsAsync(input);
} }
[HttpGet] [HttpGet]
[Route("authors/{id}")] [Route("authors/{id}")]
public Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id) public virtual Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id)
{ {
return BlogPostPublicAppService.GetAuthorHasBlogPostAsync(id); return BlogPostPublicAppService.GetAuthorHasBlogPostAsync(id);
} }
[HttpDelete]
[Route("{id}")]
public virtual Task DeleteAsync(Guid id)
{
return BlogPostPublicAppService.DeleteAsync(id);
}
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs

@ -1,4 +1,6 @@
using AutoMapper; using AutoMapper;
using Volo.CmsKit.Menus;
using Volo.CmsKit.Public.Comments;
namespace Volo.CmsKit.Public.Web; namespace Volo.CmsKit.Public.Web;
@ -6,6 +8,6 @@ public class CmsKitPublicWebAutoMapperProfile : Profile
{ {
public CmsKitPublicWebAutoMapperProfile() public CmsKitPublicWebAutoMapperProfile()
{ {
CreateMap<CreateCommentWithParameteresInput, CreateCommentInput>();
} }
} }

42
modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.ObjectMapping;
using Volo.CmsKit.Comments;
using Volo.CmsKit.Public.Comments;
using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
using Volo.CmsKit.Public.Web.Security.Captcha;
namespace Volo.CmsKit.Public.Web.Controllers;
//[Route("cms-kit/public-comments")]
public class CmsKitPublicCommentsController : AbpController
{
public ICommentPublicAppService CommentPublicAppService { get; }
protected CmsKitCommentOptions CmsKitCommentOptions { get; }
public SimpleMathsCaptchaGenerator SimpleMathsCaptchaGenerator { get; }
public CmsKitPublicCommentsController(
ICommentPublicAppService commentPublicAppService,
IOptions<CmsKitCommentOptions> cmsKitCommentOptions,
SimpleMathsCaptchaGenerator simpleMathsCaptchaGenerator)
{
CommentPublicAppService = commentPublicAppService;
CmsKitCommentOptions = cmsKitCommentOptions.Value;
SimpleMathsCaptchaGenerator = simpleMathsCaptchaGenerator;
}
[HttpPost]
public async Task ValidateAsync([FromBody] CreateCommentWithParameteresInput input)
{
if (CmsKitCommentOptions.IsRecaptchaEnabled && input.CaptchaToken.HasValue)
{
SimpleMathsCaptchaGenerator.Validate(input.CaptchaToken.Value, input.CaptchaAnswer);
}
var dto = ObjectMapper.Map<CreateCommentWithParameteresInput, CreateCommentInput> (input);
await CommentPublicAppService.CreateAsync(input.EntityType, input.EntityId, dto);
}
}

51
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -7,7 +8,9 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI; using Volo.Abp.AspNetCore.Mvc.UI;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.CmsKit.Comments;
using Volo.CmsKit.Public.Comments; using Volo.CmsKit.Public.Comments;
using Volo.CmsKit.Public.Web.Security.Captcha;
using Volo.CmsKit.Web.Renderers; using Volo.CmsKit.Web.Renderers;
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting; namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
@ -24,15 +27,34 @@ public class CommentingViewComponent : AbpViewComponent
public ICommentPublicAppService CommentPublicAppService { get; } public ICommentPublicAppService CommentPublicAppService { get; }
public IMarkdownToHtmlRenderer MarkdownToHtmlRenderer { get; } public IMarkdownToHtmlRenderer MarkdownToHtmlRenderer { get; }
public AbpMvcUiOptions AbpMvcUiOptions { get; } public AbpMvcUiOptions AbpMvcUiOptions { get; }
public CmsKitCommentOptions CmsKitCommentOptions { get; }
public SimpleMathsCaptchaGenerator SimpleMathsCaptchaGenerator { get; }
[HiddenInput]
[BindProperty]
public string RecaptchaToken { get; set; }
[HiddenInput]
[BindProperty]
public Guid CaptchaId { get; set; }
[BindProperty]
public CommentingViewModel Input { get; set; }
public CaptchaOutput CaptchaOutput { get; set; }
public CommentingViewComponent( public CommentingViewComponent(
ICommentPublicAppService commentPublicAppService, ICommentPublicAppService commentPublicAppService,
IOptions<AbpMvcUiOptions> options, IOptions<AbpMvcUiOptions> options,
IMarkdownToHtmlRenderer markdownToHtmlRenderer) IMarkdownToHtmlRenderer markdownToHtmlRenderer,
IOptions<CmsKitCommentOptions> cmsKitCommentOptions,
SimpleMathsCaptchaGenerator simpleMathsCaptchaGenerator)
{ {
CommentPublicAppService = commentPublicAppService; CommentPublicAppService = commentPublicAppService;
MarkdownToHtmlRenderer = markdownToHtmlRenderer; MarkdownToHtmlRenderer = markdownToHtmlRenderer;
AbpMvcUiOptions = options.Value; AbpMvcUiOptions = options.Value;
CmsKitCommentOptions = cmsKitCommentOptions.Value;
SimpleMathsCaptchaGenerator = simpleMathsCaptchaGenerator;
} }
public virtual async Task<IViewComponentResult> InvokeAsync( public virtual async Task<IViewComponentResult> InvokeAsync(
@ -42,7 +64,6 @@ public class CommentingViewComponent : AbpViewComponent
var comments = (await CommentPublicAppService var comments = (await CommentPublicAppService
.GetListAsync(entityType, entityId)).Items; .GetListAsync(entityType, entityId)).Items;
var loginUrl = $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}"; var loginUrl = $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}";
var viewModel = new CommentingViewModel var viewModel = new CommentingViewModel
@ -52,10 +73,26 @@ public class CommentingViewComponent : AbpViewComponent
LoginUrl = loginUrl, LoginUrl = loginUrl,
Comments = comments.OrderByDescending(i => i.CreationTime).ToList() Comments = comments.OrderByDescending(i => i.CreationTime).ToList()
}; };
await ConvertMarkdownTextsToHtml(viewModel); await ConvertMarkdownTextsToHtml(viewModel);
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel); if (CmsKitCommentOptions.IsRecaptchaEnabled)
{
CaptchaOutput = SimpleMathsCaptchaGenerator.Generate(new CaptchaOptions(
number1MinValue: 1,
number1MaxValue: 10,
number2MinValue: 5,
number2MaxValue: 15)
);
viewModel.CaptchaImageBase64 = GetCaptchaImageBase64(CaptchaOutput.ImageBytes);
}
this.Input = viewModel;
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", this);
}
private string GetCaptchaImageBase64(byte[] bytes)
{
return $"data:image/jpg;base64,{Convert.ToBase64String(bytes)}";
} }
private async Task ConvertMarkdownTextsToHtml(CommentingViewModel viewModel) private async Task ConvertMarkdownTextsToHtml(CommentingViewModel viewModel)
@ -86,6 +123,12 @@ public class CommentingViewComponent : AbpViewComponent
public IReadOnlyList<CommentWithDetailsDto> Comments { get; set; } public IReadOnlyList<CommentWithDetailsDto> Comments { get; set; }
public Dictionary<Guid, string> RawCommentTexts { get; set; } public Dictionary<Guid, string> RawCommentTexts { get; set; }
[Required]
[StringLength(100, MinimumLength = 1)]
public string Captcha { get; set; }
public string CaptchaImageBase64 { get; set; }
} }
} }

55
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml

@ -3,6 +3,7 @@
@using Microsoft.Extensions.Options @using Microsoft.Extensions.Options
@using Volo.Abp.GlobalFeatures @using Volo.Abp.GlobalFeatures
@using Volo.Abp.Users @using Volo.Abp.Users
@using Volo.CmsKit.Comments;
@using Volo.CmsKit.GlobalFeatures @using Volo.CmsKit.GlobalFeatures
@using Volo.CmsKit.Localization @using Volo.CmsKit.Localization
@using Volo.CmsKit.Public.Comments @using Volo.CmsKit.Public.Comments
@ -10,8 +11,9 @@
@using Volo.CmsKit.Web @using Volo.CmsKit.Web
@inject ICurrentUser CurrentUser @inject ICurrentUser CurrentUser
@inject IOptionsSnapshot<CmsKitUiOptions> cmsKitUiOptions; @inject IOptionsSnapshot<CmsKitUiOptions> cmsKitUiOptions;
@inject IOptions<CmsKitCommentOptions> CmsKitCommentOptions
@inject IHtmlLocalizer<CmsKitResource> L @inject IHtmlLocalizer<CmsKitResource> L
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent.CommentingViewModel @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent
@{ @{
Func<dynamic, IHtmlContent> GetCommentTitle(CmsUserDto author, DateTime creationTime) => Func<dynamic, IHtmlContent> GetCommentTitle(CmsUserDto author, DateTime creationTime) =>
@ -36,9 +38,29 @@
<textarea class="form-control" name="commentText" placeholder="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? L["YourComment"] : L["YourReply"])..." rows="3"></textarea> <textarea class="form-control" name="commentText" placeholder="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? L["YourComment"] : L["YourReply"])..." rows="3"></textarea>
</div> </div>
</div> </div>
<div class="mt-0">
<small class="text-muted float-start float-end">@L["MarkdownSupported"]</small>
</div>
@if (CmsKitCommentOptions.Value.IsRecaptchaEnabled)
{
<div class="volo-captcha">
<label class="form-label" for="Input_Captcha">@L["CaptchaCode"]</label>
<div class="d-flex">
<div class="bd-highlight">
<img src="@Model.Input.CaptchaImageBase64" />
</div>
<div class="flex-grow-1 bd-highlight">
<abp-input asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off" />
</div>
<abp-input asp-for="@Model.CaptchaId" value="@Model.CaptchaOutput.Id" />
</div>
</div>
}
<div class="col-auto"> <div class="col-auto">
<div class="text-end"> <div class="text-end">
<abp-button type="submit" button-type="Primary" size="Block"> <abp-button id="submit-button" type="submit" button-type="Primary" size="Block">
<i class="fa fa-comment-alt me-1"></i> @L["Send"] <i class="fa fa-comment-alt me-1"></i> @L["Send"]
</abp-button> </abp-button>
@if (cancelButton) @if (cancelButton)
@ -49,9 +71,6 @@
} }
</div> </div>
</div> </div>
<div class="mt-0">
<small class="text-muted float-start" >@L["MarkdownSupported"]</small>
</div>
</div> </div>
</form> </form>
</div>; </div>;
@ -71,13 +90,13 @@
{ {
@if (CurrentUser.IsAuthenticated) @if (CurrentUser.IsAuthenticated)
{ {
<a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}_{id}_link")"> <a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}_link")">
<i class="fa fa-reply mr -1"></i> @L["Reply"] <i class="fa fa-reply mr -1"></i> @L["Reply"]
</a> </a>
} }
else else
{ {
<a href="@(Model.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a> <a href="@(Model.Input.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
} }
} }
@if (authorId == CurrentUser.Id) @if (authorId == CurrentUser.Id)
@ -93,9 +112,10 @@
} }
@{ @{
Func<dynamic, IHtmlContent> GetEditArea(Guid id, string text, string concurrencyStamp) => Func<dynamic, IHtmlContent> GetEditArea(Guid id, string text, string concurrencyStamp) =>
@<div class="cms-comment-edit-area" id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}_{id}")" data-id="@id.ToString()" style="display:none"> @<div class="cms-comment-edit-area" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}")" data-id="@id.ToString()" style="display:none">
<div class="card bg-light p-3 mx-0 mt-3"> <div class="card bg-light p-3 mx-0 mt-3">
<form class="cms-comment-update-form"> <form id="ContactForm" class="cms-comment-update-form">
<abp-input asp-for="@Model.RecaptchaToken" />
<input hidden value="@id.ToString()" name="id" /> <input hidden value="@id.ToString()" name="id" />
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -119,20 +139,20 @@
</div>; </div>;
} }
<div class="cms-comment-area mb-5" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId"> <div class="cms-comment-area mb-5" data-entity-type="@Model.Input.EntityType" data-entity-id="@Model.Input.EntityId">
@if (CurrentUser.IsAuthenticated) @if (CurrentUser.IsAuthenticated)
{ {
<div id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}")"> <div id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}")">
@GetCommentArea(null).Invoke(null) @GetCommentArea(null).Invoke(null)
</div> </div>
} }
else if (!string.IsNullOrWhiteSpace(Model.LoginUrl)) else if (!string.IsNullOrWhiteSpace(Model.Input.LoginUrl))
{ {
<div class="text-end"> <div class="text-end">
<a href="@Model.LoginUrl" class="btn btn-primary"> @L["LoginToAddComment"]</a> <a href="@Model.Input.LoginUrl" class="btn btn-primary"> @L["LoginToAddComment"]</a>
</div> </div>
} }
@foreach (var comment in Model.Comments) @foreach (var comment in Model.Input.Comments)
{ {
<div class="comment"> <div class="comment">
<div class="card p-3 mx-0 my-3"> <div class="card p-3 mx-0 my-3">
@ -157,7 +177,7 @@
</div> </div>
</div> </div>
</div> </div>
@GetEditArea(comment.Id, Model.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null) @GetEditArea(comment.Id, Model.Input.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null)
@if (comment.Replies.Any()) @if (comment.Replies.Any())
{ {
@ -187,7 +207,7 @@
</div> </div>
</div> </div>
@GetEditArea(reply.Id, Model.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null) @GetEditArea(reply.Id, Model.Input.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null)
</div> </div>
</div> </div>
} }
@ -202,7 +222,7 @@
} }
else else
{ {
<a href="@(Model.LoginUrl + "_" + comment.Id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a> <a href="@(Model.Input.LoginUrl + "_" + comment.Id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
} }
</div> </div>
} }
@ -216,3 +236,4 @@
</div> </div>
} }
</div> </div>

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

@ -3,4 +3,28 @@
} }
.comment-links:hover { .comment-links:hover {
text-decoration: none; text-decoration: none;
} }
body .volo-captcha img {
border: 1px outset #dddcdc;
border-radius: 4px;
height: 45px;
}
body .volo-captcha .d-flex .form-group {
width: 100%;
}
@media (max-width: 800px) {
body .volo-captcha #Input_Captcha {
margin-left: 0px;
}
}
body .form-control {
border: 1px outset #e3e3e3 !important;
padding: 10px !important;
}
body #submit-button {
margin-top: 10px;
}

83
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

@ -1,10 +1,10 @@
(function ($) { (function ($) {
var l = abp.localization.getResource('CmsKit'); let l = abp.localization.getResource('CmsKit');
abp.widgets.CmsCommenting = function ($widget) { abp.widgets.CmsCommenting = function ($widget) {
var widgetManager = $widget.data('abp-widget-manager'); let widgetManager = $widget.data('abp-widget-manager');
var $commentArea = $widget.find('.cms-comment-area'); let $commentArea = $widget.find('.cms-comment-area');
function getFilters() { function getFilters() {
return { return {
@ -15,14 +15,14 @@
function registerEditLinks($container) { function registerEditLinks($container) {
$container.find('.comment-edit-link').each(function () { $container.find('.comment-edit-link').each(function () {
var $link = $(this); let $link = $(this);
$link.on('click', function (e) { $link.on('click', function (e) {
e.preventDefault(); e.preventDefault();
var commentId = $link.data('id'); let commentId = $link.data('id');
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']'); let $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']'); let $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
$relatedCommentContentArea.hide(); $relatedCommentContentArea.hide();
$relatedCommentEditFormArea.show(); $relatedCommentEditFormArea.show();
@ -30,15 +30,15 @@
}); });
}); });
$container.find('.comment-edit-cancel-button').each(function () { $container.find('.comment-edit-cancel-button').each(function () {
var $button = $(this); let $button = $(this);
$button.on('click', function (e) { $button.on('click', function (e) {
e.preventDefault(); e.preventDefault();
var commentId = $button.data('id'); let commentId = $button.data('id');
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']'); let $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']'); let $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
var $link = $container.find('.comment-edit-link[data-id=' + commentId + ']'); let $link = $container.find('.comment-edit-link[data-id=' + commentId + ']');
$relatedCommentContentArea.show(); $relatedCommentContentArea.show();
$relatedCommentEditFormArea.hide(); $relatedCommentEditFormArea.hide();
@ -49,14 +49,14 @@
function registerReplyLinks($container) { function registerReplyLinks($container) {
$container.find('.comment-reply-link').each(function () { $container.find('.comment-reply-link').each(function () {
var $link = $(this); let $link = $(this);
$link.on('click', function (e) { $link.on('click', function (e) {
e.preventDefault(); e.preventDefault();
var replyCommentId = $link.data('reply-id'); let replyCommentId = $link.data('reply-id');
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']'); let $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
var $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']'); let $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
$relatedCommentArea.show(); $relatedCommentArea.show();
$relatedCommentArea.find('textarea').focus(); $relatedCommentArea.find('textarea').focus();
@ -64,14 +64,14 @@
}); });
}); });
$container.find('.reply-cancel-button').each(function () { $container.find('.reply-cancel-button').each(function () {
var $button = $(this); let $button = $(this);
$button.on('click', function (e) { $button.on('click', function (e) {
e.preventDefault(); e.preventDefault();
var replyCommentId = $button.data('reply-id'); let replyCommentId = $button.data('reply-id');
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']'); let $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
var $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']'); let $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
$relatedCommentArea.hide(); $relatedCommentArea.hide();
$links.removeClass('disabled'); $links.removeClass('disabled');
@ -81,7 +81,7 @@
function registerDeleteLinks($container) { function registerDeleteLinks($container) {
$container.find('.comment-delete-link').each(function () { $container.find('.comment-delete-link').each(function () {
var $link = $(this); let $link = $(this);
$link.on('click', '', function (e) { $link.on('click', '', function (e) {
e.preventDefault(); e.preventDefault();
@ -99,10 +99,10 @@
function registerUpdateOfNewComment($container) { function registerUpdateOfNewComment($container) {
$container.find('.cms-comment-update-form').each(function () { $container.find('.cms-comment-update-form').each(function () {
var $form = $(this); let $form = $(this);
$form.submit(function (e) { $form.submit(function (e) {
e.preventDefault(); e.preventDefault();
var formAsObject = $form.serializeFormToObject(); let formAsObject = $form.serializeFormToObject();
volo.cmsKit.public.comments.commentPublic.update( volo.cmsKit.public.comments.commentPublic.update(
formAsObject.id, formAsObject.id,
{ {
@ -118,24 +118,39 @@
function registerSubmissionOfNewComment($container) { function registerSubmissionOfNewComment($container) {
$container.find('.cms-comment-form').each(function () { $container.find('.cms-comment-form').each(function () {
var $form = $(this); let $form = $(this);
$form.submit(function (e) { $form.submit(function (e) {
e.preventDefault(); e.preventDefault();
var formAsObject = $form.serializeFormToObject(); let formAsObject = $form.serializeFormToObject();
if (formAsObject.repliedCommentId == ''){ if (formAsObject.repliedCommentId == '') {
formAsObject.repliedCommentId = null; formAsObject.repliedCommentId = null;
} }
volo.cmsKit.public.comments.commentPublic.create( if (formAsObject.commentText == '') {
$commentArea.attr('data-entity-type'), abp.message.error(l("CommentTextRequired"));
$commentArea.attr('data-entity-id'), return;
{ }
$.ajax({
type: 'POST',
url: '/CmsKitPublicComments/Validate',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify({
entityId: $commentArea.attr('data-entity-id'),
entityType: $commentArea.attr('data-entity-type'),
repliedCommentId: formAsObject.repliedCommentId, repliedCommentId: formAsObject.repliedCommentId,
text: formAsObject.commentText text: formAsObject.commentText,
captchaToken: formAsObject.captchaId,
captchaAnswer: formAsObject.input?.captcha
}),
success: function () {
widgetManager.refresh($widget);
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
} }
).then(function () {
widgetManager.refresh($widget);
}); });
}); });
}); });
@ -146,7 +161,7 @@
return; return;
} }
var $link = $(location.hash + '_link'); let $link = $(location.hash + '_link');
if ($link.length > 0) { if ($link.length > 0) {
$link.click(); $link.click();

30
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.scss

@ -0,0 +1,30 @@
body {
.volo-captcha {
img {
border: 1px outset #dddcdc;
border-radius: 4px;
height: 45px;
}
.d-flex {
.form-group {
width: 100%;
}
}
@media (max-width: 800px) {
#Input_Captcha {
margin-left: 0px;
}
}
}
.form-control {
border: 1px outset #e3e3e3 !important;
padding: 10px !important;
}
#submit-button {
margin-top: 10px;
}
}

61
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml

@ -1,6 +1,7 @@
@page @page
@using Volo.Abp.Data @using Volo.Abp.Data
@using Volo.Abp.Users;
@using Volo.CmsKit.Public.Blogs @using Volo.CmsKit.Public.Blogs
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment
@using Volo.CmsKit.Public.Web.Pages @using Volo.CmsKit.Public.Web.Pages
@ -17,9 +18,9 @@
@model Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs.BlogPostModel @model Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs.BlogPostModel
@inject ICurrentUser CurrentUser
@inject IMarkdownToHtmlRenderer MarkdownRenderer @inject IMarkdownToHtmlRenderer MarkdownRenderer
@{ @{
string dummyImageSource = "https://dummyimage.com/1280x720/a3a3a3/fff.png?text=" + Model.ViewModel.Title; string dummyImageSource = "https://dummyimage.com/1280x720/a3a3a3/fff.png?text=" + Model.ViewModel.Title;
var isScrollIndexEnabled = GlobalFeatureManager.Instance.IsEnabled<BlogPostScrollIndexFeature>() && Model.BlogPostScrollIndexFeature?.IsEnabled == true; var isScrollIndexEnabled = GlobalFeatureManager.Instance.IsEnabled<BlogPostScrollIndexFeature>() && Model.BlogPostScrollIndexFeature?.IsEnabled == true;
@ -50,14 +51,15 @@
<abp-script-bundle> <abp-script-bundle>
<abp-script type="typeof(HighlightJsScriptContributor)" /> <abp-script type="typeof(HighlightJsScriptContributor)" />
<abp-script src="/Pages/Public/CmsKit/highlightOnLoad.js" /> <abp-script src="/Pages/Public/CmsKit/highlightOnLoad.js" />
<abp-script src="/Pages/Public/CmsKit/Blogs/blogPost.js" />
</abp-script-bundle> </abp-script-bundle>
} }
<div class="row"> <div class="row">
<div @Html.Raw(isScrollIndexEnabled ? "class=\"col-md-10 col-sm-12\"" : "class=\"col-md-12\"")> <div @Html.Raw(isScrollIndexEnabled ? "class=\"col-md-10 col-sm-12\"" : "class=\"col-md-12\"")>
<abp-card class="mb-4"> <abp-card class="mb-4">
<img src="/api/cms-kit/media/@Model.ViewModel.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" /> <img src="/api/cms-kit/media/@Model.ViewModel.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />
<input hidden id="BlogId" value="@Model.ViewModel.Id" />
<abp-card-body> <abp-card-body>
<abp-row> <abp-row>
<div class="col-lg-8 col-md-10 mx-auto pb-4"> <div class="col-lg-8 col-md-10 mx-auto pb-4">
@ -109,10 +111,10 @@
if (Model.ReactionsFeature?.IsEnabled == true) if (Model.ReactionsFeature?.IsEnabled == true)
{ {
@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new
{ {
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString() entityId = Model.ViewModel.Id.ToString()
}) })
} }
} }
</abp-column> </abp-column>
@ -122,10 +124,10 @@
if (Model.RatingsFeature?.IsEnabled == true) if (Model.RatingsFeature?.IsEnabled == true)
{ {
@await Component.InvokeAsync(typeof(RatingViewComponent), new @await Component.InvokeAsync(typeof(RatingViewComponent), new
{ {
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString() entityId = Model.ViewModel.Id.ToString()
}) })
} }
} }
</abp-column> </abp-column>
@ -133,24 +135,33 @@
</abp-card-body> </abp-card-body>
</abp-card> </abp-card>
</div> </div>
@if (isScrollIndexEnabled)
{
<div class="col-md-2 d-sm-none d-md-block">
<div id="scroll-index" class="docs-inner-anchors mt-2">
<h5>@L["InThisDocument"]</h5>
<nav id="blog-post-sticky-index" class="navbar index-scroll pt-0">
</nav>
<div class="row"> <div class="col-md-2 d-sm-none d-md-block">
<div class="col p-0 py-3"> @if (Model.ViewModel.Author.Id == CurrentUser.Id)
<a href="#" class="scroll-top-btn"> {
<i class="fa fa-chevron-up"></i> @L["GoToTop"] <button id="deleteBlogPost" class="btn btn-danger">
</a> <i class="fa fa-remove">
@L["Delete"]
</i>
</button>
}
@if (isScrollIndexEnabled)
{
<div id="scroll-index" class="docs-inner-anchors mt-2">
<h5>@L["InThisDocument"]</h5>
<nav id="blog-post-sticky-index" class="navbar index-scroll pt-0">
</nav>
<div class="row">
<div class="col p-0 py-3">
<a href="#" class="scroll-top-btn">
<i class="fa fa-chevron-up"></i> @L["GoToTop"]
</a>
</div>
</div> </div>
</div> </div>
</div> }
</div> </div>
}
</div> </div>

1
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs

@ -1,7 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp.GlobalFeatures; using Volo.Abp.GlobalFeatures;
using Volo.Abp.ObjectMapping;
using Volo.CmsKit.Blogs; using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents; using Volo.CmsKit.Contents;
using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.GlobalFeatures;

16
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/blogPost.js

@ -0,0 +1,16 @@
$(function () {
let l = abp.localization.getResource("CmsKit");
$('#deleteBlogPost').on('click', '', function (e) {
abp.message.confirm(l("DeleteBlogPostMessage"), function (ok) {
if (ok) {
volo.cmsKit.public.blogs.blogPostPublic.delete(
$('#BlogId').val()
).then(function () {
document.location.href = "/";
});
}
})
});
});

8
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.js

@ -1,14 +1,14 @@
$(function () { $(function () {
var $selectAuthor = $('#AuthorSelect'); let $selectAuthor = $('#AuthorSelect');
var $authorNameSpan = $('.author-name-span'); let $authorNameSpan = $('.author-name-span');
$selectAuthor.on('change', function () { $selectAuthor.on('change', function () {
var authorId = $selectAuthor.val(); let authorId = $selectAuthor.val();
reloadPageWithQueryString({'authorId': authorId}); reloadPageWithQueryString({'authorId': authorId});
}); });
$authorNameSpan.click(function () { $authorNameSpan.click(function () {
var authorId = $(this).data('author-id'); let authorId = $(this).data('author-id');
reloadPageWithQueryString({'authorId': authorId}); reloadPageWithQueryString({'authorId': authorId});
}); });

15
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs

@ -0,0 +1,15 @@
using System.Runtime.Serialization;
using Volo.Abp;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaException : UserFriendlyException
{
public CaptchaException(string message) : base(message)
{
}
public CaptchaException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context)
{
}
}

66
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs

@ -0,0 +1,66 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaOptions
{
/// <summary>
/// Default fonts are "Arial", "Verdana", "Times New Roman" in Windows. These fonts must exist in the target OS.
/// </summary>
public string[] FontFamilies { get; set; } = new string[] { "Arial", "Verdana", "Times New Roman" };
public Color[] TextColor { get; set; } = new Color[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
};
public Color[] DrawLinesColor { get; set; } = new Color[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
};
public float MinLineThickness { get; set; } = 0.7f;
public float MaxLineThickness { get; set; } = 2.0f;
public ushort Width { get; set; } = 180;
public ushort Height { get; set; } = 70;
public ushort NoiseRate { get; set; } = 500;
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray };
public byte FontSize { get; set; } = 32;
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Png;
public IImageEncoder Encoder => RandomTextGenerator.GetEncoder(EncoderType);
public byte DrawLines { get; set; } = 2;
public byte MaxRotationDegrees { get; set; } = 4;
public int Number1MinValue { get; set; } = 1;
public int Number1MaxValue { get; set; } = 99;
public int Number2MinValue { get; set; } = 1;
public int Number2MaxValue { get; set; } = 99;
public CaptchaOptions()
{
}
public CaptchaOptions(int number1MinValue, int number1MaxValue, int number2MinValue, int number2MaxValue)
{
Number1MinValue = number1MinValue;
Number1MaxValue = number1MaxValue;
Number2MinValue = number2MinValue;
Number1MaxValue = number2MaxValue;
}
}

32
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs

@ -0,0 +1,32 @@
using System;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaOutput
{
public Guid Id { get; set; }
public string Text { get; set; }
public byte[] ImageBytes { get; set; }
public int Result { get; set; }
}
public class CaptchaInput
{
public int Number1 { get; set; }
public int Number2 { get; set; }
}
public class CaptchaRequest
{
public CaptchaInput Input { get; set; }
public CaptchaOutput Output { get; set; }
public CaptchaRequest()
{
Input = new CaptchaInput();
Output = new CaptchaOutput
{
Id = Guid.NewGuid()
};
}
}

7
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs

@ -0,0 +1,7 @@
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public enum EncoderTypes
{
Jpeg,
Png,
}

73
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs

@ -0,0 +1,73 @@
using System;
using System.Security.Cryptography;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public static class RandomTextGenerator
{
private static readonly char[] AllowedChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVXYZW23456789".ToCharArray();
public static IImageEncoder GetEncoder(EncoderTypes encoderType)
{
IImageEncoder encoder = encoderType switch
{
EncoderTypes.Png => new PngEncoder(),
EncoderTypes.Jpeg => new JpegEncoder(),
_ => throw new ArgumentException($"Encoder '{encoderType}' not found!")
};
return encoder;
}
public static string GetRandomText(int size)
{
var data = new byte[4 * size];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
var result = new StringBuilder(size);
for (var i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % AllowedChars.Length;
result.Append(AllowedChars[idx]);
}
return result.ToString();
}
public static string GetUniqueKey(int size, char[] chars)
{
var data = new byte[4 * size];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
var result = new StringBuilder(size);
for (var i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % chars.Length;
result.Append(chars[idx]);
}
return result.ToString();
}
public static float GenerateNextFloat(double min = -3.40282347E+38, double max = 3.40282347E+38)
{
var random = new Random();
var range = max - min;
var sample = random.NextDouble();
var scaled = sample * range + min;
var result = (float)scaled;
return result;
}
}

175
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Volo.Abp.DependencyInjection;
using Color = SixLabors.ImageSharp.Color;
using PointF = SixLabors.ImageSharp.PointF;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class SimpleMathsCaptchaGenerator : ISingletonDependency
{
private static Dictionary<Guid, CaptchaRequest> Session { get; set; } = new Dictionary<Guid, CaptchaRequest>();
public CaptchaOutput Generate()
{
return Generate(options: null, number1: null, number2: null);
}
public CaptchaOutput Generate(CaptchaOptions options)
{
return Generate(options, number1: null, number2: null);
}
/// <summary>
/// Creates a simple captcha code.
/// </summary>
/// <param name="options">Options for captcha generation</param>
/// <param name="number1">First number for maths operation</param>
/// <param name="number2">Second number for maths operation</param>
/// <returns></returns>
public CaptchaOutput Generate(CaptchaOptions options, int? number1, int? number2)
{
var random = new Random();
options ??= new CaptchaOptions();
number1 ??= random.Next(options.Number1MinValue, options.Number1MaxValue);
number2 ??= random.Next(options.Number2MinValue, options.Number2MaxValue);
var text = number1 + "+" + number2;
var request = new CaptchaRequest
{
Input =
{
Number1 = number1.Value,
Number2 = number2.Value
},
Output =
{
Text = text,
Result = Calculate(number1.Value, number2.Value),
ImageBytes = GenerateInternal(text, options)
}
};
Session[request.Output.Id] = request;
return request.Output;
}
private static int Calculate(int number1, int number2)
{
return number1 + number2;
}
public void Validate(Guid requestId, int value)
{
var request = Session[requestId];
if (request.Output.Result != value)
{
throw new CaptchaException("The captcha code doesn't match text on the picture! Please try again.");
}
}
public void Validate(Guid requestId, string value)
{
if (int.TryParse(value, out var captchaInput))
{
Validate(requestId, captchaInput);
}
else
{
throw new CaptchaException("The captcha code is missing!");
}
}
private byte[] GenerateInternal(string stringText, CaptchaOptions options)
{
byte[] result;
using (var image = new Image<Rgba32>(options.Width, options.Height))
{
float position = 0;
var random = new Random();
var startWith = (byte)random.Next(5, 10);
image.Mutate(ctx => ctx.BackgroundColor(Color.Transparent));
var fontName = options.FontFamilies[random.Next(0, options.FontFamilies.Length)];
var font = SystemFonts.CreateFont(fontName, options.FontSize, options.FontStyle);
foreach (var character in stringText)
{
var text = character.ToString();
var color = options.TextColor[random.Next(0, options.TextColor.Length)];
var location = new PointF(startWith + position, random.Next(6, 13));
image.Mutate(ctx => ctx.DrawText(text, font, color, location));
position += TextMeasurer.Measure(character.ToString(), new RendererOptions(font, location)).Width;
}
//add rotation
var rotation = GetRotation(options);
image.Mutate(ctx => ctx.Transform(rotation));
// add the dynamic image to original image
var size = (ushort)TextMeasurer.Measure(stringText, new RendererOptions(font)).Width;
var img = new Image<Rgba32>(size + 15, options.Height);
img.Mutate(ctx => ctx.BackgroundColor(Color.White));
Parallel.For(0, options.DrawLines, i =>
{
var x0 = random.Next(0, random.Next(0, 30));
var y0 = random.Next(10, img.Height);
var x1 = random.Next(30, img.Width);
var y1 = random.Next(0, img.Height);
img.Mutate(ctx =>
ctx.DrawLines(options.TextColor[random.Next(0, options.TextColor.Length)],
RandomTextGenerator.GenerateNextFloat(options.MinLineThickness, options.MaxLineThickness),
new PointF[] { new PointF(x0, y0), new PointF(x1, y1) })
);
});
img.Mutate(ctx => ctx.DrawImage(image, 0.80f));
Parallel.For(0, options.NoiseRate, i =>
{
var x0 = random.Next(0, img.Width);
var y0 = random.Next(0, img.Height);
img.Mutate(
ctx => ctx
.DrawLines(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)],
RandomTextGenerator.GenerateNextFloat(0.5, 1.5), new PointF[] { new Vector2(x0, y0), new Vector2(x0, y0) })
);
});
img.Mutate(x =>
{
x.Resize(options.Width, options.Height);
});
using (var ms = new MemoryStream())
{
img.Save(ms, options.Encoder);
result = ms.ToArray();
}
}
return result;
}
private static AffineTransformBuilder GetRotation(CaptchaOptions options)
{
var random = new Random();
var width = random.Next(10, options.Width);
var height = random.Next(10, options.Height);
var pointF = new PointF(width, height);
var rotationDegrees = random.Next(0, options.MaxRotationDegrees);
return new AffineTransformBuilder().PrependRotationDegrees(rotationDegrees, pointF);
}
}

5
modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj

@ -14,10 +14,13 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.CmsKit.Common.Web\Volo.CmsKit.Common.Web.csproj" /> <ProjectReference Include="..\Volo.CmsKit.Common.Web\Volo.CmsKit.Common.Web.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Markdig.Signed" Version="0.26.0" /> <PackageReference Include="Markdig.Signed" Version="0.26.0" />
<PackageReference Include="HtmlSanitizer" Version="5.0.331" /> <PackageReference Include="HtmlSanitizer" Version="5.0.331" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

8
modules/cms-kit/src/Volo.CmsKit.Public.Web/wwwroot/client-proxies/cms-kit-proxy.js

@ -214,6 +214,14 @@
}, ajaxParams)); }, ajaxParams));
}; };
volo.cmsKit.public.blogs.blogPostPublic['delete'] = function(id, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/cms-kit-public/blog-posts/' + id + '',
type: 'DELETE',
dataType: null
}, ajaxParams));
};
})(); })();
})(); })();

Loading…
Cancel
Save