283 lines
7.0 KiB
JavaScript

import { Map } from 'immutable';
import produce from 'immer';
import { oneLine } from 'common-tags';
import EditorComponent from '../valueObjects/EditorComponent';
const allowedEvents = [
'prePublish',
'postPublish',
'preUnpublish',
'postUnpublish',
'preSave',
'postSave',
];
const eventHandlers = {};
allowedEvents.forEach(e => {
eventHandlers[e] = [];
});
/**
* Global Registry Object
*/
const registry = {
backends: {},
templates: {},
previewStyles: [],
widgets: {},
editorComponents: Map(),
remarkPlugins: [],
widgetValueSerializers: {},
mediaLibraries: [],
locales: {},
eventHandlers,
};
export default {
registerPreviewStyle,
getPreviewStyles,
registerPreviewTemplate,
getPreviewTemplate,
registerWidget,
getWidget,
getWidgets,
resolveWidget,
registerEditorComponent,
getEditorComponents,
registerRemarkPlugin,
getRemarkPlugins,
registerWidgetValueSerializer,
getWidgetValueSerializer,
registerBackend,
getBackend,
registerMediaLibrary,
getMediaLibrary,
registerLocale,
getLocale,
registerEventListener,
removeEventListener,
getEventListeners,
invokeEvent,
};
/**
* Preview Styles
*
* Valid options:
* - raw {boolean} if `true`, `style` value is expected to be a CSS string
*/
export function registerPreviewStyle(style, opts) {
registry.previewStyles.push({ ...opts, value: style });
}
export function getPreviewStyles() {
return registry.previewStyles;
}
/**
* Preview Templates
*/
export function registerPreviewTemplate(name, component) {
registry.templates[name] = component;
}
export function getPreviewTemplate(name) {
return registry.templates[name];
}
/**
* Editor Widgets
*/
export function registerWidget(name, control, preview, schema = {}) {
if (Array.isArray(name)) {
name.forEach(widget => {
if (typeof widget !== 'object') {
console.error(`Cannot register widget: ${widget}`);
} else {
registerWidget(widget);
}
});
} else if (typeof name === 'string') {
// A registered widget control can be reused by a new widget, allowing
// multiple copies with different previews.
const newControl = typeof control === 'string' ? registry.widgets[control].control : control;
registry.widgets[name] = { control: newControl, preview, schema };
} else if (typeof name === 'object') {
const {
name: widgetName,
controlComponent: control,
previewComponent: preview,
schema = {},
allowMapValue,
globalStyles,
...options
} = name;
if (registry.widgets[widgetName]) {
console.warn(oneLine`
Multiple widgets registered with name "${widgetName}". Only the last widget registered with
this name will be used.
`);
}
if (!control) {
throw Error(`Widget "${widgetName}" registered without \`controlComponent\`.`);
}
registry.widgets[widgetName] = {
control,
preview,
schema,
globalStyles,
allowMapValue,
...options,
};
} else {
console.error('`registerWidget` failed, called with incorrect arguments.');
}
}
export function getWidget(name) {
return registry.widgets[name];
}
export function getWidgets() {
return produce(Object.entries(registry.widgets), draft => {
return draft.map(([key, value]) => ({ name: key, ...value }));
});
}
export function resolveWidget(name) {
return getWidget(name || 'string') || getWidget('unknown');
}
/**
* Markdown Editor Custom Components
*/
export function registerEditorComponent(component) {
const plugin = EditorComponent(component);
if (plugin.type === 'code-block') {
const codeBlock = registry.editorComponents.find(c => c.type === 'code-block');
if (codeBlock) {
console.warn(oneLine`
Only one editor component of type "code-block" may be registered. Previously registered code
block component(s) will be overwritten.
`);
registry.editorComponents = registry.editorComponents.delete(codeBlock.id);
}
}
registry.editorComponents = registry.editorComponents.set(plugin.id, plugin);
}
export function getEditorComponents() {
return registry.editorComponents;
}
/**
* Remark plugins
*/
/** @typedef {import('unified').Pluggable} RemarkPlugin */
/** @type {(plugin: RemarkPlugin) => void} */
export function registerRemarkPlugin(plugin) {
registry.remarkPlugins.push(plugin);
}
/** @type {() => Array<RemarkPlugin>} */
export function getRemarkPlugins() {
return registry.remarkPlugins;
}
/**
* Widget Serializers
*/
export function registerWidgetValueSerializer(widgetName, serializer) {
registry.widgetValueSerializers[widgetName] = serializer;
}
export function getWidgetValueSerializer(widgetName) {
return registry.widgetValueSerializers[widgetName];
}
/**
* Backend API
*/
export function registerBackend(name, BackendClass) {
if (!name || !BackendClass) {
console.error(
"Backend parameters invalid. example: CMS.registerBackend('myBackend', BackendClass)",
);
} else if (registry.backends[name]) {
console.error(`Backend [${name}] already registered. Please choose a different name.`);
} else {
registry.backends[name] = {
init: (...args) => new BackendClass(...args),
};
}
}
export function getBackend(name) {
return registry.backends[name];
}
/**
* Media Libraries
*/
export function registerMediaLibrary(mediaLibrary, options) {
if (registry.mediaLibraries.find(ml => mediaLibrary.name === ml.name)) {
throw new Error(`A media library named ${mediaLibrary.name} has already been registered.`);
}
registry.mediaLibraries.push({ ...mediaLibrary, options });
}
export function getMediaLibrary(name) {
return registry.mediaLibraries.find(ml => ml.name === name);
}
function validateEventName(name) {
if (!allowedEvents.includes(name)) {
throw new Error(`Invalid event name '${name}'`);
}
}
export function getEventListeners(name) {
validateEventName(name);
return [...registry.eventHandlers[name]];
}
export function registerEventListener({ name, handler }, options = {}) {
validateEventName(name);
registry.eventHandlers[name].push({ handler, options });
}
export async function invokeEvent({ name, data }) {
validateEventName(name);
const handlers = registry.eventHandlers[name];
let _data = { ...data };
for (const { handler, options } of handlers) {
const result = await handler(_data, options);
if (result !== undefined) {
const entry = _data.entry.set('data', result);
_data = { ...data, entry };
}
}
return _data.entry.get('data');
}
export function removeEventListener({ name, handler }) {
validateEventName(name);
if (handler) {
registry.eventHandlers[name] = registry.eventHandlers[name].filter(
item => item.handler !== handler,
);
} else {
registry.eventHandlers[name] = [];
}
}
/**
* Locales
*/
export function registerLocale(locale, phrases) {
if (!locale || !phrases) {
console.error("Locale parameters invalid. example: CMS.registerLocale('locale', phrases)");
} else {
registry.locales[locale] = phrases;
}
}
export function getLocale(locale) {
return registry.locales[locale];
}