Haciendo complementos útiles
Introducción
Sección titulada «Introducción»Crear un complemento para StudioCMS es una forma poderosa de extender la funcionalidad de StudioCMS. Los complementos proporcionan una manera simple y flexible de agregar nuevas características a tu proyecto StudioCMS. A continuación se muestra un ejemplo básico de cómo crear un complemento de StudioCMS y cómo funciona.
Primeros Pasos
Sección titulada «Primeros Pasos»Para comenzar, necesitarás crear un nuevo complemento de StudioCMS. A continuación se muestra un ejemplo básico de la estructura de archivos para un complemento de StudioCMS:
- package.json
Directorysrc
- index.ts
Directoryroutes
- […slug].astro
Directorydashboard-grid-items
- MyPluginGridItem.astro
Creando el complemento
Sección titulada «Creando el complemento»En el archivo principal src/index.ts, definirás el complemento de StudioCMS. A continuación se muestra un ejemplo de cómo definir un complemento de StudioCMS que incluye una Integración Astro para crear un ejemplo simple de blog:
import { function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin } from 'studiocms/plugins';import { (alias) interface AstroIntegrationimport AstroIntegration
AstroIntegration } from 'astro';import { import addVirtualImports
addVirtualImports, import createResolver
createResolver } from 'astro-integration-kit';Error ts(2307) ―
// Define las opciones para el complemento y la integracióninterface interface Options
Options { Options.route: string
route: string;}
export function function studioCMSPageInjector(options: Options): StudioCMSPlugin
studioCMSPageInjector(options: Options
options: interface Options
Options) { // Resuelve la ruta al archivo actual const { const resolve: any
resolve } = import createResolver
createResolver(import.
The type of import.meta.
If you need to declare that a given property exists on import.meta,
this type may be augmented via interface merging.
meta.ImportMeta.url: string
The absolute file: URL of the module.
This is defined exactly the same as it is in browsers providing the URL of the
current module file.
This enables useful patterns such as relative file loading:
import { readFileSync } from 'node:fs';const buffer = readFileSync(new URL('./data.proto', import.meta.url));
url);
// Define la integración Astro function function (local function) myIntegration(options: Options): AstroIntegration
myIntegration(options: Options
options: interface Options
Options): (alias) interface AstroIntegrationimport AstroIntegration
AstroIntegration { const const route: string
route = `/${options: Options
options?.Options.route: string
route || 'my-plugin'}`;
return { AstroIntegration.name: string
The name of the integration.
name: 'my-astro-integration', AstroIntegration.hooks: { 'astro:config:setup'?: (options: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; addRenderer: (renderer: AstroRenderer) => void; addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; addClientDirective: (directive: ClientDirectiveConfig) => void; addDevToolbarApp: (entrypoint: DevToolbarAppEntry) => void; addMiddleware: (mid: AstroIntegrationMiddleware) => void; createCodegenDir: () => URL; logger: AstroIntegrationLogger; }) => void | Promise<void>; ... 10 more ...; 'astro:routes:resolved'?: (options: { routes: IntegrationResolvedRoute[]; logger: AstroIntegrationLogger; }) => void | Promise<void>;} & Partial<...>
The different hooks available to extend.
hooks: { "astro:config:setup": (params: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; addRenderer: (renderer: AstroRenderer) => void; addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; ... 4 more ...; logger: AstroIntegrationLogger;}
params) => { const { const injectRoute: (injectRoute: InjectedRoute) => void
injectRoute } = params: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; addRenderer: (renderer: AstroRenderer) => void; addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; ... 4 more ...; logger: AstroIntegrationLogger;}
params;
// Inyecta la ruta para el complemento const injectRoute: (injectRoute: InjectedRoute) => void
injectRoute({ entrypoint: string | URL
entrypoint: const resolve: any
resolve('./routes/[...slug].astro'), pattern: string
pattern: `/${const route: string
route}/[...slug]`, prerender?: boolean
prerender: false, })
import addVirtualImports
addVirtualImports(params: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; addRenderer: (renderer: AstroRenderer) => void; addWatchFile: (path: URL | string) => void; injectScript: (stage: InjectedScriptStage, content: string) => void; injectRoute: (injectRoute: InjectedRoute) => void; ... 4 more ...; logger: AstroIntegrationLogger;}
params, { name: string
name: 'my-astro-integration', imports: { 'myplugin:config': string;}
imports: { 'myplugin:config': ` export const options = ${var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
stringify({ route: string
route })}; export default options; `, } }) } } } }
// Define el complemento de StudioCMS return function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin({ StudioCMSPlugin.identifier: string
The identifier of the plugin, usually the package name.
identifier: 'my-plugin', StudioCMSPlugin.name: string
The name of the plugin, displayed in the StudioCMS Dashboard.
name: 'My Plugin', StudioCMSPlugin.studiocmsMinimumVersion: string
The minimum version of StudioCMS required for this plugin to function correctly.
This is used to ensure compatibility between the plugin and the StudioCMS core.
It should be a semantic version string (e.g., "1.0.0").
If the plugin is not compatible with the current version of StudioCMS, it should not be loaded.
This is a required field.
studiocmsMinimumVersion: '0.1.0', StudioCMSPlugin.hooks: { 'studiocms:astro-config'?: PluginHook<{ logger: AstroIntegrationLogger; addIntegrations: (args_0: AstroIntegration | AstroIntegration[]) => void; }>; ... 5 more ...; 'studiocms:sitemap'?: PluginHook<...>;} & Partial<...>
hooks: { 'studiocms:astro-config': ({ addIntegrations: (args_0: AstroIntegration | AstroIntegration[]) => void
addIntegrations }) => { addIntegrations: (args_0: AstroIntegration | AstroIntegration[]) => void
addIntegrations(function (local function) myIntegration(options: Options): AstroIntegration
myIntegration(options: Options
options)); // Opcional, pero recomendado }, 'studiocms:dashboard': ({ setDashboard: (args_0: { translations: PluginTranslations; settingsPage?: { fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "row"; fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; })[]; required?: boolean | undefined; readOnly?: boolean | undefined; alignCenter?: boolean | undefined; gapSize?: "sm" | "md" | "lg" | undefined; })[]; endpoint: string; } | undefined; dashboardGridItems?: GridItemInput[] | undefined; dashboardPages?: { admin?: ({ title: Record<string, string>; description: string; route: string; pageBodyComponent: string ...
setDashboard }) => { setDashboard: (args_0: { translations: PluginTranslations; settingsPage?: { fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "row"; fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; })[]; required?: boolean | undefined; readOnly?: boolean | undefined; alignCenter?: boolean | undefined; gapSize?: "sm" | "md" | "lg" | undefined; })[]; endpoint: string; } | undefined; dashboardGridItems?: GridItemInput[] | undefined; dashboardPages?: { admin?: ({ title: Record<string, string>; description: string; route: string; pageBodyComponent: string ...
setDashboard({ translations: PluginTranslations
translations: { en: { example: { title: string; 'other-text': string; };}
en: { example: { title: string; 'other-text': string;}
example: { title: string
title: 'Example', 'other-text': 'Some other text', } }, fr: { example: { title: string; 'other-text': string; };}
fr: { example: { title: string; 'other-text': string;}
example: { title: string
title: 'Exemple', 'other-text': 'Un autre texte', } } },
// Define los elementos de la cuadrícula para el dashboard // Estos son los elementos que se mostrarán en el Dashboard de StudioCMS // Puedes definir tantos elementos como quieras // En este ejemplo, estamos definiendo un solo elemento, que tiene un span de 2 y requiere el permiso 'editor' y inyecta un componente Astro que reemplaza el elemento html personalizado. dashboardGridItems?: GridItemInput[] | undefined
dashboardGridItems: [ { GridItemInput.name: string
The name of the grid item.
name: 'example', GridItemInput.span: 2 | 1 | 3
The span of the grid item, which can be 1, 2, or 3.
span: 2, GridItemInput.variant: "default" | "filled"
The variant of the grid item, which can be 'default' or 'filled'.
variant: 'default', GridItemInput.requiresPermission?: "editor" | "owner" | "admin" | "visitor"
The required permission level to view the grid item.
Optional. Can be 'owner', 'admin', 'editor', or 'visitor'.
requiresPermission: 'editor', GridItemInput.header?: { title: string; icon?: addVirtualImports;}
The header of the grid item.
Optional.
header: { title: string
The title of the header.
title: 'Example', icon?: any
The icon of the header.
Optional.
icon: 'bolt' }, GridItemInput.body?: { html: string; components?: Record<string, string>; sanitizeOpts?: SanitizeOptions;}
The body of the grid item.
Optional.
body: { // Siempre usa html plano sin `-` o caracteres especiales en las etiquetas, se reemplazarán con el componente Astro y este HTML nunca se renderizará html: string
The HTML content of the body.
html: '<examplegriditem></examplegriditem>', components?: Record<string, string>
The components within the body.
Optional.
components: { // Inyecta el componente Astro para reemplazar el elemento html personalizado examplegriditem: any
examplegriditem: const resolve: any
resolve('./dashboard-grid-items/MyPluginGridItem.astro') } } } ], }); },
'studiocms:frontend': ({ setFrontend: (args_0: { frontendNavigationLinks?: { label: string; href: string; }[] | undefined;}) => void
setFrontend }) => { setFrontend: (args_0: { frontendNavigationLinks?: { label: string; href: string; }[] | undefined;}) => void
setFrontend({ // Define los enlaces de navegación del frontend para el complemento // Esto es útil si estás usando los ayudantes de navegación de StudioCMS incorporados en tu diseño, // como cuando usas el complemento `@studiocms/blog`. frontendNavigationLinks?: { label: string; href: string;}[] | undefined
frontendNavigationLinks: [{ label: string
label: 'My Plugin', href: string
href: options: Options
options?.Options.route: string
route || 'my-plugin' }], }); }, 'studiocms:rendering': ({ setRendering: (args_0: { pageTypes?: { label: string; identifier: string; fields?: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "row"; fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; })[]; required?: boolean | undefined; readOnly?: boolean | undefined; alignCenter?: boolean | undefined; gapSize?: "sm" | "md" | "lg" | undefined; })[] | undefined; description?: string | undefined; pageContentComponent?: string | undefined; rendererComponent?: string | undefined; apiEndpoint?: string | undefined; }[] | undefined; augments?: ({ type: "component"; components ...
setRendering }) => { setRendering: (args_0: { pageTypes?: { label: string; identifier: string; fields?: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "row"; fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; })[]; required?: boolean | undefined; readOnly?: boolean | undefined; alignCenter?: boolean | undefined; gapSize?: "sm" | "md" | "lg" | undefined; })[] | undefined; description?: string | undefined; pageContentComponent?: string | undefined; rendererComponent?: string | undefined; apiEndpoint?: string | undefined; }[] | undefined; augments?: ({ type: "component"; components ...
setRendering({ // Cuando creas pageTypes, también puedes definir un `pageContentComponent` si tu complemento requiere un editor de contenido personalizado. // pageTypes: [{ identifier: 'my-plugin', label: 'Blog Post (My Plugin)', pageContentComponent: resolve('./components/MyContentEditor.astro') }], // En este ejemplo estamos usando el editor de contenido por defecto (markdown). pageTypes?: { label: string; identifier: string; fields?: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "row"; fields: ({ name: string; label: string; input: "checkbox"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultChecked?: boolean | undefined; size?: "sm" | "md" | "lg" | undefined; } | { name: string; label: string; input: "input"; type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { name: string; label: string; input: "textarea"; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { options: { value: string; label: string; disabled?: boolean | undefined; }[]; name: string; label: string; input: "select"; type?: "search" | "basic" | undefined; required?: boolean | undefined; readOnly?: boolean | undefined; placeholder?: string | undefined; defaultValue?: string | undefined; })[]; required?: boolean | undefined; readOnly?: boolean | undefined; alignCenter?: boolean | undefined; gapSize?: "sm" | "md" | "lg" | undefined; })[] | undefined; description?: string | undefined; pageContentComponent?: string | undefined; rendererComponent?: string | undefined; apiEndpoint?: string | undefined;}[] | undefined
pageTypes: [{ identifier: string
identifier: 'my-plugin', label: string
label: 'Blog Post (My Plugin)' }], }); } } });}El ejemplo anterior define un complemento de StudioCMS que incluye una integración Astro para crear un ejemplo simple de blog. El complemento incluye una ruta que se inyecta en el proyecto StudioCMS y un elemento de cuadrícula que se muestra en el panel de control de StudioCMS.
Ejemplo de ruta
Sección titulada «Ejemplo de ruta»En el archivo src/routes/[...slug].astro, definirás la ruta para el complemento. A continuación se muestra un ejemplo de cómo definir una ruta para el complemento, lo dividiremos en dos partes, la primera parte es el frontmatter (entre las marcas ---), y la segunda parte es la plantilla HTML que se coloca debajo del segundo ---.
import { const StudioCMSRenderer: any
StudioCMSRenderer } from 'studiocms:renderer';import module "studiocms:sdk"
sdk from 'studiocms:sdk';import const config: { route: string;}
config from 'myplugin:config';
const const makeRoute: (slug: string) => string
makeRoute = (slug: string
slug: string) => { return `/${const config: { route: string;}
config.route: string
route}/${slug: string
slug}`;}
// 'my-plugin' aquí se usa como identificador para// el pageType de la definición del complementoconst const pages: any
pages = await module "studiocms:sdk"
sdk.any
GET.any
packagePages('my-plugin');
const { const slug: string | undefined
slug } = const Astro: AstroGlobal<Record<string, any>, AstroComponentFactory, Record<string, string | undefined>>
Astro.AstroGlobal<Record<string, any>, AstroComponentFactory, Record<string, string | undefined>>.params: Record<string, string | undefined>
Parameters passed to a dynamic page generated using getStaticPaths
Example usage:
---export async function getStaticPaths() { return [ { params: { id: '1' } }, ];}
const { id } = Astro.params;---<h1>{id}</h1>
params;
const const page: any
page = const pages: any
pages.any
find((page: any
page) => page: any
page.any
slug === const slug: string | undefined
slug || '');{ slug && page ? ( <div> <h1>{page.title}</h1> <StudioCMSRenderer content={page.defaultContent?.content || ''} /> </div> ) : ( <div> <h1>Mi complemento</h1> <ul> {pages.length > 0 && pages.map((page) => ( <li> <a href={makeRoute(page.slug)}>{page.title}</a> </li> ))} </ul> </div> )}El ejemplo anterior define una ruta dinámica^ para el complemento que muestra una lista de entradas de blog cuando no se proporciona un slug y muestra el contenido de una entrada de blog cuando se proporciona un slug.
Ejemplo de elemento de cuadrícula
Sección titulada «Ejemplo de elemento de cuadrícula»En el archivo src/dashboard-grid-items/MyPluginGridItem.astro, definirás el elemento de cuadrícula para el complemento. A continuación se muestra un ejemplo de cómo definir un elemento de cuadrícula para el complemento:
---import { SDKCoreJs, runSDK } from 'studiocms:sdk';
// 'my-plugin' aquí se usa como identificador para// el pageType de la definición del complementoconst pages = await runSDK(SDKCoreJs.GET.packagePages('my-plugin'));
// Consigue las 5 páginas actualizadas más recientemente de los últimos 30 díasconst recentlyUpdatedPages = pages .filter((page) => { const now = new Date(); const thirtyDaysAgo = new Date(now.setDate(now.getDate() - 30)); return new Date(page.updatedAt) > thirtyDaysAgo; }) .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) .slice(0, 5);---
<div> <h2>Páginas actualizadas recientemente</h2> <ul> {recentlyUpdatedPages.length > 0 && recentlyUpdatedPages.map((page) => ( <li> <a href={Astro.locals.routeMap.mainLinks.contentManagementEdit + `?edit=${page.id}`}>{page.title}</a> </li> ))} </ul></div>El ejemplo anterior define un elemento de cuadrícula para el complemento que muestra las 5 páginas actualizadas más recientemente de los últimos 30 días. El elemento de cuadrícula incluye una lista de enlaces a la página de edición de gestión de contenido para cada página.
Integrando con los ayudantes (helpers) de FrontendNavigationLinks
Sección titulada «Integrando con los ayudantes (helpers) de FrontendNavigationLinks»Si deseas usar los ayudantes (helpers) de navegación incorporados de StudioCMS en tu proyecto, similar a como lo hace el complemento @studiocms/blog, puedes crear un componente personalizado Navigation.astro:
---import { frontendNavigation } from 'studiocms:plugin-helpers';
// Define las props para el componente de navegacióninterface Props { topLevelLinkCount?: number;};
// Consigue el enlace de nivel superior de las propsconst { topLevelLinkCount = 3 } = Astro.props;
// Consigue la configuración del sitio y la lista de páginasconst config = Astro.locals.siteConfig.data;
// Consigue el título del sitio de la configuraciónconst { title } = config || { title: 'StudioCMS' };
// Consigue la URL principal del sitioconst { mainLinks: { baseSiteURL },} = Astro.locals.routeMap;
// Define las props de enlace para la navegacióntype LinkProps = { text: string; href: string;};
// Define los enlaces para la navegaciónconst links: LinkProps[] = await frontendNavigation();---{/* Si no hay elementos dropdown */}{ ( links.length < topLevelLinkCount || links.length === topLevelLinkCount ) && ( <div class="navigation"> <div class="title"><a href={baseSiteURL}>{title}</a></div> <div class="mini-nav"> <button>Menu</button> <div class="mini-nav-content"> { links.map(({ text, href }) => ( <a {href}>{text}</a> )) } </div> </div> { links.map(({ text, href }) => ( <a class="links" {href}>{text}</a> )) } </div>) }
{/* Si hay elementos dropdown */}{ links.length > topLevelLinkCount && ( <div class="navigation"> <div class="title"><a href={baseSiteURL}>{title}</a></div>
<div class="mini-nav"> <button>Menu</button> <div class="mini-nav-content"> { links.map(({ text, href }) => ( <a {href}>{text}</a> )) } </div> </div> { links.slice(0, topLevelLinkCount).map(({ text, href }) => ( <a class="links" {href}>{text}</a> )) } <div class="dropdown"> <button>More ▼</button> <div class="dropdown-content"> { links.slice(topLevelLinkCount).map(({ text, href }) => ( <a {href}>{text}</a> )) } </div> </div> </div>) }El ejemplo anterior define un componente personalizado Navigation.astro que utiliza los ayudantes de navegación incorporados de StudioCMS para crear un menú de navegación para el proyecto. El componente incluye enlaces a la URL principal del sitio, la página de índice y cualquier otra página que esté configurada para mostrarse en la navegación.
Todo lo que necesitas hacer es agregar algunos estilos, y tendrás un menú de navegación completamente funcional que funciona con los ayudantes de navegación incorporados de StudioCMS.