diff --git a/src/Umbraco.Community.BlockPreview.UI/src/api/index.ts b/src/Umbraco.Community.BlockPreview.UI/src/api/index.ts index 3731393d..fcc8d64a 100644 --- a/src/Umbraco.Community.BlockPreview.UI/src/api/index.ts +++ b/src/Umbraco.Community.BlockPreview.UI/src/api/index.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -export type * from './types.gen'; +export * from './types.gen'; export * from './client.gen'; export * from './sdk.gen'; diff --git a/src/Umbraco.Community.BlockPreview.UI/src/api/sdk.gen.ts b/src/Umbraco.Community.BlockPreview.UI/src/api/sdk.gen.ts index 5b3cc0c2..889e3e6e 100644 --- a/src/Umbraco.Community.BlockPreview.UI/src/api/sdk.gen.ts +++ b/src/Umbraco.Community.BlockPreview.UI/src/api/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { GetGridStylesheetData, GetGridStylesheetResponses, GetGridStylesheetsData, GetGridStylesheetsResponses, GetListStylesheetData, GetListStylesheetResponses, GetListStylesheetsData, GetListStylesheetsResponses, GetRteStylesheetData, GetRteStylesheetResponses, GetRteStylesheetsData, GetRteStylesheetsResponses, GetSettingsData, GetSettingsResponses, GetSingleBlockStylesheetsData, GetSingleBlockStylesheetsResponses, PreviewGridBlockData, PreviewGridBlockResponses, PreviewListBlockData, PreviewListBlockResponses, PreviewRichTextMarkupData, PreviewRichTextMarkupResponses, PreviewSingleBlockData, PreviewSingleBlockResponses } from './types.gen'; +import type { GetGridStylesheetData, GetGridStylesheetResponses, GetGridStylesheetsData, GetGridStylesheetsResponses, GetListStylesheetData, GetListStylesheetResponses, GetListStylesheetsData, GetListStylesheetsResponses, GetRteStylesheetData, GetRteStylesheetResponses, GetRteStylesheetsData, GetRteStylesheetsResponses, GetSettingsData, GetSettingsResponses, GetSingleBlockStylesheetsData, GetSingleBlockStylesheetsResponses, GetStylesheetsData, GetStylesheetsResponses, PreviewGridBlockData, PreviewGridBlockResponses, PreviewListBlockData, PreviewListBlockResponses, PreviewRichTextMarkupData, PreviewRichTextMarkupResponses, PreviewSingleBlockData, PreviewSingleBlockResponses } from './types.gen'; export type Options = Options2 & { /** @@ -121,6 +121,13 @@ export class BlockPreviewService { }); } + public static getStylesheets(options: Options) { + return (options.client ?? client).get({ + url: '/umbraco/block-preview/api/v1/preview/stylesheets', + ...options + }); + } + public static getSettings(options?: Options) { return (options?.client ?? client).get({ url: '/umbraco/block-preview/api/v1/settings', diff --git a/src/Umbraco.Community.BlockPreview.UI/src/api/types.gen.ts b/src/Umbraco.Community.BlockPreview.UI/src/api/types.gen.ts index 0dc07a7f..8aa12c46 100644 --- a/src/Umbraco.Community.BlockPreview.UI/src/api/types.gen.ts +++ b/src/Umbraco.Community.BlockPreview.UI/src/api/types.gen.ts @@ -11,6 +11,13 @@ export type BlockPreviewOptions = { singleBlock: BlockTypeSettings; }; +export enum BlockType { + BLOCK_GRID = 'BlockGrid', + BLOCK_LIST = 'BlockList', + RICH_TEXT = 'RichText', + SINGLE_BLOCK = 'SingleBlock' +} + export type BlockTypeSettings = { enabled: boolean; viewLocations?: Array | null; @@ -253,6 +260,26 @@ export type GetSingleBlockStylesheetsResponses = { export type GetSingleBlockStylesheetsResponse = GetSingleBlockStylesheetsResponses[keyof GetSingleBlockStylesheetsResponses]; +export type GetStylesheetsData = { + body?: never; + path?: never; + query: { + blockType: BlockType; + nodeKey?: string; + documentTypeUnique?: string; + }; + url: '/umbraco/block-preview/api/v1/preview/stylesheets'; +}; + +export type GetStylesheetsResponses = { + /** + * OK + */ + 200: Array; +}; + +export type GetStylesheetsResponse = GetStylesheetsResponses[keyof GetStylesheetsResponses]; + export type GetSettingsData = { body?: never; path?: never; diff --git a/src/Umbraco.Community.BlockPreview.UI/src/blockEditor/block-grid-preview.custom-view.element.ts b/src/Umbraco.Community.BlockPreview.UI/src/blockEditor/block-grid-preview.custom-view.element.ts index 313256e0..04f49d1f 100644 --- a/src/Umbraco.Community.BlockPreview.UI/src/blockEditor/block-grid-preview.custom-view.element.ts +++ b/src/Umbraco.Community.BlockPreview.UI/src/blockEditor/block-grid-preview.custom-view.element.ts @@ -1,6 +1,7 @@ import { BlockPreviewBaseElement } from './block-preview-base.element'; import { BlockGridContext } from './types'; import { PreviewDataSource } from '../repository'; +import { BlockType } from '../api'; import { css, customElement, property } from "@umbraco-cms/backoffice/external/lit"; import { UMB_BLOCK_GRID_ENTRY_CONTEXT, UMB_BLOCK_GRID_MANAGER_CONTEXT, UmbBlockGridLayoutModel, UmbBlockGridValueModel, UmbBlockGridLayoutAreaItemModel } from "@umbraco-cms/backoffice/block-grid"; import { UMB_CONTENT_WORKSPACE_CONTEXT } from "@umbraco-cms/backoffice/content"; @@ -230,7 +231,7 @@ export class BlockGridPreviewCustomView extends BlockPreviewBaseElement>; getSingleBlockStylesheets(query: StylesheetQuery): Promise>; getRteStylesheets(query: StylesheetQuery): Promise>; + getStylesheets(blockType: BlockType, query: StylesheetQuery): Promise>; } export class PreviewDataSource implements IPreviewDataSource { @@ -77,4 +78,8 @@ export class PreviewDataSource implements IPreviewDataSource { async getRteStylesheets(query: StylesheetQuery): Promise> { return await tryExecute(this.#host, BlockPreviewService.getRteStylesheets({ query })); } + + async getStylesheets(blockType: BlockType, query: StylesheetQuery): Promise> { + return await tryExecute(this.#host, BlockPreviewService.getStylesheets({ query: { ...query, blockType } })); + } } diff --git a/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs b/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs index 57c8e410..fa6eb0ec 100644 --- a/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs +++ b/src/Umbraco.Community.BlockPreview/Controllers/BlockPreviewApiController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; @@ -194,18 +195,10 @@ private async Task RunPreviewAsync( /// A list of stylesheet paths if configured; otherwise, an empty list. [HttpGet("preview/single/stylesheets")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] - public async Task GetSingleBlockStylesheets( + public Task GetSingleBlockStylesheets( [FromQuery] Guid nodeKey = default, [FromQuery] Guid documentTypeUnique = default) - { - IPublishedContent? content = GetPublishedContent(nodeKey, documentTypeUnique); - - await _requestEnricher.EnrichAsync(HttpContext, content); - - var stylesheetPaths = await _blockPreviewService.GetStylesheetPaths(BlockType.SingleBlock, content!, ControllerContext); - - return Ok(stylesheetPaths); - } + => GetStylesheets(BlockType.SingleBlock, nodeKey, documentTypeUnique); /// /// Loads the in-memory settings from appsettings.json @@ -272,6 +265,34 @@ private static BlockTypeSettings ApplyIgnoredContentTypes(BlockTypeSettings orig } + /// + /// Retrieves the stylesheet paths for a block preview of the given type. + /// + /// The block editor type. + /// The key of the node. + /// The unique identifier for the document type. + /// A list of stylesheet paths if configured; otherwise, an empty list. + [HttpGet("preview/stylesheets")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + public async Task GetStylesheets( + [FromQuery][BindRequired] BlockType blockType, + [FromQuery] Guid nodeKey = default, + [FromQuery] Guid documentTypeUnique = default) + { + if (!Enum.IsDefined(typeof(BlockType), blockType)) + { + return BadRequest("Invalid blockType."); + } + + IPublishedContent? content = GetPublishedContent(nodeKey, documentTypeUnique); + + await _requestEnricher.EnrichAsync(HttpContext, content); + + var stylesheetPaths = await _blockPreviewService.GetStylesheetPaths(blockType, content!, ControllerContext); + + return Ok(stylesheetPaths); + } + /// /// Retrieves the stylesheet path for a grid block preview. /// @@ -308,18 +329,10 @@ public async Task GetGridStylesheet( /// A list of stylesheet paths if configured; otherwise, a 404 response. [HttpGet("preview/grid/stylesheets")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] - public async Task GetGridStylesheets( + public Task GetGridStylesheets( [FromQuery] Guid nodeKey = default, [FromQuery] Guid documentTypeUnique = default) - { - IPublishedContent? content = GetPublishedContent(nodeKey, documentTypeUnique); - - await _requestEnricher.EnrichAsync(HttpContext, content); - - var stylesheetPaths = await _blockPreviewService.GetStylesheetPaths(BlockType.BlockGrid, content!, ControllerContext); - - return Ok(stylesheetPaths); - } + => GetStylesheets(BlockType.BlockGrid, nodeKey, documentTypeUnique); /// /// Retrieves the stylesheet path for a list block preview. @@ -357,18 +370,10 @@ public async Task GetListStylesheet( /// A list of stylesheet paths if configured; otherwise, a 404 response. [HttpGet("preview/list/stylesheets")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] - public async Task GetListStylesheets( + public Task GetListStylesheets( [FromQuery] Guid nodeKey = default, [FromQuery] Guid documentTypeUnique = default) - { - IPublishedContent? content = GetPublishedContent(nodeKey, documentTypeUnique); - - await _requestEnricher.EnrichAsync(HttpContext, content); - - var stylesheetPaths = await _blockPreviewService.GetStylesheetPaths(BlockType.BlockList, content!, ControllerContext); - - return Ok(stylesheetPaths); - } + => GetStylesheets(BlockType.BlockList, nodeKey, documentTypeUnique); /// /// Retrieves the stylesheet path for a rich text block preview. @@ -406,18 +411,10 @@ public async Task GetRteStylesheet( /// A list of stylesheet paths if configured; otherwise, a 404 response. [HttpGet("preview/rte/stylesheets")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] - public async Task GetRteStylesheets( + public Task GetRteStylesheets( [FromQuery] Guid nodeKey = default, [FromQuery] Guid documentTypeUnique = default) - { - IPublishedContent? content = GetPublishedContent(nodeKey, documentTypeUnique); - - await _requestEnricher.EnrichAsync(HttpContext, content); - - var stylesheetPaths = await _blockPreviewService.GetStylesheetPaths(BlockType.RichText, content!, ControllerContext); - - return Ok(stylesheetPaths); - } + => GetStylesheets(BlockType.RichText, nodeKey, documentTypeUnique); #endregion #region Private diff --git a/tests/Umbraco.Community.BlockPreview.Tests/Controllers/BlockPreviewApiControllerTests.cs b/tests/Umbraco.Community.BlockPreview.Tests/Controllers/BlockPreviewApiControllerTests.cs index 75444365..48c755bf 100644 --- a/tests/Umbraco.Community.BlockPreview.Tests/Controllers/BlockPreviewApiControllerTests.cs +++ b/tests/Umbraco.Community.BlockPreview.Tests/Controllers/BlockPreviewApiControllerTests.cs @@ -171,14 +171,38 @@ public async Task PreviewRichTextMarkup_PassesRenderDelegateThatCallsRenderRichT } [Test] - public async Task GetGridStylesheets_ResolvesContentViaContentResolverThenAsksBlockPreviewService() + public async Task GetStylesheets_ForBlockGrid_ReturnsPathsFromBlockPreviewService() + { + _blockPreviewService + .Setup(s => s.GetStylesheetPaths(BlockType.BlockGrid, It.IsAny(), It.IsAny())) + .ReturnsAsync(new List { "/css/grid.css" }); + + var result = await _controller.GetStylesheets(BlockType.BlockGrid, Guid.NewGuid(), Guid.NewGuid()); + + var ok = result as OkObjectResult; + Assert.That(ok, Is.Not.Null); + Assert.That(ok!.Value, Is.EqualTo(new List { "/css/grid.css" })); + } + + [Test] + public async Task GetStylesheets_WithUndefinedBlockType_ReturnsBadRequest() + { + var result = await _controller.GetStylesheets((BlockType)99, Guid.NewGuid(), Guid.NewGuid()); + + Assert.That(result, Is.InstanceOf()); + _blockPreviewService.Verify( + s => s.GetStylesheetPaths(It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } + + [Test] + public async Task GetGridStylesheets_DelegatesToGetStylesheetsWithBlockGrid() { var nodeKey = Guid.NewGuid(); var docType = Guid.NewGuid(); var content = Mock.Of(); // IPreviewContentResolver.Resolve's 3rd parameter is `out bool isActualContent`, not an - // out IPublishedContent — the resolved content comes back via the return value instead - // (matches Task 3's PreviewRequestExecutorTests.cs usage of the same interface). + // out IPublishedContent — the resolved content comes back via the return value instead. bool isActualContent = true; _contentResolver.Setup(r => r.Resolve(nodeKey, docType, out isActualContent)).Returns(content); _blockPreviewService @@ -191,5 +215,66 @@ public async Task GetGridStylesheets_ResolvesContentViaContentResolverThenAsksBl Assert.That(ok, Is.Not.Null); Assert.That(ok!.Value, Is.EqualTo(new List { "/css/grid.css" })); _contentResolver.Verify(r => r.Resolve(nodeKey, docType, out isActualContent), Times.Once); + _blockPreviewService.Verify(s => s.GetStylesheetPaths(BlockType.BlockGrid, content, It.IsAny()), Times.Once); + } + + [Test] + public async Task GetListStylesheets_DelegatesToGetStylesheetsWithBlockList() + { + var nodeKey = Guid.NewGuid(); + var docType = Guid.NewGuid(); + var content = Mock.Of(); + bool isActualContent = true; + _contentResolver.Setup(r => r.Resolve(nodeKey, docType, out isActualContent)).Returns(content); + _blockPreviewService + .Setup(s => s.GetStylesheetPaths(BlockType.BlockList, content, It.IsAny())) + .ReturnsAsync(new List { "/css/list.css" }); + + var result = await _controller.GetListStylesheets(nodeKey, docType); + + var ok = result as OkObjectResult; + Assert.That(ok!.Value, Is.EqualTo(new List { "/css/list.css" })); + _contentResolver.Verify(r => r.Resolve(nodeKey, docType, out isActualContent), Times.Once); + _blockPreviewService.Verify(s => s.GetStylesheetPaths(BlockType.BlockList, content, It.IsAny()), Times.Once); + } + + [Test] + public async Task GetRteStylesheets_DelegatesToGetStylesheetsWithRichText() + { + var nodeKey = Guid.NewGuid(); + var docType = Guid.NewGuid(); + var content = Mock.Of(); + bool isActualContent = true; + _contentResolver.Setup(r => r.Resolve(nodeKey, docType, out isActualContent)).Returns(content); + _blockPreviewService + .Setup(s => s.GetStylesheetPaths(BlockType.RichText, content, It.IsAny())) + .ReturnsAsync(new List { "/css/rte.css" }); + + var result = await _controller.GetRteStylesheets(nodeKey, docType); + + var ok = result as OkObjectResult; + Assert.That(ok!.Value, Is.EqualTo(new List { "/css/rte.css" })); + _contentResolver.Verify(r => r.Resolve(nodeKey, docType, out isActualContent), Times.Once); + _blockPreviewService.Verify(s => s.GetStylesheetPaths(BlockType.RichText, content, It.IsAny()), Times.Once); + } + + [Test] + public async Task GetSingleBlockStylesheets_DelegatesToGetStylesheetsWithSingleBlock() + { + var nodeKey = Guid.NewGuid(); + var docType = Guid.NewGuid(); + var content = Mock.Of(); + bool isActualContent = true; + _contentResolver.Setup(r => r.Resolve(nodeKey, docType, out isActualContent)).Returns(content); + _blockPreviewService + .Setup(s => s.GetStylesheetPaths(BlockType.SingleBlock, content, It.IsAny())) + .ReturnsAsync(new List { "/css/single.css" }); + + var result = await _controller.GetSingleBlockStylesheets(nodeKey, docType); + + var ok = result as OkObjectResult; + Assert.That(ok!.Value, Is.EqualTo(new List { "/css/single.css" })); + _contentResolver.Verify(r => r.Resolve(nodeKey, docType, out isActualContent), Times.Once); + _blockPreviewService.Verify(s => s.GetStylesheetPaths(BlockType.SingleBlock, content, It.IsAny()), Times.Once); } }