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"
|
||||
]
|
||||
},
|
||||
"files": ["dist/", "README.md"],
|
||||
"files": [
|
||||
"dist/",
|
||||
"README.md"
|
||||
],
|
||||
"pre-commit": "lint:staged",
|
||||
"jest": {
|
||||
"moduleNameMapper": {
|
||||
@ -127,7 +130,7 @@
|
||||
"react-datetime": "^2.6.0",
|
||||
"react-dom": "^15.1.0",
|
||||
"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-portal": "^2.2.1",
|
||||
"react-pure-render": "^1.0.2",
|
||||
|
@ -10,7 +10,7 @@ function isHidden(field) {
|
||||
export default class ControlPane extends Component {
|
||||
|
||||
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 fieldName = field.get('name');
|
||||
const value = entry.getIn(['data', fieldName]);
|
||||
@ -41,14 +41,12 @@ export default class ControlPane extends Component {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
fields.map(field =>
|
||||
isHidden(field) ? null : <div
|
||||
key={field.get('name')}
|
||||
className={styles.widget}
|
||||
>
|
||||
{this.controlFor(field)}
|
||||
</div>
|
||||
)
|
||||
fields.map((field) => {
|
||||
if (isHidden(field)) {
|
||||
return null;
|
||||
}
|
||||
return <div key={field.get('name')} className={styles.widget}>{this.controlFor(field)}</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
@ -15,7 +15,7 @@ export default class PreviewPane extends React.Component {
|
||||
|
||||
widgetFor = (name) => {
|
||||
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'));
|
||||
return React.createElement(widget.preview, {
|
||||
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() {
|
||||
const { entry, collection } = this.props;
|
||||
const component = registry.getPreviewTemplate(selectTemplateName(collection, entry.get('slug'))) || Preview;
|
||||
@ -42,21 +57,6 @@ export default class PreviewPane extends React.Component {
|
||||
, 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() {
|
||||
const { collection } = this.props;
|
||||
if (!collection) {
|
||||
@ -72,7 +72,4 @@ PreviewPane.propTypes = {
|
||||
fields: ImmutablePropTypes.list.isRequired,
|
||||
entry: ImmutablePropTypes.map.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 ImageControl from './Widgets/ImageControl';
|
||||
import ImagePreview from './Widgets/ImagePreview';
|
||||
import DateControl from './Widgets/DateControl';
|
||||
import DatePreview from './Widgets/DatePreview';
|
||||
import DateTimeControl from './Widgets/DateTimeControl';
|
||||
import DateTimePreview from './Widgets/DateTimePreview';
|
||||
import SelectControl from './Widgets/SelectControl';
|
||||
import SelectPreview from './Widgets/SelectPreview';
|
||||
import ObjectControl from './Widgets/ObjectControl';
|
||||
import ObjectPreview from './Widgets/ObjectPreview';
|
||||
|
||||
@ -24,10 +28,12 @@ registry.registerWidget('number', NumberControl, NumberPreview);
|
||||
registry.registerWidget('list', ListControl, ListPreview);
|
||||
registry.registerWidget('markdown', MarkdownControl, MarkdownPreview);
|
||||
registry.registerWidget('image', ImageControl, ImagePreview);
|
||||
registry.registerWidget('date', DateControl, DatePreview);
|
||||
registry.registerWidget('datetime', DateTimeControl, DateTimePreview);
|
||||
registry.registerWidget('select', SelectControl, SelectPreview);
|
||||
registry.registerWidget('object', ObjectControl, ObjectPreview);
|
||||
registry.registerWidget('unknown', UnknownControl, UnknownPreview);
|
||||
|
||||
export function resolveWidget(name) {
|
||||
return registry.getWidget(name) || registry.getWidget('unknown');
|
||||
export function resolveWidget(name) { // eslint-disable-line
|
||||
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';
|
||||
|
||||
function valueToString(value) {
|
||||
return value ? value.join(',').replace(/,([^\s]|$)/g, ', $1') : '';
|
||||
}
|
||||
|
||||
const SortableListItem = sortable(ListItem);
|
||||
|
||||
export default class ListControl extends Component {
|
||||
@ -20,15 +24,32 @@ export default class ListControl extends Component {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
field: PropTypes.node,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
onAddMedia: PropTypes.func.isRequired,
|
||||
onRemoveMedia: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { itemStates: Map() };
|
||||
this.state = { itemStates: Map(), value: valueToString(props.value) };
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -80,13 +101,13 @@ export default class ListControl extends Component {
|
||||
|
||||
renderItem(item, index) {
|
||||
const { value, field, getMedia, onAddMedia, onRemoveMedia } = this.props;
|
||||
const { itemStates, draggedItem } = this.state;
|
||||
const { itemStates } = this.state;
|
||||
const collapsed = itemStates.getIn([index, 'collapsed']);
|
||||
const classNames = [styles.item, collapsed ? styles.collapsed : styles.expanded];
|
||||
|
||||
return (<SortableListItem
|
||||
key={index}
|
||||
updateState={this.handleSort}
|
||||
updateState={this.handleSort} // eslint-disable-line
|
||||
items={value ? value.toJS() : []}
|
||||
draggingIndex={this.state.draggingIndex}
|
||||
sortId={index}
|
||||
@ -113,7 +134,7 @@ export default class ListControl extends Component {
|
||||
}
|
||||
|
||||
renderListControl() {
|
||||
const { value, field } = this.props;
|
||||
const { value } = this.props;
|
||||
return (<div>
|
||||
{value && value.map((item, index) => this.renderItem(item, index))}
|
||||
<div><button className={styles.addButton} onClick={this.handleAdd}>new</button></div>
|
||||
@ -121,12 +142,18 @@ export default class ListControl extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, field } = this.props;
|
||||
const { field } = this.props;
|
||||
const { value } = this.state;
|
||||
|
||||
if (field.get('fields')) {
|
||||
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 { fromJS } from 'immutable';
|
||||
import MarkupIt from 'markup-it';
|
||||
import markdownSyntax from 'markup-it/syntaxes/markdown';
|
||||
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 {
|
||||
constructor(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)) {
|
||||
try {
|
||||
const selectionPosition = this.caretPosition.get(selection.start, selection.end);
|
||||
console.log('pos: %o', selectionPosition);
|
||||
this.setState({ showToolbar: true, showBlockMenu: false, selectionPosition });
|
||||
} catch (e) {
|
||||
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.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"
|
||||
source-map "^0.4.4"
|
||||
|
||||
react-immutable-proptypes@^1.6.0:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-1.7.2.tgz#fb1fdca24e30501617732781f4341b704ef7c320"
|
||||
react-immutable-proptypes:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
|
||||
|
||||
react-inspector@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user