Widget fixes (#162)
* Make string the default widget if none is specified * Linting fixes for PreviewPane * Linting fixes for ControlPane * Add date widget * Fix name of date control class * Fix spaces in list control with no fields * Fix linting error for List Control * Fix linting errors in raw editor * Add Select widget * Fix linting error
This commit is contained in:
parent
0338e5c9c2
commit
d81d0d416f
@ -32,7 +32,10 @@
|
|||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": ["dist/", "README.md"],
|
"files": [
|
||||||
|
"dist/",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
"pre-commit": "lint:staged",
|
"pre-commit": "lint:staged",
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
@ -127,7 +130,7 @@
|
|||||||
"react-datetime": "^2.6.0",
|
"react-datetime": "^2.6.0",
|
||||||
"react-dom": "^15.1.0",
|
"react-dom": "^15.1.0",
|
||||||
"react-hot-loader": "^3.0.0-beta.2",
|
"react-hot-loader": "^3.0.0-beta.2",
|
||||||
"react-immutable-proptypes": "^1.6.0",
|
"react-immutable-proptypes": "^2.1.0",
|
||||||
"react-lazy-load": "^3.0.3",
|
"react-lazy-load": "^3.0.3",
|
||||||
"react-portal": "^2.2.1",
|
"react-portal": "^2.2.1",
|
||||||
"react-pure-render": "^1.0.2",
|
"react-pure-render": "^1.0.2",
|
||||||
|
@ -10,7 +10,7 @@ function isHidden(field) {
|
|||||||
export default class ControlPane extends Component {
|
export default class ControlPane extends Component {
|
||||||
|
|
||||||
controlFor(field) {
|
controlFor(field) {
|
||||||
const { entry, fields, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
|
const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
|
||||||
const widget = resolveWidget(field.get('widget'));
|
const widget = resolveWidget(field.get('widget'));
|
||||||
const fieldName = field.get('name');
|
const fieldName = field.get('name');
|
||||||
const value = entry.getIn(['data', fieldName]);
|
const value = entry.getIn(['data', fieldName]);
|
||||||
@ -41,14 +41,12 @@ export default class ControlPane extends Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
fields.map(field =>
|
fields.map((field) => {
|
||||||
isHidden(field) ? null : <div
|
if (isHidden(field)) {
|
||||||
key={field.get('name')}
|
return null;
|
||||||
className={styles.widget}
|
}
|
||||||
>
|
return <div key={field.get('name')} className={styles.widget}>{this.controlFor(field)}</div>;
|
||||||
{this.controlFor(field)}
|
})
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ export default class PreviewPane extends React.Component {
|
|||||||
|
|
||||||
widgetFor = (name) => {
|
widgetFor = (name) => {
|
||||||
const { fields, entry, getMedia } = this.props;
|
const { fields, entry, getMedia } = this.props;
|
||||||
const field = fields.find(field => field.get('name') === name);
|
const field = fields.find(f => f.get('name') === name);
|
||||||
const widget = resolveWidget(field.get('widget'));
|
const widget = resolveWidget(field.get('widget'));
|
||||||
return React.createElement(widget.preview, {
|
return React.createElement(widget.preview, {
|
||||||
key: field.get('name'),
|
key: field.get('name'),
|
||||||
@ -25,6 +25,21 @@ export default class PreviewPane extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleIframeRef = (ref) => {
|
||||||
|
if (ref) {
|
||||||
|
registry.getPreviewStyles().forEach((style) => {
|
||||||
|
const linkEl = document.createElement('link');
|
||||||
|
linkEl.setAttribute('rel', 'stylesheet');
|
||||||
|
linkEl.setAttribute('href', style);
|
||||||
|
ref.contentDocument.head.appendChild(linkEl);
|
||||||
|
});
|
||||||
|
this.previewEl = document.createElement('div');
|
||||||
|
this.iframeBody = ref.contentDocument.body;
|
||||||
|
this.iframeBody.appendChild(this.previewEl);
|
||||||
|
this.renderPreview();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
renderPreview() {
|
renderPreview() {
|
||||||
const { entry, collection } = this.props;
|
const { entry, collection } = this.props;
|
||||||
const component = registry.getPreviewTemplate(selectTemplateName(collection, entry.get('slug'))) || Preview;
|
const component = registry.getPreviewTemplate(selectTemplateName(collection, entry.get('slug'))) || Preview;
|
||||||
@ -42,21 +57,6 @@ export default class PreviewPane extends React.Component {
|
|||||||
, this.previewEl);
|
, this.previewEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIframeRef = (ref) => {
|
|
||||||
if (ref) {
|
|
||||||
registry.getPreviewStyles().forEach((style) => {
|
|
||||||
const linkEl = document.createElement('link');
|
|
||||||
linkEl.setAttribute('rel', 'stylesheet');
|
|
||||||
linkEl.setAttribute('href', style);
|
|
||||||
ref.contentDocument.head.appendChild(linkEl);
|
|
||||||
});
|
|
||||||
this.previewEl = document.createElement('div');
|
|
||||||
this.iframeBody = ref.contentDocument.body;
|
|
||||||
this.iframeBody.appendChild(this.previewEl);
|
|
||||||
this.renderPreview();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { collection } = this.props;
|
const { collection } = this.props;
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
@ -72,7 +72,4 @@ PreviewPane.propTypes = {
|
|||||||
fields: ImmutablePropTypes.list.isRequired,
|
fields: ImmutablePropTypes.list.isRequired,
|
||||||
entry: ImmutablePropTypes.map.isRequired,
|
entry: ImmutablePropTypes.map.isRequired,
|
||||||
getMedia: PropTypes.func.isRequired,
|
getMedia: PropTypes.func.isRequired,
|
||||||
scrollTop: PropTypes.number,
|
|
||||||
scrollHeight: PropTypes.number,
|
|
||||||
offsetHeight: PropTypes.number,
|
|
||||||
};
|
};
|
||||||
|
@ -13,8 +13,12 @@ import MarkdownControl from './Widgets/MarkdownControl';
|
|||||||
import MarkdownPreview from './Widgets/MarkdownPreview';
|
import MarkdownPreview from './Widgets/MarkdownPreview';
|
||||||
import ImageControl from './Widgets/ImageControl';
|
import ImageControl from './Widgets/ImageControl';
|
||||||
import ImagePreview from './Widgets/ImagePreview';
|
import ImagePreview from './Widgets/ImagePreview';
|
||||||
|
import DateControl from './Widgets/DateControl';
|
||||||
|
import DatePreview from './Widgets/DatePreview';
|
||||||
import DateTimeControl from './Widgets/DateTimeControl';
|
import DateTimeControl from './Widgets/DateTimeControl';
|
||||||
import DateTimePreview from './Widgets/DateTimePreview';
|
import DateTimePreview from './Widgets/DateTimePreview';
|
||||||
|
import SelectControl from './Widgets/SelectControl';
|
||||||
|
import SelectPreview from './Widgets/SelectPreview';
|
||||||
import ObjectControl from './Widgets/ObjectControl';
|
import ObjectControl from './Widgets/ObjectControl';
|
||||||
import ObjectPreview from './Widgets/ObjectPreview';
|
import ObjectPreview from './Widgets/ObjectPreview';
|
||||||
|
|
||||||
@ -24,10 +28,12 @@ registry.registerWidget('number', NumberControl, NumberPreview);
|
|||||||
registry.registerWidget('list', ListControl, ListPreview);
|
registry.registerWidget('list', ListControl, ListPreview);
|
||||||
registry.registerWidget('markdown', MarkdownControl, MarkdownPreview);
|
registry.registerWidget('markdown', MarkdownControl, MarkdownPreview);
|
||||||
registry.registerWidget('image', ImageControl, ImagePreview);
|
registry.registerWidget('image', ImageControl, ImagePreview);
|
||||||
|
registry.registerWidget('date', DateControl, DatePreview);
|
||||||
registry.registerWidget('datetime', DateTimeControl, DateTimePreview);
|
registry.registerWidget('datetime', DateTimeControl, DateTimePreview);
|
||||||
|
registry.registerWidget('select', SelectControl, SelectPreview);
|
||||||
registry.registerWidget('object', ObjectControl, ObjectPreview);
|
registry.registerWidget('object', ObjectControl, ObjectPreview);
|
||||||
registry.registerWidget('unknown', UnknownControl, UnknownPreview);
|
registry.registerWidget('unknown', UnknownControl, UnknownPreview);
|
||||||
|
|
||||||
export function resolveWidget(name) {
|
export function resolveWidget(name) { // eslint-disable-line
|
||||||
return registry.getWidget(name) || registry.getWidget('unknown');
|
return registry.getWidget(name || 'string') || registry.getWidget('unknown');
|
||||||
}
|
}
|
||||||
|
21
src/components/Widgets/DateControl.js
Normal file
21
src/components/Widgets/DateControl.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import DateTime from 'react-datetime';
|
||||||
|
|
||||||
|
export default class DateControl extends React.Component {
|
||||||
|
handleChange = (datetime) => {
|
||||||
|
this.props.onChange(datetime);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<DateTime
|
||||||
|
timeFormat={false}
|
||||||
|
value={this.props.value || new Date()}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateControl.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
value: PropTypes.object,
|
||||||
|
};
|
9
src/components/Widgets/DatePreview.js
Normal file
9
src/components/Widgets/DatePreview.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
|
export default function DatePreview({ value }) {
|
||||||
|
return <span>{value ? value.toString() : null}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatePreview.propTypes = {
|
||||||
|
value: PropTypes.node,
|
||||||
|
};
|
@ -13,6 +13,10 @@ ListItem.propTypes = {
|
|||||||
};
|
};
|
||||||
ListItem.displayName = 'list-item';
|
ListItem.displayName = 'list-item';
|
||||||
|
|
||||||
|
function valueToString(value) {
|
||||||
|
return value ? value.join(',').replace(/,([^\s]|$)/g, ', $1') : '';
|
||||||
|
}
|
||||||
|
|
||||||
const SortableListItem = sortable(ListItem);
|
const SortableListItem = sortable(ListItem);
|
||||||
|
|
||||||
export default class ListControl extends Component {
|
export default class ListControl extends Component {
|
||||||
@ -20,15 +24,32 @@ export default class ListControl extends Component {
|
|||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.node,
|
value: PropTypes.node,
|
||||||
field: PropTypes.node,
|
field: PropTypes.node,
|
||||||
|
getMedia: PropTypes.func.isRequired,
|
||||||
|
onAddMedia: PropTypes.func.isRequired,
|
||||||
|
onRemoveMedia: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { itemStates: Map() };
|
this.state = { itemStates: Map(), value: valueToString(props.value) };
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange = (e) => {
|
handleChange = (e) => {
|
||||||
this.props.onChange(e.target.value.split(',').map(item => item.trim()));
|
const oldValue = this.state.value;
|
||||||
|
const newValue = e.target.value;
|
||||||
|
const listValue = e.target.value.split(',');
|
||||||
|
if (newValue.match(/,$/) && oldValue.match(/, $/)) {
|
||||||
|
listValue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ value: valueToString(listValue) });
|
||||||
|
this.props.onChange(listValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCleanup = (e) => {
|
||||||
|
const listValue = e.target.value.split(',').map(el => el.trim()).filter(el => el);
|
||||||
|
this.setState({ value: valueToString(listValue) });
|
||||||
|
this.props.onChange(listValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAdd = (e) => {
|
handleAdd = (e) => {
|
||||||
@ -80,13 +101,13 @@ export default class ListControl extends Component {
|
|||||||
|
|
||||||
renderItem(item, index) {
|
renderItem(item, index) {
|
||||||
const { value, field, getMedia, onAddMedia, onRemoveMedia } = this.props;
|
const { value, field, getMedia, onAddMedia, onRemoveMedia } = this.props;
|
||||||
const { itemStates, draggedItem } = this.state;
|
const { itemStates } = this.state;
|
||||||
const collapsed = itemStates.getIn([index, 'collapsed']);
|
const collapsed = itemStates.getIn([index, 'collapsed']);
|
||||||
const classNames = [styles.item, collapsed ? styles.collapsed : styles.expanded];
|
const classNames = [styles.item, collapsed ? styles.collapsed : styles.expanded];
|
||||||
|
|
||||||
return (<SortableListItem
|
return (<SortableListItem
|
||||||
key={index}
|
key={index}
|
||||||
updateState={this.handleSort}
|
updateState={this.handleSort} // eslint-disable-line
|
||||||
items={value ? value.toJS() : []}
|
items={value ? value.toJS() : []}
|
||||||
draggingIndex={this.state.draggingIndex}
|
draggingIndex={this.state.draggingIndex}
|
||||||
sortId={index}
|
sortId={index}
|
||||||
@ -113,7 +134,7 @@ export default class ListControl extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderListControl() {
|
renderListControl() {
|
||||||
const { value, field } = this.props;
|
const { value } = this.props;
|
||||||
return (<div>
|
return (<div>
|
||||||
{value && value.map((item, index) => this.renderItem(item, index))}
|
{value && value.map((item, index) => this.renderItem(item, index))}
|
||||||
<div><button className={styles.addButton} onClick={this.handleAdd}>new</button></div>
|
<div><button className={styles.addButton} onClick={this.handleAdd}>new</button></div>
|
||||||
@ -121,12 +142,18 @@ export default class ListControl extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, field } = this.props;
|
const { field } = this.props;
|
||||||
|
const { value } = this.state;
|
||||||
|
|
||||||
if (field.get('fields')) {
|
if (field.get('fields')) {
|
||||||
return this.renderListControl();
|
return this.renderListControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
return <input type="text" value={value ? value.join(', ') : ''} onChange={this.handleChange} />;
|
return (<input
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onBlur={this.handleCleanup}
|
||||||
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
import MarkupIt from 'markup-it';
|
import MarkupIt from 'markup-it';
|
||||||
import markdownSyntax from 'markup-it/syntaxes/markdown';
|
import markdownSyntax from 'markup-it/syntaxes/markdown';
|
||||||
import htmlSyntax from 'markup-it/syntaxes/html';
|
import htmlSyntax from 'markup-it/syntaxes/html';
|
||||||
@ -65,29 +64,6 @@ function getCleanPaste(e) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildtInPlugins = [{
|
|
||||||
label: 'Image',
|
|
||||||
id: 'image',
|
|
||||||
fromBlock: match => match && {
|
|
||||||
image: match[2],
|
|
||||||
alt: match[1],
|
|
||||||
},
|
|
||||||
toBlock: data => `![${ data.alt }](${ data.image })`,
|
|
||||||
toPreview: (data) => {
|
|
||||||
return <img src={data.image} alt={data.alt} />;
|
|
||||||
},
|
|
||||||
pattern: /^!\[([^\]]+)\]\(([^\)]+)\)$/,
|
|
||||||
fields: [{
|
|
||||||
label: 'Image',
|
|
||||||
name: 'image',
|
|
||||||
widget: 'image',
|
|
||||||
}, {
|
|
||||||
label: 'Alt Text',
|
|
||||||
name: 'alt',
|
|
||||||
}],
|
|
||||||
}];
|
|
||||||
buildtInPlugins.forEach(plugin => registry.registerEditorComponent(plugin));
|
|
||||||
|
|
||||||
export default class RawEditor extends React.Component {
|
export default class RawEditor extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -237,7 +213,6 @@ export default class RawEditor extends React.Component {
|
|||||||
if (selection.start !== selection.end && !HAS_LINE_BREAK.test(selection.selected)) {
|
if (selection.start !== selection.end && !HAS_LINE_BREAK.test(selection.selected)) {
|
||||||
try {
|
try {
|
||||||
const selectionPosition = this.caretPosition.get(selection.start, selection.end);
|
const selectionPosition = this.caretPosition.get(selection.start, selection.end);
|
||||||
console.log('pos: %o', selectionPosition);
|
|
||||||
this.setState({ showToolbar: true, showBlockMenu: false, selectionPosition });
|
this.setState({ showToolbar: true, showBlockMenu: false, selectionPosition });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ showToolbar: false, showBlockMenu: false });
|
this.setState({ showToolbar: false, showBlockMenu: false });
|
||||||
|
44
src/components/Widgets/SelectControl.js
Normal file
44
src/components/Widgets/SelectControl.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
|
export default class SelectControl extends React.Component {
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.props.onChange(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { field, value } = this.props;
|
||||||
|
const fieldOptions = field.get('options');
|
||||||
|
|
||||||
|
if (!fieldOptions) {
|
||||||
|
return <div>Error rendering select control for {field.get('name')}: No options</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = fieldOptions.map((option) => {
|
||||||
|
if (typeof option === 'string') {
|
||||||
|
return { label: option, value: option };
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<select value={value || ''} onChange={this.handleChange}>
|
||||||
|
{options.map((option, idx) => <option key={idx} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>)}
|
||||||
|
</select>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectControl.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
value: PropTypes.node,
|
||||||
|
field: ImmutablePropTypes.contains({
|
||||||
|
options: ImmutablePropTypes.listOf(PropTypes.oneOf([
|
||||||
|
PropTypes.string,
|
||||||
|
ImmutablePropTypes.contains({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
])).isRequired,
|
||||||
|
}),
|
||||||
|
};
|
9
src/components/Widgets/SelectPreview.js
Normal file
9
src/components/Widgets/SelectPreview.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
|
export default function SelectPreview({ value }) {
|
||||||
|
return <span>{value ? value.toString() : null}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectPreview.propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
};
|
23
src/index.js
23
src/index.js
@ -39,3 +39,26 @@ for (const method in registry) {
|
|||||||
}
|
}
|
||||||
window.createClass = React.createClass;
|
window.createClass = React.createClass;
|
||||||
window.h = React.createElement;
|
window.h = React.createElement;
|
||||||
|
|
||||||
|
const buildtInPlugins = [{
|
||||||
|
label: 'Image',
|
||||||
|
id: 'image',
|
||||||
|
fromBlock: match => match && {
|
||||||
|
image: match[2],
|
||||||
|
alt: match[1],
|
||||||
|
},
|
||||||
|
toBlock: data => `![${ data.alt }](${ data.image })`,
|
||||||
|
toPreview: (data) => {
|
||||||
|
return <img src={data.image} alt={data.alt} />;
|
||||||
|
},
|
||||||
|
pattern: /^!\[([^\]]+)\]\(([^\)]+)\)$/,
|
||||||
|
fields: [{
|
||||||
|
label: 'Image',
|
||||||
|
name: 'image',
|
||||||
|
widget: 'image',
|
||||||
|
}, {
|
||||||
|
label: 'Alt Text',
|
||||||
|
name: 'alt',
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
buildtInPlugins.forEach(plugin => registry.registerEditorComponent(plugin));
|
||||||
|
@ -6989,9 +6989,9 @@ react-hot-loader@^3.0.0-beta.2:
|
|||||||
redbox-react "^1.2.5"
|
redbox-react "^1.2.5"
|
||||||
source-map "^0.4.4"
|
source-map "^0.4.4"
|
||||||
|
|
||||||
react-immutable-proptypes@^1.6.0:
|
react-immutable-proptypes:
|
||||||
version "1.7.2"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-1.7.2.tgz#fb1fdca24e30501617732781f4341b704ef7c320"
|
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
|
||||||
|
|
||||||
react-inspector@^1.1.0:
|
react-inspector@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user