From 4444dd791c777f59ef120a9a6c968567c968f187 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Wed, 26 Apr 2023 15:12:17 -0400 Subject: [PATCH] feat: improve cms event types (#769) --- packages/core/src/backend.ts | 16 +++--- packages/core/src/components/App.tsx | 2 +- packages/core/src/interface.ts | 32 ++++++++--- packages/core/src/lib/registry.ts | 65 +++++++++++++++++----- packages/docs/content/docs/widget-uuid.mdx | 1 + 5 files changed, 86 insertions(+), 30 deletions(-) diff --git a/packages/core/src/backend.ts b/packages/core/src/backend.ts index ba99d609..2c78d51e 100644 --- a/packages/core/src/backend.ts +++ b/packages/core/src/backend.ts @@ -59,6 +59,7 @@ import type { Entry, EntryData, EntryDraft, + EventData, FilterRule, ImplementationEntry, MediaField, @@ -68,7 +69,6 @@ import type { UnknownField, User, } from './interface'; -import type { AllowedEvent } from './lib/registry'; import type { AsyncLock } from './lib/util'; import type { RootState } from './store'; import type AssetProxy from './valueObjects/AssetProxy'; @@ -908,17 +908,19 @@ export class Backend { const { login, name = '' } = (await this.currentUser()) as User; - return await invokeEvent({ name: event, data: { entry, author: { login, name } } }); + return { entry, author: { login, name } }; } - async invokePreSaveEvent(entry: Entry) { - return await this.invokeEventWithEntry('preSave', entry); + async invokePreSaveEvent(entry: Entry): Promise { + const eventData = await this.getEventData(entry); + return await invokeEvent('preSave', eventData); } - async invokePostSaveEvent(entry: Entry) { - await this.invokeEventWithEntry('postSave', entry); + async invokePostSaveEvent(entry: Entry): Promise { + const eventData = await this.getEventData(entry); + await invokeEvent('postSave', eventData); } async persistMedia(config: Config, file: AssetProxy) { diff --git a/packages/core/src/components/App.tsx b/packages/core/src/components/App.tsx index 6a90341c..9280ba8c 100644 --- a/packages/core/src/components/App.tsx +++ b/packages/core/src/components/App.tsx @@ -232,7 +232,7 @@ const App = ({ useEffect(() => { setTimeout(() => { - invokeEvent({ name: 'mounted' }); + invokeEvent('mounted'); }); }, []); diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 72a19e33..4972876c 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -825,16 +825,32 @@ export interface EventData { author: { login: string | undefined; name: string }; } -export type EventListenerOptions = Record; +export type PreSaveEventHandler = Record> = ( + data: EventData, + options: O, +) => EntryData | undefined | null | void | Promise; -export type EventListenerHandler = ( - data: EventData | undefined, - options: EventListenerOptions, -) => Promise; +export type PostSaveEventHandler = Record> = ( + data: EventData, + options: O, +) => void | Promise; -export interface EventListener { - name: AllowedEvent; - handler: EventListenerHandler; +export type MountedEventHandler = Record> = ( + options: O, +) => void | Promise; + +export type EventHandlers = Record> = { + preSave: PreSaveEventHandler; + postSave: PostSaveEventHandler; + mounted: MountedEventHandler; +}; + +export interface EventListener< + E extends AllowedEvent = 'mounted', + O extends Record = Record, +> { + name: E; + handler: EventHandlers[E]; } export interface AdditionalLinkOptions { diff --git a/packages/core/src/lib/registry.ts b/packages/core/src/lib/registry.ts index 1541fba3..3e7028a1 100644 --- a/packages/core/src/lib/registry.ts +++ b/packages/core/src/lib/registry.ts @@ -9,11 +9,15 @@ import type { Config, CustomIcon, Entry, + EntryData, EventData, EventListener, FieldPreviewComponent, LocalePhrasesRoot, + MountedEventHandler, ObjectValue, + PostSaveEventHandler, + PreSaveEventHandler, PreviewStyle, PreviewStyleOptions, ShortcodeConfig, @@ -29,10 +33,16 @@ import type { export const allowedEvents = ['mounted', 'preSave', 'postSave'] as const; export type AllowedEvent = (typeof allowedEvents)[number]; +type EventHandlerRegistry = { + preSave: { handler: PreSaveEventHandler; options: Record }[]; + postSave: { handler: PostSaveEventHandler; options: Record }[]; + mounted: { handler: MountedEventHandler; options: Record }[]; +}; + const eventHandlers = allowedEvents.reduce((acc, e) => { acc[e] = []; return acc; -}, {} as Record }[]>); +}, {} as EventHandlerRegistry); interface Registry { backends: Record; @@ -44,7 +54,7 @@ interface Registry { additionalLinks: Record; widgetValueSerializers: Record; locales: Record; - eventHandlers: typeof eventHandlers; + eventHandlers: EventHandlerRegistry; previewStyles: PreviewStyle[]; /** Markdown editor */ @@ -309,9 +319,8 @@ export function getBackend( /** * Event Handlers */ -function validateEventName(name: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (!allowedEvents.includes(name as any)) { +function validateEventName(name: AllowedEvent) { + if (!allowedEvents.includes(name)) { throw new Error(`Invalid event name '${name}'`); } } @@ -321,21 +330,48 @@ export function getEventListeners(name: AllowedEvent) { return [...registry.eventHandlers[name]]; } -export function registerEventListener( - { name, handler }: EventListener, - options: Record = {}, -) { +export function registerEventListener< + E extends AllowedEvent, + O extends Record = Record, +>({ name, handler }: EventListener, options?: O) { validateEventName(name); - registry.eventHandlers[name].push({ handler, options }); + registry.eventHandlers[name].push({ + handler: handler as MountedEventHandler & PreSaveEventHandler & PostSaveEventHandler, + options: options ?? {}, + }); } -export async function invokeEvent({ name, data }: { name: AllowedEvent; data?: EventData }) { +export async function invokeEvent(name: 'preSave', data: EventData): Promise; +export async function invokeEvent(name: 'postSave', data: EventData): Promise; +export async function invokeEvent(name: 'mounted'): Promise; +export async function invokeEvent(name: AllowedEvent, data?: EventData): Promise { validateEventName(name); + + if (name === 'mounted') { + console.info('[StaticCMS] Firing mounted event'); + const handlers = registry.eventHandlers[name]; + for (const { handler, options } of handlers) { + handler(options); + } + + return; + } + + if (name === 'postSave') { + console.info(`[StaticCMS] Firing post save event`, data); + const handlers = registry.eventHandlers[name]; + for (const { handler, options } of handlers) { + handler(data!, options); + } + + return; + } + const handlers = registry.eventHandlers[name]; - console.info(`[StaticCMS] Firing event ${name}`, data); + console.info(`[StaticCMS] Firing pre save event`, data); - let _data = data ? { ...data } : undefined; + let _data = { ...data! }; for (const { handler, options } of handlers) { const result = await handler(_data, options); if (_data !== undefined && result !== undefined) { @@ -346,7 +382,8 @@ export async function invokeEvent({ name, data }: { name: AllowedEvent; data?: E _data = { ..._data, entry }; } } - return _data?.entry.data; + + return _data.entry.data; } export function removeEventListener({ name, handler }: EventListener) { diff --git a/packages/docs/content/docs/widget-uuid.mdx b/packages/docs/content/docs/widget-uuid.mdx index 573bea3e..22205509 100644 --- a/packages/docs/content/docs/widget-uuid.mdx +++ b/packages/docs/content/docs/widget-uuid.mdx @@ -17,6 +17,7 @@ For common options, see [Common widget options](/docs/widgets#common-widget-opti | Name | Type | Default | Description | | ---------------- | ------- | ------- | -------------------------------------------------------------------------------------- | | allow_regenerate | boolean | `true` | _Optional_. If set to `false` the `Generate new UUID` button does not appear in the UI | +| prefix | string | | _Optional_. A value to add at the beginning of the UUID | ## Example