From 04482b686c63fdadebd4c6c1db7595af1140cfc7 Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Mon, 27 May 2024 08:35:29 +0200 Subject: [PATCH] feat(api-headless-cms): expose a RichTextRenderer utility --- .../richText/RichTextPluginsProcessor.ts | 10 ++--- .../richText/richTextResolver.ts | 38 +++++-------------- .../htmlRenderer/createLexicalHTMLRenderer.ts | 4 +- packages/api-headless-cms/src/index.ts | 1 + .../src/plugins/CmsRichTextRendererPlugin.ts | 12 +++--- .../src/utils/RichTextRenderer.ts | 35 +++++++++++++++++ 6 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 packages/api-headless-cms/src/utils/RichTextRenderer.ts diff --git a/packages/api-headless-cms/src/graphqlFields/richText/RichTextPluginsProcessor.ts b/packages/api-headless-cms/src/graphqlFields/richText/RichTextPluginsProcessor.ts index cd47db75d31..7c7f46738a0 100644 --- a/packages/api-headless-cms/src/graphqlFields/richText/RichTextPluginsProcessor.ts +++ b/packages/api-headless-cms/src/graphqlFields/richText/RichTextPluginsProcessor.ts @@ -1,7 +1,7 @@ -import { CmsRichTextRendererPlugin, RTEContents } from "~/plugins"; +import { CmsRichTextRendererPlugin, RichTextContents } from "~/plugins"; interface RichTextRenderer { - render(contents: RTEContents): Promise; + render(contents: RichTextContents): Promise; } class NullRenderer implements RichTextRenderer { @@ -20,7 +20,7 @@ export class RichTextPluginsProcessor { ); } - async render(contents: RTEContents) { + async render(contents: RichTextContents) { return this.renderer.render(contents); } } @@ -34,11 +34,11 @@ class RichTextRendererDecorator implements RichTextRenderer { this.plugin = plugin; } - render(contents: RTEContents) { + render(contents: RichTextContents) { return this.plugin.render(contents, this.next); } - private next = (contents: RTEContents) => { + private next = (contents: RichTextContents) => { return this.renderer.render(contents); }; } diff --git a/packages/api-headless-cms/src/graphqlFields/richText/richTextResolver.ts b/packages/api-headless-cms/src/graphqlFields/richText/richTextResolver.ts index f47850aa9fb..a645d00ce7d 100644 --- a/packages/api-headless-cms/src/graphqlFields/richText/richTextResolver.ts +++ b/packages/api-headless-cms/src/graphqlFields/richText/richTextResolver.ts @@ -1,29 +1,7 @@ -import { Context } from "@webiny/api/types"; import { GraphQLFieldResolver } from "@webiny/handler-graphql/types"; -import { CmsModelField } from "~/types"; -import { CmsRichTextRendererPlugin, RTEContents, RichTextRenderer } from "~/plugins"; -import { RichTextPluginsProcessor } from "./RichTextPluginsProcessor"; - -const renderersMap = new Map>>(); - -const getRendererByType = async (format: string, context: Context) => { - if (!renderersMap.has(format)) { - const renderersFactory = new Promise>(resolve => { - const plugins = context.plugins - .byType>(CmsRichTextRendererPlugin.type) - .filter(plugin => plugin.format === format); - - const renderer = new RichTextPluginsProcessor(plugins); - - resolve((contents: RTEContents) => { - return renderer.render(contents); - }); - }); - renderersMap.set(format, renderersFactory); - } - - return renderersMap.get(format) as Promise>; -}; +import { CmsContext, CmsModelField } from "~/types"; +import { RichTextContents } from "~/plugins"; +import { RichTextRenderer } from "~/utils/RichTextRenderer"; interface ResolverArgs { format?: string; @@ -33,19 +11,21 @@ export const createRichTextResolver = ( field: CmsModelField ): GraphQLFieldResolver => { return async (entry, args, context) => { - const rawValue = entry[field.fieldId] as RTEContents; + const rawValue = entry[field.fieldId] as RichTextContents; const outputFormat = args.format; if (!outputFormat) { return rawValue; } - const render = await getRendererByType(outputFormat, context); + const renderer = RichTextRenderer.create(context as CmsContext); if (field.multipleValues) { - return rawValue.map(render); + return (rawValue as RichTextContents[]).map(value => + renderer.render(outputFormat, value) + ); } - return render(rawValue); + return renderer.render(outputFormat, rawValue); }; }; diff --git a/packages/api-headless-cms/src/htmlRenderer/createLexicalHTMLRenderer.ts b/packages/api-headless-cms/src/htmlRenderer/createLexicalHTMLRenderer.ts index d43bd93d9de..304eac534dd 100644 --- a/packages/api-headless-cms/src/htmlRenderer/createLexicalHTMLRenderer.ts +++ b/packages/api-headless-cms/src/htmlRenderer/createLexicalHTMLRenderer.ts @@ -1,7 +1,7 @@ import type { SerializedEditorState } from "@webiny/lexical-converter"; -import { CmsRichTextRendererPlugin, RTEContents } from "~/plugins"; +import { CmsRichTextRendererPlugin, RichTextContents } from "~/plugins"; -const isLexicalContents = (contents: RTEContents): contents is SerializedEditorState => { +const isLexicalContents = (contents: RichTextContents): contents is SerializedEditorState => { return contents.hasOwnProperty("root"); }; diff --git a/packages/api-headless-cms/src/index.ts b/packages/api-headless-cms/src/index.ts index 1afe7ac5d03..abbea1c0e6b 100644 --- a/packages/api-headless-cms/src/index.ts +++ b/packages/api-headless-cms/src/index.ts @@ -58,5 +58,6 @@ export const createHeadlessCmsContext = (params: ContentContextParams) => { export * from "~/graphqlFields"; export * from "~/plugins"; export * from "~/utils/incrementEntryIdVersion"; +export * from "~/utils/RichTextRenderer"; export * from "./graphql/handleRequest"; export { entryToStorageTransform, entryFieldFromStorageTransform, entryFromStorageTransform }; diff --git a/packages/api-headless-cms/src/plugins/CmsRichTextRendererPlugin.ts b/packages/api-headless-cms/src/plugins/CmsRichTextRendererPlugin.ts index f7590970ef8..e96d1bd69f7 100644 --- a/packages/api-headless-cms/src/plugins/CmsRichTextRendererPlugin.ts +++ b/packages/api-headless-cms/src/plugins/CmsRichTextRendererPlugin.ts @@ -1,15 +1,15 @@ import { Plugin } from "@webiny/plugins"; -export interface RTEContents { +export interface RichTextContents { [key: string]: any; } -export interface RichTextRenderer { - (contents: RTEContents): Promise; +export interface CmsRichTextRenderer { + (contents: RichTextContents): Promise; } export interface RichTextRendererMiddleware { - (contents: RTEContents, next: RichTextRenderer): Promise | T; + (contents: RichTextContents, next: CmsRichTextRenderer): Promise | T; } interface CmsRichTextRendererConstructorParams { @@ -17,7 +17,7 @@ interface CmsRichTextRendererConstructorParams { render: RichTextRendererMiddleware; } -export class CmsRichTextRendererPlugin extends Plugin { +export class CmsRichTextRendererPlugin extends Plugin { public static override readonly type: string = "cms-rich-text-renderer"; private readonly outputFormat: string; private readonly renderer: RichTextRendererMiddleware; @@ -32,7 +32,7 @@ export class CmsRichTextRendererPlugin extends Plugin { return this.outputFormat; } - async render(contents: RTEContents, next: RichTextRenderer) { + async render(contents: RichTextContents, next: CmsRichTextRenderer) { return this.renderer(contents, next); } } diff --git a/packages/api-headless-cms/src/utils/RichTextRenderer.ts b/packages/api-headless-cms/src/utils/RichTextRenderer.ts new file mode 100644 index 00000000000..caa1dc2e36a --- /dev/null +++ b/packages/api-headless-cms/src/utils/RichTextRenderer.ts @@ -0,0 +1,35 @@ +import { CmsRichTextRendererPlugin, RichTextContents } from "~/plugins"; +import { CmsContext } from "~/types"; +import { RichTextPluginsProcessor } from "~/graphqlFields/richText/RichTextPluginsProcessor"; + +export class RichTextRenderer { + private renderersMap = new Map(); + private plugins: CmsRichTextRendererPlugin[] = []; + + private constructor(plugins: CmsRichTextRendererPlugin[]) { + this.plugins = plugins; + } + + static create(context: CmsContext) { + const rendererPlugins = context.plugins.byType( + CmsRichTextRendererPlugin.type + ); + return new RichTextRenderer(rendererPlugins); + } + + async render(format: string, contents: RichTextContents) { + const renderer = this.getRendererByType(format); + + return renderer.render(contents); + } + + private getRendererByType(format: string) { + if (!this.renderersMap.has(format)) { + const plugins = this.plugins.filter(plugin => plugin.format === format); + + this.renderersMap.set(format, new RichTextPluginsProcessor(plugins)); + } + + return this.renderersMap.get(format) as RichTextPluginsProcessor; + } +}