Merge pull request #10792 from abpframework/issue/10789

Support markdown in CMS-Kit comments
pull/10863/head
Halil İbrahim Kalkan 4 years ago committed by GitHub
commit f609f5de13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -158,6 +158,7 @@
"YourEmailAddress": "Your e-mail address",
"YourFullName": "Your full name",
"YourMessage": "Your Message",
"YourReply": "Your reply"
"YourReply": "Your reply",
"MarkdownSupported": "<a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown</a> supported."
}
}

@ -158,6 +158,7 @@
"YourEmailAddress": "Email adresiniz",
"YourFullName": "Tam adınız",
"YourMessage": "Mesajınız",
"YourReply": "Cevabınız"
"YourReply": "Cevabınız",
"MarkdownSupported": "<a href=\"https://www.markdownguide.org/basic-syntax/\">Markdown</a> destekler."
}
}
}

@ -8,6 +8,7 @@ using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.CmsKit.Public.Comments;
using Volo.CmsKit.Public.Web.Renderers;
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
@ -21,13 +22,16 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
public class CommentingViewComponent : AbpViewComponent
{
public ICommentPublicAppService CommentPublicAppService { get; }
public IMarkdownToHtmlRenderer MarkdownToHtmlRenderer { get; }
public AbpMvcUiOptions AbpMvcUiOptions { get; }
public CommentingViewComponent(
ICommentPublicAppService commentPublicAppService,
IOptions<AbpMvcUiOptions> options)
IOptions<AbpMvcUiOptions> options,
IMarkdownToHtmlRenderer markdownToHtmlRenderer)
{
CommentPublicAppService = commentPublicAppService;
MarkdownToHtmlRenderer = markdownToHtmlRenderer;
AbpMvcUiOptions = options.Value;
}
@ -35,8 +39,9 @@ public class CommentingViewComponent : AbpViewComponent
string entityType,
string entityId)
{
var result = await CommentPublicAppService
.GetListAsync(entityType, entityId);
var comments = (await CommentPublicAppService
.GetListAsync(entityType, entityId)).Items;
var loginUrl = $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}";
@ -45,12 +50,31 @@ public class CommentingViewComponent : AbpViewComponent
EntityId = entityId,
EntityType = entityType,
LoginUrl = loginUrl,
Comments = result.Items.OrderByDescending(i => i.CreationTime).ToList()
Comments = comments.OrderByDescending(i => i.CreationTime).ToList()
};
await ConvertMarkdownTextsToHtml(viewModel);
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel);
}
private async Task ConvertMarkdownTextsToHtml(CommentingViewModel viewModel)
{
viewModel.RawCommentTexts = new Dictionary<Guid, string>();
foreach (var comment in viewModel.Comments)
{
viewModel.RawCommentTexts.Add(comment.Id, comment.Text);
comment.Text = await MarkdownToHtmlRenderer.RenderAsync(comment.Text, true);
foreach (var reply in comment.Replies)
{
viewModel.RawCommentTexts.Add(reply.Id, reply.Text);
reply.Text = await MarkdownToHtmlRenderer.RenderAsync(reply.Text, true);
}
}
}
public class CommentingViewModel
{
public string EntityType { get; set; }
@ -60,5 +84,8 @@ public class CommentingViewComponent : AbpViewComponent
public string LoginUrl { get; set; }
public IReadOnlyList<CommentWithDetailsDto> Comments { get; set; }
public Dictionary<Guid, string> RawCommentTexts { get; set; }
}
}

@ -49,6 +49,9 @@
}
</div>
</div>
<div class="mt-0">
<small class="text-muted float-start" >@L["MarkdownSupported"]</small>
</div>
</div>
</form>
</div>;
@ -57,16 +60,7 @@
Func<dynamic, IHtmlContent> GetCommentContentArea(Guid id, string text) =>
@<div class="cms-comment-content-area" data-id="@id.ToString()">
<p class="m-0">
@{
var lines = text.SplitToLines();
if (lines.Any())
{
foreach (var line in lines)
{
@line <br />
}
}
}
@Html.Raw(text)
</p>
</div>;
}
@ -114,9 +108,11 @@
<div class="text-end">
<abp-button type="submit" button-type="Primary" size="Block"> @L["Update"] </abp-button>
<abp-button type="button" button-type="Light" size="Block_Small" class="comment-edit-cancel-button" data-id="@id.ToString()"><i class="fa fa-times me-1"></i> @L["Cancel"] </abp-button>
</div>
</div>
<div class="mt-0">
<small class="text-muted float-start" >@L["MarkdownSupported"]</small>
</div>
</div>
</form>
</div>
@ -161,7 +157,7 @@
</div>
</div>
</div>
@GetEditArea(comment.Id, comment.Text, comment.ConcurrencyStamp).Invoke(null)
@GetEditArea(comment.Id, Model.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null)
@if (comment.Replies.Any())
{
@ -191,7 +187,7 @@
</div>
</div>
@GetEditArea(reply.Id, reply.Text, reply.ConcurrencyStamp).Invoke(null)
@GetEditArea(reply.Id, Model.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null)
</div>
</div>
}
@ -220,4 +216,3 @@
</div>
}
</div>

@ -4,5 +4,5 @@ namespace Volo.CmsKit.Public.Web.Renderers;
public interface IMarkdownToHtmlRenderer
{
Task<string> RenderAsync(string rawMarkdown);
Task<string> RenderAsync(string rawMarkdown, bool preventXSS = true);
}

@ -1,21 +1,129 @@
using Markdig;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Markdig;
using System.Threading.Tasks;
using System.Web;
using Volo.Abp.DependencyInjection;
using Ganss.XSS;
namespace Volo.CmsKit.Public.Web.Renderers;
public class MarkdownToHtmlRenderer : IMarkdownToHtmlRenderer, ITransientDependency
{
private readonly HtmlSanitizer _htmlSanitizer;
protected MarkdownPipeline MarkdownPipeline { get; }
public MarkdownToHtmlRenderer(MarkdownPipeline markdownPipeline)
{
MarkdownPipeline = markdownPipeline;
_htmlSanitizer = new HtmlSanitizer();
}
public Task<string> RenderAsync(string rawMarkdown)
public async Task<string> RenderAsync(string rawMarkdown, bool preventXSS = false)
{
return Task.FromResult(
Markdown.ToHtml(rawMarkdown, MarkdownPipeline));
if (preventXSS)
{
rawMarkdown = EncodeHtmlTags(rawMarkdown, true);
}
var html = Markdown.ToHtml(rawMarkdown, MarkdownPipeline);
if (preventXSS)
{
html = _htmlSanitizer.Sanitize(html);
}
return html;
}
private static List<CodeBlockIndexPair> GetCodeBlockIndices(string markdownText)
{
var regexObj = new Regex(@"```(\w)*|`(\w)*", RegexOptions.IgnoreCase |
RegexOptions.IgnorePatternWhitespace |
RegexOptions.Singleline |
RegexOptions.Multiline |
RegexOptions.ExplicitCapture);
var matches = regexObj.Matches(markdownText);
var indices = new List<CodeBlockIndexPair>();
for (var i = 0; i < matches.Count; i++)
{
if (!indices.Any() || indices.Last().EndIndex.HasValue)
{
indices.Add(new CodeBlockIndexPair(matches[i].Index));
}
else
{
indices.Last().EndIndex = matches[i].Index;
}
}
return indices;
}
/// <summary>
/// Encodes html tags.
/// </summary>
private static string EncodeHtmlTags(string text, bool dontEncodeCodeBlocks = true)
{
List<CodeBlockIndexPair> codeBlockIndices = null;
if (dontEncodeCodeBlocks)
{
codeBlockIndices = GetCodeBlockIndices(text);
}
return Regex.Replace(text, @"<[^>]*>", match =>
{
if (dontEncodeCodeBlocks && codeBlockIndices != null)
{
var isInCodeBlock = false;
foreach (var codeBlock in codeBlockIndices)
{
if (IsInCodeBlock(match.Index, codeBlock.StartIndex, codeBlock.EndIndex))
{
isInCodeBlock = true;
break;
}
}
if (isInCodeBlock)
{
return match.ToString();
}
else
{
return HttpUtility.HtmlEncode(match.ToString());
}
}
else
{
return HttpUtility.HtmlEncode(match.ToString());
}
});
}
private static bool IsInCodeBlock(int currentIndex, int codeBlockStartIndex, int? codeBlockEndIndex)
{
if (codeBlockEndIndex.HasValue)
{
return (currentIndex >= codeBlockStartIndex && currentIndex <= codeBlockEndIndex);
}
return currentIndex >= codeBlockStartIndex;
}
private class CodeBlockIndexPair
{
public int StartIndex { get; private set; }
public int? EndIndex { get; set; }
public CodeBlockIndexPair(int startIndex, int? endIndex = null)
{
StartIndex = startIndex;
EndIndex = endIndex;
}
}
}

@ -17,6 +17,7 @@
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Markdig.Signed" Version="0.26.0" />
<PackageReference Include="HtmlSanitizer" Version="5.0.331" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save