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
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:
- { label: 'SEO Description', name: 'description', widget: 'text' }

View File

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

View File

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

View File

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

View File

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