docs: update examples (#411)

This commit is contained in:
Daniel Lautzenheiser 2023-01-23 13:20:17 -05:00 committed by GitHub
parent fcfbd6de13
commit cab7166eb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 585 additions and 158 deletions

View File

@ -374,12 +374,12 @@ export function getAdditionalLink(id: string): AdditionalLink | undefined {
/** /**
* Markdown editor shortcodes * Markdown editor shortcodes
*/ */
export function registerShortcode(name: string, config: ShortcodeConfig) { export function registerShortcode<P = {}>(name: string, config: ShortcodeConfig<P>) {
if (registry.backends[name]) { if (registry.backends[name]) {
console.error(`Shortcode [${name}] already registered. Please choose a different name.`); console.error(`Shortcode [${name}] already registered. Please choose a different name.`);
return; return;
} }
registry.shortcodes[name] = config; registry.shortcodes[name] = config as unknown as ShortcodeConfig;
} }
export function getShortcode(name: string): ShortcodeConfig { export function getShortcode(name: string): ShortcodeConfig {

View File

@ -50,6 +50,8 @@ CMS.registerAdditionalLink({
### Custom Page ### Custom Page
<CodeTabs>
```js ```js
const CustomPage = () => { const CustomPage = () => {
return h('div', {}, 'I am a custom page!'); return h('div', {}, 'I am a custom page!');
@ -64,3 +66,37 @@ CMS.registerAdditionalLink({
}, },
}); });
``` ```
```jsx
const CustomPage = () => {
return <div>I am a custom page!</div>;
};
CMS.registerAdditionalLink({
id: 'custom-page',
title: 'Custom Page',
data: CustomPage,
options: {
icon: 'page',
},
});
```
```tsx
import type { FC } from 'react';
const CustomPage: FC = () => {
return <div>I am a custom page!</div>;
};
CMS.registerAdditionalLink({
id: 'custom-page',
title: 'Custom Page',
data: CustomPage,
options: {
icon: 'page',
},
});
```
</CodeTabs>

View File

@ -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. This example uses Font Awesome to supply the icon.
<CodeTabs>
```js ```js
import { faHouse } from '@fortawesome/free-solid-svg-icons'; import { faHouse } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -27,6 +29,24 @@ import CMS from '@staticcms/core';
CMS.registerIcon('house', () => h(FontAwesomeIcon, {icon=faHouse, size="lg"})); 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', () => <FontAwesomeIcon icon={faHouse} size="lg" />);
```
```tsx
import { faHouse } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import CMS from '@staticcms/core';
CMS.registerIcon('house', () => <FontAwesomeIcon icon={faHouse} size="lg" />);
```
</CodeTabs>
## Usage ## Usage
### Collection ### Collection

View File

@ -34,66 +34,95 @@ The following parameters will be passed to your `react_component` during render:
### Example ### Example
```html <CodeTabs>
<script src="https://unpkg.com/@staticcms/app@^1.0.0/dist/static-cms-app.js"></script>
<script>
const PostPreview = ({ widgetFor, getAsset, entry }) => {
const [imageUrl, setImageUrl] = useState('');
const image = useMemo(() => entry.data.image, [entry.data.image]);
useEffect(() => { ```js
let alive = true; const PostPreview = ({ widgetFor, getAsset, entry, collection, field }) => {
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
const loadImage = async () => {
const imageAsset = await getAsset(image);
if (alive) {
setImageUrl(imageAsset.toString());
}
};
loadImage();
return () => {
alive = false;
};
}, [image]);
return h( return h(
'div', 'div',
{}, {},
h('h1', {}, entry.data.title), h('h1', {}, entry.data.title),
h('img', { src: imageUrl }), h('img', { src: imageUrl }),
h('div', { classtitle: 'text' }, widgetFor('body')), h('div', { className: 'text' }, widgetFor('body')),
); );
}); };
CMS.registerPreviewTemplate('posts', PostPreview); CMS.registerPreviewTemplate('posts', PostPreview);
</script>
``` ```
```jsx
import CMS, { useMediaAsset } from '@staticcms/core';
const PostPreview = ({ widgetFor, getAsset, entry, collection, field }) => {
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
return (
<div>
<h1>{entry.data.title}</h1>
<img src={imageUrl} />
<div className='text'>{widgetFor('body')}</div>
</div>
);
};
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<Post>) => {
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
return (
<div>
<h1>{entry.data.title}</h1>
<img src={imageUrl} />
<div className='text'>{widgetFor('body')}</div>
</div>
);
};
CMS.registerPreviewTemplate('posts', PostPreview);
```
</CodeTabs>
### Lists and Objects ### 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. 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 For list fields, the widgetFor function returns an array of objects that you can map over in your template. If your field is a list of authors containing two entries, with fields `name` and `description`, the return value of `widgetsFor` would look like this:
<script>
// For list fields, the widgetFor function returns an array of objects ```js
// that you can map over in your template. If our field is a list of [{
// authors containing two entries, with fields `name` and `description`, data: { title: 'Mathias', description: 'Co-Founder'},
// the return value of `widgetsFor` would look like this: widgets: { title: <WidgetComponent>, description: <WidgetComponent>}
// },
// [{ {
// data: { title: 'Mathias', description: 'Co-Founder'}, data: { title: 'Chris', description: 'Co-Founder'},
// widgets: { title: (<WidgetComponent>), description: (WidgetComponent>)} widgets: { title: <WidgetComponent>, description: <WidgetComponent>}
// }, }]
// { ```
// data: { title: 'Chris', description: 'Co-Founder'},
// widgets: { title: (<WidgetComponent>), description: (WidgetComponent>)} <CodeTabs>
// }]
// ```js
// Templating would look something like this:
const AuthorsPreview = ({ widgetsFor }) => { const AuthorsPreview = ({ widgetsFor }) => {
return h( return h(
'div', 'div',
@ -117,21 +146,84 @@ The API for accessing the individual fields of list- and object-type entries is
}; };
CMS.registerPreviewTemplate('authors', AuthorsPreview); CMS.registerPreviewTemplate('authors', AuthorsPreview);
</script>
``` ```
**Object Example:** ```jsx
import CMS from '@staticcms/core';
```html const AuthorsPreview = ({ widgetsFor }) => {
<script> return (
// Object fields are simpler than lists - instead of `widgetsFor` returning <div>
// an array of objects, it returns a single object. Accessing the shape of {/* This is a static header that would only be rendered once for the entire list */}
// that object is the same as the shape of objects returned for list fields: <h1>Authors</h1>
// {/* Here we provide a simple mapping function that will be applied to each object in the array of authors */}
// { {widgetsFor('authors').map((author, index) => (
// data: { front_limit: 0, author: 'Chris' }, <div key={index}>
// widgets: { front_limit: (<WidgetComponent>), author: (WidgetComponent>)} <hr />
// } <strong>{author.data.name}</strong>
{author.widgets.description}
</div>
))}
</div>
);
};
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<Authors>) => {
return (
<div>
{/* This is a static header that would only be rendered once for the entire list */}
<h1>Authors</h1>
{/* Here we provide a simple mapping function that will be applied to each object in the array of authors */}
{widgetsFor('authors').map((author, index) => (
<div key={index}>
<hr />
<strong>{author.data.name}</strong>
{author.widgets.description}
</div>
))}
</div>
);
};
CMS.registerPreviewTemplate('authors', AuthorsPreview);
```
</CodeTabs>
#### 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: <WidgetComponent>, author: <WidgetComponent>}
}
```
<CodeTabs>
```js
const GeneralPreview = ({ entry, widgetsFor }) => { const GeneralPreview = ({ entry, widgetsFor }) => {
const title = entry.data.site_title; const title = entry.data.site_title;
const posts = entry.data.posts; const posts = entry.data.posts;
@ -153,5 +245,67 @@ The API for accessing the individual fields of list- and object-type entries is
}; };
CMS.registerPreviewTemplate('general', GeneralPreview); CMS.registerPreviewTemplate('general', GeneralPreview);
</script>
``` ```
```jsx
import CMS from '@staticcms/core';
const GeneralPreview = ({ entry, widgetsFor }) => {
const title = entry.data.site_title;
const posts = entry.data.posts;
return (
<div>
<h1>{title}</h1>
<dl>
<dt>Posts on Frontpage</dt>
<dd>{widgetsFor('posts').widgets.front_limit || 0)}</dd>
<dt>Default Author</dt>
<dd>{widgetsFor('posts').data.author || 'None')}</dd>
</dl>
</div>
);
};
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<GeneralSettings>) => {
const title = entry.data.site_title;
const posts = entry.data.posts;
return (
<div>
<h1>{title}</h1>
<dl>
<dt>Posts on Frontpage</dt>
<dd>{widgetsFor('posts').widgets.front_limit || 0)}</dd>
<dt>Default Author</dt>
<dd>{widgetsFor('posts').data.author || 'None')}</dd>
</dl>
</div>
);
};
CMS.registerPreviewTemplate('general', GeneralPreview);
```
</CodeTabs>

View File

@ -24,6 +24,7 @@ CMS.registerWidget(name, control, [preview], [{ schema }]);
// Using npm module import // Using npm module import
import CMS from '@staticcms/core'; import CMS from '@staticcms/core';
CMS.registerWidget(name, control, [preview], [{ schema }]); CMS.registerWidget(name, control, [preview], [{ schema }]);
``` ```
@ -120,11 +121,9 @@ Register widget takes an optional object of options. These options include:
### Example ### Example
`admin/index.html` <CodeTabs>
```html ```js
<script src="https://unpkg.com/@staticcms/app@^1.0.0/dist/static-cms-app.js"></script>
<script>
const CategoriesControl = ({ label, value, field, onChange }) => { const CategoriesControl = ({ label, value, field, onChange }) => {
const separator = useMemo(() => field.separator ?? ', ', [field.separator]); const separator = useMemo(() => field.separator ?? ', ', [field.separator]);
@ -138,7 +137,7 @@ Register widget takes an optional object of options. These options include:
id: 'inputId', id: 'inputId',
type: 'text', type: 'text',
value: value ? value.join(separator) : '', value: value ? value.join(separator) : '',
onChange: this.handleChange, onChange: handleChange,
}) })
}); });
}; };
@ -147,7 +146,7 @@ Register widget takes an optional object of options. These options include:
return h( return h(
'ul', 'ul',
{}, {},
value.map(function (val, index) { value.map((val, index) => {
return h('li', { key: index }, val); return h('li', { key: index }, val);
}), }),
); );
@ -160,9 +159,98 @@ Register widget takes an optional object of options. These options include:
}; };
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema }); CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema });
</script>
``` ```
```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 (
<div>
<label for="inputId">{label}</label>
<input
id="inputId"
type="text"
value={value ? value.join(separator) : ''}
onChange={handleChange} />
</div>
);
};
const CategoriesPreview = ({ value }) => {
return (
<ul>
{value.map((val, index) => {
return <li key={index}>{value}</li>;
})}
</ul>
);
};
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<string[], CategoriesField>) => {
const separator = useMemo(() => field.separator ?? ', ', [field.separator]);
const handleChange = useCallback((e) => {
onChange(e.target.value.split(separator).map(e => e.trim()));
}, [separator, onChange]);
return (
<div>
<label for="inputId">{label}</label>
<input
id="inputId"
type="text"
value={value ? value.join(separator) : ''}
onChange={handleChange} />
</div>
);
};
const CategoriesPreview = ({ value }: WidgetPreviewProps<string[], CategoriesField>) => {
return (
<ul>
{value.map((val, index) => {
return <li key={index}>{value}</li>;
})}
</ul>
);
};
const schema = {
properties: {
separator: { type: 'string' },
},
};
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, { schema });
```
</CodeTabs>
`admin/config.yml` (or `admin/config.js`) `admin/config.yml` (or `admin/config.js`)
<CodeTabs> <CodeTabs>
@ -272,48 +360,61 @@ If you want to use the media library in your custom widget you will need to use
<CodeTabs> <CodeTabs>
```js ```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 useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
const FileControl = ({ collection, field, value, entry, onChange }) => { const FileControl = ({ collection, field, value, entry, onChange }) => {
const handleOpenMediaLibrary = useMediaInsert( const handleOpenMediaLibrary = useMediaInsert(value, { field, controlID }, onChange);
value,
{ field, controlID },
onChange,
);
const assetSource = useMediaAsset(value, collection, field, entry); const assetSource = useMediaAsset(value, collection, field, entry);
return ( return (
<> <>
<button type="button" onClick={handleOpenMediaLibrary}>Upload</button> <button type="button" onClick={handleOpenMediaLibrary}>
Upload
</button>
<img role="presentation" src={assetSource} /> <img role="presentation" src={assetSource} />
</> </>
); );
}; };
``` ```
```ts ```tsx
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert'; import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import type { WidgetControlProps } from '@staticcms/core/interface'; import type { WidgetControlProps } from '@staticcms/core/interface';
import type { FC } from 'react'; import type { FC } from 'react';
const FileControl: FC<WidgetControlProps<string, MyField>> = const FileControl: FC<WidgetControlProps<string, MyField>> = ({
({ collection, field, value, entry, onChange }) => { collection,
field,
const handleOpenMediaLibrary = useMediaInsert( value,
internalValue, entry,
{ field, controlID },
onChange, onChange,
); }) => {
const handleOpenMediaLibrary = useMediaInsert(internalValue, { field, controlID }, onChange);
const assetSource = useMediaAsset(value, collection, field, entry); const assetSource = useMediaAsset(value, collection, field, entry);
return ( return (
<> <>
<button type="button" onClick={handleOpenMediaLibrary}>Upload</button> <button type="button" onClick={handleOpenMediaLibrary}>
Upload
</button>
<img role="presentation" src={assetSource} /> <img role="presentation" src={assetSource} />
</> </>
); );

View File

@ -60,6 +60,10 @@ Shortcodes can be added to customize the Markdown editor via `registerShortcode`
### Usage ### Usage
```markdown
[youtube|p6h-rYSVX90]
```
<CodeTabs> <CodeTabs>
```js ```js
@ -117,8 +121,110 @@ CMS.registerShortcode('youtube', {
}); });
``` ```
```markdown ```jsx
[youtube|p6h-rYSVX90] 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 (
<span>
<input
key="control-input"
value={src}
onChange={event => {
onChange({ src: event.target.value });
}}
/>
<iframe
key="control-preview"
width="420"
height="315"
src={`https://www.youtube.com/embed/${src}`}
/>
</span>
);
},
preview: ({ src }) => {
return (
<span>
<iframe
width="420"
height="315"
src={`https://www.youtube.com/embed/${src}`}
/>
</span>
);
},
});
```
```tsx
import CMS from '@staticcms/core';
interface YouTubeShortcodeProps {
src: string;
}
CMS.registerShortcode<YouTubeShortcodeProps>('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 (
<span>
<input
key="control-input"
value={src}
onChange={event => {
onChange({ src: event.target.value });
}}
/>
<iframe
key="control-preview"
width="420"
height="315"
src={`https://www.youtube.com/embed/${src}`}
/>
</span>
);
},
preview: ({ src }) => {
return (
<span>
<iframe
width="420"
height="315"
src={`https://www.youtube.com/embed/${src}`}
/>
</span>
);
},
});
``` ```
</CodeTabs> </CodeTabs>

View File

@ -55,6 +55,11 @@ const supportedLanguages: Record<string, CodeLanguage> = {
grammar: Prism.languages.javascript, grammar: Prism.languages.javascript,
language: 'javascript', language: 'javascript',
}, },
'language-jsx': {
title: 'JSX',
grammar: Prism.languages.jsx,
language: 'javascript',
},
'language-markdown': { 'language-markdown': {
title: 'Markdown', title: 'Markdown',
grammar: Prism.languages.markdown, grammar: Prism.languages.markdown,
@ -65,6 +70,11 @@ const supportedLanguages: Record<string, CodeLanguage> = {
grammar: Prism.languages.typescript, grammar: Prism.languages.typescript,
language: 'typescript', language: 'typescript',
}, },
'language-tsx': {
title: 'Typescript',
grammar: Prism.languages.tsx,
language: 'typescript',
},
}; };
interface TabData { interface TabData {