mirror of https://github.com/abpframework/abp.git
committed by
GitHub
25 changed files with 541 additions and 170 deletions
@ -1,5 +1,5 @@ |
|||
{ |
|||
"ConnectionStrings": { |
|||
"Default": "Server=(localdb)\\.\\MSSQLLocalDB;Database=CmsKit_Unified;Trusted_Connection=True" |
|||
"Default": "Server=(localdb)\\MSSQLLocalDB;Database=CmsKit_Unified;Trusted_Connection=True" |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.CmsKit.Contents; |
|||
public static class ContentConsts |
|||
{ |
|||
public static string Delimeter { get; set; } = "----"; |
|||
public static string Widget { get; set; } = "Widget"; |
|||
public static string Markdown { get; set; } = "Markdown"; |
|||
public static string Content { get; set; } = "Content"; |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace Volo.CmsKit.Contents; |
|||
|
|||
[Serializable] |
|||
public class ContentFragment : ExtensibleObject |
|||
{ |
|||
public string Type { get; set; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.CmsKit.Contents; |
|||
|
|||
public class CmsKitContentWidgetOptions |
|||
{ |
|||
public Dictionary<string, ContentWidgetConfig> WidgetConfigs { get; } |
|||
|
|||
public CmsKitContentWidgetOptions() |
|||
{ |
|||
WidgetConfigs = new(); |
|||
} |
|||
|
|||
public void AddWidget(string widgetName, string widgetKey) |
|||
{ |
|||
WidgetConfigs.Add(widgetName, new ContentWidgetConfig(widgetKey)); |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using static Volo.CmsKit.Contents.ContentConsts; |
|||
|
|||
namespace Volo.CmsKit.Contents; |
|||
|
|||
public class ContentParser : ITransientDependency |
|||
{ |
|||
private readonly CmsKitContentWidgetOptions _options; |
|||
|
|||
public ContentParser(IOptions<CmsKitContentWidgetOptions> options) |
|||
{ |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public Task<List<ContentFragment>> ParseAsync(string content) |
|||
{ |
|||
if (!_options.WidgetConfigs.Any()) |
|||
{ |
|||
return Task.FromResult(new List<ContentFragment> |
|||
{ |
|||
new ContentFragment { Type = Markdown }.SetProperty(Content, content), |
|||
}); |
|||
} |
|||
|
|||
var parsedList = new List<string>(); |
|||
ParseContent(content, parsedList); |
|||
|
|||
var contentFragments = new List<ContentFragment>(); |
|||
FillContentFragment(content, parsedList, contentFragments); |
|||
|
|||
return Task.FromResult(contentFragments); |
|||
} |
|||
|
|||
private void ParseContent(string content, List<string> parsedList) |
|||
{ |
|||
var replacedText = Regex.Replace(content, @"\[.*?\]", Delimeter); |
|||
if (!replacedText.Contains(Delimeter)) |
|||
{ |
|||
parsedList.Add(replacedText); |
|||
} |
|||
|
|||
while (replacedText.Contains(Delimeter)) |
|||
{ |
|||
//For parsing delimeter
|
|||
var index = replacedText.IndexOf(Delimeter); |
|||
if (index != 0) |
|||
{ |
|||
parsedList.Add(replacedText.Substring(0, index)); |
|||
replacedText = replacedText.Substring(index, replacedText.Length - index); |
|||
index = 0; |
|||
} |
|||
|
|||
parsedList.Add(replacedText.Substring(index, Delimeter.Length)); |
|||
replacedText = replacedText.Substring(Delimeter.Length, replacedText.Length - Delimeter.Length); |
|||
|
|||
//for parsing the other side
|
|||
index = replacedText.IndexOf(Delimeter); |
|||
if (index != -1) |
|||
{ |
|||
parsedList.Add(replacedText.Substring(0, index)); |
|||
replacedText = replacedText.Substring(index, replacedText.Length - index); |
|||
} |
|||
else if (replacedText.Length > 0) |
|||
{ |
|||
parsedList.Add(replacedText); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void FillContentFragment(string content, List<string> parsedList, List<ContentFragment> contentFragments) |
|||
{ |
|||
content = Regex.Replace(content, @"=\s*""", @"="""); |
|||
content = Regex.Replace(content, @"""\s*=", @"""="); |
|||
var widgets = Regex.Matches(content, @"(?<=\[Widget)(.*?)(?=\])").Cast<Match>().Select(p => p.Value).ToList(); |
|||
for (int i = 0, k = 0; i < parsedList.Count; i++) |
|||
{ |
|||
if (parsedList[i] == Delimeter) |
|||
{ |
|||
if (widgets.Count > k) |
|||
{ |
|||
var preparedContent = string.Join("", widgets[k]); |
|||
var keys = Regex.Matches(preparedContent, @"(?<=\s)(.*?)(?==\s*"")").Cast<Match>() |
|||
.Select(p => p.Value).Where(p => p != string.Empty).ToList(); |
|||
var values = Regex.Matches(preparedContent, @"(?<=\s*[a-zA-Z]*=\s*"")(.*?)(?="")").Cast<Match>() |
|||
.Select(p => p.Value).ToList(); |
|||
|
|||
var widgetTypeIndex = keys.IndexOf("Type"); |
|||
if (widgetTypeIndex != -1) |
|||
{ |
|||
var widgetType = values[widgetTypeIndex]; |
|||
var name = _options.WidgetConfigs.Where(p => p.Key == widgetType).Select(p => p.Value.Name).FirstOrDefault(); |
|||
if (name is not null && widgets.Count > k) |
|||
{ |
|||
values[0] = name; |
|||
var contentFragment = new ContentFragment() { Type = Widget }; |
|||
contentFragments.Add(contentFragment); |
|||
for (int kv = 0; kv < values.Count; kv++) |
|||
{ |
|||
contentFragments.FindLast(p => p == contentFragment) |
|||
.SetProperty(keys[kv], values[kv]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
k++; |
|||
} |
|||
else |
|||
{ |
|||
contentFragments.Add(new ContentFragment() { Type = Markdown } |
|||
.SetProperty(Content, parsedList[i])); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.CmsKit.Contents; |
|||
|
|||
public class ContentWidgetConfig |
|||
{ |
|||
public string Name { get; } |
|||
|
|||
public ContentWidgetConfig(string widgetName) |
|||
{ |
|||
Name = widgetName; |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
|||
|
|||
@using Volo.CmsKit.Public.Web.Renderers |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs |
|||
|
|||
@inject IMarkdownToHtmlRenderer MarkdownRenderer |
|||
|
|||
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages.PageViewModel |
|||
|
|||
<abp-card> |
|||
<abp-card-body> |
|||
@if (!Model.Content.IsNullOrEmpty()) |
|||
{ |
|||
@Html.Raw(await MarkdownRenderer.RenderAsync(Model.Content)) |
|||
} |
|||
</abp-card-body> |
|||
</abp-card> |
|||
@ -1,54 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.CmsKit.Public.Pages; |
|||
using Volo.CmsKit.Public.Web.Renderers; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages; |
|||
|
|||
[Widget( |
|||
StyleTypes = new[] |
|||
{ |
|||
typeof(HighlightJsStyleContributor) |
|||
}, |
|||
ScriptTypes = new[] |
|||
{ |
|||
typeof(HighlightJsScriptContributor) |
|||
}, |
|||
ScriptFiles = new[] |
|||
{ |
|||
"/Pages/Public/CmsKit/highlightOnLoad.js" |
|||
})] |
|||
public class DefaultPageViewComponent : AbpViewComponent |
|||
{ |
|||
protected IPagePublicAppService PagePublicAppService { get; } |
|||
|
|||
public DefaultPageViewComponent(IPagePublicAppService pagePublicAppService) |
|||
{ |
|||
PagePublicAppService = pagePublicAppService; |
|||
} |
|||
|
|||
public virtual async Task<IViewComponentResult> InvokeAsync(Guid pageId, string title, string content) |
|||
{ |
|||
var model = new PageViewModel |
|||
{ |
|||
Id = pageId, |
|||
Title = title, |
|||
Content = content |
|||
}; |
|||
|
|||
return View("~/Pages/CmsKit/Shared/Components/Pages/Default.cshtml", model); |
|||
} |
|||
} |
|||
|
|||
public class PageViewModel |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Title { get; set; } |
|||
|
|||
public string Content { get; set; } |
|||
} |
|||
@ -1,31 +1,46 @@ |
|||
@page |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.CmsKit.Localization |
|||
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
|||
@using Volo.CmsKit.Contents |
|||
@using System.Dynamic |
|||
@using Volo.CmsKit.Public.Web.Renderers |
|||
@using Volo.Abp.Data |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs; |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
@inject IMarkdownToHtmlRenderer MarkdownRenderer |
|||
|
|||
|
|||
@model Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Pages.IndexModel |
|||
|
|||
@inject IHtmlLocalizer<CmsKitResource> L |
|||
|
|||
@section styles{ |
|||
|
|||
<abp-style src="/Pages/Public/CmsKit/Pages/index.css" /> |
|||
|
|||
<style> |
|||
@Html.Raw(Model.Page.Style) |
|||
@Html.Raw(Model.PageDto.Style) |
|||
</style> |
|||
} |
|||
|
|||
@section scripts{ |
|||
<script> |
|||
@Html.Raw(Model.Page.Script) |
|||
@Html.Raw(Model.PageDto.Script) |
|||
</script> |
|||
} |
|||
|
|||
@await Component.InvokeAsync(typeof(DefaultPageViewComponent), |
|||
new |
|||
{ |
|||
pageId = Model.Page.Id, |
|||
title = Model.Page.Title, |
|||
content = Model.Page.Content |
|||
}) |
|||
<abp-card> |
|||
<abp-card-body> |
|||
@foreach (ContentFragment contentFragment in Model.PageDto.ContentFragments) |
|||
{ |
|||
if (contentFragment.Type == ContentConsts.Markdown) |
|||
{ |
|||
@Html.Raw(await MarkdownRenderer.RenderAsync(contentFragment.GetProperty<string>("Content"))) |
|||
} |
|||
else if (contentFragment.Type == ContentConsts.Widget) |
|||
{ |
|||
@await Component.InvokeAsync(contentFragment.GetProperty<string>("Type"), contentFragment.ExtraProperties.ConvertToDynamicObject()) |
|||
} |
|||
} |
|||
</abp-card-body> |
|||
</abp-card> |
|||
|
|||
|
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.CmsKit.Contents; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Pages; |
|||
|
|||
public class PageViewModel |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Title { get; set; } |
|||
|
|||
public List<ContentFragment> ContentFragments { get; set; } |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.CmsKit.Contents; |
|||
public class ContentParser_Test : CmsKitDomainTestBase |
|||
{ |
|||
private readonly CmsKitTestData testData; |
|||
private readonly IOptions<CmsKitContentWidgetOptions> _options; |
|||
private ContentParser contentParser; |
|||
|
|||
public ContentParser_Test() |
|||
{ |
|||
testData = GetRequiredService<CmsKitTestData>(); |
|||
_options = GetRequiredService<IOptions<CmsKitContentWidgetOptions>>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ParseAsync_ShouldWorkWithDifferentWidgetTypes() |
|||
{ |
|||
_options.Value.AddWidget(testData.PollName, testData.WidgetName); |
|||
_options.Value.AddWidget("ImageGallery", "ImageGallery"); |
|||
contentParser = new ContentParser(_options); |
|||
|
|||
var content = @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type=""Poll"" Code=""poll-name""] |
|||
Thanks _for_ *your * feedback. |
|||
[Widget GalleryName=""Xyz"" Type=""ImageGallery"" Source=""GoogleDrive""]";
|
|||
|
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(4); |
|||
widgets[1].ExtraProperties.Count.ShouldBe(2); |
|||
widgets[3].ExtraProperties.Count.ShouldBe(3); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ParseAsync_ShouldWorkWithoutConfigOptions() |
|||
{ |
|||
var content = @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type= ""Poll"" Code =""poll-name""] |
|||
Thanks _for_ *your * feedback.";
|
|||
|
|||
contentParser = new ContentParser(_options); |
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(1);//Ignored Widget
|
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ParseAsync_ShouldWorkWithWrongConfigOptions() |
|||
{ |
|||
_options.Value.AddWidget(testData.WidgetName, testData.PollName); |
|||
contentParser = new ContentParser(_options); |
|||
|
|||
var content = @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type= ""Poll"" Code =""poll-name""] |
|||
Thanks _for_ *your * feedback.";
|
|||
|
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ParseAsync_ShouldWorkWithWrongWidgetType() |
|||
{ |
|||
_options.Value.AddWidget(testData.PollName, testData.WidgetName); |
|||
contentParser = new ContentParser(_options); |
|||
|
|||
var content = @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Wrong Type= ""Poll"" Code =""poll-name""] |
|||
Thanks _for_ *your * feedback.";
|
|||
|
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ParseAsync_ShouldWorkWithWrongPollName() |
|||
{ |
|||
_options.Value.AddWidget(testData.PollName, testData.WidgetName); |
|||
contentParser = new ContentParser(_options); |
|||
|
|||
var content = @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type= ""Poll"" PollWrongName =""poll-name""] |
|||
Thanks _for_ *your * feedback.";
|
|||
|
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(3); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ExampleData))] |
|||
public async Task ParseAsync_ShouldWorkProperlyWithCorrectInputs(string content, int expectedLine) |
|||
{ |
|||
_options.Value.AddWidget(testData.PollName, testData.WidgetName); |
|||
contentParser = new ContentParser(_options); |
|||
|
|||
var widgets = await contentParser.ParseAsync(content); |
|||
|
|||
widgets.ShouldNotBeNull(); |
|||
widgets.Count.ShouldBe(expectedLine); |
|||
} |
|||
|
|||
public static IEnumerable<object[]> ExampleData => |
|||
new List<object[]> |
|||
{ |
|||
new object[] { @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type=""Poll"" Code=""poll-name""] |
|||
Thanks _for_ *your * feedback.", 3},
|
|||
|
|||
new object[] { @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
[Widget Type=""Poll"" Code=""poll-name""] |
|||
Thanks _for_ *your * feedback. |
|||
[Widget Type=""Poll"" Code=""poll-name1""]", 4 },
|
|||
|
|||
new object[] { @"**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
Thanks _for_ *your * feedback. |
|||
[Widget Type=""Poll"" Code=""poll-name""]", 2 },
|
|||
|
|||
new object[] { @"[Widget Type=""Poll"" Code=""poll-name""] gg [Widget Type=""Poll"" Code=""poll-name1""]**ABP Framework** is completely open source and developed in a community-driven manner.
|
|||
Thanks _for_ *your * feedback. |
|||
Thanks _for_ *your * feedback.", 4},
|
|||
|
|||
new object[] { @"Thanks _for_ *your * feedback.
|
|||
Thanks _for_ *your * feedback.", 1}
|
|||
}; |
|||
} |
|||
Loading…
Reference in new issue