feat: Configure included editor components per field, add optional minimal height (#3299)
This commit is contained in:
parent
93bd0529f1
commit
b7b4bcb609
@ -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' }
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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') })}
|
||||
`,
|
||||
)}
|
||||
>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user