유용한 플러그인 만들기
StudioCMS 플러그인을 만드는 것은 StudioCMS의 기능을 확장하는 강력한 방법입니다. 플러그인은 StudioCMS 프로젝트에 새로운 기능을 간단하고 유연하게 추가할 수 있는 방법을 제공합니다. 다음은 StudioCMS 플러그인을 만들고 작동하는 방식에 대한 기본적인 예시입니다.
시작하려면 새로운 StudioCMS 플러그인을 생성해야 합니다. 다음은 StudioCMS 플러그인의 기본적인 파일 구조 예시입니다.
- package.json
디렉터리src
- index.ts
디렉터리routes
- […slug].astro
디렉터리dashboard-grid-items
- MyPluginGridItem.astro
플러그인 만들기
Section titled “플러그인 만들기”src/index.ts
파일에서 StudioCMS 플러그인을 정의합니다. 다음은 간단한 블로그 예시를 만들기 위해 Astro 통합을 포함하는 StudioCMS 플러그인을 정의하는 방법의 예시입니다.
import { function definePlugin(options: StudioCMSPlugin): 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';
// 플러그인 및 통합에 대한 옵션을 정의합니다.interface interface Options
Options { Options.route: string
route: string;}
export function function studioCMSPageInjector(options: Options): StudioCMSPlugin
studioCMSPageInjector(options: Options
options: interface Options
Options) {
// 현재 파일의 경로를 확인합니다. 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);
// 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:db:setup'?: (options: { extendDb: (options: { configEntrypoint?: URL | string; seedEntrypoint?: URL | string; }) => void; }) => void | Promise<void>; ... 12 more ...; 'studiocms:plugins'?: PluginHook<...>;} & 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;
// 플러그인 라우트를 삽입합니다. 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; `, } }) } } } }
// StudioCMS 플러그인을 정의합니다. return function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin({ StudioCMSPlugin.identifier: string
identifier: 'my-plugin', StudioCMSPlugin.name: string
name: 'My Plugin', StudioCMSPlugin.studiocmsMinimumVersion: string
studiocmsMinimumVersion: '0.1.0-beta.8', integration: function (local function) myIntegration(options: Options): AstroIntegration
myIntegration(options: Options
options), // 선택 사항이지만, 권장 사항입니다.Error ts(2353) ― // 플러그인의 프런트엔드 탐색 링크를 정의합니다. (선택 사항) // 예를 들어, `@studiocms/blog` 플러그인을 사용할 때처럼 // 레이아웃에서 StudioCMS 내장 탐색 도우미를 사용하는 경우에 유용합니다. frontendNavigationLinks: { label: string; href: string;}[]
frontendNavigationLinks: [{ label: string
label: 'Title here', href: string
href: options: Options
options?.Options.route: string
route || 'my-plugin' }], // pageTypes를 생성할 때 플러그인에 사용자 정의 콘텐츠 편집기가 필요한 경우 `pageContentComponent`를 정의할 수도 있습니다. // pageTypes: [{ identifier: 'my-plugin', label: 'Blog Post (My Plugin)', pageContentComponent: resolve('./components/MyContentEditor.astro') }], // 이 예시에서는 기본 콘텐츠 편집기 (Markdown)를 사용해도 괜찮습니다. pageTypes: { identifier: string; label: string;}[]
pageTypes: [{ identifier: string
identifier: 'my-plugin', label: string
label: 'Blog Post (My Plugin)' }], // 대시보드에 표시할 그리드 항목을 정의합니다. // StudioCMS 대시보드에 표시될 항목들입니다. // 원하는 만큼 여러 항목을 정의할 수 있습니다. // 이 예시에서는 span이 2이고 'editor' 권한이 필요하며 일반 HTML 사용자 정의 요소를 대체하는 Astro 컴포넌트를 삽입하는 단일 항목을 정의합니다. dashboardGridItems: { name: string; span: number; variant: string; requiresPermission: string; header: { title: string; icon: string; }; body: { html: string; components: { examplegriditem: string; }; };}[]
dashboardGridItems: [ { name: string
name: 'example', span: number
span: 2, variant: string
variant: 'default', requiresPermission: string
requiresPermission: 'editor', header: { title: string; icon: string;}
header: { title: string
title: 'Example', icon: string
icon: 'bolt' }, body: { html: string; components: { examplegriditem: string; };}
body: { // 태그에는 `-` 또는 특수 문자 없이 항상 일반 HTML을 사용하세요. 이는 Astro 컴포넌트로 대체될 것이며, 이 HTML은 렌더링되지 않습니다. html: string
html: '<examplegriditem></examplegriditem>', components: { examplegriditem: string;}
components: { // 일반 HTML 사용자 정의 요소를 대체할 Astro 컴포넌트를 삽입합니다. examplegriditem: string
examplegriditem: const resolve: (...path: Array<string>) => string
resolve('./dashboard-grid-items/MyPluginGridItem.astro') } } } ], });}
위 예시는 간단한 블로그 예시를 만들기 위해 Astro 통합을 포함하는 StudioCMS 플러그인을 정의합니다. 이 플러그인은 StudioCMS 프로젝트에 삽입되는 라우트와 StudioCMS 대시보드에 표시되는 그리드 항목을 포함합니다.
라우트 예시
Section titled “라우트 예시”src/routes/[...slug].astro
파일에서 플러그인의 라우트를 정의합니다. 다음은 플러그인의 라우트를 정의하는 방법의 예시이며, 이를 두 부분으로 나누어 설명하겠습니다. 첫 번째 부분은 프런트매터 (---
표시 사이)이고, 두 번째 부분은 두 번째 ---
아래에 배치되는 HTML 템플릿입니다.
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'은// 플러그인 정의에서 가져온 `pageType`의 식별자로 사용됩니다.const 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, hideDefaultIndex?: boolean, tree?: FolderNode[], metaOnly?: false, paginate?: PaginateInput): Promise<CombinedPageData[]>; (includeDrafts?: boolean, hideDefaultIndex?: boolean, tree?: FolderNode[], metaOnly?: true, paginate?: PaginateInput): Promise<MetaOnlyPageData[]>; }; folderPages: { (id: string, includeDrafts?: boolean, hideDefaultIndex?: boolean, tree?: FolderNode[], metaOnly?: false, paginate?: PaginateInput): Promise<CombinedPageData[]>; (id: string, includeDrafts?: boolean, hideDefaultIndex?: boolean, tree?: FolderNode[], metaOnly?: true, paginate?: PaginateInput): Promise<MetaOnlyPageData[]>; }; config: () => Promise<{ ...; } | 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>; (id: string, tree?: FolderNode[], metaOnly?: boolean): Promise<MetaOnlyPageData | undefined>; }; bySlug: { (slug: string, tree?: FolderNode[]): Promise<CombinedPageData | undefined>; ( ...
GET.packagePages: (packageName: string, tree?: FolderNode[]) => Promise<CombinedPageData[]> (+1 overload)
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> )}
위의 예시는 슬러그가 제공되지 않으면 블로그 게시물 목록을 표시하고, 슬러그가 제공되면 특정 블로그 게시물의 내용을 표시하는 플러그인을 위한 동적 라우트^를 정의합니다.
그리드 항목 예시
Section titled “그리드 항목 예시”src/dashboard-grid-items/MyPluginGridItem.astro
파일에서 플러그인의 그리드 항목을 정의합니다. 다음은 플러그인의 그리드 항목을 정의하는 방법의 예시입니다.
---import { StudioCMSRoutes } from 'studiocms:lib';import sdk from 'studiocms:sdk';
// 여기서 'my-plugin'은// 플러그인 정의에서 가져온 `pageType`의 식별자로 사용됩니다.const pages = await sdk.GET.packagePages('my-plugin');
// 지난 30일 동안 가장 최근에 업데이트된 5개의 페이지를 가져옵니다.const 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>
위의 예시는 지난 30일 동안 가장 최근에 업데이트된 5개의 페이지를 표시하는 플러그인용 그리드 항목을 정의합니다. 이 그리드 항목은 각 페이지의 콘텐츠 관리 편집 페이지로 연결되는 링크 목록을 포함합니다.
FrontendNavigationLinks 도우미와 통합
Section titled “FrontendNavigationLinks 도우미와 통합”프로젝트에서 @studiocms/blog
플러그인이 사용하는 방식과 유사하게, StudioCMS 내장 탐색 도우미를 사용하려면 다음과 같이 사용자 정의 Navigation.astro
컴포넌트를 생성할 수 있습니다.
---import { StudioCMSRoutes } from 'studiocms:lib';import studioCMS_SDK from 'studiocms:sdk/cache';import { frontendNavigation } from 'studiocms:plugin-helpers';
// Navigation 컴포넌트의 props를 정의합니다.interface Props { topLevelLinkCount?: number;};
// props에서 최상위 링크 개수를 가져옵니다.const { topLevelLinkCount = 3 } = Astro.props;
// 사이트 구성과 페이지 목록을 가져옵니다.const config = (await studioCMS_SDK.GET.siteConfig()).data;
// 구성에서 사이트 제목을 가져옵니다.const { title } = config || { title: 'StudioCMS' };
// 메인 사이트 URL을 가져옵니다.const { mainLinks: { baseSiteURL },} = StudioCMSRoutes;
// 탐색 메뉴의 링크 props를 정의합니다.type LinkProps = { text: string; href: string;};
// 탐색 메뉴의 링크들을 정의합니다.const links: LinkProps[] = await frontendNavigation();---{/* 드롭다운 아이템이 없는 경우 */}{ ( 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>) }
{/* 드롭다운 아이템이 있는 경우 */}{ 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>) }
위의 예시는 StudioCMS 내장 탐색 도우미를 사용하여 프로젝트의 탐색 메뉴를 생성하는 사용자 정의 Navigation.astro
컴포넌트를 정의합니다. 이 컴포넌트는 메인 사이트 URL, 인덱스 페이지 및 탐색 메뉴에 표시되도록 설정된 다른 모든 페이지로 연결되는 링크를 포함합니다.
몇 가지 스타일만 추가하면 StudioCMS 내장 탐색 도우미와 연동되어 완벽하게 작동하는 탐색 메뉴를 갖게 됩니다.