Feature/typescript conversion (#44)

This commit is contained in:
Daniel Lautzenheiser
2022-10-20 11:57:30 -04:00
committed by GitHub
parent 7fe23baf0b
commit e60e1fa755
517 changed files with 27034 additions and 27191 deletions

View File

@ -5,6 +5,6 @@ author: Daniel Lautzenheiser
description: >-
Announcing the release of Static CMS v1.0.
twitter_image: /img/static-cms.png
date: 2022-10-30T12:00:00.000Z
date: 2022-11-30T12:00:00.000Z
---
Today were releasing Static CMS 1.0!

View File

@ -22,7 +22,7 @@ Entries are loaded and persisted through a `backend` that will typically represe
**Config:** Holds the environment configuration (backend type, available collections and fields).
**Collections:** List of available collections, their fields and metadata information.
**Collections:** List of available collections and their fields information.
**Entries:** Entries for each field.
@ -58,7 +58,7 @@ For either updating an existing entry or creating a new one, the `EntryEditor` i
The control component receives one (1) callback as a prop: `onChange`.
* onChange (required): Should be called when the users changes the current value. It will ultimately end up updating the EntryDraft object in the Redux Store, thus updating the preview component.
* onAddAsset & onRemoveAsset (optionals): Should be invoked with an `AssetProxy` value object if the field accepts file uploads for media (images, for example). `onAddAsset` will get the current media stored in the Redux state tree while `onRemoveAsset` will remove it. AssetProxy objects are stored in the `Medias` object and referenced in the `EntryDraft` object on the state tree.
* addAsset & onRemoveAsset (optionals): Should be invoked with an `AssetProxy` value object if the field accepts file uploads for media (images, for example). `addAsset` will get the current media stored in the Redux state tree while `onRemoveAsset` will remove it. AssetProxy objects are stored in the `Medias` object and referenced in the `EntryDraft` object on the state tree.
Both control and preview widgets receive a `getAsset` selector via props. Displaying the media (or its URI) for the user should always be done via `getAsset`, as it returns an AssetProxy that can return the correct value for both medias already persisted on the server and cached media not yet uploaded.

View File

@ -71,7 +71,7 @@ i18n:
# Optional, defaults to the first item in locales.
# The locale to be used for fields validation and as a baseline for the entry.
default_locale: en
defaultLocale: en
```
### Collection level configuration
@ -442,20 +442,6 @@ init({
CMS.registerPreviewTemplate(...);
```
## Raw CSS in `registerPreviewStyle`
`registerPreviewStyle` can now accept a CSS string, in addition to accepting a url. The feature is activated by passing in an object as the second argument, with `raw` set to a truthy value. This is critical for integrating with modern build tooling. Here's an example using webpack:
```js
/**
* Assumes a webpack project with `sass-loader` and `css-loader` installed.
* Takes advantage of the `toString` method in the return value of `css-loader`.
*/
import CMS from '@staticcms/core';
import styles from '!css-loader!sass-loader!../main.scss';
CMS.registerPreviewStyle(styles.toString(), { raw: true });
```
## Commit Message Templates
You can customize the templates used by Static CMS to generate commit messages by setting the `commit_messages` option under `backend` in your Static CMS `config.yml`.
@ -539,7 +525,7 @@ Example usage:
```javascript
CMS.registerEventListener({
name: 'prePublish',
handler: ({ author, entry }) => console.info(JSON.stringify({ author, data: entry.get('data') })),
handler: ({ author, entry }) => console.info(JSON.stringify({ author, data: entry.data })),
});
```
@ -549,7 +535,7 @@ Supported events are `prePublish`, `postPublish`, `preSave` and `postSave`. The
CMS.registerEventListener({
name: 'preSave',
handler: ({ entry }) => {
return entry.get('data').set('title', 'new title');
return entry.data.set('title', 'new title');
},
});
```
@ -613,9 +599,6 @@ collections:
- label: Body
name: body
widget: markdown
# adding a meta object with a path property allows editing the path of entries
# moving an existing entry will move the entire sub tree of the entry to the new location
meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
```
Nested collections expect the following directory structure:

View File

@ -33,7 +33,7 @@ CMS.registerWidget(name, control, [preview], [schema]);
| ----------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | Widget name, allows this widget to be used via the field `widget` property in config |
| `control` | `React.Component` or `string` | <ul><li>React component that renders the control, receives the following props: <ul><li>**value:** Current field value</li><li>**field:** Immutable map of current field configuration</li><li>**forID:** Unique identifier for the field</li><li>**classNameWrapper:** class name to apply CMS styling to the field</li><li>**onChange:** Callback function to update the field value</li></ul></li><li>Name of a registered widget whose control should be used (includes built in widgets).</li></ul> |
| [`preview`] | `React.Component`, optional | Renders the widget preview, receives the following props: <ul><li>**value:** Current preview value</li><li>**field:** Immutable map of current field configuration</li><li>**metadata:** Immutable map of any available metadata for the current field</li><li>**getAsset:** Function for retrieving an asset url for image/file fields</li><li>**entry:** Immutable Map of all entry data</li><li>**fieldsMetaData:** Immutable map of metadata from all fields.</li></ul> |
| [`preview`] | `React.Component`, optional | Renders the widget preview, receives the following props: <ul><li>**value:** Current preview value</li><li>**field:** Immutable map of current field configuration</li><li>**getAsset:** Function for retrieving an asset url for image/file fields</li><li>**entry:** Immutable Record of all entry data</li></ul> |
| [`schema`] | `JSON Schema object`, optional | Enforces a schema for the widget's field configuration |
**Example:**

View File

@ -6,50 +6,12 @@ group: Customization
The Static CMS exposes a `window.CMS` global object that you can use to register custom widgets, previews and editor plugins. The available customization methods are:
* **registerPreviewStyle:** Register a custom stylesheet to use on the preview pane.
* **registerPreviewTemplate:** Registers a template for a collection.
- **registerPreviewTemplate:** Registers a template for a collection.
### React Components inline interaction
Static CMS is a collection of React components and exposes two constructs globally to allow you to create components inline: createClass and h (alias for React.createElement).
## `registerPreviewStyle`
Register a custom stylesheet to use on the preview pane.
```js
CMS.registerPreviewStyle(file);
```
**Params:**
* **file:** css file path
**Example:**
```html
// index.html
<script src="https://unpkg.com/@staticcms/core@%5E0.1.0/dist/static-cms-core.js"></script>
<script>
CMS.registerPreviewStyle("/example.css");
</script>
```
```css
/* example.css */
html,
body {
color: #444;
font-size: 14px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
body {
padding: 20px;
}
```
## `registerPreviewTemplate`
Registers a template for a folder collection or an individual file in a file collection.
@ -58,44 +20,50 @@ Registers a template for a folder collection or an individual file in a file col
**Params:**
* name: The name of the collection (or file for file collections) which this preview component will be used for.
* Folder collections: Use the name of the collection
* File collections: Use the name of the file
* react_component: A React component that renders the collection data. Six props will be passed to your component during render:
* entry: Immutable collection containing the entry data.
* widgetFor: Returns the appropriate widget preview component for a given field.
* [widgetsFor](#lists-and-objects): Returns an array of objects with widgets and associated field data. For use with list and object type entries.
* getAsset: Returns the correct filePath or in-memory preview for uploaded images.
- name: The name of the collection (or file for file collections) which this preview component will be used for.
- Folder collections: Use the name of the collection
- File collections: Use the name of the file
- react_component: A React component that renders the collection data. Six props will be passed to your component during render:
- entry: Immutable collection containing the entry data.
- widgetFor: Returns the appropriate widget preview component for a given field.
- [widgetsFor](#lists-and-objects): Returns an array of objects with widgets and associated field data. For use with list and object type entries.
- getAsset: Returns the correct filePath or in-memory preview for uploaded images.
**Example:**
```html
<script src="https://unpkg.com/@staticcms/core@%5E0.1.0/dist/static-cms-core.js"></script>
<script>
var PostPreview = createClass({
render: function() {
render: function () {
var entry = this.props.entry;
var image = entry.getIn(['data', 'image']);
var image = entry.data.image;
var bg = this.props.getAsset(image);
return h('div', {},
h('h1', {}, entry.getIn(['data', 'title'])),
h('img', {src: bg.toString()}),
h('div', {"className": "text"}, this.props.widgetFor('body'))
return h(
'div',
{},
h('h1', {}, entry.data.title),
h('img', { src: bg.toString() }),
h('div', { className: 'text' }, this.props.widgetFor('body')),
);
}
},
});
CMS.registerPreviewTemplate("posts", PostPreview);
CMS.registerPreviewTemplate('posts', PostPreview);
</script>
```
* document: The preview pane iframe's [document instance](https://github.com/ryanseddon/react-frame-component/tree/9f8f06e1d3fc40da7122f0a57c62f7dec306e6cb#accessing-the-iframes-window-and-document).
* window: The preview pane iframe's [window instance](https://github.com/ryanseddon/react-frame-component/tree/9f8f06e1d3fc40da7122f0a57c62f7dec306e6cb#accessing-the-iframes-window-and-document).
- document: The preview pane iframe's [document instance](https://github.com/ryanseddon/react-frame-component/tree/9f8f06e1d3fc40da7122f0a57c62f7dec306e6cb#accessing-the-iframes-window-and-document).
- window: The preview pane iframe's [window instance](https://github.com/ryanseddon/react-frame-component/tree/9f8f06e1d3fc40da7122f0a57c62f7dec306e6cb#accessing-the-iframes-window-and-document).
### 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.
**Note**: as is often the case with the Static CMS API, arrays and objects are created with Immutable.js. If some of the methods that we use are unfamiliar, such as `getIn`, check out [their docs](https://facebook.github.io/immutable-js/docs/#/) to get a better understanding.
**List Example:**
```html
<script>
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.
**Note**: as is often the case with the Static CMS API, arrays and objects are created with Immutable.js. If some of the methods that we use are unfamiliar, such as `getIn`, check out [their docs](https://facebook.github.io/immutable-js/docs/#/) to get a better understanding.
**List Example:**
```html
<script>
var AuthorsPreview = createClass({
// For list fields, the widgetFor function returns an array of objects
// that you can map over in your template. If our field is a list of
@ -113,31 +81,36 @@ Registers a template for a folder collection or an individual file in a file col
//
// Templating would look something like this:
render: function() {
return h('div', {},
render: function () {
return h(
'div',
{},
// This is a static header that would only be rendered once for the entire list
h('h1', {}, 'Authors'),
// Here we provide a simple mapping function that will be applied to each
// object in the array of authors
this.props.widgetsFor('authors').map(function(author, index) {
return h('div', {key: index},
this.props.widgetsFor('authors').map(function (author, index) {
return h(
'div',
{ key: index },
h('hr', {}),
h('strong', {}, author.getIn(['data', 'name'])),
author.getIn(['widgets', 'description'])
h('strong', {}, author.data.name),
author.widgets.description,
);
})
}),
);
}
},
});
CMS.registerPreviewTemplate("authors", AuthorsPreview);
</script>
```
**Object Example:**
```html
<script>
CMS.registerPreviewTemplate('authors', AuthorsPreview);
</script>
```
**Object Example:**
```html
<script>
var GeneralPreview = createClass({
// Object fields are simpler than lists - instead of `widgetsFor` returning
// an array of objects, it returns a single object. Accessing the shape of
@ -147,38 +120,28 @@ Registers a template for a folder collection or an individual file in a file col
// data: { front_limit: 0, author: 'Chris' },
// widgets: { front_limit: (<WidgetComponent>), author: (WidgetComponent>)}
// }
render: function() {
render: function () {
var entry = this.props.entry;
var title = entry.getIn(['data', 'site_title']);
var posts = entry.getIn(['data', 'posts']);
var title = entry.data.site_title;
var posts = entry.data.posts;
return h('div', {},
return h(
'div',
{},
h('h1', {}, title),
h('dl', {},
h(
'dl',
{},
h('dt', {}, 'Posts on Frontpage'),
h('dd', {}, this.props.widgetsFor('posts').getIn(['widgets', 'front_limit']) || 0),
h('dd', {}, this.props.widgetsFor('posts').widgets.front_limit || 0),
h('dt', {}, 'Default Author'),
h('dd', {}, this.props.widgetsFor('posts').getIn(['data', 'author']) || 'None'),
)
h('dd', {}, this.props.widgetsFor('posts').data.author || 'None'),
),
);
}
},
});
CMS.registerPreviewTemplate("general", GeneralPreview);
</script>
```
### Accessing Metadata
Preview Components also receive an additional prop: `fieldsMetaData`. It contains aditional information (besides the plain textual value of each field) that can be useful for preview purposes. For example, the Relation widget passes the whole selected relation data in `fieldsMetaData`.
```js
export default class ArticlePreview extends React.Component {
render() {
const {entry, fieldsMetaData} = this.props;
const author = fieldsMetaData.getIn(['authors', data.author]);
return <article><h2>{ entry.getIn(['data', 'title']) }</h2>
{author &&<AuthorBio author={author.toJS()}/>}
</article>
}
}
```
CMS.registerPreviewTemplate('general', GeneralPreview);
</script>
```

View File

@ -296,4 +296,4 @@ Finally, add the following to the collections array in `config.yml`
- { label: Link, name: link, widget: string }
```
Now you can add, rename, and rearrange the navigation items on your blog.
Now you can add, rename, and rearrange the navigation items on your blog.

View File

@ -10,13 +10,13 @@ The color widget translates a color picker to a color string.
- **Data type:** string
- **Options:**
- `default`: accepts a string; defaults to an empty string. Sets the default value
- `allowInput`: accepts a boolean, defaults to `false`. Allows manual editing of the color input value
- `enableAlpha`: accepts a boolean, defaults to `false`. Enables Alpha editing
- `allow_input`: accepts a boolean, defaults to `false`. Allows manual editing of the color input value
- `enable_alpha`: accepts a boolean, defaults to `false`. Enables Alpha editing
- **Example:**
```yaml
- { label: 'Color', name: 'color', widget: 'color' }
```
- **Example:**
```yaml
- { label: 'Color', name: 'color', widget: 'color', enableAlpha: true, allowInput: true }
- { label: 'Color', name: 'color', widget: 'color', enable_alpha: true, allow_input: true }
```

View File

@ -15,7 +15,6 @@ The markdown widget provides a full fledged text editor allowing users to format
* `minimal`: accepts a boolean value, `false` by default. Sets the widget height to minimum possible.
* `buttons`: an array of strings representing the formatting buttons to display (all shown by default). Buttons include: `bold`, `italic`, `code`, `link`, `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`, `quote`, `bulleted-list`, and `numbered-list`.
* `editor_components`: an array of strings representing the names of editor components to display (all shown by default). Static CMS includes `image` and `code-block` editor components by default, and custom components may be [created and registered](/docs/custom-widgets/#registereditorcomponent).
* `modes`: an array of strings representing the names of allowed editor modes. Possible modes are `raw` and `rich_text`. A toggle button appears in the toolbar when more than one mode is available.
* `sanitize_preview`: accepts a boolean value, `false` by default. Sanitizes markdown preview to prevent XSS attacks - might alter the preview content.
* **Example:**

View File

@ -1,5 +1,5 @@
updates:
- date: 2022-10-30T00:00:00.000Z
- date: 2022-11-30T00:00:00.000Z
version: 1.0.0
description: The first major release of Static CMS with an all-new UI, revamped
documentation and much more.

View File

@ -60,13 +60,13 @@ class Highlight extends React.Component {
}
function BlogPostPreview({ entry, widgetFor }) {
const data = entry.get('data');
const data = entry.data;
return (
<PreviewContainer highlight={true}>
<BlogPostTemplate
title={data.get('title')}
author={data.get('author')}
date={dayjs(data.get('date')).format('MMMM D, YYYY')}
title={data.title}
author={data.author}
date={dayjs(data.date).format('MMMM D, YYYY')}
body={widgetFor('body')}
/>
</PreviewContainer>
@ -74,7 +74,7 @@ function BlogPostPreview({ entry, widgetFor }) {
}
function CommunityPreview({ entry }) {
const { title, headline, subhead, sections } = entry.get('data').toJS();
const { title, headline, subhead, sections } = entry.data.toJS();
return (
<PreviewContainer>
<Community title={title} headline={headline} subhead={subhead} sections={sections} />
@ -85,7 +85,7 @@ function CommunityPreview({ entry }) {
function DocsPreview({ entry, widgetFor }) {
return (
<PreviewContainer highlight={true}>
<DocsTemplate title={entry.getIn(['data', 'title'])} body={widgetFor('body')} />
<DocsTemplate title={entry.data.title} body={widgetFor('body')} />
</PreviewContainer>
);
}
@ -93,7 +93,7 @@ function DocsPreview({ entry, widgetFor }) {
function WidgetDocPreview({ entry, widgetFor }) {
return (
<PreviewContainer highlight={true}>
<WidgetDoc visible={true} label={entry.get('label')} body={widgetFor('body')} />
<WidgetDoc visible={true} label={entry.label} body={widgetFor('body')} />
</PreviewContainer>
);
}
@ -102,12 +102,11 @@ function ReleasePreview({ entry }) {
return (
<PreviewContainer highlight={true}>
<WhatsNew
updates={entry
.getIn(['data', 'updates'])
updates={entry.data.updates
.map(release => ({
version: release.get('version'),
date: dayjs(release.get('date')).format('MMMM D, YYYY'),
description: release.get('description'),
version: release.version,
date: dayjs(release.date).format('MMMM D, YYYY'),
description: release.description,
}))
.toJS()}
/>
@ -118,12 +117,11 @@ function ReleasePreview({ entry }) {
function NotificationPreview({ entry }) {
return (
<PreviewContainer>
{entry
.getIn(['data', 'notifications'])
.filter(notif => notif.get('published'))
{entry.data.notifications
.filter(notif => notif.published)
.map((notif, idx) => (
<Notification key={idx} url={notif.get('url')} loud={notif.get('loud')}>
{notif.get('message')}
<Notification key={idx} url={notif.url} loud={notif.loud}>
{notif.message}
</Notification>
))}
</PreviewContainer>

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import { graphql } from 'gatsby';
import { trimStart, trimEnd } from 'lodash';
import trimStart from 'lodash/trimStart';
import trimEnd from 'lodash/trimEnd';
import TwitterMeta from '../components/twitter-meta';
import Layout from '../components/layout';

View File

@ -195,12 +195,6 @@ collections:
- { label: Title, name: title }
- { label: Author, name: author }
- { label: Description (for blog list), name: description, widget: text }
- {
label: Meta Description (defaults to Description),
name: meta_description,
widget: text,
required: false,
}
- { label: Twitter Image, name: twitter_image, widget: image }
- { label: Date, name: date, widget: date }
- { label: Body, name: body, widget: markdown }