From b1a5ea95d3d1658e6e9559d7e7641870617098bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Souza?= Date: Wed, 23 Nov 2016 16:23:32 -0200 Subject: [PATCH] Refinements & Preview Defaults (#167) * No need for set width for base Card anymore * entries are not required * Redirect from Dashboard to first collection if publish mode is simple * collection inference: Add more synonyms to description * Implemented a better default preview for editing entries * Add label field in default preview for small text values * Added margin for default preview --- src/components/EntryListing/EntryListing.css | 11 ++++- src/components/EntryListing/EntryListing.js | 6 ++- src/components/PreviewPane/Preview.js | 6 ++- src/components/PreviewPane/PreviewPane.js | 28 +++++++++++- src/components/UI/card/Card.css | 5 +-- .../UnpublishedListing/UnpublishedListing.js | 2 +- src/components/Widgets/DatePreview.js | 3 +- src/components/Widgets/DateTimePreview.js | 3 +- src/components/Widgets/ImagePreview.js | 13 ++++-- src/components/Widgets/ListPreview.js | 11 +++-- src/components/Widgets/MarkdownPreview.js | 13 +++--- src/components/Widgets/NumberPreview.js | 3 +- src/components/Widgets/ObjectPreview.js | 19 +++++--- src/components/Widgets/SelectPreview.js | 3 +- src/components/Widgets/StringPreview.js | 3 +- src/components/Widgets/TextPreview.js | 3 +- src/components/Widgets/UnknownPreview.js | 3 +- src/components/Widgets/defaultPreviewStyle.js | 10 +++++ src/constants/fieldInference.js | 45 +++++++++++++++++++ src/containers/DashboardPage.js | 42 +++++++++++++---- .../UnpublishedEntriesPanel.js | 2 +- src/reducers/collections.js | 27 +---------- 22 files changed, 191 insertions(+), 70 deletions(-) create mode 100644 src/components/Widgets/defaultPreviewStyle.js create mode 100644 src/constants/fieldInference.js diff --git a/src/components/EntryListing/EntryListing.css b/src/components/EntryListing/EntryListing.css index 1e072639..5021b6c5 100644 --- a/src/components/EntryListing/EntryListing.css +++ b/src/components/EntryListing/EntryListing.css @@ -1,10 +1,10 @@ .card { overflow: hidden; margin-bottom: 10px; + margin-left: 15px; max-height: 290px; width: 240px; cursor: pointer; - margin-left: 15px; } .cardImage { @@ -20,3 +20,12 @@ flex-flow: row wrap; margin-left: -15px; } + +.cardList { + margin-bottom: 1.1rem; +} + +.cardListLabel { + white-space: nowrap; + font-weight: bold; +} diff --git a/src/components/EntryListing/EntryListing.js b/src/components/EntryListing/EntryListing.js index 60b66419..7ab28b45 100644 --- a/src/components/EntryListing/EntryListing.js +++ b/src/components/EntryListing/EntryListing.js @@ -41,6 +41,7 @@ export default class EntryListing extends React.Component { const title = label || entry.getIn(['data', inferedFields.titleField]); let image = entry.getIn(['data', inferedFields.imageField]); image = resolvePath(image, publicFolder); + return ( {entry.getIn(['data', inferedFields.descriptionField])}

: inferedFields.remainingFields && inferedFields.remainingFields.map(f => ( -

- {f.get('label')}: {entry.getIn(['data', f.get('name')])} +

+ {f.get('label')}:{' '} + { entry.getIn(['data', f.get('name')], '').toString() }

)) } diff --git a/src/components/PreviewPane/Preview.js b/src/components/PreviewPane/Preview.js index 1fde2335..6f2aac02 100644 --- a/src/components/PreviewPane/Preview.js +++ b/src/components/PreviewPane/Preview.js @@ -5,12 +5,16 @@ function isVisible(field) { return field.get('widget') !== 'hidden'; } +const style = { + fontFamily: 'Roboto, "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif', +}; + export default function Preview({ collection, fields, widgetFor }) { if (!collection || !fields) { return null; } return ( -
+
{fields.filter(isVisible).map(field => widgetFor(field.get('name')))}
); diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index e9f14abc..7ba41713 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -4,22 +4,44 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { ScrollSyncPane } from '../ScrollSync'; import registry from '../../lib/registry'; import { resolveWidget } from '../Widgets'; -import { selectTemplateName } from '../../reducers/collections'; +import { selectTemplateName, selectInferedField } from '../../reducers/collections'; +import { INFERABLE_FIELDS } from '../../constants/fieldInference'; import Preview from './Preview'; import styles from './PreviewPane.css'; export default class PreviewPane extends React.Component { + componentDidUpdate() { this.renderPreview(); } + inferedFields = {}; + + inferFields() { + const titleField = selectInferedField(this.props.collection, 'title'); + const shortTitleField = selectInferedField(this.props.collection, 'shortTitle'); + const authorField = selectInferedField(this.props.collection, 'author'); + + this.inferedFields = {}; + if (titleField) this.inferedFields[titleField] = INFERABLE_FIELDS.title; + if (shortTitleField) this.inferedFields[shortTitleField] = INFERABLE_FIELDS.shortTitle; + if (authorField) this.inferedFields[authorField] = INFERABLE_FIELDS.author; + } + widgetFor = (name) => { const { fields, entry, getMedia } = this.props; const field = fields.find(f => f.get('name') === name); + let value = entry.getIn(['data', field.get('name')]); + const labelledWidgets = ['string', 'text', 'number']; + if (Object.keys(this.inferedFields).indexOf(name) !== -1) { + value = this.inferedFields[name].defaultPreview(value); + } else if (value && labelledWidgets.indexOf(field.get('widget')) !== -1 && value.toString().length < 50) { + value =
{field.get('label')}: {value}
; + } const widget = resolveWidget(field.get('widget')); return React.createElement(widget.preview, { key: field.get('name'), - value: entry.getIn(['data', field.get('name')]), + value, field, getMedia, }); @@ -44,6 +66,8 @@ export default class PreviewPane extends React.Component { const { entry, collection } = this.props; const component = registry.getPreviewTemplate(selectTemplateName(collection, entry.get('slug'))) || Preview; + this.inferFields(); + const previewProps = { ...this.props, widgetFor: this.widgetFor, diff --git a/src/components/UI/card/Card.css b/src/components/UI/card/Card.css index 2a286f7a..67dd471b 100644 --- a/src/components/UI/card/Card.css +++ b/src/components/UI/card/Card.css @@ -3,7 +3,6 @@ .card { composes: base container rounded depth; overflow: hidden; - width: 240px; } .card > *:not(iframe, video, img, header, footer) { @@ -26,9 +25,9 @@ } .card h1 { + margin: 15px 0; + padding: 0; border: none; color: var(--defaultColor); font-size: 18px; - margin: 15px 0; - padding: 0; } diff --git a/src/components/UnpublishedListing/UnpublishedListing.js b/src/components/UnpublishedListing/UnpublishedListing.js index 6d41ac28..a5ddac64 100644 --- a/src/components/UnpublishedListing/UnpublishedListing.js +++ b/src/components/UnpublishedListing/UnpublishedListing.js @@ -107,7 +107,7 @@ class UnpublishedListing extends React.Component { render() { const columns = this.renderColumns(this.props.entries); return ( -
+
Editorial Workflow
{columns} diff --git a/src/components/Widgets/DatePreview.js b/src/components/Widgets/DatePreview.js index e009069b..154625df 100644 --- a/src/components/Widgets/DatePreview.js +++ b/src/components/Widgets/DatePreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function DatePreview({ value }) { - return {value ? value.toString() : null}; + return
{value ? value.toString() : null}
; } DatePreview.propTypes = { diff --git a/src/components/Widgets/DateTimePreview.js b/src/components/Widgets/DateTimePreview.js index e009069b..154625df 100644 --- a/src/components/Widgets/DateTimePreview.js +++ b/src/components/Widgets/DateTimePreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function DatePreview({ value }) { - return {value ? value.toString() : null}; + return
{value ? value.toString() : null}
; } DatePreview.propTypes = { diff --git a/src/components/Widgets/ImagePreview.js b/src/components/Widgets/ImagePreview.js index 1f45f84c..b9098cbe 100644 --- a/src/components/Widgets/ImagePreview.js +++ b/src/components/Widgets/ImagePreview.js @@ -1,9 +1,16 @@ import React, { PropTypes } from 'react'; +import previewStyle, { imagePreviewStyle } from './defaultPreviewStyle'; export default function ImagePreview({ value, getMedia }) { - return - {value ? : null} - ; + return (
+ { value ? + + : null} +
); } ImagePreview.propTypes = { diff --git a/src/components/Widgets/ListPreview.js b/src/components/Widgets/ListPreview.js index dcfbdcae..42e935dc 100644 --- a/src/components/Widgets/ListPreview.js +++ b/src/components/Widgets/ListPreview.js @@ -1,5 +1,6 @@ import React, { PropTypes, Component } from 'react'; import { resolveWidget } from '../Widgets'; +import previewStyle from './defaultPreviewStyle'; export default class ListPreview extends Component { widgetFor = (field, value) => { @@ -17,12 +18,14 @@ export default class ListPreview extends Component { const { field, value } = this.props; const fields = field && field.get('fields'); if (fields) { - return value ? (
{value.map((val, index) =>
- {fields && fields.map(f => this.widgetFor(f, val))} -
)}
) : null; + return value ? (
+ {value.map((val, index) =>
+ {fields && fields.map(f => this.widgetFor(f, val))} +
)} +
) : null; } - return {value ? value.join(', ') : null}; + return
{value ? value.join(', ') : null}
; } } diff --git a/src/components/Widgets/MarkdownPreview.js b/src/components/Widgets/MarkdownPreview.js index 9b1fb9c4..1d55ad3e 100644 --- a/src/components/Widgets/MarkdownPreview.js +++ b/src/components/Widgets/MarkdownPreview.js @@ -1,6 +1,7 @@ import React, { PropTypes } from 'react'; import { getSyntaxes } from './richText'; import MarkupItReactRenderer from '../MarkupItReactRenderer/index'; +import previewStyle from './defaultPreviewStyle'; const MarkdownPreview = ({ value, getMedia }) => { if (value == null) { @@ -18,11 +19,13 @@ const MarkdownPreview = ({ value, getMedia }) => { const { markdown } = getSyntaxes(); return ( - +
+ +
); }; diff --git a/src/components/Widgets/NumberPreview.js b/src/components/Widgets/NumberPreview.js index a1c41c1c..7e6337ac 100644 --- a/src/components/Widgets/NumberPreview.js +++ b/src/components/Widgets/NumberPreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function NumberPreview({ value }) { - return {value ? value.toString() : null}; + return
{value}
; } NumberPreview.propTypes = { diff --git a/src/components/Widgets/ObjectPreview.js b/src/components/Widgets/ObjectPreview.js index a7384b8c..22a10ce8 100644 --- a/src/components/Widgets/ObjectPreview.js +++ b/src/components/Widgets/ObjectPreview.js @@ -1,23 +1,28 @@ import React, { PropTypes, Component } from 'react'; import { resolveWidget } from '../Widgets'; +import previewStyle from './defaultPreviewStyle'; export default class ObjectPreview extends Component { widgetFor = (field) => { const { value, getMedia } = this.props; const widget = resolveWidget(field.get('widget')); - return (
{React.createElement(widget.preview, { - key: field.get('name'), - value: value && value.get(field.get('name')), - field, - getMedia, - })}
); + return ( +
+ {React.createElement(widget.preview, { + key: field.get('name'), + value: value && value.get(field.get('name')), + field, + getMedia, + })} +
+ ); }; render() { const { field } = this.props; const fields = field && field.get('fields'); - return
{fields ? fields.map(f => this.widgetFor(f)) : null}
; + return
{fields ? fields.map(f => this.widgetFor(f)) : null}
; } } diff --git a/src/components/Widgets/SelectPreview.js b/src/components/Widgets/SelectPreview.js index f03b7e64..edf38a0f 100644 --- a/src/components/Widgets/SelectPreview.js +++ b/src/components/Widgets/SelectPreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function SelectPreview({ value }) { - return {value ? value.toString() : null}; + return
{value ? value.toString() : null}
; } SelectPreview.propTypes = { diff --git a/src/components/Widgets/StringPreview.js b/src/components/Widgets/StringPreview.js index 84af9b5c..0885405d 100644 --- a/src/components/Widgets/StringPreview.js +++ b/src/components/Widgets/StringPreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function StringPreview({ value }) { - return {value ? value.toString() : null}; + return
{ value }
; } StringPreview.propTypes = { diff --git a/src/components/Widgets/TextPreview.js b/src/components/Widgets/TextPreview.js index 71ff407b..1a0d8a1b 100644 --- a/src/components/Widgets/TextPreview.js +++ b/src/components/Widgets/TextPreview.js @@ -1,7 +1,8 @@ import React, { PropTypes } from 'react'; +import previewStyle from './defaultPreviewStyle'; export default function TextPreview({ value }) { - return {value ? value.toString() : null}; + return
{value ? value.toString() : null}
; } TextPreview.propTypes = { diff --git a/src/components/Widgets/UnknownPreview.js b/src/components/Widgets/UnknownPreview.js index 10b08e26..a094e1a0 100644 --- a/src/components/Widgets/UnknownPreview.js +++ b/src/components/Widgets/UnknownPreview.js @@ -1,8 +1,9 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import previewStyle from './defaultPreviewStyle'; export default function UnknownPreview({ field }) { - return
No preview for widget '{field.get('widget')}'.
; + return
No preview for widget “{field.get('widget')}”.
; } UnknownPreview.propTypes = { diff --git a/src/components/Widgets/defaultPreviewStyle.js b/src/components/Widgets/defaultPreviewStyle.js new file mode 100644 index 00000000..5725472e --- /dev/null +++ b/src/components/Widgets/defaultPreviewStyle.js @@ -0,0 +1,10 @@ +const defaultPrevieStyle = { + margin: '15px 2px', +}; + +export const imagePreviewStyle = { + width: '100%', + height: 'auto', +}; + +export default defaultPrevieStyle; diff --git a/src/constants/fieldInference.js b/src/constants/fieldInference.js new file mode 100644 index 00000000..cbced64a --- /dev/null +++ b/src/constants/fieldInference.js @@ -0,0 +1,45 @@ +import React from 'react'; + +/* eslint-disable */ +export const INFERABLE_FIELDS = { + title: { + type: 'string', + secondaryTypes: [], + synonyms: ['title', 'name', 'label', 'headline'], + defaultPreview: value =>

{ value }

, + fallbackToFirstField: true, + showError: true, + }, + shortTitle: { + type: 'string', + secondaryTypes: [], + synonyms: ['short_title', 'shortTitle'], + defaultPreview: value =>

{ value }

, + fallbackToFirstField: false, + showError: false, + }, + author: { + type: 'string', + secondaryTypes: [], + synonyms: ['author', 'name', 'by'], + defaultPreview: value => { value }, + fallbackToFirstField: false, + showError: false, + }, + description: { + type: 'string', + secondaryTypes: ['text', 'markdown'], + synonyms: ['shortDescription', 'short_description', 'shortdescription', 'description', 'intro', 'introduction', 'brief', 'body', 'content', 'biography', 'bio'], + defaultPreview: value => value, + fallbackToFirstField: false, + showError: false, + }, + image: { + type: 'image', + secondaryTypes: [], + synonyms: ['image', 'thumbnail', 'thumb', 'picture', 'avatar'], + defaultPreview: value => value, + fallbackToFirstField: false, + showError: false, + }, +}; diff --git a/src/containers/DashboardPage.js b/src/containers/DashboardPage.js index 26b72c96..6f5b7f97 100644 --- a/src/containers/DashboardPage.js +++ b/src/containers/DashboardPage.js @@ -1,13 +1,39 @@ -import React from 'react'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes'; +import history from '../routing/history'; import UnpublishedEntriesPanel from './editorialWorkflow/UnpublishedEntriesPanel'; import styles from './breakpoints.css'; -export default function DashboardPage() { - return ( -
-

Dashboard

- -
- ); +class DashboardPage extends Component { + componentWillMount() { + if (this.props.publishMode === SIMPLE) { + history.push(`/collections/${ this.props.firstCollection }`); + } + } + + render() { + return ( +
+

Dashboard

+ +
+ ); + } } + +DashboardPage.propTypes = { + firstCollection: PropTypes.string, + publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]), +}; + +function mapStateToProps(state) { + const { config, collections } = state; + return { + firstCollection: collections.first().get('name'), + publishMode: config.get('publish_mode'), + }; +} + +export default connect(mapStateToProps)(DashboardPage); diff --git a/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js b/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js index 5c3878f4..7abca9f5 100644 --- a/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js +++ b/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js @@ -11,7 +11,7 @@ import { Loader } from '../../components/UI'; class unpublishedEntriesPanel extends Component { static propTypes = { isEditorialWorkflow: PropTypes.bool.isRequired, - isFetching: PropTypes.bool.isRequired, + isFetching: PropTypes.bool, unpublishedEntries: ImmutablePropTypes.map, loadUnpublishedEntries: PropTypes.func.isRequired, updateUnpublishedEntryStatus: PropTypes.func.isRequired, diff --git a/src/reducers/collections.js b/src/reducers/collections.js index c3a9934f..ef5908a5 100644 --- a/src/reducers/collections.js +++ b/src/reducers/collections.js @@ -2,6 +2,7 @@ import { OrderedMap, fromJS } from 'immutable'; import consoleError from '../lib/consoleError'; import { CONFIG_SUCCESS } from '../actions/config'; import { FILES, FOLDER } from '../constants/collectionTypes'; +import { INFERABLE_FIELDS } from '../constants/fieldInference'; const hasProperty = (config, property) => ({}.hasOwnProperty.call(config, property)); @@ -33,30 +34,6 @@ const formatToExtension = format => ({ html: 'html', }[format]); -const inferables = { - title: { - type: 'string', - secondaryTypes: [], - synonyms: ['title', 'name', 'label', 'headline'], - fallbackToFirstField: true, - showError: true, - }, - description: { - type: 'string', - secondaryTypes: ['text', 'markdown'], - synonyms: ['shortDescription', 'short_description', 'shortdescription', 'description', 'brief', 'body', 'content', 'biography', 'bio'], - fallbackToFirstField: false, - showError: false, - }, - image: { - type: 'image', - secondaryTypes: [], - synonyms: ['image', 'thumbnail', 'thumb', 'picture', 'avatar'], - fallbackToFirstField: false, - showError: false, - }, -}; - const selectors = { [FOLDER]: { entryExtension(collection) { @@ -117,7 +94,7 @@ export const selectListMethod = collection => selectors[collection.get('type')]. export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(collection); export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug); export const selectInferedField = (collection, fieldName) => { - const inferableField = inferables[fieldName]; + const inferableField = INFERABLE_FIELDS[fieldName]; const fields = collection.get('fields'); let field;