feat: improve cms event types (#769)

This commit is contained in:
Daniel Lautzenheiser 2023-04-26 15:12:17 -04:00 committed by GitHub
parent b967b61e51
commit 4444dd791c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 30 deletions

View File

@ -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<EF extends BaseField = UnknownField, BC extends BackendClas
return slug;
}
async invokeEventWithEntry(event: AllowedEvent, entry: Entry) {
async getEventData(entry: Entry): Promise<EventData> {
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<EntryData> {
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<void> {
const eventData = await this.getEventData(entry);
await invokeEvent('postSave', eventData);
}
async persistMedia(config: Config, file: AssetProxy) {

View File

@ -232,7 +232,7 @@ const App = ({
useEffect(() => {
setTimeout(() => {
invokeEvent({ name: 'mounted' });
invokeEvent('mounted');
});
}, []);

View File

@ -825,16 +825,32 @@ export interface EventData {
author: { login: string | undefined; name: string };
}
export type EventListenerOptions = Record<string, unknown>;
export type PreSaveEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
data: EventData,
options: O,
) => EntryData | undefined | null | void | Promise<EntryData | undefined | null | void>;
export type EventListenerHandler = (
data: EventData | undefined,
options: EventListenerOptions,
) => Promise<EntryData | undefined | null | void>;
export type PostSaveEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
data: EventData,
options: O,
) => void | Promise<void>;
export interface EventListener {
name: AllowedEvent;
handler: EventListenerHandler;
export type MountedEventHandler<O extends Record<string, unknown> = Record<string, unknown>> = (
options: O,
) => void | Promise<void>;
export type EventHandlers<O extends Record<string, unknown> = Record<string, unknown>> = {
preSave: PreSaveEventHandler<O>;
postSave: PostSaveEventHandler<O>;
mounted: MountedEventHandler<O>;
};
export interface EventListener<
E extends AllowedEvent = 'mounted',
O extends Record<string, unknown> = Record<string, unknown>,
> {
name: E;
handler: EventHandlers<O>[E];
}
export interface AdditionalLinkOptions {

View File

@ -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<string, unknown> }[];
postSave: { handler: PostSaveEventHandler; options: Record<string, unknown> }[];
mounted: { handler: MountedEventHandler; options: Record<string, unknown> }[];
};
const eventHandlers = allowedEvents.reduce((acc, e) => {
acc[e] = [];
return acc;
}, {} as Record<AllowedEvent, { handler: EventListener['handler']; options: Record<string, unknown> }[]>);
}, {} as EventHandlerRegistry);
interface Registry {
backends: Record<string, BackendInitializer>;
@ -44,7 +54,7 @@ interface Registry {
additionalLinks: Record<string, AdditionalLink>;
widgetValueSerializers: Record<string, WidgetValueSerializer>;
locales: Record<string, LocalePhrasesRoot>;
eventHandlers: typeof eventHandlers;
eventHandlers: EventHandlerRegistry;
previewStyles: PreviewStyle[];
/** Markdown editor */
@ -309,9 +319,8 @@ export function getBackend<EF extends BaseField = UnknownField>(
/**
* 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<string, unknown> = {},
) {
export function registerEventListener<
E extends AllowedEvent,
O extends Record<string, unknown> = Record<string, unknown>,
>({ name, handler }: EventListener<E, O>, 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<EntryData>;
export async function invokeEvent(name: 'postSave', data: EventData): Promise<void>;
export async function invokeEvent(name: 'mounted'): Promise<void>;
export async function invokeEvent(name: AllowedEvent, data?: EventData): Promise<void | EntryData> {
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) {

View File

@ -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