From fd4689daba333b269293c7a306b080e094809961 Mon Sep 17 00:00:00 2001 From: Rick Butterfield Date: Wed, 7 Aug 2024 11:48:49 +0100 Subject: [PATCH] Fixes #59 #55 --- src/Umbraco.Cms.13.x/Umbraco.Cms.13.x.csproj | 9 ++ .../AppSettings.cs | 21 ++++ .../BlockPreviewSchemaGenerator.cs | 81 ++++++++++++++ .../Options.cs | 12 ++ .../Program.cs | 39 +++++++ ...munity.BlockPreview.SchemaGenerator.csproj | 27 +++++ src/Umbraco.Community.BlockPreview.sln | 6 + .../BlockPreviewComposer.cs | 3 + .../BlockPreviewOptions.cs | 34 ++++-- .../Constants.cs | 1 + .../Controllers/BlockPreviewApiController.cs | 19 +++- .../BlockGridPreviewTemplateExtensions.cs | 2 +- .../BlockPreviewUmbracoBuilderExtensions.cs | 1 + .../IBackOfficeRtePreviewService.cs | 6 + .../Services/BackOfficeGridPreviewService.cs | 2 +- .../Services/BackOfficeListPreviewService.cs | 2 +- .../Services/BackOfficePreviewServiceBase.cs | 103 ++++++++++++------ .../Services/BackOfficeRtePreviewService.cs | 74 +++++++++++++ .../Umbraco.Community.BlockPreview.csproj | 11 +- .../BlockPreviewViewEngineOptionsSetup.cs | 12 +- .../appsettings-schema.blockpreview.json | 43 ++++++++ .../Umbraco.Community.BlockPreview.targets | 5 + .../css/block-preview.css | 0 .../controllers/block-preview.controller.js | 15 ++- .../js/directives/bind-compile.directive.js | 0 .../directives/published-check.directive.js | 0 .../js/resources/preview.resource.js | 6 +- .../views/block-preview.html | 0 28 files changed, 475 insertions(+), 59 deletions(-) create mode 100644 src/Umbraco.Community.BlockPreview.SchemaGenerator/AppSettings.cs create mode 100644 src/Umbraco.Community.BlockPreview.SchemaGenerator/BlockPreviewSchemaGenerator.cs create mode 100644 src/Umbraco.Community.BlockPreview.SchemaGenerator/Options.cs create mode 100644 src/Umbraco.Community.BlockPreview.SchemaGenerator/Program.cs create mode 100644 src/Umbraco.Community.BlockPreview.SchemaGenerator/Umbraco.Community.BlockPreview.SchemaGenerator.csproj create mode 100644 src/Umbraco.Community.BlockPreview/Interfaces/IBackOfficeRtePreviewService.cs create mode 100644 src/Umbraco.Community.BlockPreview/Services/BackOfficeRtePreviewService.cs create mode 100644 src/Umbraco.Community.BlockPreview/appsettings-schema.blockpreview.json create mode 100644 src/Umbraco.Community.BlockPreview/buildTransitive/Umbraco.Community.BlockPreview.targets rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/css/block-preview.css (100%) rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js (86%) rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/js/directives/bind-compile.directive.js (100%) rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/js/directives/published-check.directive.js (100%) rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/js/resources/preview.resource.js (79%) rename src/Umbraco.Community.BlockPreview/wwwroot/{ => App_Plugins}/Umbraco.Community.BlockPreview/views/block-preview.html (100%) diff --git a/src/Umbraco.Cms.13.x/Umbraco.Cms.13.x.csproj b/src/Umbraco.Cms.13.x/Umbraco.Cms.13.x.csproj index 91a04ca..a240fcd 100644 --- a/src/Umbraco.Cms.13.x/Umbraco.Cms.13.x.csproj +++ b/src/Umbraco.Cms.13.x/Umbraco.Cms.13.x.csproj @@ -5,6 +5,15 @@ enable Umbraco.Cms._13.x + + + + + + \ + true + + diff --git a/src/Umbraco.Community.BlockPreview.SchemaGenerator/AppSettings.cs b/src/Umbraco.Community.BlockPreview.SchemaGenerator/AppSettings.cs new file mode 100644 index 0000000..a607724 --- /dev/null +++ b/src/Umbraco.Community.BlockPreview.SchemaGenerator/AppSettings.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Umbraco.Community.BlockPreview.SchemaGenerator +{ + internal class AppSettings + { + public BlockPreviewDefinition BlockPreview { get; set; } + + internal class BlockPreviewDefinition + { + public ViewLocations ViewLocations { get; set; } + } + } + + public class ViewLocations + { + public List BlockList { get; set; } + public List BlockGrid { get; set; } + public List RichText { get; set; } + } +} diff --git a/src/Umbraco.Community.BlockPreview.SchemaGenerator/BlockPreviewSchemaGenerator.cs b/src/Umbraco.Community.BlockPreview.SchemaGenerator/BlockPreviewSchemaGenerator.cs new file mode 100644 index 0000000..1d6bf2a --- /dev/null +++ b/src/Umbraco.Community.BlockPreview.SchemaGenerator/BlockPreviewSchemaGenerator.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; +using NJsonSchema.Generation; +using NJsonSchema.NewtonsoftJson.Generation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Community.BlockPreview.SchemaGenerator +{ + internal class BlockPreviewSchemaGenerator + { + private readonly JsonSchemaGenerator _schemaGenerator; + + public BlockPreviewSchemaGenerator() + { + _schemaGenerator = new JsonSchemaGenerator(new BlockPreviewSchemaGeneratorSettings()); + } + + public string Generate() + { + var blockPreviewSchema = GenerateBlockPreviewSchema(); + return blockPreviewSchema.ToString(); + } + + private JObject GenerateBlockPreviewSchema() + { + var schema = _schemaGenerator.Generate(typeof(AppSettings)); + return JsonConvert.DeserializeObject(schema.ToJson()); + } + } + + internal class BlockPreviewSchemaGeneratorSettings : NewtonsoftJsonSchemaGeneratorSettings + { + public BlockPreviewSchemaGeneratorSettings() + { + AlwaysAllowAdditionalObjectProperties = true; + SerializerSettings = new JsonSerializerSettings() + { + ContractResolver = new WritablePropertiesOnlyResolver(), + }; + DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull; + SchemaNameGenerator = new NamespacePrefixedSchemaNameGenerator(); + SerializerSettings.Converters.Add(new StringEnumConverter()); + IgnoreObsoleteProperties = true; + GenerateExamples = true; + } + + private class WritablePropertiesOnlyResolver : DefaultContractResolver + { + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) + { + IList props = base.CreateProperties(type, memberSerialization); + var result = props.Where(p => p.Writable).ToList(); + result.ForEach(x => x.PropertyName = ToPascalCase(x.PropertyName)); + return result; + } + + /// + /// we serialize everything camel case inside uSync but the settings are actually PascalCase + /// for appsettings.json, so we need to PascalCase each property. + /// + private string ToPascalCase(string str) + { + if (!string.IsNullOrEmpty(str)) + { + return char.ToUpperInvariant(str[0]) + str.Substring(1); + } + + return str; + } + } + } + + internal class NamespacePrefixedSchemaNameGenerator : DefaultSchemaNameGenerator + { + public override string Generate(Type type) => type.Namespace.Replace(".", string.Empty) + base.Generate(type); + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview.SchemaGenerator/Options.cs b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Options.cs new file mode 100644 index 0000000..eac02c9 --- /dev/null +++ b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Options.cs @@ -0,0 +1,12 @@ +using CommandLine; + +namespace Umbraco.Community.BlockPreview.SchemaGenerator +{ + internal class Options + { + [Option('o', "outputFile", Required = false, + HelpText = "", + Default = "..\\..\\..\\..\\Umbraco.Community.BlockPreview\\appsettings-schema.blockpreview.json")] + public string OutputFile { get; set; } = "..\\..\\..\\..\\Umbraco.Community.BlockPreview\\appsettings-schema.blockpreview.json"; + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview.SchemaGenerator/Program.cs b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Program.cs new file mode 100644 index 0000000..db4a1b6 --- /dev/null +++ b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Program.cs @@ -0,0 +1,39 @@ +using CommandLine; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Umbraco.Community.BlockPreview.SchemaGenerator +{ + internal class Program + { + public static async Task Main(string[] args) + { + try + { + await Parser.Default.ParseArguments(args) + .WithParsedAsync(Execute); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + private static async Task Execute(Options options) + { + var generator = new BlockPreviewSchemaGenerator(); + + var schema = generator.Generate(); + + var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, options.OutputFile)); + Console.WriteLine("Path to use {0}", path); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + Console.WriteLine("Ensured directory exists"); + await File.WriteAllTextAsync(path, schema); + + Console.WriteLine("File written at {0}", path); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview.SchemaGenerator/Umbraco.Community.BlockPreview.SchemaGenerator.csproj b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Umbraco.Community.BlockPreview.SchemaGenerator.csproj new file mode 100644 index 0000000..44cdfa0 --- /dev/null +++ b/src/Umbraco.Community.BlockPreview.SchemaGenerator/Umbraco.Community.BlockPreview.SchemaGenerator.csproj @@ -0,0 +1,27 @@ + + + + Exe + net8.0 + disable + + + + + + + + + + + bin\Release\$(TargetFramework)\Umbraco.Community.BlockPreview.SchemaGenerator.xml + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview.sln b/src/Umbraco.Community.BlockPreview.sln index a6796f4..b689116 100644 --- a/src/Umbraco.Community.BlockPreview.sln +++ b/src/Umbraco.Community.BlockPreview.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.11.x", "Umbraco EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.13.x", "Umbraco.Cms.13.x\Umbraco.Cms.13.x.csproj", "{782BE0CF-6D6D-42B3-82F6-8415CAB3B911}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Community.BlockPreview.SchemaGenerator", "Umbraco.Community.BlockPreview.SchemaGenerator\Umbraco.Community.BlockPreview.SchemaGenerator.csproj", "{924CBE49-2AC7-4B8B-8977-967BB3221F36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {782BE0CF-6D6D-42B3-82F6-8415CAB3B911}.Debug|Any CPU.Build.0 = Debug|Any CPU {782BE0CF-6D6D-42B3-82F6-8415CAB3B911}.Release|Any CPU.ActiveCfg = Release|Any CPU {782BE0CF-6D6D-42B3-82F6-8415CAB3B911}.Release|Any CPU.Build.0 = Release|Any CPU + {924CBE49-2AC7-4B8B-8977-967BB3221F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {924CBE49-2AC7-4B8B-8977-967BB3221F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {924CBE49-2AC7-4B8B-8977-967BB3221F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {924CBE49-2AC7-4B8B-8977-967BB3221F36}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Umbraco.Community.BlockPreview/BlockPreviewComposer.cs b/src/Umbraco.Community.BlockPreview/BlockPreviewComposer.cs index ab1ddf0..96bc024 100644 --- a/src/Umbraco.Community.BlockPreview/BlockPreviewComposer.cs +++ b/src/Umbraco.Community.BlockPreview/BlockPreviewComposer.cs @@ -35,6 +35,9 @@ public void Compose(IUmbracoBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +#if NET8_0 + builder.Services.AddScoped(); +#endif builder.Services.AddScoped(); builder.Services.ConfigureOptions(); diff --git a/src/Umbraco.Community.BlockPreview/BlockPreviewOptions.cs b/src/Umbraco.Community.BlockPreview/BlockPreviewOptions.cs index 2ec53c4..9faa3a8 100644 --- a/src/Umbraco.Community.BlockPreview/BlockPreviewOptions.cs +++ b/src/Umbraco.Community.BlockPreview/BlockPreviewOptions.cs @@ -1,16 +1,35 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Community.BlockPreview +namespace Umbraco.Community.BlockPreview { public class BlockPreviewOptions { + public ViewLocations ViewLocations { get; set; } + + public List? GetAllViewLocations() + { + var locations = new List(); + + if (ViewLocations.BlockGrid?.Any() == true) + locations.AddRange(ViewLocations.BlockGrid); + + if (ViewLocations.BlockList?.Any() == true) + locations.AddRange(ViewLocations.BlockList); + + if (ViewLocations.RichText?.Any() == true) + locations.AddRange(ViewLocations.RichText); + + locations.Add(Constants.DefaultViewLocations.BlockGrid); + locations.Add(Constants.DefaultViewLocations.BlockList); + locations.Add(Constants.DefaultViewLocations.RichText); + + locations = locations.Distinct().ToList(); + + return locations; + } + public BlockPreviewOptions() { ViewLocations = new ViewLocations(); } - - public ViewLocations ViewLocations { get; set; } } public class ViewLocations @@ -19,12 +38,13 @@ public ViewLocations() { BlockList = new List(); BlockGrid = new List(); + RichText = new List(); } public List BlockList { get; set; } public List BlockGrid { get; set; } - public IEnumerable GetAll() => BlockList.Concat(BlockGrid); + public List RichText { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview/Constants.cs b/src/Umbraco.Community.BlockPreview/Constants.cs index 271c07e..47643c7 100644 --- a/src/Umbraco.Community.BlockPreview/Constants.cs +++ b/src/Umbraco.Community.BlockPreview/Constants.cs @@ -6,6 +6,7 @@ public static partial class DefaultViewLocations { public static string BlockGrid => "/Views/Partials/blockgrid/Components/{0}.cshtml"; public static string BlockList => "/Views/Partials/blocklist/Components/{0}.cshtml"; + public static string RichText => "/Views/Partials/richtext/Components/{0}.cshtml"; } public static partial class Configuration diff --git a/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs b/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs index 904587a..06fa250 100644 --- a/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs +++ b/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs @@ -28,6 +28,7 @@ public class BlockPreviewApiController : UmbracoAuthorizedJsonController private readonly ContextCultureService _contextCultureService; private readonly IBackOfficeListPreviewService _backOfficeListPreviewService; private readonly IBackOfficeGridPreviewService _backOfficeGridPreviewService; + private readonly IBackOfficeRtePreviewService _backOfficeRtePreviewService; private readonly ILocalizationService _localizationService; private readonly ISiteDomainMapper _siteDomainMapper; @@ -41,6 +42,7 @@ public BlockPreviewApiController( ContextCultureService contextCultureSwitcher, IBackOfficeListPreviewService backOfficeListPreviewService, IBackOfficeGridPreviewService backOfficeGridPreviewService, + IBackOfficeRtePreviewService backOfficeRtePreviewService, ILocalizationService localizationService, ISiteDomainMapper siteDomainMapper) { @@ -50,6 +52,7 @@ public BlockPreviewApiController( _contextCultureService = contextCultureSwitcher; _backOfficeListPreviewService = backOfficeListPreviewService; _backOfficeGridPreviewService = backOfficeGridPreviewService; + _backOfficeRtePreviewService = backOfficeRtePreviewService; _localizationService = localizationService; _siteDomainMapper = siteDomainMapper; } @@ -68,6 +71,7 @@ public async Task PreviewMarkup( [FromQuery] int pageId = 0, [FromQuery] string blockEditorAlias = "", [FromQuery] bool isGrid = false, + [FromQuery] bool isRte = false, [FromQuery] string culture = "") { string markup; @@ -91,9 +95,18 @@ public async Task PreviewMarkup( await SetupPublishedRequest(page, currentCulture); - markup = isGrid ? - await _backOfficeGridPreviewService.GetMarkupForBlock(page, data, blockEditorAlias, ControllerContext, currentCulture) : - await _backOfficeListPreviewService.GetMarkupForBlock(page, data, blockEditorAlias, ControllerContext, currentCulture); + if (isGrid) + { + markup = await _backOfficeGridPreviewService.GetMarkupForBlock(page, data, blockEditorAlias, ControllerContext, currentCulture); + } + else if (isRte) + { + markup = await _backOfficeRtePreviewService.GetMarkupForBlock(page, data, blockEditorAlias, ControllerContext, currentCulture); + } + else + { + markup = await _backOfficeListPreviewService.GetMarkupForBlock(page, data, blockEditorAlias, ControllerContext, currentCulture); + } } catch (Exception ex) { diff --git a/src/Umbraco.Community.BlockPreview/Extensions/BlockGridPreviewTemplateExtensions.cs b/src/Umbraco.Community.BlockPreview/Extensions/BlockGridPreviewTemplateExtensions.cs index 179bf33..1ec023b 100644 --- a/src/Umbraco.Community.BlockPreview/Extensions/BlockGridPreviewTemplateExtensions.cs +++ b/src/Umbraco.Community.BlockPreview/Extensions/BlockGridPreviewTemplateExtensions.cs @@ -10,7 +10,7 @@ namespace Umbraco.Community.BlockPreview.Extensions { public static class BlockGridPreviewTemplateExtensions { - private static readonly string AREA_TEMPLATE = ""; + private static readonly string AREA_TEMPLATE = ""; public static async Task GetPreviewBlockGridItemAreasHtmlAsync(this IHtmlHelper html, BlockGridItem item, string template = BlockGridTemplateExtensions.DefaultItemAreasTemplate) { diff --git a/src/Umbraco.Community.BlockPreview/Extensions/BlockPreviewUmbracoBuilderExtensions.cs b/src/Umbraco.Community.BlockPreview/Extensions/BlockPreviewUmbracoBuilderExtensions.cs index 630df73..6afb21c 100644 --- a/src/Umbraco.Community.BlockPreview/Extensions/BlockPreviewUmbracoBuilderExtensions.cs +++ b/src/Umbraco.Community.BlockPreview/Extensions/BlockPreviewUmbracoBuilderExtensions.cs @@ -16,6 +16,7 @@ public static IUmbracoBuilder AddBlockPreviewOptions(this IUmbracoBuilder builde { x.ViewLocations.BlockGrid.Add(Constants.DefaultViewLocations.BlockGrid); x.ViewLocations.BlockList.Add(Constants.DefaultViewLocations.BlockList); + x.ViewLocations.RichText.Add(Constants.DefaultViewLocations.RichText); }) .ValidateDataAnnotations() .ValidateOnStart(); diff --git a/src/Umbraco.Community.BlockPreview/Interfaces/IBackOfficeRtePreviewService.cs b/src/Umbraco.Community.BlockPreview/Interfaces/IBackOfficeRtePreviewService.cs new file mode 100644 index 0000000..809686e --- /dev/null +++ b/src/Umbraco.Community.BlockPreview/Interfaces/IBackOfficeRtePreviewService.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Community.BlockPreview.Interfaces +{ + public interface IBackOfficeRtePreviewService : IBackOfficePreviewService + { + } +} diff --git a/src/Umbraco.Community.BlockPreview/Services/BackOfficeGridPreviewService.cs b/src/Umbraco.Community.BlockPreview/Services/BackOfficeGridPreviewService.cs index bab1327..d4ca054 100644 --- a/src/Umbraco.Community.BlockPreview/Services/BackOfficeGridPreviewService.cs +++ b/src/Umbraco.Community.BlockPreview/Services/BackOfficeGridPreviewService.cs @@ -58,7 +58,7 @@ public override async Task GetMarkupForBlock( Type? contentBlockType = FindBlockType(contentTypeAlias); Type? settingsBlockType = settingsElement != null ? FindBlockType(settingsTypeAlias) : default; - object? blockInstance = CreateBlockInstance(true, contentBlockType, contentElement, settingsBlockType, settingsElement, contentData.Udi, settingsData?.Udi); + object? blockInstance = CreateBlockInstance(true, false, contentBlockType, contentElement, settingsBlockType, settingsElement, contentData.Udi, settingsData?.Udi); BlockGridItem? typedBlockInstance = blockInstance as BlockGridItem; diff --git a/src/Umbraco.Community.BlockPreview/Services/BackOfficeListPreviewService.cs b/src/Umbraco.Community.BlockPreview/Services/BackOfficeListPreviewService.cs index e526633..2893a3d 100644 --- a/src/Umbraco.Community.BlockPreview/Services/BackOfficeListPreviewService.cs +++ b/src/Umbraco.Community.BlockPreview/Services/BackOfficeListPreviewService.cs @@ -57,7 +57,7 @@ public override async Task GetMarkupForBlock( Type? contentBlockType = FindBlockType(contentTypeAlias); Type? settingsBlockType = settingsElement != null ? FindBlockType(settingsTypeAlias) : default; - object? blockInstance = CreateBlockInstance(false, contentBlockType, contentElement, settingsBlockType, settingsElement, contentData.Udi, settingsData?.Udi); + object? blockInstance = CreateBlockInstance(false, false, contentBlockType, contentElement, settingsBlockType, settingsElement, contentData.Udi, settingsData?.Udi); BlockListItem? typedBlockInstance = blockInstance as BlockListItem; diff --git a/src/Umbraco.Community.BlockPreview/Services/BackOfficePreviewServiceBase.cs b/src/Umbraco.Community.BlockPreview/Services/BackOfficePreviewServiceBase.cs index 341fbb5..6b62450 100644 --- a/src/Umbraco.Community.BlockPreview/Services/BackOfficePreviewServiceBase.cs +++ b/src/Umbraco.Community.BlockPreview/Services/BackOfficePreviewServiceBase.cs @@ -95,7 +95,7 @@ public virtual ViewDataDictionary CreateViewData(object? typedBlockInstance) return viewData; } - public virtual object? CreateBlockInstance(bool isGrid, Type? contentBlockType, IPublishedElement? contentElement, Type? settingsBlockType, IPublishedElement? settingsElement, Udi? contentUdi, Udi? settingsUdi) + public virtual object? CreateBlockInstance(bool isGrid, bool isRte, Type? contentBlockType, IPublishedElement? contentElement, Type? settingsBlockType, IPublishedElement? settingsElement, Udi? contentUdi, Udi? settingsUdi) { if (contentBlockType != null) { @@ -105,15 +105,37 @@ public virtual ViewDataDictionary CreateViewData(object? typedBlockInstance) Type blockItemType; if (settingsBlockType != null) { - blockItemType = isGrid - ? typeof(BlockGridItem<,>).MakeGenericType(contentBlockType, settingsBlockType) - : typeof(BlockListItem<,>).MakeGenericType(contentBlockType, settingsBlockType); + if (isGrid) + { + blockItemType = typeof(BlockGridItem<,>).MakeGenericType(contentBlockType, settingsBlockType); + } +#if NET8_0 + else if (isRte) + { + blockItemType = typeof(RichTextBlockItem<,>).MakeGenericType(contentBlockType, settingsBlockType); + } +#endif + else + { + blockItemType = typeof(BlockListItem<,>).MakeGenericType(contentBlockType, settingsBlockType); + } } else { - blockItemType = isGrid - ? typeof(BlockGridItem<>).MakeGenericType(contentBlockType) - : typeof(BlockListItem<>).MakeGenericType(contentBlockType); + if (isGrid) + { + blockItemType = typeof(BlockGridItem<>).MakeGenericType(contentBlockType); + } +#if NET8_0 + else if (isRte) + { + blockItemType = typeof(RichTextBlockItem<>).MakeGenericType(contentBlockType); + } +#endif + else + { + blockItemType = typeof(BlockListItem<>).MakeGenericType(contentBlockType); + } } return Activator.CreateInstance(blockItemType, contentUdi, contentInstance, settingsUdi, settingsInstance); @@ -135,42 +157,37 @@ public virtual async Task GetMarkupFromPartial( ControllerContext controllerContext, ViewDataDictionary? viewData, string? contentAlias, - bool isGrid = false) + bool isGrid = false, + bool isRte = false) { - List viewPaths = isGrid ? _options.ViewLocations.BlockGrid : _options.ViewLocations.BlockList; + var viewResult = FindViewResult(contentAlias); - foreach (var viewPath in viewPaths) + if (viewResult == null) { - string formattedViewPath = string.Format($"~{viewPath}", contentAlias); - ViewEngineResult viewResult = _razorViewEngine.GetView("", formattedViewPath, false); + viewResult = + _razorViewEngine.FindView(controllerContext, contentAlias!, false) ?? + _razorViewEngine.FindView(controllerContext, contentAlias?.ToPascalCase()!, false); if (!viewResult.Success) - { - formattedViewPath = string.Format($"~{viewPath}", contentAlias?.ToPascalCase()); - viewResult = _razorViewEngine.GetView("", formattedViewPath, false); - - if (!viewResult.Success) - continue; - } + return string.Empty; + } - var actionContext = new ActionContext(controllerContext.HttpContext, new RouteData(), new ActionDescriptor()); - if (viewResult?.View == null) - continue; + var actionContext = new ActionContext(controllerContext.HttpContext, new RouteData(), new ActionDescriptor()); - await using var sw = new StringWriter(); + if (viewResult.View == null) + return string.Empty; - if (viewData != null) - { - var viewContext = new ViewContext(actionContext, viewResult.View, viewData, - new TempDataDictionary(actionContext.HttpContext, _tempDataProvider), sw, new HtmlHelperOptions()); + await using var sw = new StringWriter(); - await viewResult.View.RenderAsync(viewContext); - } + if (viewData != null) + { + var viewContext = new ViewContext(actionContext, viewResult.View, viewData, + new TempDataDictionary(actionContext.HttpContext, _tempDataProvider), sw, new HtmlHelperOptions()); - return sw.ToString(); + await viewResult.View.RenderAsync(viewContext); } - return string.Empty; + return sw.ToString(); } public virtual async Task GetMarkupFromViewComponent( @@ -204,6 +221,30 @@ public virtual async Task GetMarkupForBlock( return await Task.FromResult(string.Empty); } + private ViewEngineResult? FindViewResult(string? contentAlias) + { + var viewPaths = _options.GetAllViewLocations(); + + if (viewPaths == null || !viewPaths.Any()) + return null; + + foreach (var viewPath in viewPaths) + { + var formattedViewPath = $"~{viewPath}"; + var viewResult = _razorViewEngine.GetView("", string.Format(formattedViewPath, contentAlias), false); + + if (viewResult.Success) + return viewResult; + + viewResult = _razorViewEngine.GetView("", string.Format(formattedViewPath, contentAlias?.ToPascalCase()), false); + + if (viewResult.Success) + return viewResult; + } + + return null; + } + private sealed class FakeView : IView { public string Path => string.Empty; diff --git a/src/Umbraco.Community.BlockPreview/Services/BackOfficeRtePreviewService.cs b/src/Umbraco.Community.BlockPreview/Services/BackOfficeRtePreviewService.cs new file mode 100644 index 0000000..8c25659 --- /dev/null +++ b/src/Umbraco.Community.BlockPreview/Services/BackOfficeRtePreviewService.cs @@ -0,0 +1,74 @@ +#if NET8_0 +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Community.BlockPreview.Interfaces; + +namespace Umbraco.Community.BlockPreview.Services +{ + public sealed class BackOfficeRtePreviewService : BackOfficePreviewServiceBase, IBackOfficeRtePreviewService + { + private readonly ContextCultureService _contextCultureService; + + public BackOfficeRtePreviewService( + BlockEditorConverter blockEditorConverter, + ContextCultureService contextCultureService, + ITempDataProvider tempDataProvider, + ITypeFinder typeFinder, + IPublishedValueFallback publishedValueFallback, + IViewComponentHelperWrapper viewComponentHelperWrapper, + IViewComponentSelector viewComponentSelector, + IOptions options, + IRazorViewEngine razorViewEngine) : base(tempDataProvider, viewComponentHelperWrapper, razorViewEngine, typeFinder, blockEditorConverter, viewComponentSelector, publishedValueFallback, options) + { + _contextCultureService = contextCultureService; + } + + public override async Task GetMarkupForBlock( + IPublishedContent page, + BlockValue blockValue, + string blockEditorAlias, + ControllerContext controllerContext, + string? culture) + { + if (!string.IsNullOrEmpty(culture)) + { + _contextCultureService.SetCulture(culture); + } + + BlockItemData? contentData = blockValue.ContentData.FirstOrDefault(); + BlockItemData? settingsData = blockValue.SettingsData.FirstOrDefault(); + + if (contentData != null) + { + ConvertNestedValuesToString(contentData); + + IPublishedElement? contentElement = ConvertToElement(contentData, true); + string? contentTypeAlias = contentElement?.ContentType.Alias; + + IPublishedElement? settingsElement = settingsData != null ? ConvertToElement(settingsData, true) : default; + string? settingsTypeAlias = settingsElement?.ContentType.Alias; + + Type? contentBlockType = FindBlockType(contentTypeAlias); + Type? settingsBlockType = settingsElement != null ? FindBlockType(settingsTypeAlias) : default; + + object? blockInstance = CreateBlockInstance(isGrid: false, isRte: true, contentBlockType, contentElement, settingsBlockType, settingsElement, contentData.Udi, settingsData?.Udi); + + RichTextBlockItem? typedBlockInstance = blockInstance as RichTextBlockItem; + + ViewDataDictionary? viewData = CreateViewData(typedBlockInstance); + + return await GetMarkup(controllerContext, contentTypeAlias, viewData); + } + + return string.Empty; + } + } +} +#endif \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview/Umbraco.Community.BlockPreview.csproj b/src/Umbraco.Community.BlockPreview/Umbraco.Community.BlockPreview.csproj index 44cde47..5b997a3 100644 --- a/src/Umbraco.Community.BlockPreview/Umbraco.Community.BlockPreview.csproj +++ b/src/Umbraco.Community.BlockPreview/Umbraco.Community.BlockPreview.csproj @@ -3,9 +3,10 @@ net6.0;net7.0;net8.0 enable enable - App_Plugins + / . + Umbraco.Community.BlockPreview Umbraco.Community.BlockPreview Umbraco.Community.BlockPreview Umbraco.Community.BlockPreview @@ -13,10 +14,9 @@ umbraco plugin package block list grid umbraco-marketplace MIT - Umbraco.Community.BlockPreview True - 1.8.4 + 1.9.0 Rick Butterfield, Dave Woestenborghs, Matthew Wise $([System.DateTime]::UtcNow.ToString(`yyyy`)) © Rick Butterfield @@ -47,6 +47,10 @@ + + + + True @@ -56,5 +60,6 @@ True \ + diff --git a/src/Umbraco.Community.BlockPreview/ViewEngines/BlockPreviewViewEngineOptionsSetup.cs b/src/Umbraco.Community.BlockPreview/ViewEngines/BlockPreviewViewEngineOptionsSetup.cs index c311fb6..260a0d2 100644 --- a/src/Umbraco.Community.BlockPreview/ViewEngines/BlockPreviewViewEngineOptionsSetup.cs +++ b/src/Umbraco.Community.BlockPreview/ViewEngines/BlockPreviewViewEngineOptionsSetup.cs @@ -1,8 +1,5 @@ using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; namespace Umbraco.Community.BlockPreview.ViewEngines { @@ -18,13 +15,10 @@ public BlockViewEngineOptionsSetup(IOptions options) _options = options.Value; } - public void Configure(RazorViewEngineOptions options) { if (options == null) - { throw new ArgumentNullException(nameof(options)); - } options.ViewLocationExpanders.Add(new BlockViewLocationExpander(_options)); } @@ -39,9 +33,11 @@ public BlockViewLocationExpander(BlockPreviewOptions options) } public IEnumerable ExpandViewLocations( - ViewLocationExpanderContext context, IEnumerable viewLocations) + ViewLocationExpanderContext context, + IEnumerable viewLocations) { - return _options.ViewLocations.GetAll().Concat(viewLocations); + var customViewLocations = _options.GetAllViewLocations(); + return viewLocations.Concat(customViewLocations!); } // not a dynamic expander diff --git a/src/Umbraco.Community.BlockPreview/appsettings-schema.blockpreview.json b/src/Umbraco.Community.BlockPreview/appsettings-schema.blockpreview.json new file mode 100644 index 0000000..aa9f37e --- /dev/null +++ b/src/Umbraco.Community.BlockPreview/appsettings-schema.blockpreview.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "UmbracoCommunityBlockPreviewSchemaGeneratorAppSettings", + "type": "object", + "properties": { + "BlockPreview": { + "$ref": "#/definitions/UmbracoCommunityBlockPreviewSchemaGeneratorBlockPreviewDefinition" + } + }, + "definitions": { + "UmbracoCommunityBlockPreviewSchemaGeneratorBlockPreviewDefinition": { + "type": "object", + "properties": { + "ViewLocations": { + "$ref": "#/definitions/UmbracoCommunityBlockPreviewSchemaGeneratorViewLocations" + } + } + }, + "UmbracoCommunityBlockPreviewSchemaGeneratorViewLocations": { + "type": "object", + "properties": { + "BlockList": { + "type": "array", + "items": { + "type": "string" + } + }, + "BlockGrid": { + "type": "array", + "items": { + "type": "string" + } + }, + "RichText": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview/buildTransitive/Umbraco.Community.BlockPreview.targets b/src/Umbraco.Community.BlockPreview/buildTransitive/Umbraco.Community.BlockPreview.targets new file mode 100644 index 0000000..22ecddf --- /dev/null +++ b/src/Umbraco.Community.BlockPreview/buildTransitive/Umbraco.Community.BlockPreview.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/css/block-preview.css b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/css/block-preview.css similarity index 100% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/css/block-preview.css rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/css/block-preview.css diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js similarity index 86% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js index 7a142c6..816bd2b 100644 --- a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js +++ b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/controllers/block-preview.controller.js @@ -16,16 +16,27 @@ $scope.id = current.id; $scope.loading = true; - $scope.markup = '
Loading preview
'; + $scope.markup = $sce.trustAsHtml('
Loading preview
'); // There must be a better way to do this... $scope.blockEditorAlias = ''; var parent = $scope.$parent; + $scope.isGrid = false; + $scope.isRte = false; + while (parent.$parent) { if (parent.vm) { if (parent.vm.constructor.name == 'BlockGridController') { $scope.blockEditorAlias = parent.vm.model.editor; + $scope.isGrid = true; + break; + } + } + + if (parent.model) { + if (parent.model.constructor.name == 'umbRteBlockController') { + $scope.isRte = true; break; } } @@ -43,7 +54,7 @@ settingsData: [settings || $scope.block.settingsData] }; - previewResource.getPreview(formattedBlockData, $scope.id, $scope.blockEditorAlias, $scope.model.constructor.name == 'BlockGridBlockController', $scope.language).then(function (data) { + previewResource.getPreview(formattedBlockData, $scope.id, $scope.blockEditorAlias, $scope.isGrid, $scope.isRte, $scope.language).then(function (data) { $scope.markup = $sce.trustAsHtml(data); $scope.loading = false; }); diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/directives/bind-compile.directive.js b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/directives/bind-compile.directive.js similarity index 100% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/directives/bind-compile.directive.js rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/directives/bind-compile.directive.js diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/directives/published-check.directive.js b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/directives/published-check.directive.js similarity index 100% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/directives/published-check.directive.js rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/directives/published-check.directive.js diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/resources/preview.resource.js b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/resources/preview.resource.js similarity index 79% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/resources/preview.resource.js rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/resources/preview.resource.js index 926ed72..6101bb9 100644 --- a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/js/resources/preview.resource.js +++ b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/js/resources/preview.resource.js @@ -11,11 +11,13 @@ return resource; - function getPreview(data, pageId, blockEditorAlias, isGrid, culture) { + function getPreview(data, pageId, blockEditorAlias, isGrid, isRte, culture) { culture = culture || ''; + isGrid = isGrid || false; + isRte = isRte || false return umbRequestHelper.resourcePromise( - $http.post(`${apiUrl}?pageId=${pageId}&blockEditorAlias=${blockEditorAlias}&isGrid=${isGrid}&culture=${culture}`, data), + $http.post(`${apiUrl}?pageId=${pageId}&blockEditorAlias=${blockEditorAlias}&isGrid=${isGrid}&isRte=${isRte}&culture=${culture}`, data), 'Failed getting block preview markup' ); }; diff --git a/src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/views/block-preview.html b/src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/views/block-preview.html similarity index 100% rename from src/Umbraco.Community.BlockPreview/wwwroot/Umbraco.Community.BlockPreview/views/block-preview.html rename to src/Umbraco.Community.BlockPreview/wwwroot/App_Plugins/Umbraco.Community.BlockPreview/views/block-preview.html