diff --git a/package.json b/package.json index 592840a3..c86e3cbd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/ControlPanel/ControlPane.js b/src/components/ControlPanel/ControlPane.js index f11114f0..493c3da7 100644 --- a/src/components/ControlPanel/ControlPane.js +++ b/src/components/ControlPanel/ControlPane.js @@ -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 (
{ - fields.map(field => - isHidden(field) ? null :
- {this.controlFor(field)} -
- ) + fields.map((field) => { + if (isHidden(field)) { + return null; + } + return
{this.controlFor(field)}
; + }) }
); diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index 87734a2d..e9f14abc 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -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, }; diff --git a/src/components/Widgets.js b/src/components/Widgets.js index bdecd996..a0a6e118 100644 --- a/src/components/Widgets.js +++ b/src/components/Widgets.js @@ -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'); } diff --git a/src/components/Widgets/DateControl.js b/src/components/Widgets/DateControl.js new file mode 100644 index 00000000..b0170ee7 --- /dev/null +++ b/src/components/Widgets/DateControl.js @@ -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 (); + } +} + +DateControl.propTypes = { + onChange: PropTypes.func.isRequired, + value: PropTypes.object, +}; diff --git a/src/components/Widgets/DatePreview.js b/src/components/Widgets/DatePreview.js new file mode 100644 index 00000000..e009069b --- /dev/null +++ b/src/components/Widgets/DatePreview.js @@ -0,0 +1,9 @@ +import React, { PropTypes } from 'react'; + +export default function DatePreview({ value }) { + return {value ? value.toString() : null}; +} + +DatePreview.propTypes = { + value: PropTypes.node, +}; diff --git a/src/components/Widgets/ListControl.js b/src/components/Widgets/ListControl.js index 62c634b5..55617adb 100644 --- a/src/components/Widgets/ListControl.js +++ b/src/components/Widgets/ListControl.js @@ -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 ( {value && value.map((item, index) => this.renderItem(item, index))}
@@ -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 ; + return (); } } diff --git a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js index 36234c8d..d921a4c1 100644 --- a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js +++ b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js @@ -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 {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 }); diff --git a/src/components/Widgets/SelectControl.js b/src/components/Widgets/SelectControl.js new file mode 100644 index 00000000..c6c6a61d --- /dev/null +++ b/src/components/Widgets/SelectControl.js @@ -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
Error rendering select control for {field.get('name')}: No options
; + } + + const options = fieldOptions.map((option) => { + if (typeof option === 'string') { + return { label: option, value: option }; + } + return option; + }); + + return (); + } +} + +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, + }), +}; diff --git a/src/components/Widgets/SelectPreview.js b/src/components/Widgets/SelectPreview.js new file mode 100644 index 00000000..f03b7e64 --- /dev/null +++ b/src/components/Widgets/SelectPreview.js @@ -0,0 +1,9 @@ +import React, { PropTypes } from 'react'; + +export default function SelectPreview({ value }) { + return {value ? value.toString() : null}; +} + +SelectPreview.propTypes = { + value: PropTypes.string, +}; diff --git a/src/index.js b/src/index.js index daa0530f..62ec89eb 100644 --- a/src/index.js +++ b/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 {data.alt}; + }, + pattern: /^!\[([^\]]+)\]\(([^\)]+)\)$/, + fields: [{ + label: 'Image', + name: 'image', + widget: 'image', + }, { + label: 'Alt Text', + name: 'alt', + }], +}]; +buildtInPlugins.forEach(plugin => registry.registerEditorComponent(plugin)); diff --git a/yarn.lock b/yarn.lock index 2782d87c..b1221d5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"