From cab7166eb7f77240283dbb933a6cff7fe0519895 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Mon, 23 Jan 2023 13:20:17 -0500 Subject: [PATCH] docs: update examples (#411) --- packages/core/src/lib/registry.ts | 4 +- .../docs/content/docs/additional-links.mdx | 36 ++ packages/docs/content/docs/custom-icons.mdx | 20 + .../docs/content/docs/custom-previews.mdx | 360 +++++++++++++----- packages/docs/content/docs/custom-widgets.mdx | 203 +++++++--- .../docs/content/docs/widget-markdown.mdx | 110 +++++- .../components/docs/components/CodeTabs.tsx | 10 + 7 files changed, 585 insertions(+), 158 deletions(-) diff --git a/packages/core/src/lib/registry.ts b/packages/core/src/lib/registry.ts index ae46ad90..6ea19037 100644 --- a/packages/core/src/lib/registry.ts +++ b/packages/core/src/lib/registry.ts @@ -374,12 +374,12 @@ export function getAdditionalLink(id: string): AdditionalLink | undefined { /** * Markdown editor shortcodes */ -export function registerShortcode(name: string, config: ShortcodeConfig) { +export function registerShortcode

(name: string, config: ShortcodeConfig

) { if (registry.backends[name]) { console.error(`Shortcode [${name}] already registered. Please choose a different name.`); return; } - registry.shortcodes[name] = config; + registry.shortcodes[name] = config as unknown as ShortcodeConfig; } export function getShortcode(name: string): ShortcodeConfig { diff --git a/packages/docs/content/docs/additional-links.mdx b/packages/docs/content/docs/additional-links.mdx index 14043a83..00c8f447 100644 --- a/packages/docs/content/docs/additional-links.mdx +++ b/packages/docs/content/docs/additional-links.mdx @@ -50,6 +50,8 @@ CMS.registerAdditionalLink({ ### Custom Page + + ```js const CustomPage = () => { return h('div', {}, 'I am a custom page!'); @@ -64,3 +66,37 @@ CMS.registerAdditionalLink({ }, }); ``` + +```jsx +const CustomPage = () => { + return

I am a custom page!
; +}; + +CMS.registerAdditionalLink({ + id: 'custom-page', + title: 'Custom Page', + data: CustomPage, + options: { + icon: 'page', + }, +}); +``` + +```tsx +import type { FC } from 'react'; + +const CustomPage: FC = () => { + return
I am a custom page!
; +}; + +CMS.registerAdditionalLink({ + id: 'custom-page', + title: 'Custom Page', + data: CustomPage, + options: { + icon: 'page', + }, +}); +``` + + diff --git a/packages/docs/content/docs/custom-icons.mdx b/packages/docs/content/docs/custom-icons.mdx index 44fa5e45..ddbc92a1 100644 --- a/packages/docs/content/docs/custom-icons.mdx +++ b/packages/docs/content/docs/custom-icons.mdx @@ -19,6 +19,8 @@ Custom icons can be used with [Collections](/docs/collection-overview) or [Custo This example uses Font Awesome to supply the icon. + + ```js import { faHouse } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -27,6 +29,24 @@ import CMS from '@staticcms/core'; CMS.registerIcon('house', () => h(FontAwesomeIcon, {icon=faHouse, size="lg"})); ``` +```jsx +import { faHouse } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import CMS from '@staticcms/core'; + +CMS.registerIcon('house', () => ); +``` + +```tsx +import { faHouse } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import CMS from '@staticcms/core'; + +CMS.registerIcon('house', () => ); +``` + + + ## Usage ### Collection diff --git a/packages/docs/content/docs/custom-previews.mdx b/packages/docs/content/docs/custom-previews.mdx index 29b1fff3..f307589b 100644 --- a/packages/docs/content/docs/custom-previews.mdx +++ b/packages/docs/content/docs/custom-previews.mdx @@ -34,124 +34,278 @@ The following parameters will be passed to your `react_component` during render: ### Example -```html - - +CMS.registerPreviewTemplate('posts', PostPreview); ``` +```jsx +import CMS, { useMediaAsset } from '@staticcms/core'; + +const PostPreview = ({ widgetFor, getAsset, entry, collection, field }) => { + const imageUrl = useMediaAsset(entry.data.image, collection, field, entry); + + return ( +
+

{entry.data.title}

+ +
{widgetFor('body')}
+
+ ); +}; + +CMS.registerPreviewTemplate('posts', PostPreview); +``` + +```tsx +import CMS, { useMediaAsset } from '@staticcms/core'; + +import type { TemplatePreviewProps } from '@staticcms/core'; + +/** + * The type for 'entry.data' + */ +interface Post { + image: string; + title: string; + body: string; +} + +const PostPreview = ({ widgetFor, getAsset, entry, collection, field }: TemplatePreviewProps) => { + const imageUrl = useMediaAsset(entry.data.image, collection, field, entry); + + return ( +
+

{entry.data.title}

+ +
{widgetFor('body')}
+
+ ); +}; + +CMS.registerPreviewTemplate('posts', PostPreview); +``` + + + ### Lists and Objects The API for accessing the individual fields of list- and object-type entries is similar to the API for accessing fields in standard entries, but there are a few key differences. Access to these nested fields is facilitated through the `widgetsFor` function, which is passed to the preview template component during render. -**List Example:** +#### List Template Example -```html - +```js +[{ + data: { title: 'Mathias', description: 'Co-Founder'}, + widgets: { title: , description: } +}, +{ + data: { title: 'Chris', description: 'Co-Founder'}, + widgets: { title: , description: } +}] ``` -**Object Example:** + -```html - +CMS.registerPreviewTemplate('authors', AuthorsPreview); ``` + +```jsx +import CMS from '@staticcms/core'; + +const AuthorsPreview = ({ widgetsFor }) => { + return ( +
+ {/* This is a static header that would only be rendered once for the entire list */} +

Authors

+ {/* Here we provide a simple mapping function that will be applied to each object in the array of authors */} + {widgetsFor('authors').map((author, index) => ( +
+
+ {author.data.name} + {author.widgets.description} +
+ ))} +
+ ); +}; + +CMS.registerPreviewTemplate('authors', AuthorsPreview); +``` + +```tsx +import CMS from '@staticcms/core'; + +import type { TemplatePreviewProps } from '@staticcms/core'; + +interface Author { + name: string; + description: string; +} + +/** + * The type for 'entry.data' + */ +interface Authors { + authors: Author[]; +} + +const AuthorsPreview = ({ widgetsFor }: TemplatePreviewProps) => { + return ( +
+ {/* This is a static header that would only be rendered once for the entire list */} +

Authors

+ {/* Here we provide a simple mapping function that will be applied to each object in the array of authors */} + {widgetsFor('authors').map((author, index) => ( +
+
+ {author.data.name} + {author.widgets.description} +
+ ))} +
+ ); +}; + +CMS.registerPreviewTemplate('authors', AuthorsPreview); +``` + +
+ +#### Object Example + +Object fields are simpler than lists - instead of `widgetsFor` returning an array of objects, it returns a single object. Accessing the shape of that object is the same as the shape of objects returned for list fields: + +```js +{ + data: { front_limit: 0, author: 'Chris' }, + widgets: { front_limit: , author: } +} +``` + + + +```js +const GeneralPreview = ({ entry, widgetsFor }) => { + const title = entry.data.site_title; + const posts = entry.data.posts; + + return h( + 'div', + {}, + h('h1', {}, title), + h( + 'dl', + {}, + h('dt', {}, 'Posts on Frontpage'), + h('dd', {}, widgetsFor('posts').widgets.front_limit || 0), + + h('dt', {}, 'Default Author'), + h('dd', {}, widgetsFor('posts').data.author || 'None'), + ), + ); +}; + +CMS.registerPreviewTemplate('general', GeneralPreview); +``` + +```jsx +import CMS from '@staticcms/core'; + +const GeneralPreview = ({ entry, widgetsFor }) => { + const title = entry.data.site_title; + const posts = entry.data.posts; + + return ( +
+

{title}

+
+
Posts on Frontpage
+
{widgetsFor('posts').widgets.front_limit || 0)}
+
Default Author
+
{widgetsFor('posts').data.author || 'None')}
+
+
+ ); +}; + +CMS.registerPreviewTemplate('general', GeneralPreview); +``` + +```tsx +import CMS from '@staticcms/core'; + +import type { TemplatePreviewProps } from '@staticcms/core'; + +interface Posts { + front_limit?: number; + author?: string; +} + +/** + * The type for 'entry.data' + */ +interface GeneralSettings { + site_title: string; + posts: Posts; +} + +const GeneralPreview = ({ entry, widgetsFor }: TemplatePreviewProps) => { + const title = entry.data.site_title; + const posts = entry.data.posts; + + return ( +
+

{title}

+
+
Posts on Frontpage
+
{widgetsFor('posts').widgets.front_limit || 0)}
+
Default Author
+
{widgetsFor('posts').data.author || 'None')}
+
+
+ ); +}; + +CMS.registerPreviewTemplate('general', GeneralPreview); +``` + +
diff --git a/packages/docs/content/docs/custom-widgets.mdx b/packages/docs/content/docs/custom-widgets.mdx index ba268b80..a4a4d98d 100644 --- a/packages/docs/content/docs/custom-widgets.mdx +++ b/packages/docs/content/docs/custom-widgets.mdx @@ -24,6 +24,7 @@ CMS.registerWidget(name, control, [preview], [{ schema }]); // Using npm module import import CMS from '@staticcms/core'; + CMS.registerWidget(name, control, [preview], [{ schema }]); ``` @@ -120,49 +121,136 @@ Register widget takes an optional object of options. These options include: ### Example -`admin/index.html` + -```html - - +CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema }); ``` +```jsx +import CMS from '@staticcms/core'; + +const CategoriesControl = ({ label, value, field, onChange }) => { + const separator = useMemo(() => field.separator ?? ', ', [field.separator]); + + const handleChange = useCallback((e) => { + onChange(e.target.value.split(separator).map(e => e.trim())); + }, [separator, onChange]); + + return ( +
+ + +
+ ); +}; + +const CategoriesPreview = ({ value }) => { + return ( +
    + {value.map((val, index) => { + return
  • {value}
  • ; + })} +
+ ); +}; + +const schema = { + properties: { + separator: { type: 'string' }, + }, +}; + +CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema }); +``` + +```tsx +import CMS from '@staticcms/core'; + +import type { WidgetControlProps, WidgetPreviewProps } from '@staticcms/core'; + +interface CategoriesField { + widget: 'categories' +} + +const CategoriesControl = ({ label, value, field, onChange }: WidgetControlProps) => { + const separator = useMemo(() => field.separator ?? ', ', [field.separator]); + + const handleChange = useCallback((e) => { + onChange(e.target.value.split(separator).map(e => e.trim())); + }, [separator, onChange]); + + return ( +
+ + +
+ ); +}; + +const CategoriesPreview = ({ value }: WidgetPreviewProps) => { + return ( +
    + {value.map((val, index) => { + return
  • {value}
  • ; + })} +
+ ); +}; + +const schema = { + properties: { + separator: { type: 'string' }, + }, +}; + +CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema }); +``` + +
+ `admin/config.yml` (or `admin/config.js`) @@ -272,48 +360,61 @@ If you want to use the media library in your custom widget you will need to use ```js +const FileControl = ({ collection, field, value, entry, onChange }) => { + const handleOpenMediaLibrary = useMediaInsert(value, { field, controlID }, onChange); + + const assetSource = useMediaAsset(value, collection, field, entry); + + return [ + h('button', { type: 'button', onClick: handleOpenMediaLibrary }, 'Upload'), + h('img', { role: 'presentation', src: assetSource }) + ]; +}; +``` + +```jsx import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; const FileControl = ({ collection, field, value, entry, onChange }) => { - const handleOpenMediaLibrary = useMediaInsert( - value, - { field, controlID }, - onChange, - ); + const handleOpenMediaLibrary = useMediaInsert(value, { field, controlID }, onChange); const assetSource = useMediaAsset(value, collection, field, entry); return ( <> - + ); }; ``` -```ts +```tsx import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import type { WidgetControlProps } from '@staticcms/core/interface'; import type { FC } from 'react'; -const FileControl: FC> = - ({ collection, field, value, entry, onChange }) => { - - const handleOpenMediaLibrary = useMediaInsert( - internalValue, - { field, controlID }, - onChange, - ); +const FileControl: FC> = ({ + collection, + field, + value, + entry, + onChange, +}) => { + const handleOpenMediaLibrary = useMediaInsert(internalValue, { field, controlID }, onChange); const assetSource = useMediaAsset(value, collection, field, entry); return ( <> - + ); diff --git a/packages/docs/content/docs/widget-markdown.mdx b/packages/docs/content/docs/widget-markdown.mdx index 7f95cc8c..48b13ec8 100644 --- a/packages/docs/content/docs/widget-markdown.mdx +++ b/packages/docs/content/docs/widget-markdown.mdx @@ -60,6 +60,10 @@ Shortcodes can be added to customize the Markdown editor via `registerShortcode` ### Usage +```markdown +[youtube|p6h-rYSVX90] +``` + ```js @@ -117,8 +121,110 @@ CMS.registerShortcode('youtube', { }); ``` -```markdown -[youtube|p6h-rYSVX90] +```jsx +import CMS from '@staticcms/core'; + +CMS.registerShortcode('youtube', { + label: 'YouTube', + openTag: '[', + closeTag: ']', + separator: '|', + toProps: args => { + if (args.length > 0) { + return { src: args[0] }; + } + + return { src: '' }; + }, + toArgs: ({ src }) => { + return [src]; + }, + control: ({ src, onChange }) => { + return ( + + { + onChange({ src: event.target.value }); + }} + /> +