Plugins nützlich machen
Einführung
'Read the “', Einführung, '” section'Die Erstellung eines StudioCMS-Plugins ist eine leistungsstarke Möglichkeit, die Funktionalität von StudioCMS zu erweitern. Sie bieten eine einfache und flexible Möglichkeit, neue Funktionen zu deinem StudioCMS-Projekt hinzuzufügen. Das folgende Beispiel zeigt dir, wie du ein StudioCMS-Plugin erstellst und wie es funktioniert.
Erste Schritte
'Read the “', Erste Schritte, '” section'Um loszulegen, musst du ein neues StudioCMS-Plugin erstellen. Im Folgenden findest du ein grundlegendes Beispiel für die Dateistruktur eines StudioCMS-Plugins:
- package.json
Directorysrc
- index.ts
Directoryroutes
- […slug].astro
Directorydashboard-grid-items
- MyPluginGridItem.astro
Erstellen des Plugins
'Read the “', Erstellen des Plugins, '” section'In der Hauptdatei src/index.ts
definierst du das StudioCMS Plugin. Das folgende Beispiel zeigt, wie du ein StudioCMS-Plugin definierst, das eine Astro-Integration enthält, um ein einfaches Blog-Beispiel zu erstellen:
import { function definePlugin(options: StudioCMSPluginOptions): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin } from 'studiocms/plugins';import { (alias) interface AstroIntegrationimport AstroIntegration
AstroIntegration } from 'astro';import { const addVirtualImports: HookUtility<"astro:config:setup", [{ name: string; imports: Imports; __enableCorePowerDoNotUseOrYouWillBeFired?: boolean;}], void>
Creates a Vite virtual module and updates the Astro config.
Virtual imports are useful for passing things like config options, or data computed within the integration.
addVirtualImports, const createResolver: (_base: string) => { resolve: (...path: Array<string>) => string;}
Allows resolving paths relatively to the integration folder easily. Call it like this:
createResolver } from 'astro-integration-kit';
// Definiere die Optionen für das Plugin und die Integrationinterface interface Options
Options { Options.route: string
route: string;}
export function function studioCMSPageInjector(options: Options): StudioCMSPlugin
studioCMSPageInjector(options: Options
options: interface Options
Options) {
// Löse den Pfad zur aktuellen Datei auf const { const resolve: (...path: Array<string>) => string
resolve } = function createResolver(_base: string): { resolve: (...path: Array<string>) => string;}
Allows resolving paths relatively to the integration folder easily. Call it like this:
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.
url);
// Definiere die Astro-Integration 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; ... 8 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; ... 8 more ...; logger: AstroIntegrationLogger;}
params;
// Injiziere die Route für das Plugin const injectRoute: (injectRoute: InjectedRoute) => void
injectRoute({ entrypoint: string | URL
entrypoint: const resolve: (...path: Array<string>) => string
resolve('./routes/[...slug].astro'), pattern: string
pattern: `/${const route: string
route}/[...slug]`, prerender?: boolean
prerender: false, })
function addVirtualImports(params: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; ... 8 more ...; logger: AstroIntegrationLogger;}, args_0: { ...;}): void
Creates a Vite virtual module and updates the Astro config.
Virtual imports are useful for passing things like config options, or data computed within the integration.
addVirtualImports(params: { config: AstroConfig; command: "dev" | "build" | "preview" | "sync"; isRestart: boolean; updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig; ... 8 more ...; logger: AstroIntegrationLogger;}
params, { name: string
name: 'my-astro-integration', imports: Imports
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; `, } }) } } } }
// Definiere das StudioCMS Plugin return function definePlugin(options: StudioCMSPluginOptions): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin({ identifier: string
identifier: 'my-plugin', name: string
name: 'My Plugin', studiocmsMinimumVersion: string
studiocmsMinimumVersion: '0.1.0-beta.8', integration?: AstroIntegration | AstroIntegration[] | undefined
integration: function (local function) myIntegration(options: Options): AstroIntegration
myIntegration(options: Options
options), // Optional, aber empfohlen // Definiere die Frontend-Navigationslinks für das Plugin (optional) // Dies ist nützlich, wenn du die eingebauten StudioCMS-Navigationshilfen in deinem Layout verwendest, // wie z.B. bei der Verwendung des Plugins `@studiocms/blog`. frontendNavigationLinks?: { label: string; href: string;}[] | undefined
frontendNavigationLinks: [{ label: string
label: 'Title here', href: string
href: options: Options
options?.Options.route: string
route || 'my-plugin' }], // Wenn du pageTypes erstellst, kannst du auch eine `pageContentComponent` definieren, wenn dein Plugin einen eigenen Inhaltseditor benötigt. // pageTypes: [{ identifier: 'my-plugin', label: 'Blog Post (My Plugin)', pageContentComponent: resolve('./components/MyContentEditor.astro') }], // In diesem Beispiel ist es in Ordnung, den Standard-Editor (Markdown) zu verwenden. 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"; required?: boolean | undefined; readOnly?: boolean | undefined; type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | 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; } | { name: string; label: string; options: { label: string; value: string; disabled?: boolean | undefined; }[]; input: "radio"; required?: boolean | undefined; readOnly?: boolean | undefined; color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined; defaultValue?: string | undefined; direction?: "horizontal" | "vertical" | undefined; } | { name: string; label: string; options: { label: string; value: string; disabled ...
pageTypes: [{ identifier: string
identifier: 'my-plugin', label: string
label: 'Blog Post (My Plugin)' }], // Definiere die Gitterelemente für das Dashboard // Dies sind die Elemente, die auf dem StudioCMS Dashboard angezeigt werden // Du kannst so viele Elemente definieren, wie du willst // In diesem Beispiel definieren wir ein einzelnes Element, das eine Spannweite von 2 hat und die Berechtigung „Editor“ benötigt. Außerdem injizieren wir eine Astro-Komponente, die das einfache benutzerdefinierte HTML-Element ersetzt. dashboardGridItems?: GridItemInput[] | undefined
dashboardGridItems: [ { GridItemInput.name: string
The name of the grid item.
name: 'example', GridItemInput.span: 1 | 2 | 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?: HeroIconName;}
The header of the grid item.
Optional.
header: { title: string
The title of the header.
title: 'Example', icon?: "bolt" | "code-bracket-solid" | "code-bracket-square-solid" | "exclamation-circle" | "exclamation-circle-solid" | "exclamation-triangle" | "exclamation-triangle-solid" | ... 1280 more ... | "x-mark-solid"
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: { // Verwende immer einfaches HTML ohne `-` oder Sonderzeichen in den Tags. Sie werden durch die Astro-Komponente ersetzt und dieses HTML wird nie gerendert. html: string
The HTML content of the body.
html: '<examplegriditem></examplegriditem>', components?: Record<string, string>
The components within the body.
Optional.
components: { // Injiziere die Astro-Komponente, um das einfache benutzerdefinierte HTML-Element zu ersetzen examplegriditem: string
examplegriditem: const resolve: (...path: Array<string>) => string
resolve('./dashboard-grid-items/MyPluginGridItem.astro') } } } ], });}
Das obige Beispiel definiert ein StudioCMS-Plugin, das eine Astro-Integration enthält, um ein einfaches Blog-Beispiel zu erstellen. Das Plugin enthält eine Route, die in das StudioCMS-Projekt injiziert wird, und ein Grid-Element, das auf dem StudioCMS-Dashboard angezeigt wird.
Beispiel-Route
'Read the “', Beispiel-Route, '” section'In der Datei src/routes/[...slug].astro
definierst du die Route für das Plugin. Das folgende Beispiel zeigt, wie du eine Route für das Plugin definierst. Der erste Teil ist das Frontmatter (zwischen den ---
-Zeichen) und der zweite Teil ist die HTML-Vorlage, die unter dem zweiten ---
eingefügt wird.
import { const StudioCMSRenderer: any
StudioCMSRenderer } from 'studiocms:renderer';import const sdk: { addPageToFolderTree: (tree: FolderNode[], folderId: string, newPage: FolderNode) => FolderNode[]; ... 29 more ...; notificationSettings: { site: { get: () => Promise<{ id: string; emailVerification: boolean; requireAdminVerification: boolean; requireEditorVerification: boolean; oAuthBypassVerification: boolean; }>; update: (settings: { ...; }) => Promise<{ id: string; emailVerification: boolean; requireAdminVerification: boolean; requireEditorVerification: boolean; oAuthBypassVerification: boolean; }>; }; };}
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' wird hier als Bezeichner für// den pageType aus der Plugin-Definition verwendetconst const pages: CombinedPageData[]
pages = await const sdk: { addPageToFolderTree: (tree: FolderNode[], folderId: string, newPage: FolderNode) => FolderNode[]; ... 29 more ...; notificationSettings: { site: { get: () => Promise<{ id: string; emailVerification: boolean; requireAdminVerification: boolean; requireEditorVerification: boolean; oAuthBypassVerification: boolean; }>; update: (settings: { ...; }) => Promise<{ id: string; emailVerification: boolean; requireAdminVerification: boolean; requireEditorVerification: boolean; oAuthBypassVerification: boolean; }>; }; };}
sdk.type GET: { database: { users: () => Promise<CombinedUserData[]>; pages: (includeDrafts?: boolean, tree?: FolderNode[]) => Promise<CombinedPageData[]>; config: () => Promise<{ id: number; title: string; description: string; defaultOgImage: string | null; ... 6 more ...; enableMailer: boolean; } | undefined>; folders: () => Promise<{ ...; }[]>; }; databaseEntry: { users: { byId: (id: string) => Promise<CombinedUserData | undefined>; byUsername: (username: string) => Promise<CombinedUserData | undefined>; byEmail: (email: string) => Promise<CombinedUserData | undefined>; }; pages: { byId: (id: string, tree?: FolderNode[]) => Promise<CombinedPageData | undefined>; bySlug: (slug: string, tree?: FolderNode[]) => Promise<CombinedPageData | undefined>; }; folder: (id: string) => Promise<{ ...; } | undefined>; }; databaseTable: { users: () => Promise<{ name: string; password: string | null; email: string | null; url: string | null; username: string; id: string; avatar: string | null; updatedAt: Date | null; createdAt: Date | null; emailVerified: boolean; notifications: string | null; }[]>; oAuthAccounts: () => Promise<{ userId: string; provider: string ...
GET.packagePages: (packageName: string, tree?: FolderNode[]) => Promise<CombinedPageData[]>
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: CombinedPageData | undefined
page = const pages: CombinedPageData[]
pages.Array<CombinedPageData>.find(predicate: (value: CombinedPageData, index: number, obj: CombinedPageData[]) => unknown, thisArg?: any): CombinedPageData | undefined (+1 overload)
Returns the value of the first element in the array where predicate is true, and undefined
otherwise.
find((page: CombinedPageData
page) => page: CombinedPageData
page.slug: string
slug === const slug: string | undefined
slug || '');
{ slug && page ? ( <div> <h1>{page.title}</h1> <StudioCMSRenderer content={page.defaultContent?.content || ''} /> </div> ) : ( <div> <h1>My Plugin</h1> <ul> {pages.length > 0 && pages.map((page) => ( <li> <a href={makeRoute(page.slug)}>{page.title}</a> </li> ))} </ul> </div> )}
Das obige Beispiel definiert eine dynamische Route^ für das Plugin, die eine Liste von Blogposts anzeigt, wenn kein Slug angegeben wird, und die den Inhalt eines Blogposts anzeigt, wenn ein Slug angegeben wird.
Beispiel Gitterelement
'Read the “', Beispiel Gitterelement, '” section'In der Datei src/dashboard-grid-items/MyPluginGridItem.astro
definierst du das Grid Item für das Plugin. Das folgende Beispiel zeigt, wie du ein Grid Item für das Plugin definierst:
---import { StudioCMSRoutes } from 'studiocms:lib';import sdk from 'studiocms:sdk';
// 'my-plugin' wird hier als Bezeichner für// den pageType aus der Plugin-Definition verwendetconst pages = await sdk.GET.packagePages('my-plugin');
// Erhalte die 5 zuletzt aktualisierten Seiten der letzten 30 Tageconst 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>Recently Updated Pages</h2> <ul> {recentlyUpdatedPages.length > 0 && recentlyUpdatedPages.map((page) => ( <li> <a href={StudioCMSRoutes.mainLinks.contentManagementEdit + `?edit=${page.id}`}>{page.title}</a> </li> ))} </ul></div>
Das obige Beispiel definiert ein Grid-Element für das Plugin, das die 5 zuletzt aktualisierten Seiten der letzten 30 Tage anzeigt. Das Grid-Element enthält eine Liste mit Links zu den Bearbeitungsseiten der einzelnen Seiten im Content Management.
Integration mit den FrontendNavigationLinks Helfern
'Read the “', Integration mit den FrontendNavigationLinks Helfern, '” section'Wenn du die eingebauten StudioCMS-Navigationshilfen in deinem Projekt verwenden möchtest, ähnlich wie das Plugin @studiocms/blog
, kannst du eine eigene Komponente Navigation.astro
erstellen:
---import { StudioCMSRoutes } from 'studiocms:lib';import studioCMS_SDK from 'studiocms:sdk/cache';import { frontendNavigation } from 'studiocms:plugin-helpers';
// Definiere die Requisiten für die Komponente Navigationinterface Props { topLevelLinkCount?: number;};
// Erhalte die Anzahl der Links auf oberster Ebene aus den Requisitenconst { topLevelLinkCount = 3 } = Astro.props;
// Abrufen der Website-Konfiguration und der Seitenlisteconst config = (await studioCMS_SDK.GET.siteConfig()).data;
// Hol dir den Titel der Seite aus der Konfigurationconst { title } = config || { title: 'StudioCMS' };
// Erhalte die URL der Hauptseiteconst { mainLinks: { baseSiteURL },} = StudioCMSRoutes;
// Definiere die Link-Requisiten für die Navigationtype LinkProps = { text: string; href: string;};
// Definiere die Links für die Navigationconst links: LinkProps[] = await frontendNavigation();---{/* Wenn keine Dropdown-Elemente */}{ ( 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>) }
{/* Wenn Dropdown-Elemente */}{ 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>) }
Das obige Beispiel definiert eine benutzerdefinierte Komponente Navigation.astro
, die die eingebauten StudioCMS-Navigationshilfen verwendet, um ein Navigationsmenü für das Projekt zu erstellen. Die Komponente enthält Links zur Haupt-URL der Website, zur Indexseite und zu allen anderen Seiten, die in der Navigation angezeigt werden sollen.
Du musst nur noch ein paar Stile hinzufügen und schon hast du ein voll funktionsfähiges Navigationsmenü, das mit den eingebauten StudioCMS-Navigationshelfern funktioniert.