feat: Configure included editor components per field, add optional minimal height (#3299)

This commit is contained in:
Shawn Erquhart 2020-03-04 02:47:37 -05:00 committed by GitHub
parent 93bd0529f1
commit b7b4bcb609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 134 deletions

View File

@ -33,7 +33,7 @@ collections: # A list of collections the CMS should be able to edit
required: false required: false
tagname: '' tagname: ''
- { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' } - { editorComponents: ['youtube'], label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' }
meta: meta:
- { label: 'SEO Description', name: 'description', widget: 'text' } - { label: 'SEO Description', name: 'description', widget: 'text' }

View File

@ -13,19 +13,17 @@ import { markdownToHtml } from '../serializers';
import { editorStyleVars, EditorControlBar } from '../styles'; import { editorStyleVars, EditorControlBar } from '../styles';
import Toolbar from './Toolbar'; import Toolbar from './Toolbar';
const styleStrings = { const rawEditorStyles = ({ minimal }) => `
slateRaw: ` position: relative;
position: relative; overflow: hidden;
overflow: hidden; overflow-x: auto;
overflow-x: auto; min-height: ${minimal ? 'auto' : lengths.richTextEditorMinHeight};
min-height: ${lengths.richTextEditorMinHeight}; font-family: ${fonts.mono};
font-family: ${fonts.mono}; border-top-left-radius: 0;
border-top-left-radius: 0; border-top-right-radius: 0;
border-top-right-radius: 0; border-top: 0;
border-top: 0; margin-top: -${editorStyleVars.stickyDistanceBottom};
margin-top: -${editorStyleVars.stickyDistanceBottom}; `;
`,
};
const RawEditorContainer = styled.div` const RawEditorContainer = styled.div`
position: relative; position: relative;
@ -118,7 +116,7 @@ export default class RawEditor extends React.Component {
className={cx( className={cx(
className, className,
css` css`
${styleStrings.slateRaw} ${rawEditorStyles({ minimal: field.get('minimal') })}
`, `,
)} )}
value={this.state.value} value={this.state.value}

View File

@ -70,6 +70,7 @@ const headingOptions = {
export default class Toolbar extends React.Component { export default class Toolbar extends React.Component {
static propTypes = { static propTypes = {
buttons: ImmutablePropTypes.list, buttons: ImmutablePropTypes.list,
editorComponents: ImmutablePropTypes.list,
onToggleMode: PropTypes.func.isRequired, onToggleMode: PropTypes.func.isRequired,
rawMode: PropTypes.bool, rawMode: PropTypes.bool,
plugins: ImmutablePropTypes.map, plugins: ImmutablePropTypes.map,
@ -86,9 +87,9 @@ export default class Toolbar extends React.Component {
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
}; };
isHidden = button => { isVisible = button => {
const { buttons } = this.props; const { buttons } = this.props;
return List.isList(buttons) ? !buttons.includes(button) : false; return !List.isList(buttons) || buttons.includes(button);
}; };
handleBlockClick = (event, type) => { handleBlockClick = (event, type) => {
@ -114,52 +115,59 @@ export default class Toolbar extends React.Component {
hasMark = () => {}, hasMark = () => {},
hasInline = () => {}, hasInline = () => {},
hasBlock = () => {}, hasBlock = () => {},
editorComponents,
t, t,
} = this.props; } = this.props;
const isVisible = this.isVisible;
const showEditorComponents = !editorComponents || editorComponents.size >= 1;
const showPlugin = ({ id }) => (editorComponents ? editorComponents.includes(id) : true);
const pluginsList = plugins ? plugins.toList().filter(showPlugin) : List();
return ( return (
<ToolbarContainer> <ToolbarContainer>
<div> <div>
<ToolbarButton {isVisible('bold') && (
type="bold" <ToolbarButton
label="Bold" type="bold"
icon="bold" label="Bold"
onClick={this.handleMarkClick} icon="bold"
isActive={hasMark('bold')} onClick={this.handleMarkClick}
isHidden={this.isHidden('bold')} isActive={hasMark('bold')}
disabled={disabled} disabled={disabled}
/> />
<ToolbarButton )}
type="italic" {isVisible('italic') && (
label="Italic" <ToolbarButton
icon="italic" type="italic"
onClick={this.handleMarkClick} label="Italic"
isActive={hasMark('italic')} icon="italic"
isHidden={this.isHidden('italic')} onClick={this.handleMarkClick}
disabled={disabled} isActive={hasMark('italic')}
/> disabled={disabled}
<ToolbarButton />
type="code" )}
label="Code" {isVisible('code') && (
icon="code" <ToolbarButton
onClick={this.handleMarkClick} type="code"
isActive={hasMark('code')} label="Code"
isHidden={this.isHidden('code')} icon="code"
disabled={disabled} onClick={this.handleMarkClick}
/> isActive={hasMark('code')}
<ToolbarButton disabled={disabled}
type="link" />
label="Link" )}
icon="link" {isVisible('link') && (
onClick={onLinkClick} <ToolbarButton
isActive={hasInline('link')} type="link"
isHidden={this.isHidden('link')} label="Link"
disabled={disabled} icon="link"
/> onClick={onLinkClick}
isActive={hasInline('link')}
disabled={disabled}
/>
)}
{/* Show dropdown if at least one heading is not hidden */} {/* Show dropdown if at least one heading is not hidden */}
{Object.keys(headingOptions).some(optionKey => { {Object.keys(headingOptions).some(isVisible) && (
return !this.isHidden(optionKey);
}) && (
<ToolbarDropdownWrapper> <ToolbarDropdownWrapper>
<Dropdown <Dropdown
dropdownTopOverlap="36px" dropdownTopOverlap="36px"
@ -170,12 +178,7 @@ export default class Toolbar extends React.Component {
label="Headings" label="Headings"
icon="hOptions" icon="hOptions"
disabled={disabled} disabled={disabled}
isActive={ isActive={!disabled && Object.keys(headingOptions).some(hasBlock)}
!disabled &&
Object.keys(headingOptions).some(optionKey => {
return hasBlock(optionKey);
})
}
/> />
</DropdownButton> </DropdownButton>
)} )}
@ -183,7 +186,7 @@ export default class Toolbar extends React.Component {
{!disabled && {!disabled &&
Object.keys(headingOptions).map( Object.keys(headingOptions).map(
(optionKey, idx) => (optionKey, idx) =>
!this.isHidden(optionKey) && ( isVisible(optionKey) && (
<DropdownItem <DropdownItem
key={idx} key={idx}
label={headingOptions[optionKey]} label={headingOptions[optionKey]}
@ -195,56 +198,58 @@ export default class Toolbar extends React.Component {
</Dropdown> </Dropdown>
</ToolbarDropdownWrapper> </ToolbarDropdownWrapper>
)} )}
<ToolbarButton {isVisible('quote') && (
type="quote" <ToolbarButton
label="Quote" type="quote"
icon="quote" label="Quote"
onClick={this.handleBlockClick} icon="quote"
isActive={hasBlock('quote')} onClick={this.handleBlockClick}
isHidden={this.isHidden('quote')} isActive={hasBlock('quote')}
disabled={disabled} disabled={disabled}
/> />
<ToolbarButton )}
type="bulleted-list" {isVisible('bulleted-list') && (
label="Bulleted List" <ToolbarButton
icon="list-bulleted" type="bulleted-list"
onClick={this.handleBlockClick} label="Bulleted List"
isActive={hasBlock('bulleted-list')} icon="list-bulleted"
isHidden={this.isHidden('bulleted-list')} onClick={this.handleBlockClick}
disabled={disabled} isActive={hasBlock('bulleted-list')}
/> disabled={disabled}
<ToolbarButton />
type="numbered-list" )}
label="Numbered List" {isVisible('numbered-list') && (
icon="list-numbered" <ToolbarButton
onClick={this.handleBlockClick} type="numbered-list"
isActive={hasBlock('numbered-list')} label="Numbered List"
isHidden={this.isHidden('numbered-list')} icon="list-numbered"
disabled={disabled} onClick={this.handleBlockClick}
/> isActive={hasBlock('numbered-list')}
<ToolbarDropdownWrapper> disabled={disabled}
<Dropdown />
dropdownTopOverlap="36px" )}
dropdownWidth="110px" {showEditorComponents && (
renderButton={() => ( <ToolbarDropdownWrapper>
<DropdownButton> <Dropdown
<ToolbarButton dropdownTopOverlap="36px"
label="Add Component" dropdownWidth="110px"
icon="add-with" renderButton={() => (
onClick={this.handleComponentsMenuToggle} <DropdownButton>
disabled={disabled} <ToolbarButton
/> label="Add Component"
</DropdownButton> icon="add-with"
)} onClick={this.handleComponentsMenuToggle}
> disabled={disabled}
{plugins && />
plugins </DropdownButton>
.toList() )}
.map((plugin, idx) => ( >
<DropdownItem key={idx} label={plugin.label} onClick={() => onSubmit(plugin)} /> {pluginsList.map((plugin, idx) => (
))} <DropdownItem key={idx} label={plugin.label} onClick={() => onSubmit(plugin)} />
</Dropdown> ))}
</ToolbarDropdownWrapper> </Dropdown>
</ToolbarDropdownWrapper>
)}
</div> </div>
<ToolbarToggle> <ToolbarToggle>
<ToolbarToggleLabel isActive={!rawMode} offPosition> <ToolbarToggleLabel isActive={!rawMode} offPosition>

View File

@ -23,22 +23,16 @@ const StyledToolbarButton = styled.button`
} }
`; `;
const ToolbarButton = ({ type, label, icon, onClick, isActive, isHidden, disabled }) => { const ToolbarButton = ({ type, label, icon, onClick, isActive, disabled }) => (
if (isHidden) { <StyledToolbarButton
return null; isActive={isActive}
} onClick={e => onClick && onClick(e, type)}
title={label}
return ( disabled={disabled}
<StyledToolbarButton >
isActive={isActive} {icon ? <Icon type={icon} /> : label}
onClick={e => onClick && onClick(e, type)} </StyledToolbarButton>
title={label} );
disabled={disabled}
>
{icon ? <Icon type={icon} /> : label}
</StyledToolbarButton>
);
};
ToolbarButton.propTypes = { ToolbarButton.propTypes = {
type: PropTypes.string, type: PropTypes.string,
@ -46,7 +40,6 @@ ToolbarButton.propTypes = {
icon: PropTypes.string, icon: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
isActive: PropTypes.bool, isActive: PropTypes.bool,
isHidden: PropTypes.bool,
disabled: PropTypes.bool, disabled: PropTypes.bool,
}; };

View File

@ -15,12 +15,12 @@ import { renderBlock, renderInline, renderMark } from './renderers';
import plugins from './plugins/visual'; import plugins from './plugins/visual';
import schema from './schema'; import schema from './schema';
const visualEditorStyles = ` const visualEditorStyles = ({ minimal }) => `
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-x: auto; overflow-x: auto;
font-family: ${fonts.primary}; font-family: ${fonts.primary};
min-height: ${lengths.richTextEditorMinHeight}; min-height: ${minimal ? 'auto' : lengths.richTextEditorMinHeight};
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
border-top: 0; border-top: 0;
@ -195,6 +195,7 @@ export default class Editor extends React.Component {
onAddAsset={onAddAsset} onAddAsset={onAddAsset}
getAsset={getAsset} getAsset={getAsset}
buttons={field.get('buttons')} buttons={field.get('buttons')}
editorComponents={field.get('editorComponents')}
hasMark={this.hasMark} hasMark={this.hasMark}
hasInline={this.hasInline} hasInline={this.hasInline}
hasBlock={this.hasBlock} hasBlock={this.hasBlock}
@ -207,7 +208,7 @@ export default class Editor extends React.Component {
className={cx( className={cx(
className, className,
css` css`
${visualEditorStyles} ${visualEditorStyles({ minimal: field.get('minimal') })}
`, `,
)} )}
> >

View File

@ -12,7 +12,9 @@ _Please note:_ If you want to use your markdown editor to fill a markdown file c
- **Data type:** markdown - **Data type:** markdown
- **Options:** - **Options:**
- `default`: accepts markdown content - `default`: accepts markdown content
- `buttons`: an array of strings representing the formatting buttons to display, all buttons shown by default. Buttons include: `bold`, `italic`, `code`, `link`, `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`, `quote`, `code-block`, `bulleted-list`, and `numbered-list`. - `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`.
- `editorComponents`: an array of strings representing the names of editor components to display (all shown by default). The `image` and `code-block` editor components are included with Netlify CMS by default, but others may be [created and registered](/docs/custom-widgets/#registereditorcomponent).
- **Example:** - **Example:**
```yaml ```yaml