diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpDictionaryExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpDictionaryExtensions.cs index 1e6dd0288c..6ac5601f7d 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpDictionaryExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpDictionaryExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Dynamic; namespace System.Collections.Generic; @@ -128,4 +129,22 @@ public static class AbpDictionaryExtensions { return dictionary.GetOrAdd(key, k => factory()); } + + /// + /// Converts a dictionary to dynamic object so added and removed at run + /// + /// The collection object + /// If value is correct, return ExpandoObject that represents an object + public static dynamic ConvertToDynamicObject(this Dictionary dictionary) + { + var expandoObject = new ExpandoObject(); + var expendObjectCollection = (ICollection>)expandoObject; + + foreach (var keyValuePair in dictionary) + { + expendObjectCollection.Add(keyValuePair); + } + + return expandoObject; + } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/appsettings.json b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/appsettings.json index 3c872decbe..fcf428bab3 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/appsettings.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/appsettings.json @@ -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" } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentConsts.cs new file mode 100644 index 0000000000..8caaa7a03a --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentConsts.cs @@ -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"; +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentFragment.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentFragment.cs new file mode 100644 index 0000000000..20574f830c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Contents/ContentFragment.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.ObjectExtending; + +namespace Volo.CmsKit.Contents; + +[Serializable] +public class ContentFragment : ExtensibleObject +{ + public string Type { get; set; } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/CmsKitContentWidgetOptions.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/CmsKitContentWidgetOptions.cs new file mode 100644 index 0000000000..1f9dae60ab --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/CmsKitContentWidgetOptions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Volo.CmsKit.Contents; + +public class CmsKitContentWidgetOptions +{ + public Dictionary WidgetConfigs { get; } + + public CmsKitContentWidgetOptions() + { + WidgetConfigs = new(); + } + + public void AddWidget(string widgetName, string widgetKey) + { + WidgetConfigs.Add(widgetName, new ContentWidgetConfig(widgetKey)); + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentParser.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentParser.cs new file mode 100644 index 0000000000..7fe9fe5b60 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentParser.cs @@ -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 options) + { + _options = options.Value; + } + + public Task> ParseAsync(string content) + { + if (!_options.WidgetConfigs.Any()) + { + return Task.FromResult(new List + { + new ContentFragment { Type = Markdown }.SetProperty(Content, content), + }); + } + + var parsedList = new List(); + ParseContent(content, parsedList); + + var contentFragments = new List(); + FillContentFragment(content, parsedList, contentFragments); + + return Task.FromResult(contentFragments); + } + + private void ParseContent(string content, List 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 parsedList, List contentFragments) + { + content = Regex.Replace(content, @"=\s*""", @"="""); + content = Regex.Replace(content, @"""\s*=", @"""="); + var widgets = Regex.Matches(content, @"(?<=\[Widget)(.*?)(?=\])").Cast().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() + .Select(p => p.Value).Where(p => p != string.Empty).ToList(); + var values = Regex.Matches(preparedContent, @"(?<=\s*[a-zA-Z]*=\s*"")(.*?)(?="")").Cast() + .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])); + } + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentWidgetConfig.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentWidgetConfig.cs new file mode 100644 index 0000000000..a5eab5930a --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/Contents/ContentWidgetConfig.cs @@ -0,0 +1,11 @@ +namespace Volo.CmsKit.Contents; + +public class ContentWidgetConfig +{ + public string Name { get; } + + public ContentWidgetConfig(string widgetName) + { + Name = widgetName; + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs index 94a0456c66..99e6ed5b34 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs @@ -1,6 +1,4 @@ -using Volo.CmsKit.GlobalFeatures; - -namespace Volo.CmsKit.Blogs; +namespace Volo.CmsKit.Blogs; public static class BlogPostConsts { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs index 8c6a6a01e9..695f50836e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Volo.Abp.Application.Dtos; +using Volo.CmsKit.Contents; using Volo.CmsKit.Users; namespace Volo.CmsKit.Public.Blogs; @@ -15,6 +17,8 @@ public class BlogPostPublicDto : AuditedEntityDto public string ShortDescription { get; set; } + public List ContentFragments { get; set; } + public string Content { get; set; } public Guid? CoverImageMediaId { get; set; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/PageDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/PageDto.cs index 64175d0f18..a336844289 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/PageDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Pages/PageDto.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Volo.Abp.Application.Dtos; +using Volo.CmsKit.Contents; namespace Volo.CmsKit.Public.Pages; @@ -11,6 +13,8 @@ public class PageDto : EntityDto public string Slug { get; set; } public string Content { get; set; } + + public List ContentFragments { get; set; } public string Script { get; set; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs index 65e6c6fa20..ebdda3d293 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.GlobalFeatures; using Volo.CmsKit.Blogs; +using Volo.CmsKit.Contents; using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.Users; @@ -16,22 +17,29 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub protected IBlogRepository BlogRepository { get; } protected IBlogPostRepository BlogPostRepository { get; } + protected ContentParser ContentParser { get; } public BlogPostPublicAppService( IBlogRepository blogRepository, - IBlogPostRepository blogPostRepository) + IBlogPostRepository blogPostRepository, + ContentParser contentParser) { BlogRepository = blogRepository; BlogPostRepository = blogPostRepository; + ContentParser = contentParser; } - public virtual async Task GetAsync([NotNull] string blogSlug, [NotNull] string blogPostSlug) + public virtual async Task GetAsync( + [NotNull] string blogSlug, [NotNull] string blogPostSlug) { var blog = await BlogRepository.GetBySlugAsync(blogSlug); var blogPost = await BlogPostRepository.GetBySlugAsync(blog.Id, blogPostSlug); - return ObjectMapper.Map(blogPost); + var blogPostDto = ObjectMapper.Map(blogPost); + blogPostDto.ContentFragments = await ContentParser.ParseAsync(blogPost.Content); + + return blogPostDto; } public virtual async Task> GetListAsync([NotNull] string blogSlug, BlogPostGetListInput input) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PagePublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PagePublicAppService.cs index 258ef935ed..2bae7c350f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PagePublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Pages/PagePublicAppService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Volo.Abp.GlobalFeatures; +using Volo.CmsKit.Contents; using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.Pages; @@ -9,10 +10,12 @@ namespace Volo.CmsKit.Public.Pages; public class PagePublicAppService : CmsKitPublicAppServiceBase, IPagePublicAppService { protected IPageRepository PageRepository { get; } + protected ContentParser ContentParser { get; } - public PagePublicAppService(IPageRepository pageRepository) + public PagePublicAppService(IPageRepository pageRepository, ContentParser contentParser) { PageRepository = pageRepository; + ContentParser = contentParser; } public virtual async Task FindBySlugAsync(string slug) @@ -24,6 +27,8 @@ public class PagePublicAppService : CmsKitPublicAppServiceBase, IPagePublicAppSe return null; } - return ObjectMapper.Map(page); + var pageDto = ObjectMapper.Map(page); + pageDto.ContentFragments = await ContentParser.ParseAsync(page.Content); + return pageDto; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs index a6b7e497ac..704a03778c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs @@ -30,9 +30,11 @@ public class PublicApplicationAutoMapperProfile : Profile CreateMap(); - CreateMap(); + CreateMap() + .Ignore(x => x.ContentFragments); - CreateMap(MemberList.None); + CreateMap() + .Ignore(x => x.ContentFragments); CreateMap(); diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json index d39e795845..626dce7ff2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json @@ -505,6 +505,57 @@ } } }, + "Volo.CmsKit.Public.Polls.PollViewComponentController": { + "controllerName": "PollViewComponent", + "controllerGroupName": "PollViewComponent", + "isRemoteService": true, + "apiVersion": null, + "type": "Volo.CmsKit.Public.Polls.PollViewComponentController", + "interfaces": [ + { + "type": "Volo.CmsKit.Public.Polls.IPollViewComponentAppService" + } + ], + "actions": { + "ParseAsyncByContent": { + "uniqueName": "ParseAsyncByContent", + "name": "ParseAsync", + "httpMethod": "GET", + "url": "api/cms-kit-public/polls/{content}", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "content", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "content", + "name": "content", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": [], + "bindingSourceId": "Path", + "descriptorName": "" + } + ], + "returnValue": { + "type": "System.Collections.Generic.List", + "typeSimple": "[Volo.CmsKit.Polls.ContentFragment]" + }, + "allowAnonymous": null, + "implementFrom": "Volo.CmsKit.Public.Polls.IPollViewComponentAppService" + } + } + }, "Volo.CmsKit.Public.Pages.PagesPublicController": { "controllerName": "PagesPublic", "controllerGroupName": "PagesPublic", @@ -989,6 +1040,18 @@ "bindingSourceId": "ModelBinding", "descriptorName": "input" }, + { + "nameOnMethod": "input", + "name": "TagId", + "jsonName": null, + "type": "System.Guid?", + "typeSimple": "string?", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "input" + }, { "nameOnMethod": "input", "name": "Sorting", diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml deleted file mode 100644 index 50104515c9..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/Default.cshtml +++ /dev/null @@ -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 - - - - @if (!Model.Content.IsNullOrEmpty()) - { - @Html.Raw(await MarkdownRenderer.RenderAsync(Model.Content)) - } - - diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/DefaultPageViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/DefaultPageViewComponent.cs deleted file mode 100644 index c5427af114..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Pages/DefaultPageViewComponent.cs +++ /dev/null @@ -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 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; } -} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/TagViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/TagViewComponent.cs index a3dc23c61a..0e550a6ade 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/TagViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/TagViewComponent.cs @@ -1,9 +1,6 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; using Volo.CmsKit.Tags; diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml index 9d8680e96c..2cff5c9072 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml @@ -1,5 +1,7 @@ @page +@using Volo.Abp.Data +@using Volo.CmsKit.Public.Blogs @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment @using Volo.CmsKit.Public.Web.Pages @using Volo.Abp.GlobalFeatures @@ -9,6 +11,7 @@ @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Tags @using Volo.CmsKit.Public.Web.Renderers @using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs +@using Volo.CmsKit.Contents @inherits CmsKitPublicPageBase @@ -23,71 +26,77 @@ } @section styles{ - @if (isScrollIndexEnabled) - { - - - - } + @if (isScrollIndexEnabled) + { + + + + } - - + + } -@section scripts{ + @section scripts{ @if (isScrollIndexEnabled) { - - + + } - - + + } -
-
- - - - -
-

@Model.BlogPost.Title

-

- - @@@Model.BlogPost.Author?.UserName - - @Model.BlogPost.CreationTime -

- @if (!Model.BlogPost.Content.IsNullOrEmpty()) - { - @Html.Raw(await MarkdownRenderer.RenderAsync(Model.BlogPost.Content)) - } - -

- @if (Model.BlogPost.LastModificationTime != null) +

+
+ + + + +
+

@Model.BlogPost.Title

+

+ + @@@Model.BlogPost.Author?.UserName + + @Model.BlogPost.CreationTime +

+ @foreach (var contentFragment in Model.BlogPost.ContentFragments) { - @L["LastModification"].Value : @Model.BlogPost.LastModificationTime + if (contentFragment.Type == ContentConsts.Markdown) + { + @Html.Raw(await MarkdownRenderer.RenderAsync(contentFragment.GetProperty("Content"))) + } + else if (contentFragment.Type == ContentConsts.Widget) + { + @await Component.InvokeAsync(contentFragment.GetProperty("Type"), contentFragment.ExtraProperties.ConvertToDynamicObject()) + } } -

-
+

+ @if (Model.BlogPost.LastModificationTime != null) + { + @L["LastModification"].Value : @Model.BlogPost.LastModificationTime + } +

+
- @if (GlobalFeatureManager.Instance.IsEnabled()) - { - if (Model.TagsFeature?.IsEnabled == true) + @if (GlobalFeatureManager.Instance.IsEnabled()) { - @await Component.InvokeAsync(typeof(TagViewComponent), new + if (Model.TagsFeature?.IsEnabled == true) { - entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, - entityId = Model.BlogPost.Id.ToString(), - urlFormat = $"/blogs/{Model.BlogSlug}?tagId={{TagId}}" - }) + @await Component.InvokeAsync(typeof(TagViewComponent), new + { + entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, + entityId = Model.BlogPost.Id.ToString(), + urlFormat = $"/blogs/{Model.BlogSlug}?tagId={{TagId}}" + }) } }
@@ -100,10 +109,10 @@ if (Model.ReactionsFeature?.IsEnabled == true) { @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new - { - entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, - entityId = Model.BlogPost.Id.ToString() - }) + { + entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, + entityId = Model.BlogPost.Id.ToString() + }) } } @@ -113,10 +122,10 @@ if (Model.RatingsFeature?.IsEnabled == true) { @await Component.InvokeAsync(typeof(RatingViewComponent), new - { - entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, - entityId = Model.BlogPost.Id.ToString() - }) + { + entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, + entityId = Model.BlogPost.Id.ToString() + }) } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs index dcefb976a4..6227190ab8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs @@ -1,11 +1,5 @@ -using Markdig; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using Volo.Abp.GlobalFeatures; using Volo.CmsKit.Blogs; using Volo.CmsKit.GlobalFeatures; diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml index a3f1d0c95e..5a92ae2b04 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml @@ -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 L @section styles{ } @section scripts{ } -@await Component.InvokeAsync(typeof(DefaultPageViewComponent), - new - { - pageId = Model.Page.Id, - title = Model.Page.Title, - content = Model.Page.Content - }) \ No newline at end of file + + + @foreach (ContentFragment contentFragment in Model.PageDto.ContentFragments) + { + if (contentFragment.Type == ContentConsts.Markdown) + { + @Html.Raw(await MarkdownRenderer.RenderAsync(contentFragment.GetProperty("Content"))) + } + else if (contentFragment.Type == ContentConsts.Widget) + { + @await Component.InvokeAsync(contentFragment.GetProperty("Type"), contentFragment.ExtraProperties.ConvertToDynamicObject()) + } + } + + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml.cs index 6eeac638cf..1434eccb4b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/Index.cshtml.cs @@ -12,18 +12,18 @@ public class IndexModel : CommonPageModel protected IPagePublicAppService PagePublicAppService { get; } - public PageDto Page; + public PageDto PageDto{ get; private set; } - public IndexModel(IPagePublicAppService pagePublicAppService) +public IndexModel(IPagePublicAppService pagePublicAppService) { PagePublicAppService = pagePublicAppService; } public async Task OnGetAsync() { - Page = await PagePublicAppService.FindBySlugAsync(Slug); + PageDto = await PagePublicAppService.FindBySlugAsync(Slug); - if (Page == null) + if (PageDto == null) { return NotFound(); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/PageViewModel.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/PageViewModel.cs new file mode 100644 index 0000000000..5e5df3e917 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Pages/PageViewModel.cs @@ -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 ContentFragments { get; set; } +} \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentParser_Test.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentParser_Test.cs new file mode 100644 index 0000000000..108d883a70 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Contents/ContentParser_Test.cs @@ -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 _options; + private ContentParser contentParser; + + public ContentParser_Test() + { + testData = GetRequiredService(); + _options = GetRequiredService>(); + } + + [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 ExampleData => + new List + { + 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} + }; +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Pages/PageManager_Test.cs b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Pages/PageManager_Test.cs index 92dac10caf..208de3817f 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Pages/PageManager_Test.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Pages/PageManager_Test.cs @@ -1,9 +1,6 @@ -using Shouldly; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace Volo.CmsKit.Pages; @@ -20,6 +17,7 @@ public class PageManager_Test : CmsKitDomainTestBase pageRepository = GetRequiredService(); } + [Fact] public async Task CreateAsync_ShouldWorkProperly_WithNonExistingSlug() { var title = "My awesome page"; @@ -34,6 +32,7 @@ public class PageManager_Test : CmsKitDomainTestBase page.Content.ShouldBe(content); } + [Fact] public async Task CreateAsync_ShouldThrowException_WithExistingSlug() { var title = "My awesome page"; @@ -46,6 +45,7 @@ public class PageManager_Test : CmsKitDomainTestBase exception.ShouldNotBeNull(); } + [Fact] public async Task SetSlugAsync_ShouldWorkProperly_WithNonExistingSlug() { var newSlug = "freshly-generated-new-slug"; @@ -56,6 +56,7 @@ public class PageManager_Test : CmsKitDomainTestBase page.Slug.ShouldBe(newSlug); } + [Fact] public async Task SetSlugAsync_ShouldThrowException_WithExistingSlug() { var newSlug = testData.Page_2_Slug; diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs index d47f139560..cfa54d2e55 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs @@ -127,4 +127,8 @@ public class CmsKitTestData : ISingletonDependency public string MenuItem_3_Name { get; } = "Products"; public string MenuItem_3_Url { get; } = "/products"; + + public string PollName { get; } = "Poll"; + + public string WidgetName { get; } = "CmsPollByCode"; }