From 90a4befd307ef227a878f64c61b841b43170a258 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 16 Apr 2020 20:59:46 +0800 Subject: [PATCH 01/80] Redirect to home page when an invalid URL or missing document --- .../Pages/Documents/Project/Index.cshtml.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 56f9dac913..7c45cf0ae0 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -9,11 +9,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Volo.Abp; -using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.Domain.Entities; -using Volo.Abp.Localization; using Volo.Docs.Documents; using Volo.Docs.HtmlConverting; using Volo.Docs.Models; @@ -103,7 +100,9 @@ namespace Volo.Docs.Pages.Documents.Project Logger.LogWarning(exception.Message); DocumentFound = false; - return Page(); + var url = DocumentsUrlPrefix + LanguageCode + "/" + ProjectName + "/" + + (LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Version); + return Redirect(url); } } @@ -120,7 +119,7 @@ namespace Volo.Docs.Pages.Documents.Project catch (EntityNotFoundException e) { Logger.LogWarning(e.Message); - return NotFound(); + return Redirect(DocumentsUrlPrefix); } if (ShowProjectsCombobox) From f8b314f4abad2ee276f720ce785c575da819ebf6 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 17 Apr 2020 22:05:34 +0800 Subject: [PATCH 02/80] Show error and redirect when project or document not found --- .../Volo/Docs/Localization/Domain/en.json | 1 + .../Docs/Localization/Domain/zh-Hans.json | 3 +- .../Pages/Documents/Project/Index.cshtml | 38 +++++++++++++------ .../Pages/Documents/Project/Index.cshtml.cs | 17 +++++++-- .../Pages/Documents/Project/index.js | 31 +++++++++++---- .../DocumentNotFoundPageModel.cs | 15 -------- .../DocumentNotFoundViewComponent.cs | 26 ------------- .../Default.cshtml | 11 +++--- .../Shared/ErrorComponent/ErrorPageModel.cs | 11 ++++++ .../ErrorComponent/ErrorViewComponent.cs | 14 +++++++ 10 files changed, 96 insertions(+), 71 deletions(-) delete mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs delete mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs rename modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/{DocumentNotFoundComponent => ErrorComponent}/Default.cshtml (62%) create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json index 6299a9aab2..fef9102118 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json @@ -14,6 +14,7 @@ "Projects": "Project(s)", "NoProjectWarning": "There are no projects yet!", "DocumentNotFound": "Oops, the requested document was not found!", + "ProjectNotFound": "Oops, the requested project was not found!", "NavigationDocumentNotFound": "This version does not have a navigation document!", "DocumentNotFoundInSelectedLanguage": "Document in the language you wanted is not found. Document in the default language is shown.", "FilterTopics": "Filter topics", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json index ef4e95f3dc..761e28c1bb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json @@ -1,6 +1,6 @@ { "culture": "zh-Hans", - "texts": { + "texts": { "Documents": "文档", "BackToWebsite": "返回主网站", "Contributors": "贡献者", @@ -14,6 +14,7 @@ "Projects": "项目", "NoProjectWarning": "还没有项目!", "DocumentNotFound": "找不到请求的文档!", + "ProjectNotFound": "找不到請求的項目!", "NavigationDocumentNotFound": "这个版本没有导航文件!", "DocumentNotFoundInSelectedLanguage": "本文档不适用于所选语言, 将以默认语言显示文档.", "FilterTopics": "过滤主题", diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index b7db40f1fe..fa1659ab54 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -5,12 +5,12 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Clipboard @using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Popper -@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Docs @using Volo.Docs.Localization @using Volo.Docs.Pages.Documents.Project +@using Volo.Docs.Pages.Documents.Shared.ErrorComponent @inject IThemeManager ThemeManager @inject IPageLayout PageLayout @inject IHtmlLocalizer L @@ -41,7 +41,7 @@ } -@if (Model.DocumentFound) +@if (Model.LoadSuccess) {
@@ -348,14 +348,30 @@ } else { - @(await Component.InvokeAsync(new - { - model = new DocumentNotFoundPageModel + if (!Model.DocumentFound) + { + @(await Component.InvokeAsync(new { - ProjectName = Model.ProjectName, - DocumentName = Model.DocumentName, - LanguageCode = Model.LanguageCode, - Version = Model.Version, - } - })) + model = new ErrorPageModel + { + RedirectUrl = Model.DocumentsUrlPrefix + Model.LanguageCode + "/" + Model.ProjectName + "/" + + (Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version), + ErrorCode = "404", + ErrorMessage = L.GetString("DocumentNotFound") + } + })) + } + if (!Model.EntityFound) + { + @(await Component.InvokeAsync(new + { + model = new ErrorPageModel + { + RedirectUrl = Model.DocumentsUrlPrefix, + ErrorCode = "404", + ErrorMessage = L.GetString("ProjectNotFound") + } + })) + } + } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 7c45cf0ae0..8d10d876a8 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; @@ -33,6 +34,10 @@ namespace Volo.Docs.Pages.Documents.Project public string LanguageCode { get; set; } public bool DocumentFound { get; set; } = true; + + public bool EntityFound { get; set; } = true; + + public bool LoadSuccess => DocumentFound && EntityFound; public string DefaultLanguageCode { get; set; } @@ -100,9 +105,7 @@ namespace Volo.Docs.Pages.Documents.Project Logger.LogWarning(exception.Message); DocumentFound = false; - var url = DocumentsUrlPrefix + LanguageCode + "/" + ProjectName + "/" - + (LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Version); - return Redirect(url); + return Page(); } } @@ -119,7 +122,8 @@ namespace Volo.Docs.Pages.Documents.Project catch (EntityNotFoundException e) { Logger.LogWarning(e.Message); - return Redirect(DocumentsUrlPrefix); + EntityFound = false; + return Page(); } if (ShowProjectsCombobox) @@ -596,6 +600,11 @@ namespace Volo.Docs.Pages.Documents.Project public string GetDescription() { + if (Document == null || Document.Content.IsNullOrWhiteSpace()) + { + return null; + } + var firstParagraph = new Regex(@"

(.*?)

", RegexOptions.IgnoreCase); var match = firstParagraph.Match(Document.Content); if (!match.Success) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js index 1b3d5ac7c3..d85b5a8630 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js @@ -297,17 +297,32 @@ $("#crawler_link").html(html); }; - initNavigationFilter("sidebar-scroll"); - - initAnchorTags(".docs-page .docs-body"); + var errorPageRedirect = function(){ + var second = 3; + var close = setInterval(()=>{ + second--; + $(".seconds").text(`(${second})`) + if(second===0){ + clearInterval(close); + $(".redirect")[0].click(); + } + },1000) + } + + var loadSuccess = $(".docs-page").length!==0; + if(loadSuccess === true){ + initNavigationFilter("sidebar-scroll"); - initSocialShareLinks(); + initAnchorTags(".docs-page .docs-body"); - initSections(); + initSocialShareLinks(); - initCrawlerLinks(); + initSections(); + initCrawlerLinks(); + }else{ + errorPageRedirect(); + } }); -})(jQuery); - +})(jQuery); \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs deleted file mode 100644 index 72b2b97584..0000000000 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -{ - public class DocumentNotFoundPageModel - { - public string ProjectName { get; set; } - - public string LanguageCode { get; set; } - - public string Version { get; set; } - - public string DocumentName { get; set; } - - public string DocumentsUrlPrefix { get; set; } - } -} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs deleted file mode 100644 index 7aeed9beee..0000000000 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Volo.Abp.AspNetCore.Mvc; - -namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -{ - public class DocumentNotFoundViewComponent : AbpViewComponent - { - private readonly DocsUiOptions _options; - - public DocumentNotFoundViewComponent(IOptions options) - { - _options = options.Value; - } - public IViewComponentResult Invoke(DocumentNotFoundPageModel model, string defaultErrorMessageKey) - { - model.DocumentsUrlPrefix = _options.RoutePrefix; - - return View("~/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml", model); - } - } -} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml similarity index 62% rename from modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml rename to modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml index 604ff05da7..c7c7db1006 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml @@ -1,16 +1,15 @@ -@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -@model DocumentNotFoundPageModel +@model Volo.Docs.Pages.Documents.Shared.ErrorComponent.ErrorPageModel @{ }
- 404 + @Model.ErrorCode

- "@Model.DocumentName" not found in @Model.ProjectName documents, with @Model.Version version and @Model.LanguageCode language. + @Model.ErrorMessage


- - Go Back + + Go Back (3)
diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs new file mode 100644 index 0000000000..cfb65409c7 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs @@ -0,0 +1,11 @@ +namespace Volo.Docs.Pages.Documents.Shared.ErrorComponent +{ + public class ErrorPageModel + { + public string ErrorMessage { get; set; } + + public string ErrorCode { get; set; } + + public string RedirectUrl { get; set; } + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs new file mode 100644 index 0000000000..493b6d896a --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Docs.Pages.Documents.Shared.ErrorComponent +{ + public class ErrorViewComponent : AbpViewComponent + { + public IViewComponentResult Invoke(ErrorPageModel model) + { + return View("~/Pages/Documents/Shared/ErrorComponent/Default.cshtml", model); + } + } +} From bb342a4b547396efbdece36774e41e4933af4f5a Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 19 Apr 2020 20:41:14 +0800 Subject: [PATCH 03/80] Update localization files --- .../Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json | 2 +- .../Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json index 761e28c1bb..8f3a3e44fb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json @@ -14,7 +14,7 @@ "Projects": "项目", "NoProjectWarning": "还没有项目!", "DocumentNotFound": "找不到请求的文档!", - "ProjectNotFound": "找不到請求的項目!", + "ProjectNotFound": "找不到请求的项目!", "NavigationDocumentNotFound": "这个版本没有导航文件!", "DocumentNotFoundInSelectedLanguage": "本文档不适用于所选语言, 将以默认语言显示文档.", "FilterTopics": "过滤主题", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json index 6a2f4d8799..dea171863c 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json @@ -14,6 +14,7 @@ "Projects": "專案", "NoProjectWarning": "沒有專案!", "DocumentNotFound": "找不到要求的文件!", + "ProjectNotFound": "找不到請求的項目!", "NavigationDocumentNotFound": "這個版本沒有導覽文件!", "DocumentNotFoundInSelectedLanguage": "本文件不適用於所選語系,將以預設語系顯示.", "FilterTopics": "過濾主題", From a3bd2f280e428195c65cff35dfcbc38df1780454 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 21 Apr 2020 16:20:19 +0800 Subject: [PATCH 04/80] Improve code --- .../Pages/Documents/Project/Index.cshtml | 2 +- .../Pages/Documents/Project/index.js | 73 +++++++------------ .../Shared/ErrorComponent/Default.cshtml | 8 +- .../Documents/Shared/ErrorComponent/error.js | 20 +++++ 4 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index fa1659ab54..f347ec2227 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -39,6 +39,7 @@ + } @if (Model.LoadSuccess) @@ -373,5 +374,4 @@ else } })) } - } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js index d85b5a8630..71fb550539 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js @@ -203,31 +203,31 @@ 'YandexBot', 'msnbot', 'Rambler', - 'Yahoo', - 'AbachoBOT', - 'accoona', + 'Yahoo', + 'AbachoBOT', + 'accoona', 'AcoiRobot', 'ASPSeek', - 'CrocCrawler', + 'CrocCrawler', 'Dumbot', - 'FAST-WebCrawler', - 'GeonaBot', + 'FAST-WebCrawler', + 'GeonaBot', 'Gigabot', - 'Lycos', - 'MSRBOT', + 'Lycos', + 'MSRBOT', 'Scooter', 'AltaVista', - 'IDBot', - 'eStyle', - 'Scrubby', - 'Slurp', - 'DuckDuckBot', - 'Baiduspider', - 'VoilaBot', - 'ExaLead', - 'Search Dog', - 'MSN Bot' , - 'BingBot' + 'IDBot', + 'eStyle', + 'Scrubby', + 'Slurp', + 'DuckDuckBot', + 'Baiduspider', + 'VoilaBot', + 'ExaLead', + 'Search Dog', + 'MSN Bot', + 'BingBot' ]; var agent = navigator.userAgent; @@ -276,9 +276,8 @@ if (queryStrings.length <= 0) { returnList.push(key + "=" + $(this).val()); - - } - else { + + } else { for (var k = 0; k < queryStrings.length; k++) { returnList.push(key + "=" + $(this).val() + "&" + queryStrings[k]); } @@ -291,38 +290,22 @@ var queryStrings = getQueryStringsFromComboboxes(0); for (var i = 0; i < queryStrings.length; i++) { - html += "" + queryStrings[i]+" " + html += "" + queryStrings[i] + " " } $("#crawler_link").html(html); }; - - var errorPageRedirect = function(){ - var second = 3; - var close = setInterval(()=>{ - second--; - $(".seconds").text(`(${second})`) - if(second===0){ - clearInterval(close); - $(".redirect")[0].click(); - } - },1000) - } - var loadSuccess = $(".docs-page").length!==0; - if(loadSuccess === true){ - initNavigationFilter("sidebar-scroll"); + initNavigationFilter("sidebar-scroll"); + + initAnchorTags(".docs-page .docs-body"); - initAnchorTags(".docs-page .docs-body"); + initSocialShareLinks(); - initSocialShareLinks(); + initSections(); - initSections(); + initCrawlerLinks(); - initCrawlerLinks(); - }else{ - errorPageRedirect(); - } }); })(jQuery); \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml index c7c7db1006..ff10a25f2a 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml @@ -2,14 +2,14 @@ @{ }
-
- @Model.ErrorCode +
+ @Model.ErrorCode

@Model.ErrorMessage


- - Go Back (3) + + Go Back (3)
diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js new file mode 100644 index 0000000000..d1d08829ac --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js @@ -0,0 +1,20 @@ +(function ($) { + + $(function () { + var errorPageRedirect = function () { + var second = 3; + var close = setInterval(() => { + $("#ErrorRedirectSeconds").text(`(${--second})`); + if (second === 0) { + clearInterval(close); + $("#ErrorRedirect")[0].click(); + } + }, 1000); + } + + if (document.getElementById("DocumentErrorContainer")) { + errorPageRedirect(); + } + }); + +})(jQuery); From 26b056c324341e65a8fc1c95e6de06542afe8878 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 22 Apr 2020 11:16:50 +0800 Subject: [PATCH 05/80] Improve code --- .../Pages/Documents/Project/Index.cshtml | 51 ++++++++++--------- .../Pages/Documents/Project/Index.cshtml.cs | 6 +-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index f347ec2227..74f7c21464 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -153,11 +153,11 @@
-
@@ -172,11 +172,11 @@ - @@ -226,8 +226,8 @@ @if (!string.IsNullOrEmpty(Model.Document.EditLink)) { - - @(L["Edit"]) + + @(L["Edit"]) (@L["LastEditTime"]: @Model.Document.LastUpdatedTime.ToShortDateString()) } @@ -349,6 +349,19 @@ } else { + if (!Model.ProjectFound) + { + @(await Component.InvokeAsync(new + { + model = new ErrorPageModel + { + RedirectUrl = Model.DocumentsUrlPrefix, + ErrorCode = "404", + ErrorMessage = L.GetString("ProjectNotFound") + } + })) + } + if (!Model.DocumentFound) { @(await Component.InvokeAsync(new @@ -362,16 +375,4 @@ else } })) } - if (!Model.EntityFound) - { - @(await Component.InvokeAsync(new - { - model = new ErrorPageModel - { - RedirectUrl = Model.DocumentsUrlPrefix, - ErrorCode = "404", - ErrorMessage = L.GetString("ProjectNotFound") - } - })) - } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index c57ee28fd3..f5758447e4 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -35,9 +35,9 @@ namespace Volo.Docs.Pages.Documents.Project public bool DocumentFound { get; set; } = true; - public bool EntityFound { get; set; } = true; + public bool ProjectFound { get; set; } = true; - public bool LoadSuccess => DocumentFound && EntityFound; + public bool LoadSuccess => DocumentFound && ProjectFound; public string DefaultLanguageCode { get; set; } @@ -122,7 +122,7 @@ namespace Volo.Docs.Pages.Documents.Project catch (EntityNotFoundException e) { Logger.LogWarning(e.Message); - EntityFound = false; + ProjectFound = false; return Page(); } From a15ee011a4c61db782b1da1e26331aac02b75932 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 07:59:03 +0200 Subject: [PATCH 06/80] Wrong polish culture code in translation file name --- .../Localization/MyProjectName/{pl.json => pl-PL.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/{pl.json => pl-PL.json} (100%) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json similarity index 100% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json From 5dadd2cd2fc3cbe635fc3e6d54caa8bdfef1cb61 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:07:42 +0200 Subject: [PATCH 07/80] Wrong polish culture code in json file --- .../Localization/MyProjectName/pl-PL.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json index 815bbcc83f..33412f307c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Home": "Home", "Welcome": "Witaj", From 49cf38b02bdb21ba6aac7fd19beb0c85ed0808ef Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:29:31 +0200 Subject: [PATCH 08/80] Rename and change culture in all polish file for proper one pl-PL. Without tests folder. --- .../UI/MultiTenancy/Localization/{pl.json => pl-PL.json} | 2 +- .../Localization/Resource/{pl.json => pl-PL.json} | 2 +- .../Localization/Resources/AbpUi/{pl.json => pl-PL.json} | 2 +- .../Abp/Validation/Localization/{pl.json => pl-PL.json} | 2 +- .../Account/Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../Blogging/ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../Blogging/Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../Resources/VoloDocs/Web/{pl.json => pl-PL.json} | 2 +- .../Docs/ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../Volo/Docs/Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Volo/Abp/Identity/Localization/{pl.json => pl-PL.json} | 2 +- .../Localization/Domain/{pl.json => pl-PL.json} | 2 +- .../Resources/AbpSettingManagement/{pl.json => pl-PL.json} | 2 +- .../Localization/Resources/{pl.json => pl-PL.json} | 2 +- .../ApplicationContracts/{pl.json => pl-PL.json} | 2 +- .../ProductManagement/Localization/Domain/pl-PL.json | 6 ++++++ .../ProductManagement/Localization/Domain/pl.json | 6 ------ .../Resources/ProductManagement/{pl.json => pl-PL.json} | 2 +- .../Localization/MyProjectName/pl-PL.json | 6 ++++++ .../Localization/MyProjectName/pl.json | 6 ------ 21 files changed, 29 insertions(+), 29 deletions(-) rename framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/{pl.json => pl-PL.json} (92%) rename framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/{pl.json => pl-PL.json} (72%) rename framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/{pl.json => pl-PL.json} (98%) rename framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/{pl.json => pl-PL.json} (98%) rename modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/{pl.json => pl-PL.json} (98%) rename modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/{pl.json => pl-PL.json} (93%) rename modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/{pl.json => pl-PL.json} (98%) rename modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/{pl.json => pl-PL.json} (91%) rename modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/{pl.json => pl-PL.json} (98%) rename modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/{pl.json => pl-PL.json} (95%) rename modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/{pl.json => pl-PL.json} (66%) rename modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/{pl.json => pl-PL.json} (99%) rename modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/{pl.json => pl-PL.json} (91%) rename modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/{pl.json => pl-PL.json} (67%) rename modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/{pl.json => pl-PL.json} (97%) rename samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/{pl.json => pl-PL.json} (90%) create mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json delete mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json rename samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/{pl.json => pl-PL.json} (95%) create mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json delete mode 100644 templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json similarity index 92% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json rename to framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json index 8509b7d02b..438d770a7c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "GivenTenantIsNotAvailable": "Podany tenant jest niedostępny: {0}", "Tenant": "Tenant", diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json similarity index 72% rename from framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json rename to framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json index 557d01eb31..d8edcb1438 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Administration": "Administracja" } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json rename to framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index cdaf556168..1be12a51cf 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "InternalServerErrorMessage": "Błąd wewnętrzny serwera podczas przetwarzania żądania!", "ValidationErrorMessage": "Twoje żądanie jest niepoprawnie!", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json rename to framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json index eef572eb13..93145f0a3a 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "'{0}' and '{1}' do not match.": "'{0}' i '{1}' nie są takie same.", "The {0} field is not a valid credit card number.": "Pole {0} nie jest poprawnym numerem karty kredytowej.", diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json similarity index 98% rename from modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json rename to modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json index b661af8050..032ac41cfa 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "UserName": "Nazwa użytkownika", "EmailAddress": "Adres email", diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json similarity index 93% rename from modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json rename to modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json index 8806854643..05ba8451c6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:Blogging": "Blog", "Permission:Blogs": "Blogi", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json similarity index 98% rename from modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json rename to modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json index 15e0c7eb0d..3795ab93d3 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Blogs": "Blogi", "Menu:BlogManagement": "Zarządzanie blogiem", diff --git a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json similarity index 91% rename from modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json rename to modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json index f131f9e139..ca06e49ef5 100644 --- a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json +++ b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "WelcomeVoloDocs": "Witaj w VoloDocs!", "NoProjectWarning": "Nie ma jeszcze zdefiniowanego projektu!", diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json similarity index 98% rename from modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json rename to modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json index be91b22800..80922afc71 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:DocumentManagement": "Zarządzanie dokumentacją", "Permission:Projects": "Projekty", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json similarity index 95% rename from modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json rename to modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json index 5ba3dd6c10..44bd398458 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Documents": "Dokumenty", "BackToWebsite": "Powrót do strony", diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json similarity index 66% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json index d714a0d499..86d5f08258 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Features": "Funkcje" } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json similarity index 99% rename from modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json rename to modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index 3a0896147c..54d137988d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:IdentityManagement": "Zarządzanie tożsamością", "Users": "Użytkownicy", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json similarity index 91% rename from modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index e1043feea7..b552012c1b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permissions": "Uprawnienia", "OnlyProviderPermissons": "Tylko ten dostawca", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json similarity index 67% rename from modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json rename to modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json index 4aa474d82b..6c29a15453 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Settings": "Ustawienia" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json similarity index 97% rename from modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json rename to modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json index 18891c3633..be3af86d22 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:TenantManagement": "Zarządzanie tenantami", "Tenants": "Tenanty", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json similarity index 90% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json index 9181e35186..e8629599a2 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:ProductManagement": "Zarządzanie produktami", "Permission:Products": "Produkty", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json similarity index 95% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json index 5828dd8942..929b543df8 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:ProductManagement": "Zarządzanie produktami", "Menu:Products": "Produkty", diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file From 54268c2834592d9d243eacb6fedd818fe6bcef05 Mon Sep 17 00:00:00 2001 From: CrazyBaran Date: Wed, 22 Apr 2020 08:36:15 +0200 Subject: [PATCH 09/80] change pl-PL for tests projects. --- .../Mvc/Localization/Resource/{pl.json => pl-PL.json} | 2 +- .../Volo/Abp/Emailing/Localization/{pl.json => pl-PL.json} | 2 +- .../TestResources/Base/CountryNames/{pl.json => pl-PL.json} | 2 +- .../TestResources/Base/Validation/{pl.json => pl-PL.json} | 2 +- .../Localization/TestResources/Source/{pl.json => pl-PL.json} | 2 +- .../TestResources/SourceExt/{pl.json => pl-PL.json} | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/{pl.json => pl-PL.json} (79%) rename framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/{pl.json => pl-PL.json} (63%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/{pl.json => pl-PL.json} (79%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/{pl.json => pl-PL.json} (86%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/{pl.json => pl-PL.json} (92%) rename framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/{pl.json => pl-PL.json} (69%) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json rename to framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json index c794c9c123..5370ccdf57 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "BirthDate": "Data urodzenia", "Value1": "Wartość jeden" diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json similarity index 63% rename from framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json rename to framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json index 6dd6654aa1..3b4ee39a1e 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "hello": "witaj" } diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json index 5e343cf2f9..a25b79d6d8 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "USA": "Stany Zjednoczone Ameryki", "Brazil": "Brazylia" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json similarity index 86% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json index 1d0bfdf66a..cce4cfd722 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "ThisFieldIsRequired": "To pole jest wymagane", "MaxLenghtErrorMessage": "To pole może mieć maksymalnie '{0}' znaków" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json similarity index 92% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json index d014bc7b30..e208fb12b3 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Hello {0}.": "Witaj {0}.", "Car": "Samochód", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json similarity index 69% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json index 2935790010..5b754a7ae1 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "SeeYou": "Do zobaczenia" } From ca7a084c512b06ca19269ba7b1c26670851eaac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 18:36:57 +0300 Subject: [PATCH 10/80] Created Volo.Abp.TextTemplating package and copied initial files. --- framework/Volo.Abp.sln | 9 ++++- .../Volo.Abp.TextTemplating/FodyWeavers.xml | 3 ++ .../Volo.Abp.TextTemplating/FodyWeavers.xsd | 30 ++++++++++++++++ .../Volo.Abp.TextTemplating.csproj | 21 +++++++++++ .../TextTemplating/AbpTextTemplatingModule.cs | 9 +++++ .../EmailTemplateContributorList.cs | 22 ++++++++++++ .../TextTemplating/EmailTemplateDefinition.cs | 36 +++++++++++++++++++ .../EmailTemplateInitializationContext.cs | 18 ++++++++++ .../IEmailTemplateContributor.cs | 9 +++++ nupkg/common.ps1 | 1 + 10 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml create mode 100644 framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 3db0998855..97a831752c 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -277,7 +277,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating", "src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj", "{9E53F91F-EACD-4191-A487-E727741F1311}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -829,6 +831,10 @@ Global {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -970,6 +976,7 @@ Global {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {9E53F91F-EACD-4191-A487-E727741F1311} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj new file mode 100644 index 0000000000..cbc6ba05ec --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Volo.Abp.TextTemplating + Volo.Abp.TextTemplating + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs new file mode 100644 index 0000000000..6b0ad6fb11 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs new file mode 100644 index 0000000000..44a91212ea --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateContributorList : List + { + public string GetOrNull(string cultureName) + { + foreach (var contributor in this.AsQueryable().Reverse()) + { + var templateString = contributor.GetOrNull(cultureName); + if (templateString != null) + { + return templateString; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs new file mode 100644 index 0000000000..19f0eb2fa7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs @@ -0,0 +1,36 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateDefinition + { + public const string DefaultLayoutPlaceHolder = "_"; + + public string Name { get; } + + public bool IsLayout { get; } + + public string Layout { get; set; } + + public Type LocalizationResource { get; set; } + + public EmailTemplateContributorList Contributors { get; } + + public string DefaultCultureName { get; } + + public bool SingleTemplateFile { get; } + + public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, + string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + LocalizationResource = localizationResource; + Contributors = new EmailTemplateContributorList(); + IsLayout = isLayout; + Layout = layout; + DefaultCultureName = defaultCultureName; + SingleTemplateFile = singleTemplateFile; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs new file mode 100644 index 0000000000..8cd4b95bbd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateInitializationContext + { + public EmailTemplateDefinition EmailTemplateDefinition { get; } + + public IServiceProvider ServiceProvider { get; } + + public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, + IServiceProvider serviceProvider) + { + EmailTemplateDefinition = emailTemplateDefinition; + ServiceProvider = serviceProvider; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs new file mode 100644 index 0000000000..d2c2775845 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateContributor + { + void Initialize(EmailTemplateInitializationContext context); + + string GetOrNull(string cultureName); + } +} \ No newline at end of file diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index f9e1263908..9c77b05eef 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -101,6 +101,7 @@ $projects = ( "framework/src/Volo.Abp.Sms", "framework/src/Volo.Abp.Specifications", "framework/src/Volo.Abp.TestBase", + "framework/src/Volo.Abp.TextTemplating", "framework/src/Volo.Abp.Threading", "framework/src/Volo.Abp.Timing", "framework/src/Volo.Abp.UI", From af77ec2aee9f5a53d7f52ed11471bcc8693e48d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 18:53:20 +0300 Subject: [PATCH 11/80] Refactored and added test project --- framework/Volo.Abp.sln | 7 +++ ...pleVirtualFilesEmailTemplateContributor.cs | 2 +- .../TextTemplating/EmailTemplateDefinition.cs | 36 --------------- .../EmailTemplateInitializationContext.cs | 18 -------- .../IEmailTemplateContributor.cs | 9 ---- .../TextTemplating/ITemplateContributor.cs | 9 ++++ ...emplateContributorInitializationContext.cs | 22 ++++++++++ ...utorList.cs => TemplateContributorList.cs} | 4 +- .../Abp/TextTemplating/TemplateDefinition.cs | 44 +++++++++++++++++++ .../Volo.Abp.TextTemplating.Tests.csproj | 16 +++++++ .../AbpTextTemplatingTestModule.cs | 9 ++++ 11 files changed, 110 insertions(+), 66 deletions(-) delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{EmailTemplateContributorList.cs => TemplateContributorList.cs} (79%) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 97a831752c..5159f0a86e 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -281,6 +281,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating", "src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj", "{9E53F91F-EACD-4191-A487-E727741F1311}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Tests", "test\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj", "{251C7FD3-D313-4BCE-8068-352EC7EEA275}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -835,6 +837,10 @@ Global {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.Build.0 = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -977,6 +983,7 @@ Global {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} {9E53F91F-EACD-4191-A487-E727741F1311} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {251C7FD3-D313-4BCE-8068-352EC7EEA275} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs index 2ae5f88cc5..39af512350 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs @@ -46,7 +46,7 @@ namespace Volo.Abp.Emailing.Templates.VirtualFiles { return dictionaries; } - + _templateDictionary = new Dictionary(); foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs deleted file mode 100644 index 19f0eb2fa7..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateDefinition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinition - { - public const string DefaultLayoutPlaceHolder = "_"; - - public string Name { get; } - - public bool IsLayout { get; } - - public string Layout { get; set; } - - public Type LocalizationResource { get; set; } - - public EmailTemplateContributorList Contributors { get; } - - public string DefaultCultureName { get; } - - public bool SingleTemplateFile { get; } - - public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - LocalizationResource = localizationResource; - Contributors = new EmailTemplateContributorList(); - IsLayout = isLayout; - Layout = layout; - DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs deleted file mode 100644 index 8cd4b95bbd..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateInitializationContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateInitializationContext - { - public EmailTemplateDefinition EmailTemplateDefinition { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, - IServiceProvider serviceProvider) - { - EmailTemplateDefinition = emailTemplateDefinition; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs deleted file mode 100644 index d2c2775845..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/IEmailTemplateContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateContributor - { - void Initialize(EmailTemplateInitializationContext context); - - string GetOrNull(string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs new file mode 100644 index 0000000000..c1a7eb95cd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContributor + { + void Initialize(TemplateContributorInitializationContext context); + + string GetOrNull(string cultureName); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs new file mode 100644 index 0000000000..e8a7332e83 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs @@ -0,0 +1,22 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContributorInitializationContext + { + [NotNull] + public TemplateDefinition TemplateDefinition { get; } + + [NotNull] + public IServiceProvider ServiceProvider { get; } + + public TemplateContributorInitializationContext( + TemplateDefinition templateDefinition, + IServiceProvider serviceProvider) + { + TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs similarity index 79% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs index 44a91212ea..50fdb684df 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/EmailTemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Linq; -namespace Volo.Abp.Emailing.Templates +namespace Volo.Abp.TextTemplating { - public class EmailTemplateContributorList : List + public class TemplateContributorList : List { public string GetOrNull(string cultureName) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs new file mode 100644 index 0000000000..b9b7762405 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -0,0 +1,44 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinition + { + public const string DefaultLayoutPlaceHolder = "_"; + + [NotNull] + public string Name { get; } + + public bool IsLayout { get; } + + [CanBeNull] + public string Layout { get; set; } + + public Type LocalizationResource { get; set; } //TODO: ??? + + public TemplateContributorList Contributors { get; } + + [CanBeNull] + public string DefaultCultureName { get; } + + public bool SingleTemplateFile { get; } //TODO: ??? + + public TemplateDefinition( + [NotNull] string name, + Type localizationResource = null, + bool isLayout = false, + string layout = DefaultLayoutPlaceHolder, + string defaultCultureName = null, + bool singleTemplateFile = false) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + LocalizationResource = localizationResource; + Contributors = new TemplateContributorList(); + IsLayout = isLayout; + Layout = layout; + DefaultCultureName = defaultCultureName; + SingleTemplateFile = singleTemplateFile; + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj new file mode 100644 index 0000000000..56e93ae650 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + netcoreapp3.1 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs new file mode 100644 index 0000000000..280059a40e --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingTestModule : AbpModule + { + + } +} From 095bb128a0502e35a69ba9aa8750565fb0ea305b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 19:30:27 +0300 Subject: [PATCH 12/80] Added template definition system and tests. --- .../Volo/Abp/Emailing/AbpEmailingModule.cs | 1 - .../TextTemplating/AbpTextTemplatingModule.cs | 26 ++++++- .../AbpTextTemplatingOptions.cs | 14 ++++ .../ITemplateDefinitionContext.cs | 9 +++ .../ITemplateDefinitionManager.cs | 15 ++++ .../ITemplateDefinitionProvider.cs | 7 ++ .../TemplateDefinitionContext.cs | 38 ++++++++++ .../TemplateDefinitionManager.cs | 74 +++++++++++++++++++ .../TemplateDefinitionProvider.cs | 9 +++ .../Volo.Abp.TextTemplating.Tests.csproj | 3 +- .../AbpTextTemplatingOptions_Tests.cs | 24 ++++++ .../AbpTextTemplatingTestBase.cs | 12 +++ .../AbpTextTemplatingTestModule.cs | 8 +- .../TextTemplating/TemplateDefinitionTests.cs | 22 ++++++ .../TestTemplateDefinitionProvider.cs | 14 ++++ .../Volo/Abp/TextTemplating/TestTemplates.cs | 7 ++ 16 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index c2e29a7ff7..9641f7c14c 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -50,7 +50,6 @@ namespace Volo.Abp.Emailing services.OnRegistred(context => { - if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) { definitionProviders.Add(context.ImplementationType); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 6b0ad6fb11..28b71da4aa 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -1,9 +1,33 @@ -using Volo.Abp.Modularity; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; namespace Volo.Abp.TextTemplating { public class AbpTextTemplatingModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs new file mode 100644 index 0000000000..a79b66e255 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions + { + public ITypeList DefinitionProviders { get; } + + public AbpTextTemplatingOptions() + { + DefinitionProviders = new TypeList(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs new file mode 100644 index 0000000000..18402f8f9d --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionContext + { + TemplateDefinition GetOrNull(string name); + + void Add(params TemplateDefinition[] definitions); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs new file mode 100644 index 0000000000..feadd3442f --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionManager + { + [NotNull] + TemplateDefinition Get([NotNull] string name); + + IReadOnlyList GetAll(); + + TemplateDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs new file mode 100644 index 0000000000..a9e5155d0c --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionProvider + { + void Define(ITemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs new file mode 100644 index 0000000000..39da2f5e88 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionContext : ITemplateDefinitionContext + { + protected Dictionary EmailTemplates { get; } + + public TemplateDefinitionContext(Dictionary emailTemplates) + { + EmailTemplates = emailTemplates; + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return EmailTemplates.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplates.Values.ToImmutableList(); + } + + public virtual void Add(params TemplateDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + EmailTemplates[definition.Name] = definition; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs new file mode 100644 index 0000000000..534010dbe2 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency + { + protected Lazy> EmailTemplateDefinitions { get; } + + protected AbpTextTemplatingOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public TemplateDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + EmailTemplateDefinitions = + new Lazy>(CreateEmailTemplateDefinitions, true); + } + + public virtual TemplateDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNull(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplateDefinitions.Value.Values.ToImmutableList(); + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return EmailTemplateDefinitions.Value.GetOrDefault(name); + } + + protected virtual IDictionary CreateEmailTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(new TemplateDefinitionContext(templates)); + } + } + + return templates; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs new file mode 100644 index 0000000000..9afc22a8b8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency + { + public abstract void Define(ITemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 56e93ae650..bc5a0fc679 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -10,6 +10,7 @@ + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs new file mode 100644 index 0000000000..294596c693 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase + { + private readonly AbpTextTemplatingOptions _options; + + public AbpTextTemplatingOptions_Tests() + { + _options = GetRequiredService>().Value; + } + + [Fact] + public void Should_Auto_Add_TemplateDefinitionProviders_To_Options() + { + _options + .DefinitionProviders + .ShouldContain(typeof(TestTemplateDefinitionProvider)); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs new file mode 100644 index 0000000000..ca5dc20445 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.TextTemplating +{ + public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 280059a40e..057accfc23 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,7 +1,13 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; namespace Volo.Abp.TextTemplating { + [DependsOn( + typeof(AbpTextTemplatingModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule) + )] public class AbpTextTemplatingTestModule : AbpModule { diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs new file mode 100644 index 0000000000..2202c098b1 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -0,0 +1,22 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionTests : AbpTextTemplatingTestBase + { + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateDefinitionTests() + { + _templateDefinitionManager = GetRequiredService(); + } + + [Fact] + public void Should_Retrieve_Template_Definition() + { + var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); + definition.Name.ShouldBe(TestTemplates.TestTemplate1); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..1ff8be9bab --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.TextTemplating +{ + public class TestTemplateDefinitionProvider : TemplateDefinitionProvider + { + public override void Define(ITemplateDefinitionContext context) + { + context + .Add(new TemplateDefinition( + TestTemplates.TestTemplate1 + ) + ); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs new file mode 100644 index 0000000000..8e6e21ea85 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating +{ + public static class TestTemplates + { + public const string TestTemplate1 = "TestTemplate1"; + } +} \ No newline at end of file From 42047737bd002b1fbed40841b95429170a75808b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 22:23:09 +0300 Subject: [PATCH 13/80] Added more tests for template definitions. --- common.DotSettings | 6 ++++++ .../TextTemplating/ITemplateDefinitionManager.cs | 2 ++ .../TextTemplating/TemplateDefinitionTests.cs | 16 +++++++++++++++- .../TestTemplateDefinitionProvider.cs | 5 ++++- .../Volo/Abp/TextTemplating/TestTemplates.cs | 1 + 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/common.DotSettings b/common.DotSettings index 0eb4875d49..6f40d029a7 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,5 +20,11 @@ False False SQL + False + False + False + False + False + False True \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs index feadd3442f..cbd2d15463 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -8,8 +8,10 @@ namespace Volo.Abp.TextTemplating [NotNull] TemplateDefinition Get([NotNull] string name); + [NotNull] IReadOnlyList GetAll(); + [CanBeNull] TemplateDefinition GetOrNull(string name); } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index 2202c098b1..bc0e8b94c4 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -13,10 +13,24 @@ namespace Volo.Abp.TextTemplating } [Fact] - public void Should_Retrieve_Template_Definition() + public void Should_Retrieve_Template_Definition_By_Name() { var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); definition.Name.ShouldBe(TestTemplates.TestTemplate1); } + + [Fact] + public void Should_Get_Null_If_Template_Not_Found() + { + var definition = _templateDefinitionManager.GetOrNull("undefined-template"); + definition.ShouldBeNull(); + } + + [Fact] + public void Should_Retrieve_All_Template_Definitions() + { + var definitions = _templateDefinitionManager.GetAll(); + definitions.Count.ShouldBeGreaterThan(1); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 1ff8be9bab..134ebdc74a 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -5,8 +5,11 @@ public override void Define(ITemplateDefinitionContext context) { context - .Add(new TemplateDefinition( + .Add( + new TemplateDefinition( TestTemplates.TestTemplate1 + ), new TemplateDefinition( + TestTemplates.TestTemplateLayout1 ) ); } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs index 8e6e21ea85..e5b7bcc9ae 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -3,5 +3,6 @@ public static class TestTemplates { public const string TestTemplate1 = "TestTemplate1"; + public const string TestTemplateLayout1 = "TestTemplateLayout1"; } } \ No newline at end of file From 07aaaa046951f2b9614d9eb8d60a0e798d5feb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 23:08:57 +0300 Subject: [PATCH 14/80] Implemented VirtualFileTemplateContributor and added tests. --- .../Volo.Abp.TextTemplating.csproj | 4 +- .../TextTemplating/AbpTextTemplatingModule.cs | 4 + ...emplateContributorInitializationContext.cs | 4 +- .../Abp/TextTemplating/TemplateDefinition.cs | 12 +-- .../VirtualFileTemplateContributor.cs | 84 +++++++++++++++++++ .../Volo.Abp.TextTemplating.Tests.csproj | 5 ++ .../AbpTextTemplatingTestModule.cs | 9 +- .../SampleTemplates/ForgotPasswordEmail.tpl | 1 + .../SampleTemplates/WelcomeEmail/en.tpl | 1 + .../SampleTemplates/WelcomeEmail/tr.tpl | 1 + .../TestTemplateDefinitionProvider.cs | 23 +++-- .../VirtualFileTemplateContributor_Tests.cs | 50 +++++++++++ 12 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index cbc6ba05ec..ba12b60ca7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -1,4 +1,4 @@ - + @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 28b71da4aa..3ef30f5fc2 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -2,9 +2,13 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { + [DependsOn( + typeof(AbpVirtualFileSystemModule) + )] public class AbpTextTemplatingModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs index e8a7332e83..ffe97a3484 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs @@ -12,8 +12,8 @@ namespace Volo.Abp.TextTemplating public IServiceProvider ServiceProvider { get; } public TemplateContributorInitializationContext( - TemplateDefinition templateDefinition, - IServiceProvider serviceProvider) + [NotNull] TemplateDefinition templateDefinition, + [NotNull] IServiceProvider serviceProvider) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index b9b7762405..548ac87c15 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -22,15 +22,12 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public string DefaultCultureName { get; } - public bool SingleTemplateFile { get; } //TODO: ??? - public TemplateDefinition( [NotNull] string name, Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder, - string defaultCultureName = null, - bool singleTemplateFile = false) + string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; @@ -38,7 +35,12 @@ namespace Volo.Abp.TextTemplating IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; + } + + public virtual TemplateDefinition AddContributor(ITemplateContributor contributor) + { + Contributors.Add(contributor); + return this; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs new file mode 100644 index 0000000000..f7a320ab16 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFileTemplateContributor : ITemplateContributor + { + private readonly string _virtualPath; + private IVirtualFileProvider _virtualFileProvider; + private volatile Dictionary _templateDictionary; + private readonly object _syncObj = new object(); + + public VirtualFileTemplateContributor(string virtualPath) + { + _virtualPath = virtualPath; + } + + public void Initialize(TemplateContributorInitializationContext context) + { + _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + } + + public string GetOrNull([CanBeNull] string cultureName = null) + { + var dictionary = GetTemplateDictionary(); + + if (cultureName == null) + { + return dictionary.GetOrDefault("__default"); + } + else + { + return dictionary.GetOrDefault(cultureName) ?? + dictionary.GetOrDefault("__default"); + } + } + + private Dictionary GetTemplateDictionary() + { + if (_templateDictionary != null) + { + return _templateDictionary; + } + + lock (_syncObj) + { + if (_templateDictionary != null) + { + return _templateDictionary; + } + + var dictionary = new Dictionary(); + + var fileInfo = _virtualFileProvider.GetFileInfo(_virtualPath); + if (!fileInfo.IsDirectory) + { + //TODO: __default to consts + dictionary.Add("__default", fileInfo.ReadAsString()); + } + else + { + foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + // TODO: How to normalize file names? + dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + } + } + + _templateDictionary = dictionary; + return dictionary; + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index bc5a0fc679..916495b116 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -7,6 +7,11 @@ + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 057accfc23..0457e0136d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.Autofac; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { @@ -10,6 +11,12 @@ namespace Volo.Abp.TextTemplating )] public class AbpTextTemplatingTestModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); + }); + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl new file mode 100644 index 0000000000..674b734c8a --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -0,0 +1 @@ +Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl new file mode 100644 index 0000000000..7dbe5e8cb0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -0,0 +1 @@ +Welcome to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl new file mode 100644 index 0000000000..6c70e0afb1 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -0,0 +1 @@ +abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 134ebdc74a..57244c61c6 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,17 +1,22 @@ -namespace Volo.Abp.TextTemplating +using Volo.Abp.TextTemplating.VirtualFiles; + +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { public override void Define(ITemplateDefinitionContext context) { - context - .Add( - new TemplateDefinition( - TestTemplates.TestTemplate1 - ), new TemplateDefinition( - TestTemplates.TestTemplateLayout1 - ) - ); + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplate1 + ).AddContributor( + new VirtualFileTemplateContributor("/SampleTemplates/WelcomeEmail") + ) + ); + + context.Add(new TemplateDefinition( + TestTemplates.TestTemplateLayout1 + )); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs new file mode 100644 index 0000000000..d884a5e73b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -0,0 +1,50 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase + { + [Fact] + public void Should_Get_Localized_Content_By_Culture() + { + var contributor = new VirtualFileTemplateContributor( + "/SampleTemplates/WelcomeEmail" + ); + + contributor.Initialize( + new TemplateContributorInitializationContext( + new TemplateDefinition("Test"), + ServiceProvider + ) + ); + + contributor + .GetOrNull("en") + .ShouldBe("Welcome to the abp.io!"); + + contributor + .GetOrNull("tr") + .ShouldBe("abp.io'ya hoşgeldiniz!"); + } + + [Fact] + public void Should_Get_Non_Localized_Template_Content() + { + var contributor = new VirtualFileTemplateContributor( + "/SampleTemplates/ForgotPasswordEmail.tpl" + ); + + contributor.Initialize( + new TemplateContributorInitializationContext( + new TemplateDefinition("Test"), + ServiceProvider + ) + ); + + contributor + .GetOrNull() + .ShouldBe("Please click to the following link to get an email to reset your password!"); + } + } +} From 9060b695ea6c06682a8d11c390463cd84b40423b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 22 Apr 2020 23:18:12 +0300 Subject: [PATCH 15/80] Added TemplateDefinitionExtensions. --- .../TemplateDefinitionExtensions.cs | 19 +++++++++++++++++++ .../VirtualFileTemplateContributor.cs | 6 +++--- .../TestTemplateDefinitionProvider.cs | 8 ++------ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs new file mode 100644 index 0000000000..6508295da7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using Volo.Abp.TextTemplating.VirtualFiles; + +namespace Volo.Abp.TextTemplating +{ + public static class TemplateDefinitionExtensions + { + public static TemplateDefinition AddVirtualFiles( + [NotNull] this TemplateDefinition templateDefinition, + [NotNull] string virtualPath) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition.AddContributor( + new VirtualFileTemplateContributor(virtualPath) + ); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index f7a320ab16..869a9cb7b1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -15,9 +14,10 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private volatile Dictionary _templateDictionary; private readonly object _syncObj = new object(); - public VirtualFileTemplateContributor(string virtualPath) + public VirtualFileTemplateContributor( + [NotNull] string virtualPath) { - _virtualPath = virtualPath; + _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); } public void Initialize(TemplateContributorInitializationContext context) diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 57244c61c6..f47d31ac4e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,6 +1,4 @@ -using Volo.Abp.TextTemplating.VirtualFiles; - -namespace Volo.Abp.TextTemplating +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { @@ -9,9 +7,7 @@ namespace Volo.Abp.TextTemplating context.Add( new TemplateDefinition( TestTemplates.TestTemplate1 - ).AddContributor( - new VirtualFileTemplateContributor("/SampleTemplates/WelcomeEmail") - ) + ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); context.Add(new TemplateDefinition( From 67dbc9b453cdaf5564e24ff44117d8d991648851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:06:20 +0300 Subject: [PATCH 16/80] Added template content provider and template renderer. --- .../TextTemplating/AbpTextTemplatingModule.cs | 23 ++++++++++++ .../ITemplateContentProvider.cs | 9 +++++ .../TextTemplating/ITemplateContributor.cs | 6 ++-- .../Abp/TextTemplating/ITemplateRenderer.cs | 14 ++++++++ .../TextTemplating/TemplateContentProvider.cs | 35 +++++++++++++++++++ .../TextTemplating/TemplateContributorList.cs | 15 +------- .../Abp/TextTemplating/TemplateRenderer.cs | 30 ++++++++++++++++ .../TextTemplating/TemplateDefinitionTests.cs | 4 +-- .../TextTemplating/TemplateRenderer_Tests.cs | 23 ++++++++++++ .../TestTemplateDefinitionProvider.cs | 8 ++++- .../Volo/Abp/TextTemplating/TestTemplates.cs | 3 +- 11 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 3ef30f5fc2..e49f271c1c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -33,5 +33,28 @@ namespace Volo.Abp.TextTemplating options.DefinitionProviders.AddIfNotContains(definitionProviders); }); } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + //TODO: Consider to move to the TemplateContentProvider and invoke lazy (with making it singleton) + using (var scope = context.ServiceProvider.CreateScope()) + { + var templateDefinitionManager = scope.ServiceProvider + .GetRequiredService(); + + foreach (var templateDefinition in templateDefinitionManager.GetAll()) + { + var contributorInitializationContext = new TemplateContributorInitializationContext( + templateDefinition, + scope.ServiceProvider + ); + + foreach (var contributor in templateDefinition.Contributors) + { + contributor.Initialize(contributorInitializationContext); + } + } + } + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs new file mode 100644 index 0000000000..d4dc5615fb --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContentProvider + { + Task GetContentOrNullAsync(string templateName, string cultureName); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs index c1a7eb95cd..e8e1f97769 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -1,9 +1,11 @@ -namespace Volo.Abp.TextTemplating +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating { public interface ITemplateContributor { void Initialize(TemplateContributorInitializationContext context); - string GetOrNull(string cultureName); + string GetOrNull([CanBeNull] string cultureName); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs new file mode 100644 index 0000000000..d65f77653a --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -0,0 +1,14 @@ +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateRenderer + { + Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs new file mode 100644 index 0000000000..ab81d8b9f7 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency + { + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateContentProvider( + ITemplateDefinitionManager templateDefinitionManager + ) + { + _templateDefinitionManager = templateDefinitionManager; + } + + public async Task GetContentOrNullAsync(string templateName, string cultureName) + { + var template = _templateDefinitionManager.Get(templateName); + + foreach (var contributor in template.Contributors) + { + var templateString = contributor.GetOrNull(cultureName); + if (templateString != null) + { + return templateString; + } + } + + throw new AbpException( + $"None of the template contributors could get the content for the template '{templateName}'" + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs index 50fdb684df..e737897dae 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs @@ -1,22 +1,9 @@ using System.Collections.Generic; -using System.Linq; namespace Volo.Abp.TextTemplating { public class TemplateContributorList : List { - public string GetOrNull(string cultureName) - { - foreach (var contributor in this.AsQueryable().Reverse()) - { - var templateString = contributor.GetOrNull(cultureName); - if (templateString != null) - { - return templateString; - } - } - - return null; - } + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs new file mode 100644 index 0000000000..dcc36081fd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer : ITemplateRenderer, ITransientDependency + { + private readonly ITemplateContentProvider _templateContentProvider; + + public TemplateRenderer( + ITemplateContentProvider templateContentProvider + ) + { + _templateContentProvider = templateContentProvider; + } + + public virtual async Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null) + { + Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + + return await _templateContentProvider.GetContentOrNullAsync( + templateName, + cultureName + ); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index bc0e8b94c4..b711860089 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -15,8 +15,8 @@ namespace Volo.Abp.TextTemplating [Fact] public void Should_Retrieve_Template_Definition_By_Name() { - var definition = _templateDefinitionManager.Get(TestTemplates.TestTemplate1); - definition.Name.ShouldBe(TestTemplates.TestTemplate1); + var definition = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); + definition.Name.ShouldBe(TestTemplates.WelcomeEmail); } [Fact] diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs new file mode 100644 index 0000000000..e4c5481413 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer_Tests : AbpTextTemplatingTestBase + { + private readonly ITemplateRenderer _templateRenderer; + + public TemplateRenderer_Tests() + { + _templateRenderer = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_Rendered_Non_Localized_Template_Content() + { + var content = await _templateRenderer.RenderAsync(TestTemplates.ForgotPasswordEmail); + content.ShouldBe("Please click to the following link to get an email to reset your password!"); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index f47d31ac4e..d400964684 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -6,10 +6,16 @@ { context.Add( new TemplateDefinition( - TestTemplates.TestTemplate1 + TestTemplates.WelcomeEmail ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); + context.Add( + new TemplateDefinition( + TestTemplates.ForgotPasswordEmail + ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") + ); + context.Add(new TemplateDefinition( TestTemplates.TestTemplateLayout1 )); diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs index e5b7bcc9ae..a2b605c213 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -2,7 +2,8 @@ { public static class TestTemplates { - public const string TestTemplate1 = "TestTemplate1"; + public const string WelcomeEmail = "WelcomeEmail"; + public const string ForgotPasswordEmail = "ForgotPasswordEmail"; public const string TestTemplateLayout1 = "TestTemplateLayout1"; } } \ No newline at end of file From 24365e49797a109921dee0eed4938c57e5baa9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:11:19 +0300 Subject: [PATCH 17/80] Use Scriban on TemplateRenderer --- .../Volo.Abp.TextTemplating.csproj | 4 ++++ .../Volo/Abp/TextTemplating/ITemplateRenderer.cs | 1 + .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 11 ++++++++--- .../Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index ba12b60ca7..404259ae17 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs index d65f77653a..92a1c0782c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.TextTemplating { Task RenderAsync( [NotNull] string templateName, + [CanBeNull] object model = null, [CanBeNull] string cultureName = null ); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index dcc36081fd..127e7e9872 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; +using Scriban; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating @@ -9,22 +10,26 @@ namespace Volo.Abp.TextTemplating private readonly ITemplateContentProvider _templateContentProvider; public TemplateRenderer( - ITemplateContentProvider templateContentProvider - ) + ITemplateContentProvider templateContentProvider) { _templateContentProvider = templateContentProvider; } public virtual async Task RenderAsync( [NotNull] string templateName, + [CanBeNull] object model = null, [CanBeNull] string cultureName = null) { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - return await _templateContentProvider.GetContentOrNullAsync( + var content = await _templateContentProvider.GetContentOrNullAsync( templateName, cultureName ); + + var parsedTemplate = Template.Parse(content); + + return await parsedTemplate.RenderAsync(model); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index e4c5481413..70e257ae9a 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -16,7 +16,9 @@ namespace Volo.Abp.TextTemplating [Fact] public async Task Should_Get_Rendered_Non_Localized_Template_Content() { - var content = await _templateRenderer.RenderAsync(TestTemplates.ForgotPasswordEmail); + var content = await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail + ); content.ShouldBe("Please click to the following link to get an email to reset your password!"); } } From 61d2512311f832e7eb8c1be12c6915a2e7b3b9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 15:43:04 +0300 Subject: [PATCH 18/80] Implemented template renderer. --- .../Volo/Abp/Localization/CultureHelper.cs | 7 ++ .../AbpDictionaryBasedStringLocalizer.cs | 11 +-- .../ITemplateContentProvider.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 5 +- .../VirtualFileTemplateContributor.cs | 39 +++++++-- .../SampleTemplates/WelcomeEmail/en.tpl | 2 +- .../SampleTemplates/WelcomeEmail/tr.tpl | 2 +- .../TextTemplating/TemplateRenderer_Tests.cs | 81 ++++++++++++++++++- .../TestTemplateDefinitionProvider.cs | 3 +- .../VirtualFileTemplateContributor_Tests.cs | 4 +- 10 files changed, 139 insertions(+), 21 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 92320f737c..8f663e084e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -29,5 +29,12 @@ namespace Volo.Abp.Localization CultureInfo.CurrentUICulture = currentUiCulture; }); } + + public static string GetBaseCultureName(string cultureName) + { + return cultureName.Contains("-") + ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) + : cultureName; + } } } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs index 79c164eb5f..53a2d7714e 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs @@ -104,7 +104,7 @@ namespace Volo.Abp.Localization //Try to get from same language dictionary (without country code) if (cultureName.Contains("-")) //Example: "tr-TR" (length=5) { - var strLang = Resource.Contributors.GetOrNull(GetBaseCultureName(cultureName), name); + var strLang = Resource.Contributors.GetOrNull(CultureHelper.GetBaseCultureName(cultureName), name); if (strLang != null) { return strLang; @@ -168,7 +168,7 @@ namespace Volo.Abp.Localization //Overwrite all strings from the language based on country culture if (cultureName.Contains("-")) { - Resource.Contributors.Fill(GetBaseCultureName(cultureName), allStrings); + Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings); } } @@ -178,12 +178,7 @@ namespace Volo.Abp.Localization return allStrings.Values.ToImmutableList(); } - protected virtual string GetBaseCultureName(string cultureName) - { - return cultureName.Contains("-") - ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) - : cultureName; - } + public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index d4dc5615fb..526da6a381 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -1,9 +1,13 @@ using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { public interface ITemplateContentProvider { - Task GetContentOrNullAsync(string templateName, string cultureName); + Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null + ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index ab81d8b9f7..1334b5b1dd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using JetBrains.Annotations; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating @@ -14,7 +15,9 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public async Task GetContentOrNullAsync(string templateName, string cultureName) + public async Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null) { var template = _templateDefinitionManager.Get(templateName); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index 869a9cb7b1..f1a4700da7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; +using System.Globalization; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { public class VirtualFileTemplateContributor : ITemplateContributor { + public TemplateDefinition TemplateDefinition { get; private set; } + private readonly string _virtualPath; private IVirtualFileProvider _virtualFileProvider; private volatile Dictionary _templateDictionary; @@ -23,21 +27,46 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public void Initialize(TemplateContributorInitializationContext context) { _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + TemplateDefinition = context.TemplateDefinition; } public string GetOrNull([CanBeNull] string cultureName = null) { - var dictionary = GetTemplateDictionary(); + //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents if (cultureName == null) { - return dictionary.GetOrDefault("__default"); + cultureName = CultureInfo.CurrentUICulture.Name; + } + + var dictionary = GetTemplateDictionary(); + + var content = dictionary.GetOrDefault(cultureName); + if (content != null) + { + return content; + } + + if (cultureName.Contains("-")) + { + var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); + content = dictionary.GetOrDefault(baseCultureName); + if (content != null) + { + return content; + } } - else + + if (TemplateDefinition.DefaultCultureName != null) { - return dictionary.GetOrDefault(cultureName) ?? - dictionary.GetOrDefault("__default"); + content = dictionary.GetOrDefault(TemplateDefinition.DefaultCultureName); + if (content != null) + { + return content; + } } + + return dictionary.GetOrDefault("__default"); } private Dictionary GetTemplateDictionary() diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl index 7dbe5e8cb0..1088362628 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -1 +1 @@ -Welcome to the abp.io! \ No newline at end of file +Welcome {{name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl index 6c70e0afb1..b457611f1e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -1 +1 @@ -abp.io'ya hoşgeldiniz! \ No newline at end of file +Merhaba {{name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index 70e257ae9a..9e76d22ab8 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Shouldly; using Xunit; @@ -19,7 +20,85 @@ namespace Volo.Abp.TextTemplating var content = await _templateRenderer.RenderAsync( TestTemplates.ForgotPasswordEmail ); + content.ShouldBe("Please click to the following link to get an email to reset your password!"); } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "tr" + )).ShouldBe("Merhaba John, abp.io'ya hoşgeldiniz!"); + + //"en-US" fallbacks to "en" since "en-US" doesn't exists and "en" is the fallback culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en-US" + )).ShouldBe("Welcome John to the abp.io!"); + + //"fr" fallbacks to "en" since "fr" doesn't exists and "en" is the default culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + Name = "John" //Intentionally written as PascalCase since Scriban supports it + }, + cultureName: "fr" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Stronly_Typed_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new WelcomeEmailModel("John"), + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Dictionary_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new Dictionary() { { "name", "John" } }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + private class WelcomeEmailModel + { + public string Name { get; set; } + + public WelcomeEmailModel() + { + + } + + public WelcomeEmailModel(string name) + { + Name = name; + } + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index d400964684..7a1ff4121b 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -6,7 +6,8 @@ { context.Add( new TemplateDefinition( - TestTemplates.WelcomeEmail + TestTemplates.WelcomeEmail, + defaultCultureName: "en" ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") ); diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index d884a5e73b..1c636d7813 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -21,11 +21,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull("en") - .ShouldBe("Welcome to the abp.io!"); + .ShouldBe("Welcome {{name}} to the abp.io!"); contributor .GetOrNull("tr") - .ShouldBe("abp.io'ya hoşgeldiniz!"); + .ShouldBe("Merhaba {{name}}, abp.io'ya hoşgeldiniz!"); } [Fact] From 8fb41d9e430bbcd81bdb15c2a52aeca0cc22b6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 17:32:47 +0300 Subject: [PATCH 19/80] Delete email template system and use the text template system. --- .../Volo.Abp.Emailing.csproj | 5 +- .../Volo/Abp/Emailing/AbpEmailingModule.cs | 50 +------- .../Templates/AbpEmailTemplateOptions.cs | 18 --- .../Templates/DefaultEmailTemplateProvider.cs | 24 +++- .../DefaultEmailTemplates/Layout/en.tpl | 2 +- .../Abp/Emailing/Templates/EmailTemplate.cs | 42 ------ .../Templates/EmailTemplateContributorList.cs | 22 ---- .../Templates/EmailTemplateDefinition.cs | 36 ------ .../EmailTemplateDefinitionContext.cs | 38 ------ .../EmailTemplateDefinitionDictionary.cs | 22 ---- .../EmailTemplateDefinitionManager.cs | 74 ----------- .../EmailTemplateDefinitionProvider.cs | 9 -- .../EmailTemplateInitializationContext.cs | 18 --- .../Templates/EmailTemplateProvider.cs | 121 ------------------ .../Templates/IEmailTemplateContributor.cs | 9 -- .../IEmailTemplateDefinitionContext.cs | 9 -- .../IEmailTemplateDefinitionManager.cs | 15 --- .../IEmailTemplateDefinitionProvider.cs | 7 - .../Templates/IEmailTemplateProvider.cs | 11 -- .../Abp/Emailing/Templates/ITemplateRender.cs | 9 -- .../Abp/Emailing/Templates/TemplateRender.cs | 15 --- .../EmailTemplateDefinitionExtensions.cs | 19 --- ...pleVirtualFilesEmailTemplateContributor.cs | 68 ---------- ...ngleVirtualFileEmailTemplateContributor.cs | 34 ----- .../Abp/Localization/TemplateLocalizer.cs | 18 --- .../ITemplateContentProvider.cs | 5 + .../TextTemplating/TemplateContentProvider.cs | 16 ++- .../Abp/TextTemplating/TemplateDefinition.cs | 5 +- .../Abp/TextTemplating/TemplateRenderer.cs | 47 ++++++- .../Volo.Abp.Emailing.Tests.csproj | 7 - .../Abp/Emailing/AbpEmailingTestModule.cs | 10 -- .../Abp/Emailing/EmailTemplateRender_Tests.cs | 65 ---------- .../Abp/Emailing/EmailTemplateStore_Tests.cs | 54 -------- .../Localization/AbpEmailingTestResource.cs | 10 -- .../Volo/Abp/Emailing/Localization/cs.json | 6 - .../Volo/Abp/Emailing/Localization/en.json | 6 - .../Volo/Abp/Emailing/Localization/pl.json | 6 - .../Volo/Abp/Emailing/Localization/pt-BR.json | 6 - .../Volo/Abp/Emailing/Localization/tr.json | 6 - .../Volo/Abp/Emailing/Localization/vi.json | 6 - .../Abp/Emailing/Localization/zh-Hant.json | 6 - .../Abp/Emailing/TestEmailTemplateProvider.cs | 24 ---- .../Emailing/TestTemplates/Template1/en.tpl | 4 - .../Emailing/TestTemplates/Template1/tr.tpl | 4 - .../Emailing/TestTemplates/Template2/en.tpl | 4 - .../TestTemplates/Template3/Template.tpl | 1 - .../Localization/TemplateLocalizer_Tests.cs | 63 --------- 47 files changed, 85 insertions(+), 971 deletions(-) delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs delete mode 100644 framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl delete mode 100644 framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl delete mode 100644 framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index be953a1f26..783d840cf0 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -24,14 +24,11 @@ - - - - + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index 9641f7c14c..afcbaf6aef 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.BackgroundJobs; +using Volo.Abp.BackgroundJobs; using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Settings; +using Volo.Abp.TextTemplating; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Emailing @@ -15,15 +12,11 @@ namespace Volo.Abp.Emailing typeof(AbpSettingsModule), typeof(AbpVirtualFileSystemModule), typeof(AbpBackgroundJobsAbstractionsModule), - typeof(AbpLocalizationModule) + typeof(AbpLocalizationModule), + typeof(AbpTextTemplatingModule) )] public class AbpEmailingModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - AutoAddDefinitionProviders(context.Services); - } - public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -43,40 +36,5 @@ namespace Volo.Abp.Emailing options.AddJob(); }); } - - private static void AutoAddDefinitionProviders(IServiceCollection services) - { - var definitionProviders = new List(); - - services.OnRegistred(context => - { - if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) - { - definitionProviders.Add(context.ImplementationType); - } - }); - - services.Configure(options => - { - options.DefinitionProviders.AddIfNotContains(definitionProviders); - }); - } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - using (var scope = context.ServiceProvider.CreateScope()) - { - var emailTemplateDefinitionManager = - scope.ServiceProvider.GetRequiredService(); - - foreach (var templateDefinition in emailTemplateDefinitionManager.GetAll()) - { - foreach (var contributor in templateDefinition.Contributors) - { - contributor.Initialize(new EmailTemplateInitializationContext(templateDefinition, scope.ServiceProvider)); - } - } - } - } } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs deleted file mode 100644 index cb5a9d370b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Volo.Abp.Collections; - -namespace Volo.Abp.Emailing.Templates -{ - public class AbpEmailTemplateOptions - { - public string DefaultLayout { get; set; } - - public ITypeList DefinitionProviders { get; } - - public AbpEmailTemplateOptions() - { - DefaultLayout = StandardEmailTemplates.DefaultLayout; - - DefinitionProviders = new TypeList(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs index c15012f0bf..28d1ac94c1 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -1,16 +1,26 @@ -using Volo.Abp.Emailing.Templates.VirtualFiles; +using Volo.Abp.TextTemplating; namespace Volo.Abp.Emailing.Templates { - public class DefaultEmailTemplateProvider : EmailTemplateDefinitionProvider + public class DefaultEmailTemplateProvider : TemplateDefinitionProvider { - public override void Define(IEmailTemplateDefinitionContext context) + public override void Define(ITemplateDefinitionContext context) { - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, defaultCultureName: "en", isLayout: true, layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.DefaultLayout, + defaultCultureName: "en", + isLayout: true, + layout: null + ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout") + ); - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage, defaultCultureName: "en") - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.SimpleMessage, + defaultCultureName: "en" + ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message") + ); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl index 107fbb5230..57453a027f 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl @@ -4,6 +4,6 @@ - {{#content}} + {{content}} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs deleted file mode 100644 index ad6f8c4839..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplate - { - public EmailTemplateDefinition Definition { get; } - - public string Content => ContentBuilder.ToString(); - - protected StringBuilder ContentBuilder { get; set; } - - public EmailTemplate(string content, EmailTemplateDefinition definition) - { - ContentBuilder = new StringBuilder(content); - Definition = definition; - } - - public virtual void SetLayout(EmailTemplate layoutTemplate) - { - if (!layoutTemplate.Definition.IsLayout) - { - throw new AbpException($"Given template is not a layout template: {layoutTemplate.Definition.Name}"); - } - - var newStrBuilder = new StringBuilder(layoutTemplate.Content); - newStrBuilder.Replace("{{#content}}", ContentBuilder.ToString()); - - ContentBuilder = newStrBuilder; - } - - public virtual void SetContent(string content) - { - ContentBuilder = new StringBuilder(content); - } - - public virtual void Replace(string name, string value) - { - ContentBuilder.Replace("{{" + name + "}}", value); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs deleted file mode 100644 index 44a91212ea..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateContributorList : List - { - public string GetOrNull(string cultureName) - { - foreach (var contributor in this.AsQueryable().Reverse()) - { - var templateString = contributor.GetOrNull(cultureName); - if (templateString != null) - { - return templateString; - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs deleted file mode 100644 index 19f0eb2fa7..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinition - { - public const string DefaultLayoutPlaceHolder = "_"; - - public string Name { get; } - - public bool IsLayout { get; } - - public string Layout { get; set; } - - public Type LocalizationResource { get; set; } - - public EmailTemplateContributorList Contributors { get; } - - public string DefaultCultureName { get; } - - public bool SingleTemplateFile { get; } - - public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - LocalizationResource = localizationResource; - Contributors = new EmailTemplateContributorList(); - IsLayout = isLayout; - Layout = layout; - DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs deleted file mode 100644 index 03a6c95d8b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionContext : IEmailTemplateDefinitionContext - { - protected Dictionary EmailTemplates { get; } - - public EmailTemplateDefinitionContext(Dictionary emailTemplates) - { - EmailTemplates = emailTemplates; - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplates.GetOrDefault(name); - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplates.Values.ToImmutableList(); - } - - public virtual void Add(params EmailTemplateDefinition[] definitions) - { - if (definitions.IsNullOrEmpty()) - { - return; - } - - foreach (var definition in definitions) - { - EmailTemplates[definition.Name] = definition; - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs deleted file mode 100644 index aa36232156..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionDictionary : Dictionary - { - public EmailTemplateDefinitionDictionary Add(EmailTemplateDefinition emailTemplateDefinition) - { - if (ContainsKey(emailTemplateDefinition.Name)) - { - throw new AbpException( - "There is already an email template definition with given name: " + - emailTemplateDefinition.Name - ); - } - - this[emailTemplateDefinition.Name] = emailTemplateDefinition; - - return this; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs deleted file mode 100644 index 0491dc867e..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionManager : IEmailTemplateDefinitionManager, ISingletonDependency - { - protected Lazy> EmailTemplateDefinitions { get; } - - protected AbpEmailTemplateOptions Options { get; } - - protected IServiceProvider ServiceProvider { get; } - - public EmailTemplateDefinitionManager( - IOptions options, - IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - Options = options.Value; - - EmailTemplateDefinitions = - new Lazy>(CreateEmailTemplateDefinitions, true); - } - - public virtual EmailTemplateDefinition Get(string name) - { - Check.NotNull(name, nameof(name)); - - var template = GetOrNull(name); - - if (template == null) - { - throw new AbpException("Undefined template: " + name); - } - - return template; - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplateDefinitions.Value.Values.ToImmutableList(); - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplateDefinitions.Value.GetOrDefault(name); - } - - protected virtual IDictionary CreateEmailTemplateDefinitions() - { - var templates = new Dictionary(); - - using (var scope = ServiceProvider.CreateScope()) - { - var providers = Options - .DefinitionProviders - .Select(p => scope.ServiceProvider.GetRequiredService(p) as IEmailTemplateDefinitionProvider) - .ToList(); - - foreach (var provider in providers) - { - provider.Define(new EmailTemplateDefinitionContext(templates)); - } - } - - return templates; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs deleted file mode 100644 index e53505fafc..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public abstract class EmailTemplateDefinitionProvider : IEmailTemplateDefinitionProvider, ITransientDependency - { - public abstract void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs deleted file mode 100644 index 8cd4b95bbd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateInitializationContext - { - public EmailTemplateDefinition EmailTemplateDefinition { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, - IServiceProvider serviceProvider) - { - EmailTemplateDefinition = emailTemplateDefinition; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs deleted file mode 100644 index 29fcf08664..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency - { - protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager; - protected ITemplateLocalizer TemplateLocalizer { get; } - protected AbpEmailTemplateOptions Options { get; } - protected IStringLocalizerFactory StringLocalizerFactory; - - public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager, - ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory, - IOptions options) - { - EmailTemplateDefinitionManager = emailTemplateDefinitionManager; - TemplateLocalizer = templateLocalizer; - StringLocalizerFactory = stringLocalizerFactory; - Options = options.Value; - } - - public async Task GetAsync(string name) - { - return await GetAsync(name, CultureInfo.CurrentUICulture.Name); - } - - public async Task GetAsync(string name, string cultureName) - { - return await GetInternalAsync(name, cultureName); - } - - protected virtual async Task GetInternalAsync(string name, string cultureName) - { - var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name); - if (emailTemplateDefinition == null) - { - // TODO: Localized message - throw new AbpException($"email template {name} not definition"); - } - - var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName); - if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null) - { - emailTemplateString = - emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName); - if (emailTemplateString != null) - { - cultureName = emailTemplateDefinition.DefaultCultureName; - } - } - - if (emailTemplateString != null) - { - var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition); - - await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName); - - if (emailTemplateDefinition.SingleTemplateFile) - { - await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName); - } - - return emailTemplate; - } - - // TODO: Localized message - throw new AbpException($"{cultureName} template not exist!"); - } - - protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - var layout = emailTemplateDefinition.Layout; - if (layout.IsNullOrWhiteSpace()) - { - return; - } - - if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder) - { - layout = Options.DefaultLayout; - } - - var layoutTemplate = await GetInternalAsync(layout, cultureName); - - emailTemplate.SetLayout(layoutTemplate); - } - - protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - if (emailTemplateDefinition.LocalizationResource == null) - { - return Task.CompletedTask; - } - - var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource); - if (cultureName != null) - { - using (CultureHelper.Use(new CultureInfo(cultureName))) - { - emailTemplate.SetContent(TemplateLocalizer.Localize(localizer, emailTemplate.Content)); - } - } - else - { - emailTemplate.SetContent( - TemplateLocalizer.Localize(localizer, emailTemplate.Content) - ); - } - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs deleted file mode 100644 index d2c2775845..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateContributor - { - void Initialize(EmailTemplateInitializationContext context); - - string GetOrNull(string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs deleted file mode 100644 index 1641562ccf..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionContext - { - EmailTemplateDefinition GetOrNull(string name); - - void Add(params EmailTemplateDefinition[] definitions); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs deleted file mode 100644 index 0936a2fe93..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionManager - { - [NotNull] - EmailTemplateDefinition Get([NotNull] string name); - - IReadOnlyList GetAll(); - - EmailTemplateDefinition GetOrNull(string name); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs deleted file mode 100644 index 691d3874d6..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionProvider - { - void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs deleted file mode 100644 index ab68dbe2ca..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateProvider - { - Task GetAsync(string name); - - Task GetAsync(string name, string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs deleted file mode 100644 index 35ac14c8fd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface ITemplateRender - { - Task RenderAsync(string template, object model = null); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs deleted file mode 100644 index 8c4e24017c..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; -using Scriban; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class TemplateRender : ITemplateRender, ITransientDependency - { - public async Task RenderAsync(string template, object model = null) - { - var scribanTemplate = Template.Parse(template); - return await scribanTemplate.RenderAsync(model); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs deleted file mode 100644 index 7bee611d28..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public static class EmailTemplateDefinitionExtensions - { - public static EmailTemplateDefinition AddTemplateVirtualFile( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new SingleVirtualFileEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - - public static EmailTemplateDefinition AddTemplateVirtualFiles( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new MultipleVirtualFilesEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs deleted file mode 100644 index 39af512350..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class MultipleVirtualFilesEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - private Dictionary _templateDictionary; - - private readonly object _syncObj = new object(); - - public MultipleVirtualFilesEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - return GetTemplateDictionary().GetOrDefault(cultureName); - } - - private Dictionary GetTemplateDictionary() - { - var dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - lock (_syncObj) - { - dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - _templateDictionary = new Dictionary(); - foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) - { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - _templateDictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); - } - - dictionaries = _templateDictionary; - } - - return dictionaries; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs deleted file mode 100644 index d72d18e99a..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class SingleVirtualFileEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - public SingleVirtualFileEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - var file = _virtualFileProvider.GetFileInfo(_virtualPath); - if (file == null || !file.Exists || file.IsDirectory) - { - return null; - } - - return file.ReadAsString(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs deleted file mode 100644 index 5c4220c45f..0000000000 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.Extensions.Localization; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer : ITemplateLocalizer, ITransientDependency - { - public string Localize(IStringLocalizer localizer, string text) - { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] - ); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index 526da6a381..12c8f8f8d8 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -9,5 +9,10 @@ namespace Volo.Abp.TextTemplating [NotNull] string templateName, [CanBeNull] string cultureName = null ); + + Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null + ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 1334b5b1dd..4049a3861e 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -15,15 +15,23 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public async Task GetContentOrNullAsync( + public Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null) { var template = _templateDefinitionManager.Get(templateName); + return GetContentOrNullAsync(template, cultureName); + } + + public async Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in template.Contributors) + foreach (var contributor in templateDefinition.Contributors) { - var templateString = contributor.GetOrNull(cultureName); + var templateString = contributor.GetOrNull(cultureName); //TODO: GetOrNull should be async! if (templateString != null) { return templateString; @@ -31,7 +39,7 @@ namespace Volo.Abp.TextTemplating } throw new AbpException( - $"None of the template contributors could get the content for the template '{templateName}'" + $"None of the template contributors could get the content for the template '{templateDefinition.Name}'" ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 548ac87c15..ea4ba03e27 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -15,7 +15,8 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public string Layout { get; set; } - public Type LocalizationResource { get; set; } //TODO: ??? + [CanBeNull] + public Type LocalizationResource { get; set; } public TemplateContributorList Contributors { get; } @@ -24,7 +25,7 @@ namespace Volo.Abp.TextTemplating public TemplateDefinition( [NotNull] string name, - Type localizationResource = null, + [CanBeNull] Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 127e7e9872..c3e3f2b70d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Localization; using Scriban; using Volo.Abp.DependencyInjection; @@ -8,11 +10,17 @@ namespace Volo.Abp.TextTemplating public class TemplateRenderer : ITemplateRenderer, ITransientDependency { private readonly ITemplateContentProvider _templateContentProvider; + private readonly ITemplateDefinitionManager _templateDefinitionManager; + private readonly IStringLocalizerFactory _stringLocalizerFactory; public TemplateRenderer( - ITemplateContentProvider templateContentProvider) + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, + IStringLocalizerFactory stringLocalizerFactory) { _templateContentProvider = templateContentProvider; + _templateDefinitionManager = templateDefinitionManager; + _stringLocalizerFactory = stringLocalizerFactory; } public virtual async Task RenderAsync( @@ -22,14 +30,43 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + var templateDefinition = _templateDefinitionManager.Get(templateName); + var content = await _templateContentProvider.GetContentOrNullAsync( - templateName, + templateDefinition, cultureName ); - var parsedTemplate = Template.Parse(content); + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + content = Localize(localizer, content); + } + + var renderedContent = await Template.Parse(content).RenderAsync(model); + + if (templateDefinition.Layout != null) + { + renderedContent = await RenderAsync( + templateDefinition.Layout, + new + { + content = renderedContent + }, + cultureName: cultureName + ); + } - return await parsedTemplate.RenderAsync(model); + return renderedContent; + } + + public string Localize(IStringLocalizer localizer, string text) + { + return new Regex("\\{\\{#L:.+?\\}\\}") + .Replace( + text, + match => localizer[match.Value.Substring(5, match.Length - 7)] + ); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj index a86ea9f051..030eddb65a 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj @@ -7,13 +7,6 @@ - - - - - - - diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs index e4cdfe0556..7f1175bcbb 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs @@ -1,6 +1,4 @@ using Volo.Abp.Autofac; -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -18,14 +16,6 @@ namespace Volo.Abp.Emailing { options.FileSets.AddEmbedded(); }); - - Configure(options => - { - options.Resources - .Add() - .AddVirtualJson("/Volo/Abp/Emailing/Localization"); - }); - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs deleted file mode 100644 index 24d7298f6c..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateRender_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly ITemplateRender _templateRender; - - public EmailTemplateRender_Tests() - { - _templateRender = GetRequiredService(); - } - - [Fact] - public async Task RenderAsync() - { - var template = "Hello {{email}} {{ for order in orders }}{{ order.id }}:{{ order.name }},{{ end }}"; - - var model = new ModelClass - { - Email = "john@abp.io", - Orders = new List - { - new ModelClass.Order - { - Id = "1", - Name = "iphone" - }, - new ModelClass.Order - { - Id = "2", - Name = "ipad" - } - } - }; - - var result = await _templateRender.RenderAsync(template, model); - result.ShouldBe("Hello john@abp.io 1:iphone,2:ipad,"); - } - - public class ModelClass - { - public string Email { get; set; } - - public List Orders { get; set; } - - public class Order - { - public string Id { get; set; } - - public string Name { get; set; } - } - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs deleted file mode 100644 index 1e4ea8c090..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateStore_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly IEmailTemplateProvider _emailTemplateProvider; - - public EmailTemplateStore_Tests() - { - _emailTemplateProvider = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_Registered_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "tr"); - template.Content.ShouldContain("Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın."); - } - - [Fact] - public async Task Should_Get_Default_Culture_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "zh-Hans"); - template.Content.ShouldContain("Please confirm your email address by clicking the link below."); - } - - [Fact] - public async Task Should_Get_Registered_Template_With_Layout() - { - var template = await _emailTemplateProvider.GetAsync("template2", "en"); - - template.Content.ShouldContain($"{Environment.NewLine} " + "Please confirm your email address by clicking the link below."); - } - - - [Fact] - public async Task Should_Get_Registered_Template_With_Localize() - { - var template = await _emailTemplateProvider.GetAsync("template3", "tr"); - template.Content.ShouldContain("Merhaba Abp"); - } - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs deleted file mode 100644 index bdc75f9f2f..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Volo.Abp.Emailing.Localization -{ - public class AbpEmailingTestResource - { - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json deleted file mode 100644 index 5f6e35488e..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "cs", - "texts": { - "hello": "ahoj" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json deleted file mode 100644 index 38f7cf7e5b..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "en", - "texts": { - "hello": "hello" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json deleted file mode 100644 index 6dd6654aa1..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - "hello": "witaj" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json deleted file mode 100644 index 0e33dee138..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pt-BR", - "texts": { - "hello": "Ol" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json deleted file mode 100644 index 6c3c94cfdf..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "tr", - "texts": { - "hello": "Merhaba" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json deleted file mode 100644 index 8261599d78..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "vi", - "texts": { - "hello": "xin chào" - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json deleted file mode 100644 index dd12964f70..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "zh-Hant", - "texts": { - "hello": "哈囉" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs deleted file mode 100644 index 60c9a2ccf3..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Emailing.Templates.VirtualFiles; - -namespace Volo.Abp.Emailing -{ - public class TestEmailTemplateProvider : EmailTemplateDefinitionProvider - { - public override void Define(IEmailTemplateDefinitionContext context) - { - var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template1"); - context.Add(template1); - - var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template2"); - context.Add(template2); - - var template3 = new EmailTemplateDefinition("template3", layout: null, singleTemplateFile: true, localizationResource: typeof(AbpEmailingTestResource)) - .AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl"); - context.Add(template3); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl deleted file mode 100644 index 3e572aedbb..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın. -Size hizmetimizle ilgili kritik bilgileri göndermemiz gerekebilir ve doğru bir e-posta adresimizin olması önemlidir. - -E-posta adresini onayla \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl deleted file mode 100644 index f30f512848..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl +++ /dev/null @@ -1 +0,0 @@ -{{#L:hello}} Abp \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs deleted file mode 100644 index 4b31554637..0000000000 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using Shouldly; -using Volo.Abp.Localization.TestResources.Source; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Volo.Abp.VirtualFileSystem; -using Xunit; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer_Tests : AbpIntegratedTest - { - private readonly ITemplateLocalizer _templateLocalizer; - private readonly IStringLocalizer _testResource; - - public TemplateLocalizer_Tests() - { - _testResource = GetRequiredService>(); - _templateLocalizer = GetRequiredService(); - } - - [Fact] - public void Should_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

{{#L:CarPlural}} {{#L:Universe}}

") - .ShouldBe("

Cars Universe

"); - } - } - - [Fact] - public void Should_Work_Even_If_No_Text_To_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

test

") - .ShouldBe("

test

"); - } - } - - [DependsOn(typeof(AbpTestBaseModule))] - [DependsOn(typeof(AbpLocalizationModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - - Configure(options => - { - options.Resources - .Add("en") - .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source"); - }); - } - } - } -} From 0311092ae1b43212ddee6c0c82ed2eafc6c013c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:03:37 +0300 Subject: [PATCH 20/80] Refactored text templating and added more tests. --- .../Volo/Abp/Localization/CultureHelper.cs | 7 +- .../Abp/TextTemplating/TemplateDefinition.cs | 4 +- .../Abp/TextTemplating/TemplateRenderer.cs | 120 ++++++++++++++---- .../Volo.Abp.TextTemplating.Tests.csproj | 3 + .../AbpTextTemplatingTestModule.cs | 12 +- .../Localization/TestLocalizationSource.cs | 6 + .../Abp/TextTemplating/Localization/en.json | 6 + .../Abp/TextTemplating/Localization/tr.json | 6 + .../SampleTemplates/ForgotPasswordEmail.tpl | 2 +- .../SampleTemplates/TestTemplateLayout1.tpl | 1 + .../SampleTemplates/WelcomeEmail/en.tpl | 2 +- .../SampleTemplates/WelcomeEmail/tr.tpl | 2 +- .../TextTemplating/TemplateRenderer_Tests.cs | 24 ++-- .../TestTemplateDefinitionProvider.cs | 17 ++- .../VirtualFileTemplateContributor_Tests.cs | 6 +- 15 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json create mode 100644 framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 8f663e084e..d4d9cfb1c0 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -10,7 +10,12 @@ namespace Volo.Abp.Localization { Check.NotNull(culture, nameof(culture)); - return Use(new CultureInfo(culture), uiCulture == null ? null : new CultureInfo(uiCulture)); + return Use( + new CultureInfo(culture), + uiCulture == null + ? null + : new CultureInfo(uiCulture) + ); } public static IDisposable Use([NotNull] CultureInfo culture, CultureInfo uiCulture = null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index ea4ba03e27..8e113829ea 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -5,8 +5,6 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinition { - public const string DefaultLayoutPlaceHolder = "_"; - [NotNull] public string Name { get; } @@ -27,7 +25,7 @@ namespace Volo.Abp.TextTemplating [NotNull] string name, [CanBeNull] Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, + string layout = null, string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index c3e3f2b70d..4ff2beb9ba 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,9 +1,13 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; using Scriban; +using Scriban.Runtime; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { @@ -14,8 +18,8 @@ namespace Volo.Abp.TextTemplating private readonly IStringLocalizerFactory _stringLocalizerFactory; public TemplateRenderer( - ITemplateContentProvider templateContentProvider, - ITemplateDefinitionManager templateDefinitionManager, + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, IStringLocalizerFactory stringLocalizerFactory) { _templateContentProvider = templateContentProvider; @@ -30,43 +34,111 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - var templateDefinition = _templateDefinitionManager.Get(templateName); - - var content = await _templateContentProvider.GetContentOrNullAsync( - templateDefinition, - cultureName - ); + if (cultureName == null) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } - if (templateDefinition.LocalizationResource != null) + using (CultureHelper.Use(cultureName)) { - var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); - content = Localize(localizer, content); + return await RenderInternalAsync( + templateName, + new Dictionary(), + model + ); } + } - var renderedContent = await Template.Parse(content).RenderAsync(model); + private async Task RenderInternalAsync( + string templateName, + Dictionary globalContext, + object model = null) + { + var templateDefinition = _templateDefinitionManager.Get(templateName); + + var renderedContent = await RenderSingleTemplateAsync( + templateDefinition, + globalContext, + model + ); if (templateDefinition.Layout != null) { - renderedContent = await RenderAsync( + globalContext["content"] = renderedContent; + renderedContent = await RenderInternalAsync( templateDefinition.Layout, - new - { - content = renderedContent - }, - cultureName: cultureName + globalContext ); } return renderedContent; } - public string Localize(IStringLocalizer localizer, string text) + private async Task RenderSingleTemplateAsync( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) + { + var rawTemplateContent = await _templateContentProvider + .GetContentOrNullAsync( + templateDefinition + ); + + return await RenderTemplateContentWithScribanAsync( + templateDefinition, + rawTemplateContent, + globalContext, + model + ); + } + + protected virtual async Task RenderTemplateContentWithScribanAsync( + TemplateDefinition templateDefinition, + string templateContent, + Dictionary globalContext, + object model = null) + { + var context = CreateScribanTemplateContext( + templateDefinition, + globalContext, + model + ); + + return await Template + .Parse(templateContent) + .RenderAsync(context); + } + + protected virtual TemplateContext CreateScribanTemplateContext( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] + var context = new TemplateContext(); + + var scriptObject = new ScriptObject(); + + scriptObject.Import(globalContext); + + if (model != null) + { + scriptObject["model"] = model; + } + + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + scriptObject.Import( + "l", + new Func( + name => localizer[name] + ) ); + } + + context.PushGlobal(scriptObject); + + return context; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 916495b116..0d230552e8 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -8,11 +8,14 @@ + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 0457e0136d..8e8b69e116 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.Autofac; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.TextTemplating.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating @@ -7,7 +9,8 @@ namespace Volo.Abp.TextTemplating [DependsOn( typeof(AbpTextTemplatingModule), typeof(AbpTestBaseModule), - typeof(AbpAutofacModule) + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule) )] public class AbpTextTemplatingTestModule : AbpModule { @@ -17,6 +20,13 @@ namespace Volo.Abp.TextTemplating { options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization"); + }); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs new file mode 100644 index 0000000000..a4e53e3861 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.TextTemplating.Localization +{ + public class TestLocalizationSource + { + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json new file mode 100644 index 0000000000..a0dff7e930 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "HelloText": "Hello" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json new file mode 100644 index 0000000000..d09f02cd8f --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "HelloText": "Merhaba" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl index 674b734c8a..7d844c6f7c 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -1 +1 @@ -Please click to the following link to get an email to reset your password! \ No newline at end of file +{{l "HelloText"}}. Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl new file mode 100644 index 0000000000..a780e210b0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl @@ -0,0 +1 @@ +*BEGIN*{{content}}*END* \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl index 1088362628..1746eed52b 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -1 +1 @@ -Welcome {{name}} to the abp.io! \ No newline at end of file +Welcome {{model.name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl index b457611f1e..581016bc4d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -1 +1 @@ -Merhaba {{name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file +Merhaba {{model.name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index 9e76d22ab8..14b3df3486 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -14,16 +14,6 @@ namespace Volo.Abp.TextTemplating _templateRenderer = GetRequiredService(); } - [Fact] - public async Task Should_Get_Rendered_Non_Localized_Template_Content() - { - var content = await _templateRenderer.RenderAsync( - TestTemplates.ForgotPasswordEmail - ); - - content.ShouldBe("Please click to the following link to get an email to reset your password!"); - } - [Fact] public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() { @@ -86,6 +76,20 @@ namespace Volo.Abp.TextTemplating )).ShouldBe("Welcome John to the abp.io!"); } + [Fact] + public async Task Should_Get_Rendered_Inline_Localized_Template() + { + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "en" + )).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); + + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "tr" + )).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); + } + private class WelcomeEmailModel { public string Name { get; set; } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 7a1ff4121b..8fe7c98e38 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.TextTemplating +using Volo.Abp.TextTemplating.Localization; + +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { @@ -13,13 +15,18 @@ context.Add( new TemplateDefinition( - TestTemplates.ForgotPasswordEmail + TestTemplates.ForgotPasswordEmail, + localizationResource: typeof(TestLocalizationSource), + layout: TestTemplates.TestTemplateLayout1 ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") ); - context.Add(new TemplateDefinition( - TestTemplates.TestTemplateLayout1 - )); + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplateLayout1, + isLayout: true + ).AddVirtualFiles("/SampleTemplates/TestTemplateLayout1.tpl") + ); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 1c636d7813..2c104b632d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -21,11 +21,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull("en") - .ShouldBe("Welcome {{name}} to the abp.io!"); + .ShouldBe("Welcome {{model.name}} to the abp.io!"); contributor .GetOrNull("tr") - .ShouldBe("Merhaba {{name}}, abp.io'ya hoşgeldiniz!"); + .ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); } [Fact] @@ -44,7 +44,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull() - .ShouldBe("Please click to the following link to get an email to reset your password!"); + .ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); } } } From 7bd91c1d01e49bfd7850c6a10fbc15771fcd0fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:07:32 +0300 Subject: [PATCH 21/80] Get a globalContext from the TemplateRenderer --- .../Volo/Abp/TextTemplating/ITemplateRenderer.cs | 14 ++++++++++++-- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 5 +++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs index 92a1c0782c..b153a5130b 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; @@ -6,10 +7,19 @@ namespace Volo.Abp.TextTemplating { public interface ITemplateRenderer { + /// + /// Renders a text template. + /// + /// The template name + /// An optional model object that is used in the template + /// Culture name. Uses the if not specified + /// A dictionary which can be used to import global objects to the template + /// Task RenderAsync( [NotNull] string templateName, [CanBeNull] object model = null, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 4ff2beb9ba..2824c6e463 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -30,7 +30,8 @@ namespace Volo.Abp.TextTemplating public virtual async Task RenderAsync( [NotNull] string templateName, [CanBeNull] object model = null, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null) { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); @@ -43,7 +44,7 @@ namespace Volo.Abp.TextTemplating { return await RenderInternalAsync( templateName, - new Dictionary(), + globalContext ?? new Dictionary(), model ); } From 693e4ad5bf0e814622d6de058ded7bde4b450ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:09:25 +0300 Subject: [PATCH 22/80] Update TemplateRenderer.cs --- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 2824c6e463..558104e3a4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -50,7 +50,7 @@ namespace Volo.Abp.TextTemplating } } - private async Task RenderInternalAsync( + protected virtual async Task RenderInternalAsync( string templateName, Dictionary globalContext, object model = null) @@ -75,7 +75,7 @@ namespace Volo.Abp.TextTemplating return renderedContent; } - private async Task RenderSingleTemplateAsync( + protected virtual async Task RenderSingleTemplateAsync( TemplateDefinition templateDefinition, Dictionary globalContext, object model = null) From f51c67f11a8427d8152302e0c57710023a6e1260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 23 Apr 2020 23:23:10 +0300 Subject: [PATCH 23/80] Re-Organize standard email templates and renamed AddVirtualFiles to WithVirtualFilePath --- .../src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj | 8 ++++---- .../Templates/DefaultEmailTemplateProvider.cs | 14 +++++++------- .../{DefaultEmailTemplates => }/Layout/en.tpl | 0 .../{DefaultEmailTemplates => }/Message/en.tpl | 0 .../Emailing/Templates/StandardEmailTemplates.cs | 4 ++-- .../Volo/Abp/TextTemplating/TemplateDefinition.cs | 2 +- .../TextTemplating/TemplateDefinitionExtensions.cs | 10 ++++++++-- .../TestTemplateDefinitionProvider.cs | 6 +++--- 8 files changed, 25 insertions(+), 19 deletions(-) rename framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/{DefaultEmailTemplates => }/Layout/en.tpl (100%) rename framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/{DefaultEmailTemplates => }/Message/en.tpl (100%) diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index 783d840cf0..a2f83399a0 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -15,13 +15,13 @@ - - + + - - + + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs index 28d1ac94c1..385e0ef178 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -8,18 +8,18 @@ namespace Volo.Abp.Emailing.Templates { context.Add( new TemplateDefinition( - StandardEmailTemplates.DefaultLayout, + StandardEmailTemplates.Layout, defaultCultureName: "en", - isLayout: true, - layout: null - ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout") + isLayout: true + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout") ); context.Add( new TemplateDefinition( - StandardEmailTemplates.SimpleMessage, - defaultCultureName: "en" - ).AddVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message") + StandardEmailTemplates.Message, + defaultCultureName: "en", + layout: StandardEmailTemplates.Layout + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message") ); } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs index 7e8cbedc68..6a60219a5e 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs @@ -2,7 +2,7 @@ { public static class StandardEmailTemplates { - public const string DefaultLayout = "Abp.DefaultLayout"; - public const string SimpleMessage = "Abp.SimpleMessage"; + public const string Layout = "Abp.StandardEmailTemplates.Layout"; + public const string Message = "Abp.StandardEmailTemplates.Message"; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 8e113829ea..09de7bf458 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -36,7 +36,7 @@ namespace Volo.Abp.TextTemplating DefaultCultureName = defaultCultureName; } - public virtual TemplateDefinition AddContributor(ITemplateContributor contributor) + public virtual TemplateDefinition WithContributor(ITemplateContributor contributor) { Contributors.Add(contributor); return this; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index 6508295da7..9bd932c947 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -5,13 +5,19 @@ namespace Volo.Abp.TextTemplating { public static class TemplateDefinitionExtensions { - public static TemplateDefinition AddVirtualFiles( + /// + /// + /// + /// + /// + /// + public static TemplateDefinition WithVirtualFilePath( [NotNull] this TemplateDefinition templateDefinition, [NotNull] string virtualPath) { Check.NotNull(templateDefinition, nameof(templateDefinition)); - return templateDefinition.AddContributor( + return templateDefinition.WithContributor( new VirtualFileTemplateContributor(virtualPath) ); } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 8fe7c98e38..05df1378b2 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.TextTemplating new TemplateDefinition( TestTemplates.WelcomeEmail, defaultCultureName: "en" - ).AddVirtualFiles("/SampleTemplates/WelcomeEmail") + ).WithVirtualFilePath("/SampleTemplates/WelcomeEmail") ); context.Add( @@ -18,14 +18,14 @@ namespace Volo.Abp.TextTemplating TestTemplates.ForgotPasswordEmail, localizationResource: typeof(TestLocalizationSource), layout: TestTemplates.TestTemplateLayout1 - ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") + ).WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl") ); context.Add( new TemplateDefinition( TestTemplates.TestTemplateLayout1, isLayout: true - ).AddVirtualFiles("/SampleTemplates/TestTemplateLayout1.tpl") + ).WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl") ); } } From 60e4a8e8c33388f67d555d779e932a648bc85acf Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 23 Apr 2020 23:48:29 +0300 Subject: [PATCH 24/80] ITemplateContributor GetOrNull converted to Async --- .../TextTemplating/ITemplateContributor.cs | 5 +++-- .../TextTemplating/TemplateContentProvider.cs | 2 +- .../VirtualFileTemplateContributor.cs | 3 ++- .../VirtualFileTemplateContributor_Tests.cs | 22 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs index e8e1f97769..2174a65bcd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { @@ -6,6 +7,6 @@ namespace Volo.Abp.TextTemplating { void Initialize(TemplateContributorInitializationContext context); - string GetOrNull([CanBeNull] string cultureName); + Task GetOrNullAsync([CanBeNull] string cultureName); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 4049a3861e..b983c75f11 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -31,7 +31,7 @@ namespace Volo.Abp.TextTemplating foreach (var contributor in templateDefinition.Contributors) { - var templateString = contributor.GetOrNull(cultureName); //TODO: GetOrNull should be async! + var templateString = await contributor.GetOrNullAsync(cultureName); if (templateString != null) { return templateString; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index f1a4700da7..ae4b8c92f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -30,7 +31,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles TemplateDefinition = context.TemplateDefinition; } - public string GetOrNull([CanBeNull] string cultureName = null) + public async Task GetOrNullAsync([CanBeNull] string cultureName = null) { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 2c104b632d..1ff2596342 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System.Threading.Tasks; +using Shouldly; using Xunit; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -6,7 +7,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase { [Fact] - public void Should_Get_Localized_Content_By_Culture() + public async Task Should_Get_Localized_Content_By_Culture() { var contributor = new VirtualFileTemplateContributor( "/SampleTemplates/WelcomeEmail" @@ -19,17 +20,15 @@ namespace Volo.Abp.TextTemplating.VirtualFiles ) ); - contributor - .GetOrNull("en") - .ShouldBe("Welcome {{model.name}} to the abp.io!"); + (await contributor + .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); - contributor - .GetOrNull("tr") - .ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); + (await contributor + .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); } [Fact] - public void Should_Get_Non_Localized_Template_Content() + public async Task Should_Get_Non_Localized_Template_Content() { var contributor = new VirtualFileTemplateContributor( "/SampleTemplates/ForgotPasswordEmail.tpl" @@ -42,9 +41,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles ) ); - contributor - .GetOrNull() - .ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); + (await contributor + .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); } } } From 1950a8dcc5f1f7e86b6b3434bf5ad77939e2ac09 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 23 Apr 2020 23:50:11 +0300 Subject: [PATCH 25/80] TemplateRenderer null checking updated --- .../Volo/Abp/TextTemplating/TemplateRenderer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 558104e3a4..8b67a380f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -35,10 +35,7 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } + cultureName ??= CultureInfo.CurrentUICulture.Name; using (CultureHelper.Use(cultureName)) { From 0e519b765742e269e0dfff7d5c946bfc2775b412 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Fri, 24 Apr 2020 00:13:43 +0300 Subject: [PATCH 26/80] null checking updated --- .../VirtualFiles/VirtualFileTemplateContributor.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs index ae4b8c92f0..eded060a2e 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs @@ -35,10 +35,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } + cultureName ??= CultureInfo.CurrentUICulture.Name; var dictionary = GetTemplateDictionary(); From 4437bf7ec0e467097312addea99997e726ceb9ac Mon Sep 17 00:00:00 2001 From: Ahmet Date: Fri, 24 Apr 2020 01:01:06 +0300 Subject: [PATCH 27/80] renamed some properties. --- .../Abp/TextTemplating/TemplateDefinitionContext.cs | 12 ++++++------ .../Abp/TextTemplating/TemplateDefinitionManager.cs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index 39da2f5e88..59fe7fced1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -5,21 +5,21 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionContext : ITemplateDefinitionContext { - protected Dictionary EmailTemplates { get; } + protected Dictionary TextTemplates { get; } - public TemplateDefinitionContext(Dictionary emailTemplates) + public TemplateDefinitionContext(Dictionary textTemplates) { - EmailTemplates = emailTemplates; + TextTemplates = textTemplates; } public virtual TemplateDefinition GetOrNull(string name) { - return EmailTemplates.GetOrDefault(name); + return TextTemplates.GetOrDefault(name); } public virtual IReadOnlyList GetAll() { - return EmailTemplates.Values.ToImmutableList(); + return TextTemplates.Values.ToImmutableList(); } public virtual void Add(params TemplateDefinition[] definitions) @@ -31,7 +31,7 @@ namespace Volo.Abp.TextTemplating foreach (var definition in definitions) { - EmailTemplates[definition.Name] = definition; + TextTemplates[definition.Name] = definition; } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 534010dbe2..657e227e9c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency { - protected Lazy> EmailTemplateDefinitions { get; } + protected Lazy> TemplateDefinitions { get; } protected AbpTextTemplatingOptions Options { get; } @@ -23,7 +23,7 @@ namespace Volo.Abp.TextTemplating ServiceProvider = serviceProvider; Options = options.Value; - EmailTemplateDefinitions = + TemplateDefinitions = new Lazy>(CreateEmailTemplateDefinitions, true); } @@ -43,12 +43,12 @@ namespace Volo.Abp.TextTemplating public virtual IReadOnlyList GetAll() { - return EmailTemplateDefinitions.Value.Values.ToImmutableList(); + return TemplateDefinitions.Value.Values.ToImmutableList(); } public virtual TemplateDefinition GetOrNull(string name) { - return EmailTemplateDefinitions.Value.GetOrDefault(name); + return TemplateDefinitions.Value.GetOrDefault(name); } protected virtual IDictionary CreateEmailTemplateDefinitions() From e1990d1a3f2fb6d6e38b01fc73ec997fa5df5cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 16:57:45 +0300 Subject: [PATCH 28/80] rename template contributor to template content contributor --- .../Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs | 4 ++-- ...plateContributor.cs => ITemplateContentContributor.cs} | 4 ++-- ...=> TemplateContentContributorInitializationContext.cs} | 4 ++-- ...ntributorList.cs => TemplateContentContributorList.cs} | 2 +- .../Volo/Abp/TextTemplating/TemplateContentProvider.cs | 2 +- .../Volo/Abp/TextTemplating/TemplateDefinition.cs | 8 ++++---- .../Abp/TextTemplating/TemplateDefinitionExtensions.cs | 2 +- ...ibutor.cs => VirtualFileTemplateContentContributor.cs} | 6 +++--- .../VirtualFiles/VirtualFileTemplateContributor_Tests.cs | 8 ++++---- 9 files changed, 20 insertions(+), 20 deletions(-) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{ITemplateContributor.cs => ITemplateContentContributor.cs} (57%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{TemplateContributorInitializationContext.cs => TemplateContentContributorInitializationContext.cs} (81%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/{TemplateContributorList.cs => TemplateContentContributorList.cs} (52%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{VirtualFileTemplateContributor.cs => VirtualFileTemplateContentContributor.cs} (93%) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index e49f271c1c..831de9c1c4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -44,12 +44,12 @@ namespace Volo.Abp.TextTemplating foreach (var templateDefinition in templateDefinitionManager.GetAll()) { - var contributorInitializationContext = new TemplateContributorInitializationContext( + var contributorInitializationContext = new TemplateContentContributorInitializationContext( templateDefinition, scope.ServiceProvider ); - foreach (var contributor in templateDefinition.Contributors) + foreach (var contributor in templateDefinition.ContentContributors) { contributor.Initialize(contributorInitializationContext); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs similarity index 57% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs index 2174a65bcd..26aef67e94 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs @@ -3,9 +3,9 @@ using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { - public interface ITemplateContributor + public interface ITemplateContentContributor { - void Initialize(TemplateContributorInitializationContext context); + void Initialize(TemplateContentContributorInitializationContext context); Task GetOrNullAsync([CanBeNull] string cultureName); } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs similarity index 81% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs index ffe97a3484..523f7a6edf 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { - public class TemplateContributorInitializationContext + public class TemplateContentContributorInitializationContext { [NotNull] public TemplateDefinition TemplateDefinition { get; } @@ -11,7 +11,7 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - public TemplateContributorInitializationContext( + public TemplateContentContributorInitializationContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider) { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs similarity index 52% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs index e737897dae..3c67a03dcc 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContributorList.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.TextTemplating { - public class TemplateContributorList : List + public class TemplateContentContributorList : List { } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index b983c75f11..7c7031c980 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in templateDefinition.Contributors) + foreach (var contributor in templateDefinition.ContentContributors) { var templateString = await contributor.GetOrNullAsync(cultureName); if (templateString != null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 09de7bf458..4c4703d5e3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public TemplateContributorList Contributors { get; } + public TemplateContentContributorList ContentContributors { get; } [CanBeNull] public string DefaultCultureName { get; } @@ -30,15 +30,15 @@ namespace Volo.Abp.TextTemplating { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; - Contributors = new TemplateContributorList(); + ContentContributors = new TemplateContentContributorList(); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; } - public virtual TemplateDefinition WithContributor(ITemplateContributor contributor) + public virtual TemplateDefinition WithContributor(ITemplateContentContributor contentContributor) { - Contributors.Add(contributor); + ContentContributors.Add(contentContributor); return this; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index 9bd932c947..c9a644fedc 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.TextTemplating Check.NotNull(templateDefinition, nameof(templateDefinition)); return templateDefinition.WithContributor( - new VirtualFileTemplateContributor(virtualPath) + new VirtualFileTemplateContentContributor(virtualPath) ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs similarity index 93% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index eded060a2e..5a6958e29b 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -10,7 +10,7 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContributor : ITemplateContributor + public class VirtualFileTemplateContentContributor : ITemplateContentContributor { public TemplateDefinition TemplateDefinition { get; private set; } @@ -19,13 +19,13 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private volatile Dictionary _templateDictionary; private readonly object _syncObj = new object(); - public VirtualFileTemplateContributor( + public VirtualFileTemplateContentContributor( [NotNull] string virtualPath) { _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); } - public void Initialize(TemplateContributorInitializationContext context) + public void Initialize(TemplateContentContributorInitializationContext context) { _virtualFileProvider = context.ServiceProvider.GetRequiredService(); TemplateDefinition = context.TemplateDefinition; diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 1ff2596342..0bf54a5bee 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -9,12 +9,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles [Fact] public async Task Should_Get_Localized_Content_By_Culture() { - var contributor = new VirtualFileTemplateContributor( + var contributor = new VirtualFileTemplateContentContributor( "/SampleTemplates/WelcomeEmail" ); contributor.Initialize( - new TemplateContributorInitializationContext( + new TemplateContentContributorInitializationContext( new TemplateDefinition("Test"), ServiceProvider ) @@ -30,12 +30,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles [Fact] public async Task Should_Get_Non_Localized_Template_Content() { - var contributor = new VirtualFileTemplateContributor( + var contributor = new VirtualFileTemplateContentContributor( "/SampleTemplates/ForgotPasswordEmail.tpl" ); contributor.Initialize( - new TemplateContributorInitializationContext( + new TemplateContentContributorInitializationContext( new TemplateDefinition("Test"), ServiceProvider ) From f3d82a3807d0e834c4298cdf5cc3fd33f971e834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 17:24:18 +0300 Subject: [PATCH 29/80] Refactor text template contribution system. --- .../TextTemplating/AbpTextTemplatingModule.cs | 34 ++------ .../AbpTextTemplatingOptions.cs | 2 + .../ITemplateContentContributor.cs | 5 +- .../TemplateContentContributorContext.cs | 27 ++++++ ...ContentContributorInitializationContext.cs | 13 --- .../TextTemplating/TemplateContentProvider.cs | 45 ++++++++-- .../Abp/TextTemplating/TemplateDefinition.cs | 34 ++++++-- .../TemplateDefinitionExtensions.cs | 5 +- .../VirtualFileTemplateContentContributor.cs | 84 ++++++++----------- .../VirtualFileTemplateContributor_Tests.cs | 71 ++++++++-------- 10 files changed, 176 insertions(+), 144 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 831de9c1c4..aca0ac8f53 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -13,12 +13,13 @@ namespace Volo.Abp.TextTemplating { public override void PreConfigureServices(ServiceConfigurationContext context) { - AutoAddDefinitionProviders(context.Services); + AutoAddProvidersAndContributors(context.Services); } - private static void AutoAddDefinitionProviders(IServiceCollection services) + private static void AutoAddProvidersAndContributors(IServiceCollection services) { var definitionProviders = new List(); + var contentContributors = new List(); services.OnRegistred(context => { @@ -26,35 +27,18 @@ namespace Volo.Abp.TextTemplating { definitionProviders.Add(context.ImplementationType); } + + if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType)) + { + contentContributors.Add(context.ImplementationType); + } }); services.Configure(options => { options.DefinitionProviders.AddIfNotContains(definitionProviders); + options.ContentContributors.AddIfNotContains(contentContributors); }); } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - //TODO: Consider to move to the TemplateContentProvider and invoke lazy (with making it singleton) - using (var scope = context.ServiceProvider.CreateScope()) - { - var templateDefinitionManager = scope.ServiceProvider - .GetRequiredService(); - - foreach (var templateDefinition in templateDefinitionManager.GetAll()) - { - var contributorInitializationContext = new TemplateContentContributorInitializationContext( - templateDefinition, - scope.ServiceProvider - ); - - foreach (var contributor in templateDefinition.ContentContributors) - { - contributor.Initialize(contributorInitializationContext); - } - } - } - } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs index a79b66e255..b217094974 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -5,10 +5,12 @@ namespace Volo.Abp.TextTemplating public class AbpTextTemplatingOptions { public ITypeList DefinitionProviders { get; } + public ITypeList ContentContributors { get; } public AbpTextTemplatingOptions() { DefinitionProviders = new TypeList(); + ContentContributors = new TypeList(); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs index 26aef67e94..746b29a2f0 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs @@ -1,12 +1,9 @@ using System.Threading.Tasks; -using JetBrains.Annotations; namespace Volo.Abp.TextTemplating { public interface ITemplateContentContributor { - void Initialize(TemplateContentContributorInitializationContext context); - - Task GetOrNullAsync([CanBeNull] string cultureName); + Task GetOrNullAsync(TemplateContentContributorContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs new file mode 100644 index 0000000000..773bf1a0a4 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -0,0 +1,27 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentContributorContext + { + [NotNull] + public TemplateDefinition TemplateDefinition { get; } + + [NotNull] + public IServiceProvider ServiceProvider { get; } + + [CanBeNull] + public string Culture { get; } + + public TemplateContentContributorContext( + [NotNull] TemplateDefinition templateDefinition, + [NotNull] IServiceProvider serviceProvider, + [CanBeNull] string culture) + { + TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + Culture = culture; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs index 523f7a6edf..bf6ea65909 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs @@ -5,18 +5,5 @@ namespace Volo.Abp.TextTemplating { public class TemplateContentContributorInitializationContext { - [NotNull] - public TemplateDefinition TemplateDefinition { get; } - - [NotNull] - public IServiceProvider ServiceProvider { get; } - - public TemplateContentContributorInitializationContext( - [NotNull] TemplateDefinition templateDefinition, - [NotNull] IServiceProvider serviceProvider) - { - TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); - ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 7c7031c980..d7d7982f16 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,17 +1,25 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating { public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency { + public IHybridServiceScopeFactory ServiceScopeFactory { get; } + public AbpTextTemplatingOptions Options { get; } private readonly ITemplateDefinitionManager _templateDefinitionManager; public TemplateContentProvider( - ITemplateDefinitionManager templateDefinitionManager - ) + ITemplateDefinitionManager templateDefinitionManager, + IHybridServiceScopeFactory serviceScopeFactory, + IOptions options) { + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; _templateDefinitionManager = templateDefinitionManager; } @@ -29,17 +37,36 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - foreach (var contributor in templateDefinition.ContentContributors) + if (!Options.ContentContributors.Any()) { - var templateString = await contributor.GetOrNullAsync(cultureName); - if (templateString != null) + throw new AbpException( + $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" + ); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var context = new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ); + + foreach (var contentContributorType in Options.ContentContributors) { - return templateString; + var contributor = (ITemplateContentContributor) scope.ServiceProvider + .GetRequiredService(contentContributorType); + + var templateString = await contributor.GetOrNullAsync(context); + if (templateString != null) + { + return templateString; + } } } - + throw new AbpException( - $"None of the template contributors could get the content for the template '{templateDefinition.Name}'" + $"None of the template content contributors could get the content for the template '{templateDefinition.Name}'" ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 4c4703d5e3..d165eb4d31 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using JetBrains.Annotations; namespace Volo.Abp.TextTemplating @@ -16,11 +17,30 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public TemplateContentContributorList ContentContributors { get; } - [CanBeNull] public string DefaultCultureName { get; } + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + [CanBeNull] + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + public TemplateDefinition( [NotNull] string name, [CanBeNull] Type localizationResource = null, @@ -30,15 +50,19 @@ namespace Volo.Abp.TextTemplating { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); LocalizationResource = localizationResource; - ContentContributors = new TemplateContentContributorList(); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; + Properties = new Dictionary(); } - public virtual TemplateDefinition WithContributor(ITemplateContentContributor contentContributor) + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual TemplateDefinition WithProperty(string key, object value) { - ContentContributors.Add(contentContributor); + Properties[key] = value; return this; } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index c9a644fedc..ef4a0ac9a3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -17,8 +17,9 @@ namespace Volo.Abp.TextTemplating { Check.NotNull(templateDefinition, nameof(templateDefinition)); - return templateDefinition.WithContributor( - new VirtualFileTemplateContentContributor(virtualPath) + return templateDefinition.WithProperty( + VirtualFileTemplateContentContributor.VirtualPathPropertyName, + virtualPath ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 5a6958e29b..6d373dfec1 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -2,42 +2,38 @@ using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Volo.Abp.DependencyInjection; using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContentContributor : ITemplateContentContributor + public class VirtualFileTemplateContentContributor : ITemplateContentContributor, ITransientDependency { - public TemplateDefinition TemplateDefinition { get; private set; } + public const string VirtualPathPropertyName = "VirtualPath"; - private readonly string _virtualPath; - private IVirtualFileProvider _virtualFileProvider; - private volatile Dictionary _templateDictionary; - private readonly object _syncObj = new object(); + private readonly IVirtualFileProvider _virtualFileProvider; - public VirtualFileTemplateContentContributor( - [NotNull] string virtualPath) + public VirtualFileTemplateContentContributor(IVirtualFileProvider virtualFileProvider) { - _virtualPath = Check.NotNullOrWhiteSpace(virtualPath, nameof(virtualPath)); + _virtualFileProvider = virtualFileProvider; } - public void Initialize(TemplateContentContributorInitializationContext context) + public async Task GetOrNullAsync(TemplateContentContributorContext context) { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - TemplateDefinition = context.TemplateDefinition; - } + var virtualPath = context.TemplateDefinition.Properties.GetOrDefault(VirtualPathPropertyName) as string; + if (virtualPath == null) + { + return null; + } - public async Task GetOrNullAsync([CanBeNull] string cultureName = null) - { //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - cultureName ??= CultureInfo.CurrentUICulture.Name; + var cultureName = context.Culture ?? + CultureInfo.CurrentUICulture.Name; - var dictionary = GetTemplateDictionary(); + var dictionary = GetTemplateDictionary(virtualPath); var content = dictionary.GetOrDefault(cultureName); if (content != null) @@ -55,9 +51,9 @@ namespace Volo.Abp.TextTemplating.VirtualFiles } } - if (TemplateDefinition.DefaultCultureName != null) + if (context.TemplateDefinition.DefaultCultureName != null) { - content = dictionary.GetOrDefault(TemplateDefinition.DefaultCultureName); + content = dictionary.GetOrDefault(context.TemplateDefinition.DefaultCultureName); if (content != null) { return content; @@ -67,45 +63,31 @@ namespace Volo.Abp.TextTemplating.VirtualFiles return dictionary.GetOrDefault("__default"); } - private Dictionary GetTemplateDictionary() + private Dictionary GetTemplateDictionary(string virtualPath) { - if (_templateDictionary != null) + var dictionary = new Dictionary(); + + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.IsDirectory) { - return _templateDictionary; + //TODO: __default to consts + dictionary.Add("__default", fileInfo.ReadAsString()); } - - lock (_syncObj) + else { - if (_templateDictionary != null) - { - return _templateDictionary; - } - - var dictionary = new Dictionary(); - - var fileInfo = _virtualFileProvider.GetFileInfo(_virtualPath); - if (!fileInfo.IsDirectory) - { - //TODO: __default to consts - dictionary.Add("__default", fileInfo.ReadAsString()); - } - else + foreach (var file in _virtualFileProvider.GetDirectoryContents(virtualPath)) { - foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) + if (file.IsDirectory) { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + continue; } - } - _templateDictionary = dictionary; - return dictionary; + // TODO: How to normalize file names? + dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + } } + + return dictionary; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 0bf54a5bee..da2d4179de 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -4,45 +4,46 @@ using Xunit; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase - { - [Fact] - public async Task Should_Get_Localized_Content_By_Culture() - { - var contributor = new VirtualFileTemplateContentContributor( - "/SampleTemplates/WelcomeEmail" - ); + //TODO: Make tests running again! + //public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase + //{ + // [Fact] + // public async Task Should_Get_Localized_Content_By_Culture() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/WelcomeEmail" + // ); - contributor.Initialize( - new TemplateContentContributorInitializationContext( - new TemplateDefinition("Test"), - ServiceProvider - ) - ); + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); - (await contributor - .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); + // (await contributor + // .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); - (await contributor - .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); - } + // (await contributor + // .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); + // } - [Fact] - public async Task Should_Get_Non_Localized_Template_Content() - { - var contributor = new VirtualFileTemplateContentContributor( - "/SampleTemplates/ForgotPasswordEmail.tpl" - ); + // [Fact] + // public async Task Should_Get_Non_Localized_Template_Content() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/ForgotPasswordEmail.tpl" + // ); - contributor.Initialize( - new TemplateContentContributorInitializationContext( - new TemplateDefinition("Test"), - ServiceProvider - ) - ); + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); - (await contributor - .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); - } - } + // (await contributor + // .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); + // } + //} } From 1dde0dbe2352bcd29795b704d5fd7c0dc0150fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 17:31:36 +0300 Subject: [PATCH 30/80] Added PreDefine and PostDefine methods for the ITemplateDefinitionProvider --- .../TextTemplating/ITemplateDefinitionContext.cs | 6 +++++- .../TextTemplating/ITemplateDefinitionProvider.cs | 4 ++++ .../TextTemplating/TemplateDefinitionContext.cs | 5 +++++ .../TextTemplating/TemplateDefinitionManager.cs | 14 +++++++++++++- .../TextTemplating/TemplateDefinitionProvider.cs | 10 ++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs index 18402f8f9d..515cee74ca 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs @@ -1,7 +1,11 @@ -namespace Volo.Abp.TextTemplating +using System.Collections.Generic; + +namespace Volo.Abp.TextTemplating { public interface ITemplateDefinitionContext { + IReadOnlyList GetAll(string name); + TemplateDefinition GetOrNull(string name); void Add(params TemplateDefinition[] definitions); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs index a9e5155d0c..aeb6a3c2da 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs @@ -2,6 +2,10 @@ { public interface ITemplateDefinitionProvider { + void PreDefine(ITemplateDefinitionContext context); + void Define(ITemplateDefinitionContext context); + + void PostDefine(ITemplateDefinitionContext context); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index 59fe7fced1..d0911f21ac 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -12,6 +12,11 @@ namespace Volo.Abp.TextTemplating TextTemplates = textTemplates; } + public IReadOnlyList GetAll(string name) + { + return TextTemplates.Values.ToImmutableList(); + } + public virtual TemplateDefinition GetOrNull(string name) { return TextTemplates.GetOrDefault(name); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 657e227e9c..224b220396 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -62,9 +62,21 @@ namespace Volo.Abp.TextTemplating .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) .ToList(); + var context = new TemplateDefinitionContext(templates); + + foreach (var provider in providers) + { + provider.PreDefine(context); + } + + foreach (var provider in providers) + { + provider.Define(context); + } + foreach (var provider in providers) { - provider.Define(new TemplateDefinitionContext(templates)); + provider.PostDefine(context); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs index 9afc22a8b8..924a6dfdd8 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs @@ -4,6 +4,16 @@ namespace Volo.Abp.TextTemplating { public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency { + public virtual void PreDefine(ITemplateDefinitionContext context) + { + + } + public abstract void Define(ITemplateDefinitionContext context); + + public virtual void PostDefine(ITemplateDefinitionContext context) + { + + } } } \ No newline at end of file From 8af5a54ad2b3f74a9883e3f6d59366c5b7777a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:42:57 +0300 Subject: [PATCH 31/80] Refactored VirtualFileTemplateContentContributor --- .../FolderLocalizedTemplateContentReader.cs | 65 ++++++++++++++ .../ILocalizedTemplateContentReader.cs | 7 ++ .../NullLocalizedTemplateContentReader.cs | 19 +++++ ...ingleFileLocalizedTemplateContentReader.cs | 20 +++++ .../VirtualFileTemplateContentContributor.cs | 85 +++++++------------ .../FileProviders/AbpFileInfoExtensions.cs | 24 ++++++ 6 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..65bb2423a8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class FolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private Dictionary _dictionary; + + public async Task ReadContentsAsync(IVirtualFileProvider virtualFileProvider, string virtualPath) + { + _dictionary = new Dictionary(); + + var directoryInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!directoryInfo.IsDirectory) + { + throw new AbpException("Given virtual path is not a folder: " + virtualPath); + } + + foreach (var file in virtualFileProvider.GetDirectoryContents(virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + _dictionary.Add(file.Name.RemovePostFix(".tpl"), await file.ReadAsStringAsync()); + } + } + + public string GetContent(string cultureName, string defaultCultureName) + { + var content = _dictionary.GetOrDefault(cultureName); + if (content != null) + { + return content; + } + + if (cultureName.Contains("-")) + { + var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); + content = _dictionary.GetOrDefault(baseCultureName); + if (content != null) + { + return content; + } + } + + if (defaultCultureName != null) + { + content = _dictionary.GetOrDefault(defaultCultureName); + if (content != null) + { + return content; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..0b028138cd --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReader + { + public string GetContent(string culture, string defaultCultureName = null); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..0578a716ae --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class NullLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + public static NullLocalizedTemplateContentReader Instance { get; } = new NullLocalizedTemplateContentReader(); + + private NullLocalizedTemplateContentReader() + { + + } + + public string GetContent( + string culture, + string defaultCultureName = null) + { + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..23e4bfc14a --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class SingleFileLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private string _content; + + public async Task ReadContentsAsync(IFileInfo fileInfo) + { + _content = await fileInfo.ReadAsStringAsync(); + } + + public string GetContent(string culture, string defaultCultureName) + { + return _content; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 6d373dfec1..dea4920447 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; -using Microsoft.Extensions.FileProviders; using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -20,74 +17,50 @@ namespace Volo.Abp.TextTemplating.VirtualFiles _virtualFileProvider = virtualFileProvider; } - public async Task GetOrNullAsync(TemplateContentContributorContext context) + public virtual async Task GetOrNullAsync(TemplateContentContributorContext context) { - var virtualPath = context.TemplateDefinition.Properties.GetOrDefault(VirtualPathPropertyName) as string; - if (virtualPath == null) - { - return null; - } - - //TODO: Refactor: Split implementation based on single file or dictionary of culture-specific contents - var cultureName = context.Culture ?? CultureInfo.CurrentUICulture.Name; - var dictionary = GetTemplateDictionary(virtualPath); + var localizedReader = await CreateLocalizedReader(context); - var content = dictionary.GetOrDefault(cultureName); - if (content != null) - { - return content; - } + return localizedReader.GetContent( + cultureName, + context.TemplateDefinition.DefaultCultureName + ); + } + + protected async Task CreateLocalizedReader( + TemplateContentContributorContext context) + { + var virtualPath = context + .TemplateDefinition + .Properties + .GetOrDefault(VirtualPathPropertyName) as string; - if (cultureName.Contains("-")) + if (virtualPath == null) { - var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); - content = dictionary.GetOrDefault(baseCultureName); - if (content != null) - { - return content; - } + return NullLocalizedTemplateContentReader.Instance; } - if (context.TemplateDefinition.DefaultCultureName != null) + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) { - content = dictionary.GetOrDefault(context.TemplateDefinition.DefaultCultureName); - if (content != null) - { - return content; - } + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); } - return dictionary.GetOrDefault("__default"); - } - - private Dictionary GetTemplateDictionary(string virtualPath) - { - var dictionary = new Dictionary(); - - var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); - if (!fileInfo.IsDirectory) + if (fileInfo.IsDirectory) { - //TODO: __default to consts - dictionary.Add("__default", fileInfo.ReadAsString()); + var folderReader = new FolderLocalizedTemplateContentReader(); + await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); + return folderReader; } - else + else //File { - foreach (var file in _virtualFileProvider.GetDirectoryContents(virtualPath)) - { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - dictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); - } + var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + return singleFileReader; } - - return dictionary; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs index 7d3505d616..8a6d7d57a1 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs @@ -18,6 +18,14 @@ namespace Microsoft.Extensions.FileProviders return fileInfo.ReadAsString(Encoding.UTF8); } + /// + /// Reads file content as string using encoding. + /// + public static Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo) + { + return fileInfo.ReadAsStringAsync(Encoding.UTF8); + } + /// /// Reads file content as string using the given . /// @@ -34,6 +42,22 @@ namespace Microsoft.Extensions.FileProviders } } + /// + /// Reads file content as string using the given . + /// + public static async Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo, Encoding encoding) + { + Check.NotNull(fileInfo, nameof(fileInfo)); + + using (var stream = fileInfo.CreateReadStream()) + { + using (var streamReader = new StreamReader(stream, encoding, true)) + { + return await streamReader.ReadToEndAsync(); + } + } + } + /// /// Reads file content as byte[]. /// From a29cdda2c53de46579e4044b8a3ee5b4c33d17e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:51:51 +0300 Subject: [PATCH 32/80] Extract LocalizedTemplateContentReaderFactory --- .../ILocalizedTemplateContentReaderFactory.cs | 9 ++++ .../LocalizedTemplateContentReaderFactory.cs | 54 +++++++++++++++++++ .../VirtualFileTemplateContentContributor.cs | 51 +++--------------- 3 files changed, 70 insertions(+), 44 deletions(-) create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs create mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..2d4d9ef2d5 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReaderFactory + { + Task CreateAsync(TemplateDefinition templateDefinition); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..41527c4e11 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency + { + private readonly IVirtualFileProvider _virtualFileProvider; + + public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) + { + _virtualFileProvider = virtualFileProvider; + } + + public Task CreateAsync(TemplateDefinition templateDefinition) + { + return CreateLocalizedReader(templateDefinition); + } + + protected async Task CreateLocalizedReader( + TemplateDefinition templateDefinition) + { + var virtualPath = templateDefinition + .Properties + .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; + + if (virtualPath == null) + { + return NullLocalizedTemplateContentReader.Instance; + } + + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + if (fileInfo.IsDirectory) + { + var folderReader = new FolderLocalizedTemplateContentReader(); + await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); + return folderReader; + } + else //File + { + var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + return singleFileReader; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index dea4920447..1240352bd2 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { @@ -10,57 +8,22 @@ namespace Volo.Abp.TextTemplating.VirtualFiles { public const string VirtualPathPropertyName = "VirtualPath"; - private readonly IVirtualFileProvider _virtualFileProvider; + private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; - public VirtualFileTemplateContentContributor(IVirtualFileProvider virtualFileProvider) + public VirtualFileTemplateContentContributor(ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) { - _virtualFileProvider = virtualFileProvider; + _localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; } public virtual async Task GetOrNullAsync(TemplateContentContributorContext context) { - var cultureName = context.Culture ?? - CultureInfo.CurrentUICulture.Name; - - var localizedReader = await CreateLocalizedReader(context); + var localizedReader = await _localizedTemplateContentReaderFactory + .CreateAsync(context.TemplateDefinition); return localizedReader.GetContent( - cultureName, + context.Culture ?? CultureInfo.CurrentUICulture.Name, context.TemplateDefinition.DefaultCultureName ); } - - protected async Task CreateLocalizedReader( - TemplateContentContributorContext context) - { - var virtualPath = context - .TemplateDefinition - .Properties - .GetOrDefault(VirtualPathPropertyName) as string; - - if (virtualPath == null) - { - return NullLocalizedTemplateContentReader.Instance; - } - - var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); - if (!fileInfo.Exists) - { - throw new AbpException("Could not find a file/folder at the location: " + virtualPath); - } - - if (fileInfo.IsDirectory) - { - var folderReader = new FolderLocalizedTemplateContentReader(); - await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); - return folderReader; - } - else //File - { - var singleFileReader = new SingleFileLocalizedTemplateContentReader(); - await singleFileReader.ReadContentsAsync(fileInfo); - return singleFileReader; - } - } } } \ No newline at end of file From 15ce08853d881694f5a5c2f470e8f6da7dc636ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 18:53:39 +0300 Subject: [PATCH 33/80] Rename template content readers. --- ...tReader.cs => FileInfoLocalizedTemplateContentReader.cs} | 2 +- .../VirtualFiles/LocalizedTemplateContentReaderFactory.cs | 4 ++-- .../VirtualFiles/VirtualFileTemplateContentContributor.cs | 3 ++- ...er.cs => VirtualFolderLocalizedTemplateContentReader.cs} | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{SingleFileLocalizedTemplateContentReader.cs => FileInfoLocalizedTemplateContentReader.cs} (82%) rename framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/{FolderLocalizedTemplateContentReader.cs => VirtualFolderLocalizedTemplateContentReader.cs} (88%) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs similarity index 82% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 23e4bfc14a..4ff941fb84 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/SingleFileLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.FileProviders; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class SingleFileLocalizedTemplateContentReader : ILocalizedTemplateContentReader + public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader { private string _content; diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index 41527c4e11..b22a6fb7d5 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -39,13 +39,13 @@ namespace Volo.Abp.TextTemplating.VirtualFiles if (fileInfo.IsDirectory) { - var folderReader = new FolderLocalizedTemplateContentReader(); + var folderReader = new VirtualFolderLocalizedTemplateContentReader(); await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); return folderReader; } else //File { - var singleFileReader = new SingleFileLocalizedTemplateContentReader(); + var singleFileReader = new FileInfoLocalizedTemplateContentReader(); await singleFileReader.ReadContentsAsync(fileInfo); return singleFileReader; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 1240352bd2..e06336c658 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -10,7 +10,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; - public VirtualFileTemplateContentContributor(ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) + public VirtualFileTemplateContentContributor( + ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) { _localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs similarity index 88% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 65bb2423a8..0f2a23a2fa 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -7,11 +7,13 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles { - public class FolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader + public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader { private Dictionary _dictionary; - public async Task ReadContentsAsync(IVirtualFileProvider virtualFileProvider, string virtualPath) + public async Task ReadContentsAsync( + IVirtualFileProvider virtualFileProvider, + string virtualPath) { _dictionary = new Dictionary(); From f75e7baef6229e789d4b6a580bc1a159a32e5e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 19:04:16 +0300 Subject: [PATCH 34/80] Added caching for the LocalizedTemplateContentReaderFactory. --- .../LocalizedTemplateContentReaderFactory.cs | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index b22a6fb7d5..f4f8384777 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.VirtualFileSystem; @@ -8,18 +11,48 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency { private readonly IVirtualFileProvider _virtualFileProvider; + private readonly Dictionary _readerCache; + private readonly ReaderWriterLockSlim _lock; public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) { _virtualFileProvider = virtualFileProvider; + _readerCache = new Dictionary(); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); } - public Task CreateAsync(TemplateDefinition templateDefinition) + public async Task CreateAsync(TemplateDefinition templateDefinition) { - return CreateLocalizedReader(templateDefinition); + _lock.EnterUpgradeableReadLock(); + + try + { + var reader = _readerCache.GetOrDefault(templateDefinition.Name); + if (reader != null) + { + return reader; + } + + _lock.EnterWriteLock(); + + try + { + reader = await CreateInternalAsync(templateDefinition); + _readerCache[templateDefinition.Name] = reader; + return reader; + } + finally + { + _lock.ExitWriteLock(); + } + } + finally + { + _lock.ExitUpgradeableReadLock(); + } } - protected async Task CreateLocalizedReader( + protected virtual async Task CreateInternalAsync( TemplateDefinition templateDefinition) { var virtualPath = templateDefinition From da78d48b101dca381ad4a95ba9070f22fb60c065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 24 Apr 2020 19:17:10 +0300 Subject: [PATCH 35/80] Remove unused classes of the text template system --- .../TemplateContentContributorInitializationContext.cs | 9 --------- .../Abp/TextTemplating/TemplateContentContributorList.cs | 9 --------- 2 files changed, 18 deletions(-) delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs delete mode 100644 framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs deleted file mode 100644 index bf6ea65909..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorInitializationContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.TextTemplating -{ - public class TemplateContentContributorInitializationContext - { - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs deleted file mode 100644 index 3c67a03dcc..0000000000 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorList.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.TextTemplating -{ - public class TemplateContentContributorList : List - { - - } -} \ No newline at end of file From 518538705113a9c6ecacbbdecef9698592164b6b Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sat, 25 Apr 2020 19:44:40 +0800 Subject: [PATCH 36/80] Remove FormGroupItem that are not in the model --- .../Form/AbpDynamicformTagHelperService.cs | 12 +++++++++++- .../TagHelpers/Form/AbpInputTagHelperService.cs | 11 ++++++----- .../TagHelpers/Form/AbpRadioInputTagHelperService.cs | 5 +++-- .../TagHelpers/Form/AbpSelectTagHelperService.cs | 9 +++++---- .../TagHelpers/Form/FormGroupItem.cs | 2 ++ 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index 6f8ccf285a..a1601378d9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs @@ -43,6 +43,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form await ProcessFieldsAsync(context, output); + RemoveFormGroupItemsNotInModel(context, output, list); + SetContent(context, output, list, childContent); SetFormAttributes(context, output); @@ -143,6 +145,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form } } + + protected virtual void RemoveFormGroupItemsNotInModel(TagHelperContext context, TagHelperOutput output, List items) + { + var models = GetModels(context, output); + + items.RemoveAll(x => models.All(m => !m.Name.Equals(x.PropertyName, StringComparison.InvariantCultureIgnoreCase))); + } + protected virtual async Task ProcessSelectGroupAsync(TagHelperContext context, TagHelperOutput output, ModelExpression model) { var abpSelectTagHelper = GetSelectGroupTagHelper(context, output, model); @@ -152,7 +162,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual AbpTagHelper GetSelectGroupTagHelper(TagHelperContext context, TagHelperOutput output, ModelExpression model) { - return IsRadioGroup(model.ModelExplorer) ? + return IsRadioGroup(model.ModelExplorer) ? GetAbpRadioInputTagHelper(model) : GetSelectTagHelper(model); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 75fe4f23e7..470b60004e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -58,7 +58,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task<(string, bool)> GetFormInputGroupAsHtmlAsync(TagHelperContext context, TagHelperOutput output) { var (inputTag, isCheckBox) = await GetInputTagHelperOutputAsync(context, output); - + var inputHtml = inputTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, inputTag, isCheckBox); var info = GetInfoAsHtml(context, output, inputTag, isCheckBox); @@ -161,7 +161,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual void AddDisabledAttribute(TagHelperOutput inputTagHelperOutput) { - if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false && + if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false && (TagHelper.IsDisabled || TagHelper.AspFor.ModelExplorer.GetAttribute() != null)) { inputTagHelperOutput.Attributes.Add("disabled", ""); @@ -170,7 +170,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual void AddReadOnlyAttribute(TagHelperOutput inputTagHelperOutput) { - if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false && + if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false && (TagHelper.IsReadonly != false || TagHelper.AspFor.ModelExplorer.GetAttribute() != null)) { inputTagHelperOutput.Attributes.Add("readonly", ""); @@ -400,9 +400,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs index 6f71842b61..010cc1d075 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs @@ -167,9 +167,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs index b18bc6ed75..3ed4f63f82 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs @@ -125,7 +125,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return await GetLabelAsHtmlUsingTagHelperAsync(context, output) + GetRequiredSymbol(context, output); } - + protected virtual string GetRequiredSymbol(TagHelperContext context, TagHelperOutput output) { if (!TagHelper.DisplayRequiredSymbol) @@ -221,7 +221,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form } protected virtual List GetSelectItemsFromAttribute( - SelectItems selectItemsAttribute, + SelectItems selectItemsAttribute, ModelExplorer explorer) { var selectItems = selectItemsAttribute.GetItems(explorer)?.ToList(); @@ -329,9 +329,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs index 258ba544f8..e3d33a4359 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs @@ -5,5 +5,7 @@ public string HtmlContent { get; set; } public int Order { get; set; } + + public string PropertyName { get; set; } } } \ No newline at end of file From da046770940ceba0ea1ac5b0ff9f32ff19881a87 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Sat, 25 Apr 2020 14:46:12 +0300 Subject: [PATCH 37/80] feat: allow title, message, and buttons to have HTML string --- .../confirmation/confirmation.component.html | 26 ++++++++++--------- .../lib/tests/confirmation.service.spec.ts | 19 ++++++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html index f2b1417f14..a40d72adf4 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.html @@ -5,30 +5,32 @@
-

- {{ data.title | abpLocalization: data.options?.titleLocalizationParams }} -

-

- {{ data.message | abpLocalization: data.options?.messageLocalizationParams }} -

+

+

diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts index e43ff885f9..e70b88061f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts @@ -43,6 +43,25 @@ describe('ConfirmationService', () => { expect(selectConfirmationContent('.message')).toBe('MESSAGE'); })); + test('should display HTML string in title, message, and buttons', fakeAsync(() => { + service.show( + 'MESSAGE', + 'TITLE', + 'neutral', + { + cancelText: 'CANCEL', + yesText: 'YES', + }, + ); + + tick(); + + expect(selectConfirmationContent('.custom-title')).toBe('TITLE'); + expect(selectConfirmationContent('.custom-message')).toBe('MESSAGE'); + expect(selectConfirmationContent('.custom-cancel')).toBe('CANCEL'); + expect(selectConfirmationContent('.custom-yes')).toBe('YES'); + })); + test.each` type | selector | icon ${'info'} | ${'.info'} | ${'.fa-info-circle'} From 7a030e1714d2e38303a4d6ce8f449381c9c165ab Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Sat, 25 Apr 2020 14:58:44 +0300 Subject: [PATCH 38/80] docs: add how to pass HTML to confirmation dialog --- docs/en/UI/Angular/Confirmation-Service.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/en/UI/Angular/Confirmation-Service.md b/docs/en/UI/Angular/Confirmation-Service.md index a8d2c4c2a7..2d1c5c1843 100644 --- a/docs/en/UI/Angular/Confirmation-Service.md +++ b/docs/en/UI/Angular/Confirmation-Service.md @@ -94,6 +94,28 @@ With the options above, the confirmation popup looks like this: ![confirmation](./images/confirmation.png) +You are able to pass in an HTML string as title, message, or button texts. Here is an example: + +```js +const options: Partial = { + yesText: 'Yes, delete it', +}; + +this.confirmation.warn( + ` + Role Demo will be deleted +
+ Do you confirm that? + `, + 'Are you sure?', + options, +); +``` + +Since the values are HTML now, localization should be handled manually. Check out the [LocalizationService](./Localization#using-the-localization-service) to see how you can accomplish that. + +> Please note that all strings will be sanitized by Angular and not every HTML string will work. Only values that are considered as "safe" by Angular will be displayed. + ### How to Remove a Confirmation Popup The open confirmation popup can be removed manually via the `clear` method: From b3d5f1ae3dfbf2635b9ef3656ccf95e1cacc7668 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Sun, 26 Apr 2020 10:13:24 +0300 Subject: [PATCH 39/80] docs: replace addTailMany with addManyTail --- docs/en/UI/Common/Utils/Linked-List.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/en/UI/Common/Utils/Linked-List.md b/docs/en/UI/Common/Utils/Linked-List.md index 0d6b259603..0c047d28e5 100644 --- a/docs/en/UI/Common/Utils/Linked-List.md +++ b/docs/en/UI/Common/Utils/Linked-List.md @@ -1270,7 +1270,7 @@ find(predicate: ListIteratorFn): ListNode | undefined Finds the first node from the list that matches the given predicate: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1294,7 +1294,7 @@ findIndex(predicate: ListIteratorFn): number Finds the position of the first node from the list that matches the given predicate: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1322,7 +1322,7 @@ get(position: number): ListNode | undefined Finds and returns the node with specific position in the list: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1346,7 +1346,7 @@ indexOf(value: T, compareFn?: ListComparisonFn): number Finds the position of the first node from the list that has the given value: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1368,7 +1368,7 @@ i3 === -1 You may pass a custom compare function to detect the searched value: ```js -list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); +list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} @@ -1408,7 +1408,7 @@ forEach(iteratorFn: ListIteratorFn): void Runs a function on all nodes in a linked list from head to tail: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1425,7 +1425,7 @@ list.forEach((node, index) => console.log(node.value + index)); A linked list is iterable. In other words, you may use methods like `for...of` on it. ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1449,7 +1449,7 @@ toArray(): T[] Converts a linked list to an array of values: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1471,7 +1471,7 @@ toNodeArray(): ListNode[] Converts a linked list to an array of nodes: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1495,7 +1495,7 @@ toString(mapperFn: ListMapperFn = JSON.stringify): string Converts a linked list to a string representation of nodes and their relations: ```js -list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); +list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]); // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} @@ -1562,7 +1562,7 @@ export class ListNode { - `previous` refers to the previous node in the list. ```js -list.addTailMany([ 0, 1, 2 ]); +list.addManyTail([ 0, 1, 2 ]); console.log( list.head.value, // 0 From 53087de61d3cc10d64744a3f23c1101a9f571498 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 26 Apr 2020 22:11:45 +0800 Subject: [PATCH 40/80] Update document --- docs/en/UI/Angular/Toaster-Service.md | 1 + .../UI/Angular/Confirmation-Service.md | 159 ++++++++++++++++++ .../UI/Angular/Permission-Management.md | 2 +- docs/zh-Hans/UI/Angular/Toaster-Service.md | 155 +++++++++++++++++ .../UI/Angular/images/confirmation.png | Bin 0 -> 26919 bytes docs/zh-Hans/UI/Angular/images/toast.png | Bin 0 -> 20291 bytes docs/zh-Hans/docs-nav.json | 23 ++- 7 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 docs/zh-Hans/UI/Angular/Confirmation-Service.md create mode 100644 docs/zh-Hans/UI/Angular/Toaster-Service.md create mode 100644 docs/zh-Hans/UI/Angular/images/confirmation.png create mode 100644 docs/zh-Hans/UI/Angular/images/toast.png diff --git a/docs/en/UI/Angular/Toaster-Service.md b/docs/en/UI/Angular/Toaster-Service.md index 191f0b34fd..b194811d9a 100644 --- a/docs/en/UI/Angular/Toaster-Service.md +++ b/docs/en/UI/Angular/Toaster-Service.md @@ -150,6 +150,7 @@ clear(): void Removes all open toasts. ## See Also + - [Confirmation Popup](./Confirmation-Service.md) ## What's Next? diff --git a/docs/zh-Hans/UI/Angular/Confirmation-Service.md b/docs/zh-Hans/UI/Angular/Confirmation-Service.md new file mode 100644 index 0000000000..d6ff4bc3c5 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Confirmation-Service.md @@ -0,0 +1,159 @@ +# 确认弹层 + +你可以使用@abp/ng.theme.shared包中提供 `ConfirmationService` 放置在你项目的级别来显示确认弹层 + +## 入门 + +你不必在模块或组件级别提供 `ConfirmationService`,它已经在**根**级别提供,你可以在你的组件,指令或服务直接注入并使用它. + +```js +import { ConfirmationService } from '@abp/ng.theme.shared'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private confirmation: ConfirmationService) {} +} +``` + +## 用法 + +你可以使用 `ConfirmationService` 的 `success`, `warn`, `error` 和 `info` 方法显示一个确认弹层. + +### 如何显示一个确认弹层 + +```js +const confirmationStatus$ = this.confirmation.success('Message', 'Title'); +``` + +- `ConfirmationService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`. +- `success`, `warn`, `error`, 和 `info` 方法返回一个[RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject)监听确认弹层关闭事件. 事件值类型是 [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24)枚举. + +### 如何监听关闭事件 + +你可以订阅确认弹层关闭事件,例: + +```js +import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; + +constructor(private confirmation: ConfirmationService) {} + +this.confirmation + .warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' }) + .subscribe((status: Confirmation.Status) => { + // your code here + }); +``` + +- `message` 和 `title` 参数接收字符串,本地化Key或本地化对象. 参阅[本地化文档](./Localization.md) +- `Confirmation.Status` 是一个枚举,具有三个属性; + - `Confirmation.Status.confirm` 是一个关闭事件值,当通过确认按钮关闭弹出窗口时触发此事件. + - `Confirmation.Status.reject` 是一个关闭事件值,当通过“取消”按钮关闭弹出窗口时触发此事件. + - `Confirmation.Status.dismiss` 是一个关闭事件值,当通过按Escape键关闭弹出窗口时触发此事件. + +如果你对确认状态不感兴趣,则不必订阅返回的observable: + +```js +this.confirmation.error('You are not authorized.', 'Error'); +``` + +### 如何显示具有给定选项的确认弹层 + +选项可以作为第三个参数传递给`success`, `warn`, `error`, 和 `info` 方法: + +```js +const options: Partial = { + hideCancelBtn: false, + hideYesBtn: false, + cancelText: 'Close', + yesText: 'Confirm', + messageLocalizationParams: ['Demo'], + titleLocalizationParams: [], +}; + +this.confirmation.warn( + 'AbpIdentity::RoleDeletionConfirmationMessage', + 'Are you sure?', + options, +); +``` + +- `hideCancelBtn` 选项为 `true` 时隐藏取消按钮. 默认值为 `false`. +- `hideYesBtn` 选项为 `true` 时隐藏确认按钮. 默认值为 `false`. +- `cancelText` 是取消按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Cancel`. +- `yesText` 是确定按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Yes`. +- `messageLocalizationParams`是用于消息本地化的插值参数. +- `titleLocalizationParams` 是标题本地化的插值参数. + +With the options above, the confirmation popup looks like this: + +![confirmation](./images/confirmation.png) + +### 如何删除一个确认弹层 + +打开的确认弹出窗口可以通过 `clear` 方法手动删除: + +```js +this.confirmation.clear(); +``` + +## API + +### success + +```js +success( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +> 请参见[`Config.LocalizationParam`类型](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46)和[Confirmation名称空间](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts) + + +### warn + +```js +warn( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### error + +```js +error( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### info + +```js +info( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### clear + +```js +clear( + status: Confirmation.Status = Confirmation.Status.dismiss +): void +``` + +- `status` 参数是确认关闭事件的值. + + +## 下一步是什么? + +- [Toast Overlay](./Toaster-Service.md) diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 67ec1b787e..3a50da9591 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -76,4 +76,4 @@ const routes: Routes = [ ## 下一步是什么? -* [Config State](./Config-State.md) \ No newline at end of file +* [确认弹层](./Confirmation-Service.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Toaster-Service.md b/docs/zh-Hans/UI/Angular/Toaster-Service.md new file mode 100644 index 0000000000..045cf9f2cf --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Toaster-Service.md @@ -0,0 +1,155 @@ +# Toast Overlay + +你可以通常将@abp/ng.theme.shared包提供的 `ToasterService` 放置在你项目的根级别下以覆盖显示消息. + +## 入门 + +你不必在模块或组件级别提供 `ToasterService`,它已经在**根**级别提供,你可以在你的组件,指令或服务直接注入并使用它. + +```js +import { ToasterService } from '@abp/ng.theme.shared'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private toaster: ToasterService) {} +} +``` + +## 用法 + +你可以使用 `ToasterService` 的 `success`, `warn`, `error` 和 `info` 方法显示一个overlay. + +### 如何显示一个Toast Overlay + +```js +this.toast.success('Message', 'Title'); +``` + +- `ToasterService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`. +- `success`, `warn`, `error`, 和 `info` 方法返回一个已打开的 toast overlay Id. 可以使用此id删除toast. + +### 如何显示具有给定选项的Toast Overlay + +选项可以作为第三个参数传递给`success`, `warn`, `error`, 和 `info` 方法: + +```js +import { Toaster, ToasterService } from '@abp/ng.theme.shared'; +//... + +constructor(private toaster: ToasterService) {} + +//... +const options: Partial = { + life: 10000, + sticky: false, + closable: true, + tapToDismiss: true, + messageLocalizationParams: ['Demo', '1'], + titleLocalizationParams: [] + }; + + this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options); +``` + +- `life` 选项是关闭的时间毫秒数. 默认值是 `5000`. +- `sticky` 选项为 `true` 时忽略 `life` 选项,将toast overlay留在屏幕上. 默认值是 `false`. +- `closable` 选项为 `true` 时在toast overlay上显示关闭图标. 默认值是 `true`. +- `tapToDismiss` 选项为 `true` 允许通过单击关闭toast overlay. 默认值是 `false`. +- `yesText` 是确定按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Yes`. +- `messageLocalizationParams` 是用于消息本地化的插值参数. +- `titleLocalizationParams` 是标题本地化的插值参数. + +使用上面的选项,toast overlay看起来像这样: + +![toast](./images/toast.png) + +### 如何删除 Toast Overlay + +已打开的toast overlay可以通过手动调用 `remove` 方法传递指定的 toast `id`删除. + +```js +const toastId = this.toast.success('Message', 'Title') + +this.toast.remove(toastId); +``` + +### 如何删除所有的Toasts + +可以手动调用 `clear` 方法删除所有的已打开的toasts. + +```js +this.toast.clear(); +``` + +## API + +### success + +```js +success( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +- `Config` 命令空间可以从 `@abp/ng.core` 导入. +- `Toaster` 命令空间可以从 `@abp/ng.theme.shared` 导入. + +> 请参见[`Config.LocalizationParam`类型](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46)和[`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts) + +### warn + +```js +warn( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### error + +```js +error( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### info + +```js +info( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### remove + +```js +remove(id: number): void +``` + +按给定的id移除打开的toast. + +### clear + +```js +clear(): void +``` + +删除所有打开的toasts. + +## 另请参阅 + +- [Confirmation Popup](./Confirmation-Service.md) + +## 确认弹层? + +- [Config State](./Config-State.md) diff --git a/docs/zh-Hans/UI/Angular/images/confirmation.png b/docs/zh-Hans/UI/Angular/images/confirmation.png new file mode 100644 index 0000000000000000000000000000000000000000..efe4c98ea779b3798d21208966566711ec535629 GIT binary patch literal 26919 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 LZDb|F=GA`yG#y|> literal 0 HcmV?d00001 diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 384c406475..240c42dc99 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -218,8 +218,11 @@ }, { "text": "领域驱动设计", - "path": "Domain-Driven-Design.md", "items": [ + { + "text": "概述", + "path": "Domain-Driven-Design.md" + }, { "text": "领域层", "items": [ @@ -324,6 +327,14 @@ "text": "权限管理", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "确认弹层", + "path": "UI/Angular/Confirmation-Service.md" + }, + { + "text": "Toast Overlay", + "path": "UI/Angular/Toaster-Service.md" + }, { "text": "配置状态", "path": "UI/Angular/Config-State.md" @@ -372,8 +383,11 @@ }, { "text": "数据访问", - "path":"Data-Access.md", "items": [ + { + "text": "概述", + "path": "Data-Access.md" + }, { "text": "连接字符串", "path": "Connection-Strings.md" @@ -457,8 +471,11 @@ }, { "text": "启动模板", - "path": "Startup-Templates/Index.md", "items": [ + { + "text": "概述", + "path": "Startup-Templates/Index.md" + }, { "text": "应用程序", "path": "Startup-Templates/Application.md" From 7d7669aec7f538edecb97a291d4d733111285a90 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 27 Apr 2020 00:34:28 +0800 Subject: [PATCH 41/80] Replaced mvc with {{UI_Value}} in tutorials documentation --- docs/en/Tutorials/Part-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index e3f988779b..013d33cc29 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -38,7 +38,7 @@ Create a new project named `Acme.BookStore` where `Acme` is the company name and #### Create the project -By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document. +By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `{{UI_Value}}`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document. ```bash abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none From 6c3b15dddb92ae039e398a4f80b64af5b2fad65f Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 27 Apr 2020 02:37:08 +0300 Subject: [PATCH 42/80] Update en.json --- .../Admin/Localization/Resources/en.json | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index a5089e06f7..48d72f1d0e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -13,6 +13,8 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:Create": "Create", + "Permission:Accounting": "Accounting", + "Permission:Accounting:Quotation": "Quotation", "Menu:Organizations": "Organizations", "Menu:Accounting": "Accounting", "Menu:Packages": "Packages", @@ -94,13 +96,15 @@ "UsernameOrEmail": "Username or email", "UsernameOrEmailPlaceholder": "Username or email...", "Member": "Member", - "QuotationPurchasedOrderNo": "Quotation Purchased Order No", - "QuotationTime": "Quotation Time", - "CompanyName": "Company Name", - "CompanyAddress": "Company Address", + "PurchaseOrderNo": "Purchase order no", + "QuotationDate": "Quotation date", + "CompanyName": "Company name", + "CompanyAddress": "Company address", "Price": "Price", - "ExtraText": "Extra Text", - "ExtraAmount": "Extra Amount", - "DownloadQuotation": "Download Quotation" + "DiscountText": "Discount text", + "DiscountQuantity": "Discount quantity", + "DiscountPrice": "Discount price", + "Quotation": "Quotation", + "Generate": "Generate" } } \ No newline at end of file From caa676b5f89becdfdec25ff21efa0d87246fc0fb Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 27 Apr 2020 03:42:19 +0300 Subject: [PATCH 43/80] fixes #3731 --- .../Volo/Docs/Documents/DocumentAppService.cs | 2 - .../GitHub/Documents/GithubDocumentSource.cs | 144 ++++++++++++++---- 2 files changed, 115 insertions(+), 31 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 02a7c171bd..430cd9ead2 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -7,8 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Nest; -using Newtonsoft.Json; using Volo.Abp; using Volo.Abp.Caching; using Volo.Docs.Caching; diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index a6285389de..ef6f6e1ebb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -48,68 +48,155 @@ namespace Volo.Docs.GitHub.Documents fileName = documentName.Substring(documentName.LastIndexOf('/') + 1); } - var fileCommits = await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName)); - - var documentCreationTime = fileCommits.LastOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue; - - var lastSignificantUpdateTime = !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName ? - await GetLastSignificantUpdateTime( - fileCommits, - project, - project.GetGitHubInnerUrl(languageCode, documentName), - lastKnownSignificantUpdateTime, - documentCreationTime - ) ?? lastKnownSignificantUpdateTime - : null; + var content = await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent); + var commits = await GetGitHubCommitsOrNull(project, documentName, languageCode, version); + + var documentCreationTime = GetFirstCommitDate(commits); + var lastUpdateTime = GetLastCommitDate(commits); + var lastSignificantUpdateTime = await GetLastKnownSignificantUpdateTime(project, documentName, languageCode, version, lastKnownSignificantUpdateTime, isNavigationDocument, isParameterDocument, commits, documentCreationTime); - var document = new Document(GuidGenerator.Create(), + var document = new Document + ( + GuidGenerator.Create(), project.Id, documentName, version, languageCode, fileName, - await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent), + content, project.Format, editLink, rootUrl, rawRootUrl, localDirectory, documentCreationTime, - fileCommits.FirstOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue, + lastUpdateTime, DateTime.Now, - lastSignificantUpdateTime); + lastSignificantUpdateTime + ); + + if (isNavigationDocument || isParameterDocument) + { + return document; + } + + var authors = GetAuthors(commits); + foreach (var author in authors) + { + document.AddContributor(author.Login, author.HtmlUrl, author.AvatarUrl); + } - var authors = fileCommits + return document; + } + + private async Task GetLastKnownSignificantUpdateTime( + Project project, + string documentName, + string languageCode, + string version, + DateTime? lastKnownSignificantUpdateTime, + bool isNavigationDocument, + bool isParameterDocument, + IReadOnlyList commits, + DateTime documentCreationTime) + { + return !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName + ? await GetLastSignificantUpdateTime( + commits, + project, + project.GetGitHubInnerUrl(languageCode, documentName), + lastKnownSignificantUpdateTime, + documentCreationTime + ) ?? lastKnownSignificantUpdateTime + : null; + } + + private static List GetAuthors(IReadOnlyList commits) + { + if (commits == null || !commits.Any()) + { + return new List(); + } + + return commits .Where(x => x.Author != null) .Select(x => x.Author) .GroupBy(x => x.Id) .OrderByDescending(x => x.Count()) - .Select(x => x.FirstOrDefault()).ToList(); + .Select(x => x.FirstOrDefault()) + .ToList(); + } + + private static DateTime GetLastCommitDate(IReadOnlyList commits) + { + return GetCommitDate(commits, false); + } - if (!isNavigationDocument && !isParameterDocument) + private static DateTime GetFirstCommitDate(IReadOnlyList commits) + { + return GetCommitDate(commits, true); + } + + private static DateTime GetCommitDate(IReadOnlyList commits, bool isFirstCommit) + { + if (commits == null) { - foreach (var author in authors) - { - document.AddContributor(author.Login, author.HtmlUrl, author.AvatarUrl); - } + return DateTime.MinValue; } - return document; + var gitHubCommit = isFirstCommit ? + commits.LastOrDefault() : //first commit + commits.FirstOrDefault(); //last commit + + if (gitHubCommit == null) + { + return DateTime.MinValue; + } + + if (gitHubCommit.Commit == null) + { + return DateTime.MinValue; + } + + if (gitHubCommit.Commit.Author == null) + { + return DateTime.MinValue; + } + + return gitHubCommit.Commit.Author.Date.DateTime; + } + + private async Task> GetGitHubCommitsOrNull(Project project, string documentName, string languageCode, string version) + { + /* + * Getting file commits usually throws "Resource temporarily unavailable" or "Network is unreachable" + * This is a trival information and running this inside try-catch is safer. + */ + + try + { + return await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName)); + } + catch (Exception e) + { + Logger.LogError(e.ToString()); + return null; + } } private async Task GetLastSignificantUpdateTime( - IReadOnlyList fileCommits, + IReadOnlyList commits, Project project, string fileName, DateTime? lastKnownSignificantUpdateTime, DateTime documentCreationTime) { - if (!fileCommits.Any()) + if (commits == null || !commits.Any()) { return null; } - var fileCommitsAfterCreation = fileCommits.Take(fileCommits.Count - 1); + var fileCommitsAfterCreation = commits.Take(commits.Count - 1); var commitsToEvaluate = (lastKnownSignificantUpdateTime != null ? fileCommitsAfterCreation.Where(c => c.Commit.Author.Date.DateTime > lastKnownSignificantUpdateTime) @@ -117,7 +204,6 @@ namespace Volo.Docs.GitHub.Documents foreach (var gitHubCommit in commitsToEvaluate) { - var fullCommit = await _githubRepositoryManager.GetSingleCommitsAsync( GetOwnerNameFromUrl(project.GetGitHubUrl()), GetRepositoryNameFromUrl(project.GetGitHubUrl()), From bad3fe4c44461198aff81475118a6ccfc3dd8e1a Mon Sep 17 00:00:00 2001 From: Mehmet Erim <34455572+mehmet-erim@users.noreply.github.com> Date: Mon, 27 Apr 2020 04:45:53 +0300 Subject: [PATCH 44/80] Update Part-1.md --- docs/en/Tutorials/Part-1.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index e3f988779b..8d9d42875f 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -874,8 +874,6 @@ We'll see **book-list works!** text on the books page: Run the following command in the terminal to create a new state, named `BooksState`: -![Initial book list page](./images/bookstore-generate-state-books.png) - ```bash npx @ngxs/cli --name books --directory src/app/books ``` From a560c01a2eca1c9c5d5479576f250ae15921e810 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Mon, 27 Apr 2020 05:13:12 +0300 Subject: [PATCH 45/80] docs: update bookstore angular file tree screenshot --- .../images/bookstore-angular-file-tree.png | Bin 246634 -> 92799 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/en/Tutorials/images/bookstore-angular-file-tree.png b/docs/en/Tutorials/images/bookstore-angular-file-tree.png index 28e570f6042fad36a9f6e98563ac1cccb9d0f330..a3197b64574c61c589cc58fe6125a8553b1f09e9 100644 GIT binary patch literal 92799 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 From 944bfeed55bd53b4be2c62355c149911069746bd Mon Sep 17 00:00:00 2001 From: Mehmet Erim <34455572+mehmet-erim@users.noreply.github.com> Date: Mon, 27 Apr 2020 05:47:05 +0300 Subject: [PATCH 46/80] Update Part-2.md --- docs/en/Tutorials/Part-2.md | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index ddee41325f..cfe9936812 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -525,7 +525,7 @@ export class BooksState { * We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book. -When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BookService`. +When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`. #### Add a modal to BookListComponent @@ -885,32 +885,6 @@ Now, you can open your browser to see the changes: #### Saving the book -Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book. - -```html - - - - - - -``` - -Find the `

` tag and replace below content: - -```html - -``` - - -* We added the `(ngSubmit)="save()"` to `` element to save a new book by pressing the enter. -* We added `abp-button` to the bottom area of the modal to save a new book. - Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below: ```js @@ -993,6 +967,32 @@ export class BookListComponent implements OnInit { * We imported `CreateUpdateBook`. * We added `save` method +Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book. + +```html + + + + + + +``` + +Find the `` tag and replace below content: + +```html + +``` + + +* We added the `(ngSubmit)="save()"` to `` element to save a new book by pressing the enter. +* We added `abp-button` to the bottom area of the modal to save a new book. + The final modal UI looks like below: ![Save button to the modal](./images/bookstore-new-book-form-v2.png) From 96324916b4b6987ac47bfae6a05e143eb2f2c6b8 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 27 Apr 2020 15:47:43 +0800 Subject: [PATCH 47/80] Throw DocumentNotFoundException when file not exists --- .../Documents/FileSystemDocumentSource.cs | 5 ++ .../Pages/Documents/Project/Index.cshtml | 3 +- .../Pages/Documents/Project/Index.cshtml.cs | 65 +++++++++---------- .../Shared/ErrorComponent/Default.cshtml | 21 +++--- .../Shared/ErrorComponent/ErrorPageModel.cs | 2 + .../Documents/Shared/ErrorComponent/error.js | 2 +- 6 files changed, 53 insertions(+), 45 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs index cc0f03a60a..f97eddf0e2 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs @@ -87,6 +87,11 @@ namespace Volo.Docs.FileSystem.Documents { throw new SecurityException("Can not get a resource file out of the project folder!"); } + + if (!File.Exists(path)) + { + throw new DocumentNotFoundException(path); + } } } } diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 74f7c21464..7f521de34b 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -371,7 +371,8 @@ else RedirectUrl = Model.DocumentsUrlPrefix + Model.LanguageCode + "/" + Model.ProjectName + "/" + (Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version), ErrorCode = "404", - ErrorMessage = L.GetString("DocumentNotFound") + ErrorMessage = L.GetString("DocumentNotFound"), + AutoRedirect = !Model.DocumentName.IsNullOrWhiteSpace() } })) } diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index f5758447e4..b1d1c021f1 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -34,7 +34,7 @@ namespace Volo.Docs.Pages.Documents.Project public string LanguageCode { get; set; } public bool DocumentFound { get; set; } = true; - + public bool ProjectFound { get; set; } = true; public bool LoadSuccess => DocumentFound && ProjectFound; @@ -347,43 +347,13 @@ namespace Volo.Docs.Pages.Documents.Project try { - if (DocumentName.IsNullOrWhiteSpace()) - { - Document = await _documentAppService.GetDefaultAsync( - new GetDefaultDocumentInput - { - ProjectId = Project.Id, - LanguageCode = LanguageCode, - Version = Version - } - ); - } - else - { - Document = await _documentAppService.GetAsync( - new GetDocumentInput - { - ProjectId = Project.Id, - Name = DocumentNameWithExtension, - LanguageCode = LanguageCode, - Version = Version - } - ); - } + Document = await GetSpecificDocumentOrDefaultAsync(LanguageCode); } catch (DocumentNotFoundException) { if (LanguageCode != DefaultLanguageCode) { - Document = await _documentAppService.GetAsync( - new GetDocumentInput - { - ProjectId = Project.Id, - Name = DocumentNameWithExtension, - LanguageCode = DefaultLanguageCode, - Version = Version - } - ); + Document = await GetSpecificDocumentOrDefaultAsync(DefaultLanguageCode); DocumentLanguageIsDifferent = true; } @@ -545,6 +515,33 @@ namespace Volo.Docs.Pages.Documents.Project } + private async Task GetSpecificDocumentOrDefaultAsync(string languageCode) + { + if (DocumentName.IsNullOrWhiteSpace()) + { + return await _documentAppService.GetDefaultAsync( + new GetDefaultDocumentInput + { + ProjectId = Project.Id, + LanguageCode = languageCode, + Version = Version + } + ); + } + else + { + return await _documentAppService.GetAsync( + new GetDocumentInput + { + ProjectId = Project.Id, + Name = DocumentNameWithExtension, + LanguageCode = languageCode, + Version = Version + } + ); + } + } + public async Task SetDocumentPreferencesAsync() { var projectParameters = await _documentAppService.GetParametersAsync( @@ -604,7 +601,7 @@ namespace Volo.Docs.Pages.Documents.Project { return null; } - + var firstParagraph = new Regex(@"

(.*?)

", RegexOptions.IgnoreCase); var match = firstParagraph.Match(Document.Content); if (!match.Success) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml index ff10a25f2a..4aef93c593 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml @@ -3,14 +3,17 @@ }
@@ -19,7 +22,7 @@ h1 { font-size: 1.75em; line-height: 1.75; - color: #777; + color: #777; font-weight: normal; } @@ -36,7 +39,7 @@ transform: translate(-50%, -50%); text-align: center; } - + .notfound-404 { font-size: 300px; font-weight: 700; @@ -48,5 +51,5 @@ margin-bottom: -150px; z-index: -1; position: relative; - } - \ No newline at end of file + } + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs index cfb65409c7..62a64fc9d6 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs @@ -7,5 +7,7 @@ public string ErrorCode { get; set; } public string RedirectUrl { get; set; } + + public bool AutoRedirect { get; set; } = true; } } diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js index d1d08829ac..edc010aa9f 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js @@ -12,7 +12,7 @@ }, 1000); } - if (document.getElementById("DocumentErrorContainer")) { + if (document.getElementById("ErrorRedirect")) { errorPageRedirect(); } }); From eb72a699afd9a870c47cb621415ebc4f28f39e95 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Mon, 27 Apr 2020 14:42:50 +0300 Subject: [PATCH 48/80] chore: update ngxs-reset-plugin version --- npm/ng-packs/package.json | 28 +- .../scripts/install-new-dependencies.ts | 2 +- npm/ng-packs/yarn.lock | 391 ++++++++---------- 3 files changed, 198 insertions(+), 223 deletions(-) diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 93715f2a8e..d141817a6e 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -22,19 +22,19 @@ "generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" }, "devDependencies": { - "@abp/ng.account": "^2.6.1", - "@abp/ng.account.config": "^2.6.1", - "@abp/ng.core": "^2.6.1", - "@abp/ng.feature-management": "^2.6.1", - "@abp/ng.identity": "^2.6.1", - "@abp/ng.identity.config": "^2.6.1", - "@abp/ng.permission-management": "^2.6.1", - "@abp/ng.setting-management": "^2.6.1", - "@abp/ng.setting-management.config": "^2.6.1", - "@abp/ng.tenant-management": "^2.6.1", - "@abp/ng.tenant-management.config": "^2.6.1", - "@abp/ng.theme.basic": "^2.6.1", - "@abp/ng.theme.shared": "^2.6.1", + "@abp/ng.account": "~2.6.2", + "@abp/ng.account.config": "~2.6.2", + "@abp/ng.core": "^2.6.2", + "@abp/ng.feature-management": "^2.6.2", + "@abp/ng.identity": "~2.6.2", + "@abp/ng.identity.config": "~2.6.2", + "@abp/ng.permission-management": "^2.6.2", + "@abp/ng.setting-management": "~2.6.2", + "@abp/ng.setting-management.config": "~2.6.2", + "@abp/ng.tenant-management": "~2.6.2", + "@abp/ng.tenant-management.config": "~2.6.2", + "@abp/ng.theme.basic": "~2.6.2", + "@abp/ng.theme.shared": "^2.6.2", "@abp/utils": "^2.6.0", "@angular-builders/jest": "^8.2.0", "@angular-devkit/build-angular": "~0.803.21", @@ -76,7 +76,7 @@ "just-compare": "^1.3.0", "lerna": "^3.19.0", "ng-packagr": "^5.7.1", - "ngxs-reset-plugin": "^1.2.0", + "ngxs-reset-plugin": "^1.3.0", "ngxs-schematic": "^1.1.9", "prettier": "^1.18.2", "protractor": "~5.4.0", diff --git a/npm/ng-packs/scripts/install-new-dependencies.ts b/npm/ng-packs/scripts/install-new-dependencies.ts index d26d72f88b..3528396fb9 100644 --- a/npm/ng-packs/scripts/install-new-dependencies.ts +++ b/npm/ng-packs/scripts/install-new-dependencies.ts @@ -17,7 +17,7 @@ const updateAndInstall = async () => { ...packageJson.devDependencies, ...dependencies, ...peerDependencies, - [name]: `^${version}`, + [name]: `~${version}`, }; packageJson.devDependencies = Object.keys(packageJson.devDependencies) diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index d5101870d5..e8539d81be 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -2,26 +2,26 @@ # yarn lockfile v1 -"@abp/ng.account.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.6.1.tgz#7df105f84d68e3266cec486d3fd15abc8beeaa4f" - integrity sha512-zN4DzB0XujdoqE3h8K1Rgd7TVuM+LlFwXcsb8DCorHiY91c+hKDd97jSaLB5nemFOiaZZgfT0/9FYafbftbpUw== +"@abp/ng.account.config@^2.6.2", "@abp/ng.account.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.6.2.tgz#1a3e935b7144525c62f5d08adcd06d2ee12f7c9e" + integrity sha512-dC0/H1dkeQqYbu/dNISQA7NLpdOughhZSxT/7zjq6cBP8gYDceW0wYT5Rh0qIAkYWh2jO4BMXIFqzlH31Jg/XA== dependencies: tslib "^1.9.0" -"@abp/ng.account@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.6.1.tgz#aba9c461ab178de22a66aa36ec9ba48d6641d5f4" - integrity sha512-GQWGF8Bhcy98rzug10sdyKjmp+UIp8WVqG/urWa8gYukbRtZZIvQVnaJIJXDyE8qHVOU3SBybjasOKIKe3qLsQ== +"@abp/ng.account@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.6.2.tgz#aac921c49bcd8cdec506bd27d2546193416ea187" + integrity sha512-s3gLSqNdCW6D+DvuML+aGFT8braCfSXUbxvJ1WRCovd2Ec0jNxPBKj84zgYPYEb3smD294DcXxNx36jtJK+1OA== dependencies: - "@abp/ng.account.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.account.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.core@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.6.1.tgz#7a788e414403b3872eda3b90d7ee0f30d4e86f98" - integrity sha512-8CQJotkaqaDtqxDBitH+bX1ANlvv7PkwiBINrN/H4CyxZqx6oVxKhFqJ6nlDKijnogGTYMIhegHPrpVZh4O6pg== +"@abp/ng.core@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.6.2.tgz#d0e4f6666a9a207fc7425001449bea2cdfe93522" + integrity sha512-e7c82d9VF20d4JFUkyzT5iTqGKnkq5FBHdv6IW6lsVyuz2Zf/p4wNtKATRr2ntM7qNIjIQebtGNFyxxX4nmL+w== dependencies: "@abp/utils" "^2.6.0" "@angular/localize" "~9.1.0" @@ -35,86 +35,86 @@ ts-toolbelt "^6.3.6" tslib "^1.9.0" -"@abp/ng.feature-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.6.1.tgz#e81c9e55360533f8356b165e8548f3cf7e9ecab0" - integrity sha512-t8ugSLiAt/2fLo4OVnEMbk6jST/RZ+b6KysMnajKcxK/TAQG/Mj3FC9K52FdnjJg9s+MoJowR05YebAHhEnXig== +"@abp/ng.feature-management@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.6.2.tgz#da09b07ebe81f09ccf338a7c10c384920375567a" + integrity sha512-JdHuAZk9j9AR1iKU02Xoux40ULOccm+68q7/4L2G1cBET6yTAE8Ua9RPPX9HadmCG0cR8awl3d+5JPwyL3OjFA== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.identity.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.6.1.tgz#cbf56d00a3633268b11fc125fcc711637b86770b" - integrity sha512-7dyEhjutbN2Zw7W1fKcldaZppmrP08kmKQAR6XHJhijcCJPsasVJ3F0babrG8SdRnZqoZVZ/5X21IfDuShc2/g== +"@abp/ng.identity.config@^2.6.2", "@abp/ng.identity.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.6.2.tgz#79384ae4b11e53e8bf9dda25d3400def05c5beb8" + integrity sha512-jCqt2ZWEkRA9foJjHwRJtw5JNy03OVge9dUj0eAJ7Vo13IrKAT1kCNlD7cRM9xJ42Ga2utEqqz9FCJHgR2IxbQ== dependencies: tslib "^1.9.0" -"@abp/ng.identity@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.6.1.tgz#a67c5c3daae08ba8582494c869d384acbeb086f3" - integrity sha512-PsWjjxd29es8tHA8Zkb54HX9zj91Pv9fkWR5HQsUmv3mcBv0QyGEnCC/g6PJEQXNxpxdgG/aWxwrjDh59d8pAg== +"@abp/ng.identity@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.6.2.tgz#14f83305415d27b67d01405e582978682cfd7f22" + integrity sha512-wzR/QFoSSRhIxfH91QsTSCc0rBsFfsg8aiP5xPheNx3/lZygCnMogH3blRgLiNIoyxuG00MTfp+e9lFIBfDnbQ== dependencies: - "@abp/ng.identity.config" "^2.6.1" - "@abp/ng.permission-management" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.identity.config" "^2.6.2" + "@abp/ng.permission-management" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.permission-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.6.1.tgz#b2ea11ea2c3c3b9671def5164cf4879ce9adbcdd" - integrity sha512-Rm65jyxZQ/9qhze2mT8RuIfe8VA932ydeqivfykax5QPw7c6edHaRgvgdghufKSt5fS8twB2mNHzD6o3lsEXzA== +"@abp/ng.permission-management@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.6.2.tgz#ba7902256acf3e1ff49552983e4d292f670f9ad8" + integrity sha512-6NW5BF+5ZFQI8oLEQWCiJS5iF2v3gHLvohrbN84RsPNTc+ZRDte6MbIOGq2CWxM8qXwisLBiR/RWC4ssgfPqEA== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.setting-management.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.6.1.tgz#85a015399628f560c6759fb48994fd2b9613e073" - integrity sha512-Jl3uE78G42mJhJ1c94En/dHnR8YOgVu93Z8NwvcW2L58TSmtogcS+7/+pG9fgnqnYovIeoMSYQfSRqYrKtJliw== +"@abp/ng.setting-management.config@^2.6.2", "@abp/ng.setting-management.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.6.2.tgz#43e9e4399d65efc1aee6e314469c303ce22145e9" + integrity sha512-3F0vq5nvBTat2q7S0iBfdEsyPuZHJuL3+9z4Voq4Q9rA2T2VIAUMp9aBsfI2s2ycwGbsb7kCOWTKo/2by1b4AA== dependencies: tslib "^1.9.0" -"@abp/ng.setting-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.6.1.tgz#2a266de071a5a9c32230fbf8696057430ba6d2bc" - integrity sha512-K+qg6JlX0FT6LDEAB4QsBrnT/Rsla2K8NFKdT3FkwfJtSbysqehTIUoO6m7hfuQzLOUd3XVDSOpaJ+Tqz7k1Bw== +"@abp/ng.setting-management@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.6.2.tgz#f49d361ed8412380fdf2173f28b890115748a52b" + integrity sha512-0VdGa+t8lEZRsUdKL1EHoZei+ZCDMTu4n3UvXvD0OzRJyYPUjHRrMSSOYifuN4gPatluIe0E2I+tO3lQ3/2tjw== dependencies: - "@abp/ng.setting-management.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.setting-management.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.tenant-management.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.6.1.tgz#3f484df5d59e210851785ec5394f45d0e7e25d73" - integrity sha512-HG8YrQu8TJU4TxoImCPpFIajquiD7OGICcYTXpWl9K6NdvRyv/GaOZDcE0j6O7zGOpSpCHg4mhSgKEQ+pUSZnw== +"@abp/ng.tenant-management.config@^2.6.2", "@abp/ng.tenant-management.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.6.2.tgz#9a9365ad59bdd2053d7bd33fb623ce633de40b24" + integrity sha512-iZ49Els+J9lbRKTSeNU/KIJuJS7Le3dAk1pdqLg/g5iw7e6uTEih1qeDvJdcXpSKfAZJrOd/KKDjFHIzVrveuA== dependencies: tslib "^1.9.0" -"@abp/ng.tenant-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.6.1.tgz#7bb60383c05f3de76b99a4274c0f9019d5cca9f5" - integrity sha512-TM6HUS9drqxG7KaLrM6+9kYNnDmX5f/tlk4ReepvU3OzisCC87PQeVAhmrUn0DKy/RTIKKIl3ORzlxN0yyIgLg== +"@abp/ng.tenant-management@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.6.2.tgz#6be284fe6691946e2e6766e34ae958d7d2ec5e3a" + integrity sha512-BGARok01Eqn1kEltOwjwIpMJ8LD0jtYfbSbnO8V9TRg7f4ZEqFmfXudOIluDIwHjSEkNXk+Cs0/iV53lyi5+lg== dependencies: - "@abp/ng.feature-management" "^2.6.1" - "@abp/ng.tenant-management.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.feature-management" "^2.6.2" + "@abp/ng.tenant-management.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.theme.basic@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.6.1.tgz#9156bb47536d2a5191542b2d410d4297eaa3004d" - integrity sha512-LWA4ZmhNvVwFQDmvy8JG1+qOUO/7ECM5Atb04AMzoZ1OOXJjL1D0ut3KG1xp6S8FxZ5CVFSVsMj4/cxL1mZh/A== +"@abp/ng.theme.basic@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.6.2.tgz#4b0414411e164885b8c467b04d635a0a3c81bd01" + integrity sha512-qgA4hh7ipIWY88JOqjChjoJgFB0csqXbwdb/pabrsFoHYuiFVT3cqvHq6jMfKQHEPBJSEnl6/GUIm+26DQ5eDw== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.theme.shared@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.6.1.tgz#34b0738623221e26b32b8491ea4dc99bb9f875e8" - integrity sha512-V8xwqIjwhDp3szIeGDUK16gVaV2XPQUkPOFdhEN9fzTl0yuLoBByYjPuTI3Slb3C220pwcZsc5l+8rCsnKMUDA== +"@abp/ng.theme.shared@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.6.2.tgz#9d70b7aab010af4cd5a451c9c2d162d73ec5f9cc" + integrity sha512-e3QVsPpmbPGmTpQY9J1RgmWer7Tx7mrsOtxjFvbJGqKVF1UUtU5Kc3VyGPb3JsjQtT+890/9qmAf/YSj3cNPIA== dependencies: - "@abp/ng.core" "^2.6.1" + "@abp/ng.core" "^2.6.2" "@fortawesome/fontawesome-free" "^5.12.1" "@ng-bootstrap/ng-bootstrap" "^5.3.0" "@ngx-validate/core" "^0.0.7" @@ -123,9 +123,9 @@ tslib "^1.9.0" "@abp/utils@^2.6.0": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-2.6.0.tgz#ca174b19f74594a0ae8d64f56b032f07b9168ed1" - integrity sha512-ABQUgFHbv8ktGR+tKx/1s+5a9JHuBwUVAy/Hn8OjbEDoSK73UHoapfFSShF/x12dWDKLcpvErmeiASiXb+soCw== + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-2.6.2.tgz#dba9e7c0aa1531fae6c5ff22fcbdfb4d98c576e3" + integrity sha512-MqExfPA4FRYORsskVG7kbkAwzs9z4XaMaZ75XkoahD+FyZtm1e0miEEoDvwrdye9jgL/FrWeShWNE49GY7PvpA== dependencies: just-compare "^1.3.0" @@ -356,9 +356,9 @@ integrity sha512-7EhN9JJbAJcH2xCa+rIOmekjiEuB0qwPdHuD5qn/wwMfRzMZo+Db4hHbR9KHrLH6H82PTwYKye/LLpDaZqoHOA== "@angular/localize@~9.1.0": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-9.1.1.tgz#267d7cecb288b3019482744330da0ef9e049c85d" - integrity sha512-5mYdSL7IwqFXWRgBXj9c5vHT15AQy2kjD40fJJsmUx4WUFr+uf0Ss9ADCfL2FjCHpATrjYKpiyJs1mAF60USPQ== + version "9.1.3" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-9.1.3.tgz#d03d9cb1b4fe10dabaf806062d23ee1b7774945d" + integrity sha512-aV7AFgvEWm6aaEjS45EPJ1U4i2DOVR6DswUu9CsYkRfy/gSOxWE95SyrhLdg2hB64Bu4mcXxiV1So2BUI7NzBA== dependencies: "@babel/core" "7.8.3" glob "7.1.2" @@ -2101,9 +2101,9 @@ glob-to-regexp "^0.3.0" "@ng-bootstrap/ng-bootstrap@^5.3.0": - version "5.3.0" - resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-5.3.0.tgz#4d624dd70a93f99c38bc35b1d3e00afc490155fc" - integrity sha512-oNg1sgT+XXxwSBX9oF4AeCHrq+Nm1CvukfVT/NPuYiuNAoTeVOdXc5bJHTLd+cCwoZ1OPZN/yZT+1BcW1KCksQ== + version "5.3.1" + resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-5.3.1.tgz#4cf2929583e9572475d1d0f3c905b05885a086bb" + integrity sha512-xlhBJb4oNcOQk04h5sQcq9P1E97sGB1HjqBCqgL0+S2w2uvLWME9F9SuH7wU4S1+eYe7WG9SKFpq+R4BjG2kMw== dependencies: tslib "^1.9.0" @@ -2183,12 +2183,12 @@ dependencies: "@octokit/types" "^2.0.0" -"@octokit/endpoint@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.0.tgz#4c7acd79ab72df78732a7d63b09be53ec5a2230b" - integrity sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A== +"@octokit/endpoint@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.1.tgz#16d5c0e7a83e3a644d1ddbe8cded6c3d038d31d7" + integrity sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A== dependencies: - "@octokit/types" "^2.0.0" + "@octokit/types" "^2.11.1" is-plain-object "^3.0.0" universal-user-agent "^5.0.0" @@ -2236,13 +2236,13 @@ once "^1.4.0" "@octokit/request@^5.2.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.0.tgz#52382c830f0cf3295b5a03e308872ac0f5ccba9b" - integrity sha512-uAJO6GI8z8VHBqtY7VTL9iFy1Y+UTp5ShpI97tY5z0qBfYKE9rZCRsCm23VmF00x+IoNJ7a0nuVITs/+wS9/mg== + version "5.4.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.2.tgz#74f8e5bbd39dc738a1b127629791f8ad1b3193ee" + integrity sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw== dependencies: - "@octokit/endpoint" "^6.0.0" + "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^2.8.2" + "@octokit/types" "^2.11.1" deprecation "^2.0.0" is-plain-object "^3.0.0" node-fetch "^2.3.0" @@ -2271,10 +2271,10 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/types@^2.0.0", "@octokit/types@^2.0.1", "@octokit/types@^2.8.2": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.9.0.tgz#b3b69cddab629dd31a559ffb25c9f471301d7538" - integrity sha512-IzptUpoDsFlXF+AOys+KnfItIVY3EK+eH9Akv+lJYELnMSGkJnIcClt6Cm0QRR4ecsUTsmFJWn10iFgJ9BQqIQ== +"@octokit/types@^2.0.0", "@octokit/types@^2.0.1", "@octokit/types@^2.11.1": + version "2.12.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.12.1.tgz#4a26b4a85ec121043d3b0745b5798f9d8fd968ca" + integrity sha512-LRLR1tjbcCfAmUElvTmMvLEzstpx6Xt/aQVTg2xvd+kHA2Ekp1eWl5t+gU7bcwjXHYEAzh4hH4WH+kS3vh+wRw== dependencies: "@types/node" ">= 8" @@ -2429,9 +2429,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*", "@types/node@>= 8": - version "13.11.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" - integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== + version "13.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" + integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== "@types/node@^8.0.31": version "8.10.60" @@ -2789,9 +2789,9 @@ ajv@6.9.1: uri-js "^4.2.2" ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -3154,9 +3154,9 @@ babel-jest@^24.9.0: slash "^2.0.0" babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" @@ -3437,12 +3437,12 @@ browserslist@4.10.0: pkg-up "^3.1.0" browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.3, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" node-releases "^1.1.53" pkg-up "^2.0.0" @@ -3677,10 +3677,10 @@ caniuse-lite@1.0.30001035: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e" integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ== -caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039: - version "1.0.30001042" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz#c91ec21ec2d270bd76dbc2ce261260c292b8c93c" - integrity sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw== +caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043: + version "1.0.30001048" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" + integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== canonical-path@1.0.0: version "1.0.0" @@ -3768,9 +3768,9 @@ chokidar@2.0.4: fsevents "^1.2.2" "chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" + integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -3778,7 +3778,7 @@ chokidar@2.0.4: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.3.0" + readdirp "~3.4.0" optionalDependencies: fsevents "~2.1.2" @@ -5023,10 +5023,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.390: - version "1.3.407" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.407.tgz#361dbfc92a3d1b0b097b77221256271967e04c0e" - integrity sha512-ayDQgkwCLHq0AVmsU10t2H/8X9lq3nb1CRV9rWYsc2nTTbFMvh4Aaau48Ao0iW/L+Q8Y9bgbJ5iHyPse7DGMJw== +electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413: + version "1.3.418" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz#840021191f466b803a873e154113620c9f53cec6" + integrity sha512-i2QrQtHes5fK/F9QGG5XacM5WKEuR322fxTYF9e8O9Gu0mc0WmjjwGpV8c7Htso6Zf2Di18lc3SIPxmMeRFBug== elliptic@^6.0.0: version "6.5.2" @@ -5104,9 +5104,9 @@ env-paths@^2.2.0: integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== envinfo@^7.3.1: - version "7.5.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" - integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + version "7.5.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236" + integrity sha512-hQBkDf2iO4Nv0CNHpCuSBeaSrveU6nThVxFGTrq/eDlV716UQk09zChaJae4mZRsos1x4YLY2TaH3LHUae3ZmQ== err-code@^1.0.0: version "1.1.2" @@ -5503,9 +5503,9 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figlet@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.3.0.tgz#c49e3d92907ba13bebadc7124f76ba71f1f32ef0" - integrity sha512-f7A8aOJAfyehLJ7lQ6rEA8WJw7kOk3lfWRi5piSjkzbK5YkI5sqO8eiLHz1ehO+DM0QYB85i8VfA6XIGUbU1dg== + version "1.4.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.4.0.tgz#21c5878b3752a932ebdb8be400e2d10bbcddfd60" + integrity sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ== figures@^2.0.0: version "2.0.0" @@ -5744,9 +5744,9 @@ fsevents@^1.2.2, fsevents@^1.2.7: nan "^2.12.1" fsevents@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== function-bind@^1.1.1: version "1.1.1" @@ -6861,11 +6861,6 @@ is-plain-object@^3.0.0: dependencies: isobject "^4.0.0" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-reference@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" @@ -7612,9 +7607,9 @@ jsrsasign@^8.0.12: integrity sha512-6UKHqnNs5lYROn03wf1BTw7DQx5tW616DTigjbo0JHV97D3HzIqYmPVCBSNsfEfQOrfpFqmPZJvaC3cMNOT0Yw== jszip@^3.1.3: - version "3.3.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.3.0.tgz#29d72c21a54990fa885b11fc843db320640d5271" - integrity sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw== + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== dependencies: lie "~3.3.0" pako "~1.0.2" @@ -8052,9 +8047,9 @@ make-dir@^2.0.0, make-dir@^2.1.0: semver "^5.6.0" make-dir@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -8265,17 +8260,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -8347,11 +8342,6 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -8490,9 +8480,9 @@ mz@^2.5.0: thenify-all "^1.0.0" nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -8559,7 +8549,7 @@ ng-packagr@^5.7.1: terser "^4.1.2" update-notifier "^3.0.0" -ngxs-reset-plugin@^1.2.0: +ngxs-reset-plugin@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ngxs-reset-plugin/-/ngxs-reset-plugin-1.3.0.tgz#370b89190e729de94f0116b99ff65389582a7626" integrity sha512-gxAUuka1ewSs04M+cFaUu5NVMSWW9EHw0mZAxXbtagtbPyEwSRjgFvobb4nNyWVVBPsdAuBIfPCfkztZ6Dtj3w== @@ -9006,14 +8996,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimist@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -9451,7 +9433,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.7: +picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -9709,9 +9691,9 @@ protoduck@^5.0.1: genfun "^5.0.0" protractor@~5.4.0: - version "5.4.3" - resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.4.3.tgz#35f050741e404a45868618ea648745d89af31683" - integrity sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA== + version "5.4.4" + resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.4.4.tgz#b241466aaf83b76bc2c58df67deb9a5cdfc61529" + integrity sha512-BaL4vePgu3Vfa/whvTUAlgaCAId4uNSGxIFSCXMgj7LMYENPWLp85h5RBi9pdpX/bWQ8SF6flP7afmi2TC4eHw== dependencies: "@types/q" "^0.0.32" "@types/selenium-webdriver" "^3.0.0" @@ -9721,13 +9703,13 @@ protractor@~5.4.0: glob "^7.0.3" jasmine "2.8.0" jasminewd2 "^2.1.0" - optimist "~0.6.0" q "1.4.1" saucelabs "^1.5.0" selenium-webdriver "3.6.0" source-map-support "~0.4.0" webdriver-js-extender "2.1.0" webdriver-manager "^12.0.6" + yargs "^12.0.5" proxy-addr@~2.0.5: version "2.0.6" @@ -10048,12 +10030,12 @@ readdirp@^2.0.0, readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - picomatch "^2.0.7" + picomatch "^2.2.1" realpath-native@^1.1.0: version "1.1.0" @@ -10320,9 +10302,9 @@ resolve@1.1.7, resolve@~1.1.6: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@1.x, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.3.2: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -10457,11 +10439,9 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -10547,9 +10527,9 @@ sass@1.22.9: chokidar ">=2.0.0 <4.0.0" sass@^1.17.3: - version "1.26.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.3.tgz#412df54486143b76b5a65cdf7569e86f44659f46" - integrity sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw== + version "1.26.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.5.tgz#2d7aecfbbabfa298567c8f06615b6e24d2d68099" + integrity sha512-FG2swzaZUiX53YzZSjSakzvGtlds0lcbF+URuU9mxOv7WBh7NhXEVDa4kPKN4hN6fC2TkOTOKqiqp6d53N9X5Q== dependencies: chokidar ">=2.0.0 <4.0.0" @@ -10587,9 +10567,9 @@ schema-utils@^1.0.0: ajv-keywords "^3.1.0" schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.6.1: - version "2.6.5" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ== + version "2.6.6" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c" + integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA== dependencies: ajv "^6.12.0" ajv-keywords "^3.4.1" @@ -10952,9 +10932,9 @@ source-map-support@0.5.13: source-map "^0.6.0" source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -11014,9 +10994,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.0" @@ -11529,9 +11509,9 @@ terser@4.6.3: source-map-support "~0.5.12" terser@^4.1.2: - version "4.6.11" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f" - integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA== + version "4.6.12" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.12.tgz#44b98aef8703fdb09a3491bf79b43faffc5b4fee" + integrity sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -11740,9 +11720,9 @@ ts-node@~7.0.0: yn "^2.0.0" ts-toolbelt@^6.3.6: - version "6.4.2" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.4.2.tgz#97df4fc577eccccfbe8929d82ab364d86ea99548" - integrity sha512-B27axztBC+3K4D1cuaRO3ygMcyR54LRpQxlt1zNIS9RnqDwrlDoVlIIazDqWxLuRaFzGWNvDLkjDgs3eeLEVig== + version "6.5.1" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.5.1.tgz#1316b8c6522797c0bee289ca5deaa159af846231" + integrity sha512-zjnZ/vy1eUA0li3H0JXecl0R3jiP42snpLimsrppt9V3LLbM4NM4jMgjXQ4S67hvehq+r9CxpX4Wj6RnFRzReA== tsickle@^0.37.0: version "0.37.1" @@ -11848,9 +11828,9 @@ typescript@^3.3.3333, typescript@^3.5.2: integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== uglify-js@^3.1.4: - version "3.9.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.0.tgz#037163a936992050ed5d14f5d5c4014126019661" - integrity sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g== + version "3.9.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.1.tgz#a56a71c8caa2d36b5556cc1fd57df01ae3491539" + integrity sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA== dependencies: commander "~2.20.3" @@ -12399,11 +12379,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -12600,14 +12575,14 @@ yargs-parser@^15.0.1: decamelize "^1.2.0" yargs-parser@^18.1.0, yargs-parser@^18.1.1: - version "18.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" - integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ== + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@12.0.5: +yargs@12.0.5, yargs@^12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== From 545f1afb7b80b70c41a2ff335c9d379e351234da Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 28 Apr 2020 10:46:31 +0800 Subject: [PATCH 49/80] Add SolutionName option to update command. Resolve #3753 --- .../Volo/Abp/Cli/Commands/UpdateCommand.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs index e6a2319c72..ca8bfa9bf8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs @@ -57,7 +57,11 @@ namespace Volo.Abp.Cli.Commands .Options .GetOrNull(Options.IncludePreviews.Short, Options.IncludePreviews.Long) != null; - var solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault(); + var solution = commandLineArgs.Options.GetOrNull(Options.SolutionName.Short, Options.SolutionName.Long); + if (solution.IsNullOrWhiteSpace()) + { + solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault(); + } if (solution != null) { @@ -101,11 +105,14 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("-p|--include-previews (if supported by the template)"); sb.AppendLine("--npm (Only updates NPM packages)"); sb.AppendLine("--nuget (Only updates Nuget packages)"); + sb.AppendLine("-sp|--solution-path (Specify the solution path)"); + sb.AppendLine("-sn|--solution-name (Specify the solution name)"); sb.AppendLine(""); sb.AppendLine("Some examples:"); sb.AppendLine(""); sb.AppendLine(" abp update"); sb.AppendLine(" abp update -p"); + sb.AppendLine(" abp update -sp \"D:\\projects\\\" -sn Acme.BookStore"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -125,6 +132,12 @@ namespace Volo.Abp.Cli.Commands public const string Long = "solution-path"; } + public static class SolutionName + { + public const string Short = "sn"; + public const string Long = "solution-name"; + } + public static class IncludePreviews { public const string Short = "p"; From b86606d0b8bbe80287ad695d4e34a81c8f16afbb Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 28 Apr 2020 11:11:12 +0800 Subject: [PATCH 50/80] Update CLI.md --- docs/en/CLI.md | 2 ++ docs/zh-Hans/CLI.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index b687a5244a..6a5a331e8f 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -135,6 +135,8 @@ abp update [options] * `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions. * `--npm`: Only updates NPM packages. * `--nuget`: Only updates NuGet packages. +* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default +* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default. ### switch-to-preview diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 4d037ef2fd..3b440ed722 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -135,6 +135,8 @@ abp update [options] * `--include-previews` 或 `-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本. * `--npm`: 仅更新NPM包 * `--nuget`: 仅更新的NuGet包 +* `--solution-path` 或 `-sp`: 指定解决方案路径/目录. 默认使用当前目录 +* `--solution-name` 或 `-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件. ### 切换到每晚构建(预览)包 From ab20ff0e7b9a22bbed622d194904133c5d694a8d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 28 Apr 2020 17:47:19 +0800 Subject: [PATCH 51/80] Fix incorrect link --- docs/en/Getting-Started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md index 07e55f553e..72e2f2886c 100644 --- a/docs/en/Getting-Started.md +++ b/docs/en/Getting-Started.md @@ -400,8 +400,8 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the The application is up and running. You can continue to develop your application based on this startup template. -> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules. +> The [application startup template](Startup-Templates/Application.md) includes the TenantManagement and Identity modules. ## What's next? -[Application development tutorial](tutorials/book-store/part-1.md) +[Application development tutorial](Tutorials/Part-1.md) From 0b6806d559eae8c74192729d7cd24ad04a7d3a68 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 28 Apr 2020 14:44:42 +0300 Subject: [PATCH 52/80] feat: add RouteNames enums to all lazy-loadable modules #3765 --- .../lib/services/account-config.service.ts | 9 ++-- .../packages/account/src/lib/enums/index.ts | 2 + .../account/src/lib/enums/route-names.ts | 6 +++ .../packages/account/src/public-api.ts | 2 +- .../lib/services/identity-config.service.ts | 11 ++--- .../packages/identity/src/lib/enums/index.ts | 2 + .../identity/src/lib/enums/route-names.ts | 6 +++ .../packages/identity/src/public-api.ts | 2 +- .../setting-management-config.service.ts | 3 +- .../setting-management/src/lib/enums/index.ts | 2 + .../src/lib/enums/route-names.ts | 3 ++ .../setting-management/src/public-api.ts | 2 +- .../tenant-management-config.service.ts | 41 +++++++++++-------- .../tenant-management/src/lib/enums/index.ts | 2 + .../src/lib/enums/route-names.ts | 5 +++ .../tenant-management/src/public-api.ts | 2 +- 16 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 npm/ng-packs/packages/account/src/lib/enums/index.ts create mode 100644 npm/ng-packs/packages/account/src/lib/enums/route-names.ts create mode 100644 npm/ng-packs/packages/identity/src/lib/enums/index.ts create mode 100644 npm/ng-packs/packages/identity/src/lib/enums/route-names.ts create mode 100644 npm/ng-packs/packages/setting-management/src/lib/enums/index.ts create mode 100644 npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts create mode 100644 npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts create mode 100644 npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts diff --git a/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts b/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts index 0add43bc33..d94935c071 100644 --- a/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts +++ b/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts @@ -1,5 +1,6 @@ import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; import { Injectable } from '@angular/core'; +import { eAccountRouteNames } from '@abp/ng.account'; @Injectable({ providedIn: 'root', @@ -7,14 +8,14 @@ import { Injectable } from '@angular/core'; export class AccountConfigService { constructor() { addAbpRoutes({ - name: 'AbpAccount::Menu:Account', + name: eAccountRouteNames.Account, path: 'account', invisible: true, layout: eLayoutType.application, children: [ - { path: 'login', name: 'AbpAccount::Login', order: 1 }, - { path: 'register', name: 'AbpAccount::Register', order: 2 }, - { path: 'manage-profile', name: 'AbpAccount::ManageYourProfile', order: 3 }, + { path: 'login', name: eAccountRouteNames.Login, order: 1 }, + { path: 'register', name: eAccountRouteNames.Register, order: 2 }, + { path: 'manage-profile', name: eAccountRouteNames.ManageProfile, order: 3 }, ], }); } diff --git a/npm/ng-packs/packages/account/src/lib/enums/index.ts b/npm/ng-packs/packages/account/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/account/src/lib/enums/route-names.ts b/npm/ng-packs/packages/account/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..810985e027 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/enums/route-names.ts @@ -0,0 +1,6 @@ +export const enum eAccountRouteNames { + Account = 'AbpAccount::Menu:Account', + Login = 'AbpAccount::Login', + Register = 'AbpAccount::Register', + ManageProfile = 'AbpAccount::ManageYourProfile', +} diff --git a/npm/ng-packs/packages/account/src/public-api.ts b/npm/ng-packs/packages/account/src/public-api.ts index 63cb476f9b..8d1ef3c43a 100644 --- a/npm/ng-packs/packages/account/src/public-api.ts +++ b/npm/ng-packs/packages/account/src/public-api.ts @@ -1,6 +1,6 @@ export * from './lib/account.module'; export * from './lib/components'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/tokens'; export * from './lib/models'; export * from './lib/services'; diff --git a/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts b/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts index d1b52fed8b..c1fdbb102d 100644 --- a/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts +++ b/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts @@ -1,5 +1,6 @@ import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; import { Injectable } from '@angular/core'; +import { eIdentityRouteNames } from '@abp/ng.identity'; @Injectable({ providedIn: 'root', @@ -8,29 +9,29 @@ export class IdentityConfigService { constructor() { addAbpRoutes([ { - name: 'AbpUiNavigation::Menu:Administration', + name: eIdentityRouteNames.Administration, path: '', order: 1, wrapper: true, iconClass: 'fa fa-wrench', }, { - name: 'AbpIdentity::Menu:IdentityManagement', + name: eIdentityRouteNames.IdentityManagement, path: 'identity', order: 1, - parentName: 'AbpUiNavigation::Menu:Administration', + parentName: eIdentityRouteNames.Administration, layout: eLayoutType.application, iconClass: 'fa fa-id-card-o', children: [ { path: 'roles', - name: 'AbpIdentity::Roles', + name: eIdentityRouteNames.Roles, order: 1, requiredPolicy: 'AbpIdentity.Roles', }, { path: 'users', - name: 'AbpIdentity::Users', + name: eIdentityRouteNames.Users, order: 2, requiredPolicy: 'AbpIdentity.Users', }, diff --git a/npm/ng-packs/packages/identity/src/lib/enums/index.ts b/npm/ng-packs/packages/identity/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/identity/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts b/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..15933a5b77 --- /dev/null +++ b/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts @@ -0,0 +1,6 @@ +export const enum eIdentityRouteNames { + Administration = 'AbpUiNavigation::Menu:Administration', + IdentityManagement = 'AbpIdentity::Menu:IdentityManagement', + Roles = 'AbpIdentity::Roles', + Users = 'AbpIdentity::Users', +} diff --git a/npm/ng-packs/packages/identity/src/public-api.ts b/npm/ng-packs/packages/identity/src/public-api.ts index 1a2b217931..fd26c8a649 100644 --- a/npm/ng-packs/packages/identity/src/public-api.ts +++ b/npm/ng-packs/packages/identity/src/public-api.ts @@ -4,7 +4,7 @@ export * from './lib/identity.module'; export * from './lib/actions/identity.actions'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/components'; export * from './lib/models/identity'; export * from './lib/services'; diff --git a/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts b/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts index 721a5a5991..a5b319ccb1 100644 --- a/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts +++ b/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts @@ -2,6 +2,7 @@ import { Injectable, Injector } from '@angular/core'; import { addAbpRoutes, eLayoutType, PatchRouteByName, ABP } from '@abp/ng.core'; import { getSettingTabs } from '@abp/ng.theme.shared'; import { Store } from '@ngxs/store'; +import { eSettingManagementRouteNames } from '@abp/ng.setting-management'; @Injectable({ providedIn: 'root', @@ -13,7 +14,7 @@ export class SettingManagementConfigService { constructor(private injector: Injector) { const route = { - name: 'AbpSettingManagement::Settings', + name: eSettingManagementRouteNames.Settings, path: 'setting-management', parentName: 'AbpUiNavigation::Menu:Administration', requiredPolicy: 'AbpAccount.SettingManagement', diff --git a/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts b/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts b/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..86ad145f5f --- /dev/null +++ b/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts @@ -0,0 +1,3 @@ +export const enum eSettingManagementRouteNames { + Settings = 'AbpSettingManagement::Settings', +} diff --git a/npm/ng-packs/packages/setting-management/src/public-api.ts b/npm/ng-packs/packages/setting-management/src/public-api.ts index ac030c3ccf..24e62d4da1 100644 --- a/npm/ng-packs/packages/setting-management/src/public-api.ts +++ b/npm/ng-packs/packages/setting-management/src/public-api.ts @@ -1,3 +1,3 @@ export * from './lib/setting-management.module'; export * from './lib/components/setting-management.component'; -export * from './lib/enums/components'; +export * from './lib/enums'; diff --git a/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts b/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts index 48a6d31c84..ab5ca4a081 100644 --- a/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts +++ b/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts @@ -1,25 +1,34 @@ import { Injectable } from '@angular/core'; import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; - +import { eTenantManagementRouteNames } from '@abp/ng.tenant-management'; @Injectable({ providedIn: 'root', }) export class TenantManagementConfigService { constructor() { - addAbpRoutes({ - name: 'AbpTenantManagement::Menu:TenantManagement', - path: 'tenant-management', - parentName: 'AbpUiNavigation::Menu:Administration', - layout: eLayoutType.application, - iconClass: 'fa fa-users', - children: [ - { - path: 'tenants', - name: 'AbpTenantManagement::Tenants', - order: 1, - requiredPolicy: 'AbpTenantManagement.Tenants', - }, - ], - }); + addAbpRoutes([ + { + name: eTenantManagementRouteNames.Administration, + path: '', + order: 1, + wrapper: true, + iconClass: 'fa fa-wrench', + }, + { + name: eTenantManagementRouteNames.TenantManagement, + path: 'tenant-management', + parentName: eTenantManagementRouteNames.Administration, + layout: eLayoutType.application, + iconClass: 'fa fa-users', + children: [ + { + path: 'tenants', + name: eTenantManagementRouteNames.Tenants, + order: 1, + requiredPolicy: 'AbpTenantManagement.Tenants', + }, + ], + }, + ]); } } diff --git a/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts b/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts b/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..8125e28a4f --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts @@ -0,0 +1,5 @@ +export const enum eTenantManagementRouteNames { + Administration = 'AbpUiNavigation::Menu:Administration', + TenantManagement = 'AbpTenantManagement::Menu:TenantManagement', + Tenants = 'AbpTenantManagement::Tenants', +} diff --git a/npm/ng-packs/packages/tenant-management/src/public-api.ts b/npm/ng-packs/packages/tenant-management/src/public-api.ts index 003074b2c4..3fc7cf90d5 100644 --- a/npm/ng-packs/packages/tenant-management/src/public-api.ts +++ b/npm/ng-packs/packages/tenant-management/src/public-api.ts @@ -1,7 +1,7 @@ export * from './lib/tenant-management.module'; export * from './lib/actions'; export * from './lib/components'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/models'; export * from './lib/services'; export * from './lib/states'; From e8e5aa28c5d934b3ce2deb8ce6d67e1fc04e9640 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 28 Apr 2020 14:51:38 +0300 Subject: [PATCH 53/80] fix(core): resolve wrapper routes duplication problem resolves #3703 --- .../core/src/lib/plugins/config.plugin.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts index f9c8f45423..b2afab6072 100644 --- a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts +++ b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts @@ -10,7 +10,7 @@ import { } from '@ngxs/store'; import clone from 'just-clone'; import snq from 'snq'; -import { ABP } from '../models'; +import { ABP } from '../models/common'; import { getAbpRoutes, organizeRoutes } from '../utils/route-utils'; export const NGXS_CONFIG_PLUGIN_OPTIONS = new InjectionToken('NGXS_CONFIG_PLUGIN_OPTIONS'); @@ -30,8 +30,8 @@ export class ConfigPlugin implements NgxsPlugin { if (isInitAction && !this.initialized) { const transformedRoutes = transformRoutes(this.router.config); - let { routes } = transformedRoutes; - const { wrappers } = transformedRoutes; + let { routes, wrappers } = transformedRoutes; + wrappers = reduceWrappers(wrappers); routes = organizeRoutes(routes, wrappers); const flattedRoutes = flatRoutes(clone(routes)); @@ -118,3 +118,14 @@ function flatRoutes(routes: ABP.FullRoute[]): ABP.FullRoute[] { return flat(routes); } + +function reduceWrappers(wrappers: ABP.FullRoute[] = []) { + const existingWrappers = new Set(); + + return wrappers.filter(wrapper => { + if (existingWrappers.has(wrapper.name)) return false; + + existingWrappers.add(wrapper.name); + return true; + }); +} From 200a27dc1371a8a40547a7f274d399385b8e78bf Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 28 Apr 2020 19:59:32 +0800 Subject: [PATCH 54/80] Translate getting--started document --- .../Getting-Started-Angular-Template.md | 9 +- .../Getting-Started-AspNetCore-Application.md | 2 +- ...Getting-Started-AspNetCore-MVC-Template.md | 104 +---- .../Getting-Started-With-Startup-Templates.md | 8 +- docs/zh-Hans/Getting-Started.md | 411 ++++++++++++++++++ docs/zh-Hans/Tutorials/Part-1.md | 1 + docs/zh-Hans/Tutorials/Part-2.md | 1 + docs/zh-Hans/Tutorials/Part-3.md | 1 + docs/zh-Hans/docs-nav.json | 8 +- docs/zh-Hans/docs-params.json | 28 ++ 10 files changed, 461 insertions(+), 112 deletions(-) create mode 100644 docs/zh-Hans/Getting-Started.md create mode 100644 docs/zh-Hans/Tutorials/Part-1.md create mode 100644 docs/zh-Hans/Tutorials/Part-2.md create mode 100644 docs/zh-Hans/Tutorials/Part-3.md create mode 100644 docs/zh-Hans/docs-params.json diff --git a/docs/zh-Hans/Getting-Started-Angular-Template.md b/docs/zh-Hans/Getting-Started-Angular-Template.md index d2ec83293b..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-Angular-Template.md +++ b/docs/zh-Hans/Getting-Started-Angular-Template.md @@ -1,3 +1,8 @@ -## Getting Started With the Angular Application Template +# 启动模板入门 -TODO... \ No newline at end of file +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: + +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) + + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md index 46348ecbdf..9d195562fd 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md @@ -2,7 +2,7 @@ 本教程将介绍如何开始以最少的依赖关系开始使用ABP开发. -通常情况下你需要下载一个 ***[启动模板](https://abp.io/Templates)*** +通常情况下你需要下载一个 ***[启动模板](Getting-Started-AspNetCore-MVC-Template.md)*** ### 创建一个新项目 diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md index 2778f3db42..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md @@ -1,102 +1,8 @@ -## ASP.NET Core MVC 模板入门 +# 启动模板入门 -### 创建新项目 +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: -本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面. +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) -如果你之前未安装,请使用命令行安装ABP CLI: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -在空文件夹中使用 `abp new` 命令来创建项目: - -````bash -abp new Acme.BookStore -```` - -> 你可以使用不同级别的命名空间; 例如BookStore, Acme.BookStore或Acme.Retail.BookStore. - -`new` 命令创建**分层MVC应用程序**, **Entity Framework Core**作为数据库提供程序. 但是,它还有其他选择. 有关所有可用选项,请参见[CLI文档](CLI.md) - -#### 预先要求 - -创建项目的要求: - -* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) -* [Yarn v1.19+](https://classic.yarnpkg.com/) - -### 解决方案结构 - -在**Visual Studio**中打开解决方案: - -![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png) - -该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目,可与**EF Core**和**SQLite**数据库内存一起使用. - -> 请参阅[应用程序模板文档](Startup-Templates/Application.md)以详细了解解决方案结构. - -### 数据库连接字符串 - -查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**: - -````json -{ - "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" - } -} -```` - -解决方案使用 **Entity Framework Core** 和 **MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串. - -### 创建数据库并应用数据库迁移 - -你有两个选项来创建数据库. - -#### 使用DbMigrator应用程序 - -该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用. - -> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串. - -右键单击`.DbMigrator`项目并选择 **设置为启动项目**: - -![set-as-startup-project](images/set-as-startup-project.png) - -按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: - -![set-as-startup-project](images/db-migrator-app.png) - -#### 使用EF Core Update-Database命令 - -Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: - -![set-as-startup-project](images/set-as-startup-project.png) - -打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: - -![pcm-update-database](images/pcm-update-database-v2.png) - -这将基于配置的连接字符串创建新数据库. - -> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. - -### 运行应用程序 - -你现在可以运行应用程序,它将会打开**home**页面: - -![bookstore-homepage](images/bookstore-homepage.png) - -单击 **登录** 按钮, 输入用户名`admin`, 密码`1q2w3E*`, 登录应用程序. - -启动模板包括**身份管理**和**租户管理**模块. 登录后,将显示"管理"菜单, 你可以在其中管理**租户**,**角色**,**用户**和**权限**. 用户管理页面如下所示: - -![bookstore-user-management](images/bookstore-user-management-v2.png) - -### 下一步是什么? - -* [应用程序开发教程](Tutorials/AspNetCore-Mvc/Part-I.md) + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-With-Startup-Templates.md b/docs/zh-Hans/Getting-Started-With-Startup-Templates.md index 2b23350612..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-With-Startup-Templates.md +++ b/docs/zh-Hans/Getting-Started-With-Startup-Templates.md @@ -1,6 +1,8 @@ # 启动模板入门 -参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板: +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: -* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md) -* [Angular UI模板入门](Getting-Started-Angular-Template.md) \ No newline at end of file +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) + + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md new file mode 100644 index 0000000000..a2b3013fe5 --- /dev/null +++ b/docs/zh-Hans/Getting-Started.md @@ -0,0 +1,411 @@ +## 入门 + +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF", "Mongo"], + "Tiered": ["Yes", "No"] +} +```` + +本教程介绍了如何创建一个新的{{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}}. 配置并运行它. + +## 设置你的开发环境 + +创建第一个项目之前,需要正确的设置你的开发环境. + +### 预先要求 + +你需要安装以下工具: + +* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). +* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) + +* [Node v12+](https://nodejs.org) +* [Yarn v1.19+](https://classic.yarnpkg.com/) + +> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. + +### 安装ABP CLI + +[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. + +> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. + +你需要使用以下命令安排ABP CLI: + +````shell +dotnet tool install -g Volo.Abp.Cli +```` + +如果你已经安装,你可以使用以下命令更新到最新版本: + +````shell +dotnet tool update -g Volo.Abp.Cli +```` + +## 创建新项目 + +> 本文假设你使用 **{{ UI_Value }}** 做为UI框架 **{{ DB_Value }}** 做为数据库提供程序,对于其它选项,你可以更改文档顶部的首选项. + +### 使用ABP CLI创建一个新项目 + +使用ABP CLI的 `new` 命令创建新项目: + +````shell +abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} +```` + +* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md). + +{{ if UI == "NG" }} + +* `-u` 指定UI框架, 本例中是 `angular`. + +{{ if Tiered == "Yes" }} + +* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. + +{{ end }} + +{{ end }} + +{{ if DB == "Mongo" }} + +* `-d` 指定数据库提供程序, 本例中是 `mongodb`. + +{{ end }} + +{{ if Tiered == "Yes" && UI != "NG" }} + +* `--tiered` 参数用于创建n层解决方案,其中身份验证服务器层,UI层和API层在物理上是分离的. + +{{ end }} + +> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. + +## 解决方案结构 + +{{ if UI == "MVC" }} + +创建项目后你会有以下解决方案目录和文件: + +![](images/solution-files-mvc.png) + +在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构: + +{{if DB == "Mongo"}} + +![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png) + +{{else}} + +![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png) + +{{end}} + +{{ else if UI == "NG" }} +在创建的解决方案中有三个文件夹: + +![](images/solution-files-non-mvc.png) + +* `angular` 文件夹包含Angular UI应用程序. +* `aspnet-core` 文件夹包含后端应用程序. +* `react-native` 文件夹包含React Native UI 应用程序. + +打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件: +![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png) + +{{ end }} + +> ###### 关于解决方案中的项目 +> +> 根据你的**UI**,**数据库**和其他选项,你的解决方案的结构可能略有不同. + +该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目. + +{{ if DB == "EF" }} + +集成测试项目已配置为可与 **EF Core** & **SQLite 内存** database同时使用. + +{{ else if DB == "Mongo" }} + +集成测试项目已配置为每个测试创建的内存中的**MongoDB**数据库(使用的[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)库). + +{{ end }} + +> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构. + +## 创建数据库 + +### 数据库连接字符串 + +检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: + +{{ if DB == "EF" }} + +````json +"ConnectionStrings": { + "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" +} +```` + +该解决方案配置为**Entity Framework Core**与**MS SQL Server**一起使用. EF Core支持[各种](https://docs.microsoft.com/en-us/ef/core/providers/)数据库提供程序,因此你可以使用任何受支持的DBMS. 请参阅[Entity Framework集成文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)了解如何切换到另一个DBMS. + +### 数据库连接字符串 + +查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**: + +````json +{ + "ConnectionStrings": { + "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" + } +} +```` + +解决方案使用 **Entity Framework Core** 和 **MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串. + +### 应用迁移 + +该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 你需要应用迁移来创建数据库,有两种方法迁移数据库. + +#### 使用DbMigrator应用程序应用迁移 + +该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用. + +> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串. + +右键单击`.DbMigrator`项目并选择 **设置为启动项目**: + +![set-as-startup-project](images/set-as-startup-project.png) + +按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: + +![set-as-startup-project](images/db-migrator-app.png) + +#### 使用EF Core Update-Database命令 + +Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: + +{{ if UI == "MVC" }} + +右键单击{{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}}项目并选择**设置为启动项目**: + +{{ else if UI != "MVC" }} + +右键单击`.HttpApi.Host`项目并选择**设置为启动项目**: + +{{ end }} + +![set-as-startup-project](images/set-as-startup-project.png) + +打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: + +![package-manager-console-update-database](images/package-manager-console-update-database.png) + +这将基于配置的连接字符串创建新数据库. + +> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. + +{{ else if DB == "Mongo" }} + +````json +"ConnectionStrings": { + "Default": "mongodb://localhost:27017/BookStore" +} +```` + +该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器. + +### 初始化种子数据 + +该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用. + +> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个. + +右键点击 `.DbMigrator` 并选择 **设置为启动项目**. + +![set-as-startup-project](images/set-as-startup-project.png) + + 按F5(或Ctrl+F5)启动应用程序,你会看到以下输出: + + ![db-migrator-output](images/db-migrator-output.png) + +> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. + +{{ end }} + +### 运行应用程序 + +{{ if UI == "MVC" }} + +{{ if Tiered == "Yes" }} + +确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. + +确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. + +![swagger-ui](images/swagger-ui.png) + +这里是Web应用程序使用的API应用程序. + +最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面. + +![mvc-tiered-app-home](images/bookstore-home.png) + +点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序. + +![bookstore-login](images/bookstore-login.png) + +{{ else }} + +最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +![bookstore-login](images/bookstore-login.png) + +{{ end }} + +{{ else if UI != "MVC" }} + +#### 运行HTTP API Host (服务器端) + +{{ if Tiered == "Yes" }} + +确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. + +{{ end }} + +确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. + +{{ if Tiered == "No" }} + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +{{ end }} + +![swagger-ui](images/swagger-ui.png) + +你可以看到应用程序的API并进行测试. 更多信息,请参阅[Swagger UI](https://swagger.io/tools/swagger-ui/). + +> ##### Swagger UI 授权 +> +> 大多数的HTTP API都需要身份验证和授权. 如果你要测试授权API, 请手动进入 `/Account/Login` 页面, 输入用户名: `admin` 和密码: `1q2w3E*` 登录到应用程序. 然后你可以访问授权API. + +{{ end }} + +{{ if UI == "NG" }} +#### 运行 Angular 应用程序 (客户端) + +在 `angular` 下打开命令行终端, 输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). + +```bash +yarn +``` + +等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: + +```bash +yarn start +``` + +等待 `Angular CLI` 使用 `BrowserSync` 启动 `Webpack` dev-server. +它会负责编译你的 `TypeScript`代码, 并自动重新加载浏览器. +完成后 `Angular Live Development Server` 会监听 localhost:4200. +打开你的浏览器并导航到[localhost:4200](http://localhost:4200/). + +![bookstore-login](images/bookstore-login.png) + +{{ end }} + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +![bookstore-home](images/bookstore-home.png) + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. + +#### 移动开发 + +ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. + +> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. + +运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端. + +{{ if Tiered == "No"}} +![React Native host project local IP entry](images/rn-host-local-ip.png) + +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ else if Tiered == "Yes" }} + +![React Native tiered project local IP entry](images/rn-tiered-local-ip.png) + +* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ end }} + +按照**运行HTTP API Host (服务端口)**那样运行后端. + +> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP. + +在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). + +```bash +yarn +``` + +* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址: + +![react native environment local IP](images/rn-environment-local-ip.png) + +{{ if Tiered == "Yes" }} + +> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配. + +{{else}} + +> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配 + +{{ end }} + +等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: + +```bash +yarn start +``` + +等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面. + +![expo-interface](images/rn-expo-interface.png) + +在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序. + +> 请参阅[expo.io](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/)上的[Android Studio模拟器](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/)和[iOS模拟器文档](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/). + +![React Native login screen on iPhone 11](images/rn-login-iphone.png) + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. + +> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. + +## 下一步是什么? + +[应用程序开发教程](Tutorials/Part-1.md) diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md new file mode 100644 index 0000000000..d12e28290b --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -0,0 +1 @@ +TODO .... \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md new file mode 100644 index 0000000000..d12e28290b --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -0,0 +1 @@ +TODO .... \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md new file mode 100644 index 0000000000..d12e28290b --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -0,0 +1 @@ +TODO .... \ No newline at end of file diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 240c42dc99..984fc0270d 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -5,13 +5,7 @@ "items": [ { "text": "从启动模板开始", - "path": "Getting-Started-With-Startup-Templates.md", - "items": [ - { - "text": "ASP.NET Core MVC 模板", - "path": "Getting-Started-AspNetCore-MVC-Template.md" - } - ] + "path": "Getting-Started.md" }, { "text": "从空项目开始", diff --git a/docs/zh-Hans/docs-params.json b/docs/zh-Hans/docs-params.json new file mode 100644 index 0000000000..23d079f9bb --- /dev/null +++ b/docs/zh-Hans/docs-params.json @@ -0,0 +1,28 @@ +{ + "parameters": [ + { + "name": "UI", + "displayName": "UI", + "values": { + "MVC": "MVC / Razor Pages", + "NG": "Angular" + } + }, + { + "name": "DB", + "displayName": "Database", + "values": { + "EF": "Entity Framework Core", + "Mongo": "MongoDB" + } + }, + { + "name": "Tiered", + "displayName": "Tiered", + "values": { + "No": "Not Tiered", + "Yes": "Tiered" + } + } + ] +} \ No newline at end of file From d47b89cfce5130ceb8d69953665574ac65aad79b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 28 Apr 2020 15:06:28 +0300 Subject: [PATCH 55/80] chore(ci): change the build order --- npm/ng-packs/scripts/build.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/scripts/build.ts b/npm/ng-packs/scripts/build.ts index 049b044033..63b2cb593d 100644 --- a/npm/ng-packs/scripts/build.ts +++ b/npm/ng-packs/scripts/build.ts @@ -32,8 +32,9 @@ import program from 'commander'; 'copy', '--angular', '--no-watch', - '--packages', - '@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', + '--all-packages', + '--excluded-packages', + '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', ], { stdout: 'inherit', cwd: '../' }, ); @@ -45,9 +46,8 @@ import program from 'commander'; 'copy', '--angular', '--no-watch', - '--all-packages', - '--excluded-packages', - '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', + '--packages', + '@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', ], { stdout: 'inherit', cwd: '../' }, ); From fe35f33265937df6c4fc30c85fe497e5409ae07f Mon Sep 17 00:00:00 2001 From: Ahmet Date: Tue, 28 Apr 2020 21:57:07 +0300 Subject: [PATCH 56/80] NameFix --- .../Volo/Abp/TextTemplating/TemplateDefinitionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 224b220396..28909330fe 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.TextTemplating Options = options.Value; TemplateDefinitions = - new Lazy>(CreateEmailTemplateDefinitions, true); + new Lazy>(CreateTextTemplateDefinitions, true); } public virtual TemplateDefinition Get(string name) @@ -51,7 +51,7 @@ namespace Volo.Abp.TextTemplating return TemplateDefinitions.Value.GetOrDefault(name); } - protected virtual IDictionary CreateEmailTemplateDefinitions() + protected virtual IDictionary CreateTextTemplateDefinitions() { var templates = new Dictionary(); From 41d1e12a747933df42c5cf0c490ed4109dcb161b Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 29 Apr 2020 00:17:05 +0300 Subject: [PATCH 57/80] contributors reversed for requesting content --- .../Volo/Abp/TextTemplating/TemplateContentProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index d7d7982f16..fdb6a3c9dd 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -52,7 +52,7 @@ namespace Volo.Abp.TextTemplating cultureName ); - foreach (var contentContributorType in Options.ContentContributors) + foreach (var contentContributorType in Options.ContentContributors.Reverse()) { var contributor = (ITemplateContentContributor) scope.ServiceProvider .GetRequiredService(contentContributorType); From b368c61d34144d836501ecad66aad6fd79ef84ec Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 29 Apr 2020 14:22:53 +0800 Subject: [PATCH 58/80] Remove UnitOfWorkAttribute in the IdentityServerSupportedLoginModel --- .../Account/IdentityServerSupportedLoginModel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index e194c1544d..60f55497da 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -29,12 +29,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public IdentityServerSupportedLoginModel( IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions, - IIdentityServerInteractionService interaction, - IClientStore clientStore, + IOptions accountOptions, + IIdentityServerInteractionService interaction, + IClientStore clientStore, IEventService identityServerEvents) :base( - schemeProvider, + schemeProvider, accountOptions) { Interaction = interaction; @@ -95,7 +95,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public override async Task OnPostAsync(string action) { if (action == "Cancel") @@ -116,7 +115,7 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); ExternalProviders = await GetExternalProviders(); - + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); await ReplaceEmailToUsernameOfInputIfNeeds(); @@ -166,7 +165,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectSafely(ReturnUrl, ReturnUrlHash); } - [UnitOfWork] public override async Task OnPostExternalLogin(string provider) { if (AccountOptions.WindowsAuthenticationSchemeName == provider) From 9a0ecfb9948603d36f67c16f511b60022375727b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 12:03:35 +0300 Subject: [PATCH 59/80] Remove old TODO --- .../Abp/Localization/LocalizationResourceContributorList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs index daa6abe667..a81adf3562 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Localization { public LocalizedString GetOrNull(string cultureName, string name) { - foreach (var contributor in this.AsQueryable().Reverse()) //TODO: Reverse? + foreach (var contributor in this.AsQueryable().Reverse()) { var localString = contributor.GetOrNull(cultureName, name); if (localString != null) From f8f595dfc7dd246478385fd633172ebb9438ede3 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 29 Apr 2020 12:14:43 +0300 Subject: [PATCH 60/80] add ABP login with password --- docs/en/CLI.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index b687a5244a..178482f991 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -170,7 +170,11 @@ Some features of the CLI requires to be logged in to abp.io platform. To login w abp login ``` -Notice that, a new login with an already active session, will kill the previous session and creates a new one. +```bash +abp login -p +``` + +Notice that, a new login with an already active session, overwrites the previous session. ### logout From 5aec15a123940977301320b0c99087e10969f35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 12:54:25 +0300 Subject: [PATCH 61/80] Move culture fallback logic to the TemplateContentProvider --- .../ITemplateContentProvider.cs | 6 +- .../TemplateContentContributorContext.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 101 +++++++++++++++--- .../FileInfoLocalizedTemplateContentReader.cs | 2 +- .../ILocalizedTemplateContentReader.cs | 2 +- .../NullLocalizedTemplateContentReader.cs | 4 +- .../VirtualFileTemplateContentContributor.cs | 5 +- ...ualFolderLocalizedTemplateContentReader.cs | 30 +----- 8 files changed, 98 insertions(+), 58 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index 12c8f8f8d8..b352fc0503 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -7,12 +7,14 @@ namespace Volo.Abp.TextTemplating { Task GetContentOrNullAsync( [NotNull] string templateName, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + bool tryDefaults = true ); Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, - [CanBeNull] string cultureName = null + [CanBeNull] string cultureName = null, + bool tryDefaults = true ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs index 773bf1a0a4..bb304a3344 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -11,17 +11,17 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - [CanBeNull] + [NotNull] public string Culture { get; } public TemplateContentContributorContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider, - [CanBeNull] string culture) + [NotNull] string culture) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - Culture = culture; + Culture = Check.NotNull(culture, nameof(culture)); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index fdb6a3c9dd..dcb5d88850 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { @@ -23,17 +25,19 @@ namespace Volo.Abp.TextTemplating _templateDefinitionManager = templateDefinitionManager; } - public Task GetContentOrNullAsync( + public virtual Task GetContentOrNullAsync( [NotNull] string templateName, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + bool tryDefaults = true) { var template = _templateDefinitionManager.Get(templateName); return GetContentOrNullAsync(template, cultureName); } - public async Task GetContentOrNullAsync( + public virtual async Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, - [CanBeNull] string cultureName = null) + [CanBeNull] string cultureName = null, + bool tryDefaults = true) { Check.NotNull(templateDefinition, nameof(templateDefinition)); @@ -44,30 +48,93 @@ namespace Volo.Abp.TextTemplating ); } + if (cultureName == null) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } + using (var scope = ServiceScopeFactory.CreateScope()) { - var context = new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - cultureName + var contributors = Options.ContentContributors + .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) + .Reverse() + .ToArray(); + + //Try to get from the requested culture + var templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) ); - foreach (var contentContributorType in Options.ContentContributors.Reverse()) + if (templateString != null) + { + return templateString; + } + + if (!tryDefaults) { - var contributor = (ITemplateContentContributor) scope.ServiceProvider - .GetRequiredService(contentContributorType); + return null; + } + + //Try to get from same culture without country code + if (cultureName.Contains("-")) //Example: "tr-TR" + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + CultureHelper.GetBaseCultureName(cultureName) + ) + ); + + if (templateString != null) + { + return templateString; + } + } + + //Try to get from default culture + if (templateDefinition.DefaultCultureName != null) + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + templateDefinition.DefaultCultureName + ) + ); - var templateString = await contributor.GetOrNullAsync(context); if (templateString != null) { return templateString; } } } - - throw new AbpException( - $"None of the template content contributors could get the content for the template '{templateDefinition.Name}'" - ); + + //Not found + return null; + } + + protected virtual async Task GetContentOrNullAsync( + ITemplateContentContributor[] contributors, + TemplateContentContributorContext context) + { + foreach (var contributor in contributors) + { + var templateString = await contributor.GetOrNullAsync(context); + if (templateString != null) + { + return templateString; + } + } + + return null; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 4ff941fb84..3ddfb973f3 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles _content = await fileInfo.ReadAsStringAsync(); } - public string GetContent(string culture, string defaultCultureName) + public string GetContentOrNull(string culture) { return _content; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs index 0b028138cd..611e50b005 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -2,6 +2,6 @@ { public interface ILocalizedTemplateContentReader { - public string GetContent(string culture, string defaultCultureName = null); + public string GetContentOrNull(string culture); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs index 0578a716ae..475559c634 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs @@ -9,9 +9,7 @@ } - public string GetContent( - string culture, - string defaultCultureName = null) + public string GetContentOrNull(string culture) { return null; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index e06336c658..668d1dc3ec 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -21,9 +21,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles var localizedReader = await _localizedTemplateContentReaderFactory .CreateAsync(context.TemplateDefinition); - return localizedReader.GetContent( - context.Culture ?? CultureInfo.CurrentUICulture.Name, - context.TemplateDefinition.DefaultCultureName + return localizedReader.GetContentOrNull( + context.Culture ); } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 0f2a23a2fa..b43ab722b7 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; -using Volo.Abp.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating.VirtualFiles @@ -34,34 +33,9 @@ namespace Volo.Abp.TextTemplating.VirtualFiles } } - public string GetContent(string cultureName, string defaultCultureName) + public string GetContentOrNull(string cultureName) { - var content = _dictionary.GetOrDefault(cultureName); - if (content != null) - { - return content; - } - - if (cultureName.Contains("-")) - { - var baseCultureName = CultureHelper.GetBaseCultureName(cultureName); - content = _dictionary.GetOrDefault(baseCultureName); - if (content != null) - { - return content; - } - } - - if (defaultCultureName != null) - { - content = _dictionary.GetOrDefault(defaultCultureName); - if (content != null) - { - return content; - } - } - - return null; + return _dictionary.GetOrDefault(cultureName); } } } \ No newline at end of file From 720b5d7e2f25bc9cc6fa2afd88a29cbff0f73cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 13:19:25 +0300 Subject: [PATCH 62/80] Add TemplateDefinitionIsInlineLocalized. --- .../Abp/TextTemplating/TemplateDefinition.cs | 2 ++ .../TemplateDefinitionContext.cs | 14 ++++----- .../TemplateDefinitionExtensions.cs | 19 +++++++----- .../TemplateDefinitionManager.cs | 30 +++++++++++++++++++ .../LocalizedTemplateContentReaderFactory.cs | 6 +--- .../TextTemplating/TemplateDefinitionTests.cs | 9 ++++-- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index d165eb4d31..f3b31a769d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -17,6 +17,8 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } + public bool IsInlineLocalized { get; internal set; } + [CanBeNull] public string DefaultCultureName { get; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs index d0911f21ac..04c3876531 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -5,26 +5,26 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinitionContext : ITemplateDefinitionContext { - protected Dictionary TextTemplates { get; } + protected Dictionary Templates { get; } - public TemplateDefinitionContext(Dictionary textTemplates) + public TemplateDefinitionContext(Dictionary templates) { - TextTemplates = textTemplates; + Templates = templates; } public IReadOnlyList GetAll(string name) { - return TextTemplates.Values.ToImmutableList(); + return Templates.Values.ToImmutableList(); } public virtual TemplateDefinition GetOrNull(string name) { - return TextTemplates.GetOrDefault(name); + return Templates.GetOrDefault(name); } public virtual IReadOnlyList GetAll() { - return TextTemplates.Values.ToImmutableList(); + return Templates.Values.ToImmutableList(); } public virtual void Add(params TemplateDefinition[] definitions) @@ -36,7 +36,7 @@ namespace Volo.Abp.TextTemplating foreach (var definition in definitions) { - TextTemplates[definition.Name] = definition; + Templates[definition.Name] = definition; } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs index ef4a0ac9a3..0d17e9969c 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -1,16 +1,11 @@ -using JetBrains.Annotations; +using System.Collections.Generic; +using JetBrains.Annotations; using Volo.Abp.TextTemplating.VirtualFiles; namespace Volo.Abp.TextTemplating { public static class TemplateDefinitionExtensions { - /// - /// - /// - /// - /// - /// public static TemplateDefinition WithVirtualFilePath( [NotNull] this TemplateDefinition templateDefinition, [NotNull] string virtualPath) @@ -22,5 +17,15 @@ namespace Volo.Abp.TextTemplating virtualPath ); } + + public static string GetVirtualFilePathOrNull( + [NotNull] this TemplateDefinition templateDefinition) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition + .Properties + .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; + } } } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index 28909330fe..11081f6dff 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { @@ -78,9 +79,38 @@ namespace Volo.Abp.TextTemplating { provider.PostDefine(context); } + + SetIsInlineLocalized( + templates, + scope.ServiceProvider + ); } return templates; } + + protected virtual void SetIsInlineLocalized( + Dictionary templates, + IServiceProvider serviceProvider) + { + var virtualFileProvider = serviceProvider.GetRequiredService(); + + foreach (var templateDefinition in templates.Values) + { + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + continue; + } + + var fileInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + templateDefinition.IsInlineLocalized = !fileInfo.IsDirectory; + } + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index f4f8384777..b30e5771f4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -55,10 +54,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles protected virtual async Task CreateInternalAsync( TemplateDefinition templateDefinition) { - var virtualPath = templateDefinition - .Properties - .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; - + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); if (virtualPath == null) { return NullLocalizedTemplateContentReader.Instance; diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index b711860089..51c78cc13e 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -15,8 +15,13 @@ namespace Volo.Abp.TextTemplating [Fact] public void Should_Retrieve_Template_Definition_By_Name() { - var definition = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); - definition.Name.ShouldBe(TestTemplates.WelcomeEmail); + var welcomeEmailTemplate = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); + + var forgotPasswordEmailTemplate = _templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); } [Fact] From 12bde17d6688bdfb43dce016199267956764e777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 13:56:25 +0300 Subject: [PATCH 63/80] Revisit getting template content --- .../TemplateContentContributorContext.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 62 +++++++++++++++---- .../Abp/TextTemplating/TemplateDefinition.cs | 2 +- .../Abp/TextTemplating/TemplateRenderer.cs | 21 +++++-- .../FileInfoLocalizedTemplateContentReader.cs | 7 ++- .../ILocalizedTemplateContentReader.cs | 6 +- .../VirtualFileTemplateContentContributor.cs | 3 +- ...ualFolderLocalizedTemplateContentReader.cs | 5 ++ 8 files changed, 86 insertions(+), 26 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs index bb304a3344..773bf1a0a4 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -11,17 +11,17 @@ namespace Volo.Abp.TextTemplating [NotNull] public IServiceProvider ServiceProvider { get; } - [NotNull] + [CanBeNull] public string Culture { get; } public TemplateContentContributorContext( [NotNull] TemplateDefinition templateDefinition, [NotNull] IServiceProvider serviceProvider, - [NotNull] string culture) + [CanBeNull] string culture) { TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); - Culture = Check.NotNull(culture, nameof(culture)); + Culture = culture; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index dcb5d88850..2e9c7af269 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -47,14 +47,12 @@ namespace Volo.Abp.TextTemplating $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" ); } - - if (cultureName == null) - { - cultureName = CultureInfo.CurrentUICulture.Name; - } - + using (var scope = ServiceScopeFactory.CreateScope()) { + var searchCultureName = cultureName ?? + CultureInfo.CurrentUICulture.Name; + var contributors = Options.ContentContributors .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) .Reverse() @@ -66,7 +64,7 @@ namespace Volo.Abp.TextTemplating new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - cultureName + searchCultureName ) ); @@ -77,18 +75,36 @@ namespace Volo.Abp.TextTemplating if (!tryDefaults) { + if (templateDefinition.IsInlineLocalized && cultureName == null) + { + //Try to get culture independent content + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + null + ) + ); + + if (templateString != null) + { + return templateString; + } + } + return null; } //Try to get from same culture without country code - if (cultureName.Contains("-")) //Example: "tr-TR" + if (searchCultureName.Contains("-")) //Example: "tr-TR" { templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - CultureHelper.GetBaseCultureName(cultureName) + CultureHelper.GetBaseCultureName(searchCultureName) ) ); @@ -97,16 +113,16 @@ namespace Volo.Abp.TextTemplating return templateString; } } - - //Try to get from default culture - if (templateDefinition.DefaultCultureName != null) + + if (templateDefinition.IsInlineLocalized) { + //Try to get culture independent content templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - templateDefinition.DefaultCultureName + null ) ); @@ -115,6 +131,26 @@ namespace Volo.Abp.TextTemplating return templateString; } } + else + { + //Try to get from default culture + if (templateDefinition.DefaultCultureName != null) + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + templateDefinition.DefaultCultureName + ) + ); + + if (templateString != null) + { + return templateString; + } + } + } } //Not found diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index f3b31a769d..769b970733 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.TextTemplating [CanBeNull] public Type LocalizationResource { get; set; } - public bool IsInlineLocalized { get; internal set; } + public bool IsInlineLocalized { get; set; } [CanBeNull] public string DefaultCultureName { get; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index 8b67a380f0..6a1fff00ef 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; @@ -35,16 +34,30 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - cultureName ??= CultureInfo.CurrentUICulture.Name; + if (globalContext == null) + { + globalContext = new Dictionary(); + } - using (CultureHelper.Use(cultureName)) + if (cultureName == null) { return await RenderInternalAsync( templateName, - globalContext ?? new Dictionary(), + globalContext, model ); } + else + { + using (CultureHelper.Use(cultureName)) + { + return await RenderInternalAsync( + templateName, + globalContext, + model + ); + } + } } protected virtual async Task RenderInternalAsync( diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 3ddfb973f3..cde913f307 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -14,7 +14,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public string GetContentOrNull(string culture) { - return _content; + if (culture == null) + { + return _content; + } + + return null; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs index 611e50b005..7d837d731d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.TextTemplating.VirtualFiles +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating.VirtualFiles { public interface ILocalizedTemplateContentReader { - public string GetContentOrNull(string culture); + public string GetContentOrNull([CanBeNull] string culture); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs index 668d1dc3ec..ede33a3652 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using System.Threading.Tasks; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating.VirtualFiles diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index b43ab722b7..638a7db190 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -35,6 +35,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles public string GetContentOrNull(string cultureName) { + if (cultureName == null) + { + return null; + } + return _dictionary.GetOrDefault(cultureName); } } From c71550b98ccdff528761ac69df22ce6aad68f5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 29 Apr 2020 14:16:55 +0300 Subject: [PATCH 64/80] Add useCurrentCultureIfCultureNameIsNull option. --- .../ITemplateContentProvider.cs | 6 +- .../TextTemplating/TemplateContentProvider.cs | 83 +++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs index b352fc0503..19248dc161 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -8,13 +8,15 @@ namespace Volo.Abp.TextTemplating Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null, - bool tryDefaults = true + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true ); Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, [CanBeNull] string cultureName = null, - bool tryDefaults = true + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 2e9c7af269..8710068bca 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; @@ -26,9 +27,10 @@ namespace Volo.Abp.TextTemplating } public virtual Task GetContentOrNullAsync( - [NotNull] string templateName, + [NotNull] string templateName, [CanBeNull] string cultureName = null, - bool tryDefaults = true) + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) { var template = _templateDefinitionManager.Get(templateName); return GetContentOrNullAsync(template, cultureName); @@ -37,7 +39,8 @@ namespace Volo.Abp.TextTemplating public virtual async Task GetContentOrNullAsync( [NotNull] TemplateDefinition templateDefinition, [CanBeNull] string cultureName = null, - bool tryDefaults = true) + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) { Check.NotNull(templateDefinition, nameof(templateDefinition)); @@ -47,64 +50,50 @@ namespace Volo.Abp.TextTemplating $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" ); } - + using (var scope = ServiceScopeFactory.CreateScope()) { - var searchCultureName = cultureName ?? - CultureInfo.CurrentUICulture.Name; - - var contributors = Options.ContentContributors - .Select(type => (ITemplateContentContributor) scope.ServiceProvider.GetRequiredService(type)) - .Reverse() - .ToArray(); - - //Try to get from the requested culture - var templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - searchCultureName - ) - ); + string templateString = null; - if (templateString != null) + if (cultureName == null && useCurrentCultureIfCultureNameIsNull) { - return templateString; + cultureName = CultureInfo.CurrentUICulture.Name; } - if (!tryDefaults) + var contributors = CreateTemplateContentContributors(scope.ServiceProvider); + + //Try to get from the requested culture + if (cultureName != null) { - if (templateDefinition.IsInlineLocalized && cultureName == null) - { - //Try to get culture independent content - templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - null - ) - ); + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) + ); - if (templateString != null) - { - return templateString; - } + if (templateString != null) + { + return templateString; } + } + if (!tryDefaults) + { return null; } //Try to get from same culture without country code - if (searchCultureName.Contains("-")) //Example: "tr-TR" + if (cultureName != null && cultureName.Contains("-")) //Example: "tr-TR" { templateString = await GetContentOrNullAsync( contributors, new TemplateContentContributorContext( templateDefinition, scope.ServiceProvider, - CultureHelper.GetBaseCultureName(searchCultureName) + CultureHelper.GetBaseCultureName(cultureName) ) ); @@ -113,7 +102,7 @@ namespace Volo.Abp.TextTemplating return templateString; } } - + if (templateDefinition.IsInlineLocalized) { //Try to get culture independent content @@ -157,6 +146,14 @@ namespace Volo.Abp.TextTemplating return null; } + protected virtual ITemplateContentContributor[] CreateTemplateContentContributors(IServiceProvider serviceProvider) + { + return Options.ContentContributors + .Select(type => (ITemplateContentContributor)serviceProvider.GetRequiredService(type)) + .Reverse() + .ToArray(); + } + protected virtual async Task GetContentOrNullAsync( ITemplateContentContributor[] contributors, TemplateContentContributorContext context) From 9c9b096bb2361468aec346060078d90072ae4d3f Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 29 Apr 2020 20:49:57 +0800 Subject: [PATCH 65/80] Update document --- docs/zh-Hans/CLI.md | 4 ++++ .../UI/Angular/Confirmation-Service.md | 24 ++++++++++++++++++- docs/zh-Hans/UI/Common/Utils/Linked-List.md | 22 ++++++++--------- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 3b440ed722..658545c4fd 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -166,6 +166,10 @@ CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录 abp login ``` +```bash +abp login -p +``` + 请注意,新的登录将终止先前的会话并创建一个新的会话. ### logout diff --git a/docs/zh-Hans/UI/Angular/Confirmation-Service.md b/docs/zh-Hans/UI/Angular/Confirmation-Service.md index d6ff4bc3c5..a1a181837c 100644 --- a/docs/zh-Hans/UI/Angular/Confirmation-Service.md +++ b/docs/zh-Hans/UI/Angular/Confirmation-Service.md @@ -86,10 +86,32 @@ this.confirmation.warn( - `messageLocalizationParams`是用于消息本地化的插值参数. - `titleLocalizationParams` 是标题本地化的插值参数. -With the options above, the confirmation popup looks like this: +使用以上选项确认弹层窗口如下所示: ![confirmation](./images/confirmation.png) +你可以传递HTML字符串作为标题,消息或按钮文本. 例如: + +```js +const options: Partial = { + yesText: 'Yes, delete it', +}; + +this.confirmation.warn( + ` + Role Demo will be deleted +
+ Do you confirm that? + `, + 'Are you sure?', + options, +); +``` + +由于这些值现在是HTML,因此应该手动处理本地化. 参阅[LocalizationService](./Localization#using-the-localization-service)了解如何实现. + +> 注意,Angular会清除所有字符串,并且并非每个HTML字符串都可以使用. 仅显示被Angular视为"安全"的值. + ### 如何删除一个确认弹层 打开的确认弹出窗口可以通过 `clear` 方法手动删除: diff --git a/docs/zh-Hans/UI/Common/Utils/Linked-List.md b/docs/zh-Hans/UI/Common/Utils/Linked-List.md index 871db960ab..53c356628f 100644 --- a/docs/zh-Hans/UI/Common/Utils/Linked-List.md +++ b/docs/zh-Hans/UI/Common/Utils/Linked-List.md @@ -1229,7 +1229,7 @@ find(predicate: ListIteratorFunction): ListNode | undefined 从链表中找到与给定谓词匹配的第一个节点: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1251,7 +1251,7 @@ findIndex(predicate: ListIteratorFunction): number 从链表中找到与给定谓词匹配的第一个节点的位置: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1279,7 +1279,7 @@ get(position: number): ListNode | undefined 查找并返回链表中特定位置的节点: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1303,7 +1303,7 @@ indexOf(value: T, compareFn?: ListComparisonFn): number 在链表中找到匹配给定值的第一个节点位置: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1325,7 +1325,7 @@ i3 === -1 你可以自定义比较器 ```js -list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); +list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} @@ -1365,7 +1365,7 @@ forEach(iteratorFn: ListIteratorFn): void 从头到尾在链表中的所有节点上运行回调函数: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1381,7 +1381,7 @@ list.forEach((node, index) => console.log(node.value + index)); 链表是可迭代的. 换句话说你可以使用诸如`for ... of`之类的方法. ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1405,7 +1405,7 @@ toArray(): T[] 转换链表值为数组: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1427,7 +1427,7 @@ toNodeArray(): T[] 转换链表节点为数组: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1449,7 +1449,7 @@ toString(mapperFn: ListMapperFn = JSON.stringify): string 将链表转换为节点及其关系的字符串表示形式: ```js -list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); +list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]); // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} @@ -1507,7 +1507,7 @@ export class ListNode { - `previous`引用列表中的上一个节点. ```js -list.addTailMany([ 0, 1, 2 ]); +list.addManyTail([ 0, 1, 2 ]); console.log( list.head.value, // 0 From c10f97c1d1ff3c480599f7efd11b04cc3d25556a Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 29 Apr 2020 17:02:08 +0300 Subject: [PATCH 66/80] TemplateDefinition DisplayName added --- .../Volo.Abp.TextTemplating.csproj | 1 + .../TextTemplating/AbpTextTemplatingModule.cs | 4 ++- .../TextTemplating/TemplateContentProvider.cs | 25 ++++++++----------- .../Abp/TextTemplating/TemplateDefinition.cs | 25 ++++++++++++++++--- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index 404259ae17..7bcc0400b5 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -19,6 +19,7 @@ + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index aca0ac8f53..8d76391829 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating { [DependsOn( - typeof(AbpVirtualFileSystemModule) + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationAbstractionsModule) )] public class AbpTextTemplatingModule : AbpModule { diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 8710068bca..55a3979e21 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -63,21 +63,18 @@ namespace Volo.Abp.TextTemplating var contributors = CreateTemplateContentContributors(scope.ServiceProvider); //Try to get from the requested culture - if (cultureName != null) - { - templateString = await GetContentOrNullAsync( - contributors, - new TemplateContentContributorContext( - templateDefinition, - scope.ServiceProvider, - cultureName - ) - ); + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) + ); - if (templateString != null) - { - return templateString; - } + if (templateString != null) + { + return templateString; } if (!tryDefaults) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index 769b970733..db2ccf6f63 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -1,14 +1,29 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { public class TemplateDefinition { + public const int MaxNameLength = 128; + [NotNull] public string Name { get; } + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set + { + Check.NotNull(value, nameof(value)); + _displayName = value; + } + } + private ILocalizableString _displayName; + public bool IsLayout { get; } [CanBeNull] @@ -44,14 +59,16 @@ namespace Volo.Abp.TextTemplating public Dictionary Properties { get; } public TemplateDefinition( - [NotNull] string name, - [CanBeNull] Type localizationResource = null, + [NotNull] string name, + [CanBeNull] Type localizationResource = null, + [CanBeNull] ILocalizableString displayName = null, bool isLayout = false, - string layout = null, + string layout = null, string defaultCultureName = null) { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); LocalizationResource = localizationResource; + DisplayName = displayName ?? new FixedLocalizableString(Name); IsLayout = isLayout; Layout = layout; DefaultCultureName = defaultCultureName; From 89e6a8a7e2cd98c455832bbe025fb9a26063145b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 29 Apr 2020 19:22:18 +0300 Subject: [PATCH 67/80] feat(theme-basic): add eNavigationElementNames --- .../application-layout/application-layout.component.html | 8 +++++++- .../application-layout/application-layout.component.ts | 7 ++++--- npm/ng-packs/packages/theme-basic/src/lib/enums/index.ts | 2 ++ .../theme-basic/src/lib/enums/navigation-element-names.ts | 4 ++++ npm/ng-packs/packages/theme-basic/src/public-api.ts | 3 ++- 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 npm/ng-packs/packages/theme-basic/src/lib/enums/index.ts create mode 100644 npm/ng-packs/packages/theme-basic/src/lib/enums/navigation-element-names.ts diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html index 2687c2cf81..438c315c16 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html @@ -5,7 +5,13 @@ >
- +