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
This commit is contained in:
Cássio Souza 2016-11-23 16:23:32 -02:00 committed by GitHub
parent fed0066ca5
commit b1a5ea95d3
22 changed files with 191 additions and 70 deletions

View File

@ -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;
}

View File

@ -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 (
<Card
key={entry.get('slug')}
@ -54,8 +55,9 @@ export default class EntryListing extends React.Component {
{inferedFields.descriptionField ?
<p>{entry.getIn(['data', inferedFields.descriptionField])}</p>
: inferedFields.remainingFields && inferedFields.remainingFields.map(f => (
<p key={f.get('name')}>
<strong>{f.get('label')}:</strong> {entry.getIn(['data', f.get('name')])}
<p key={f.get('name')} className={styles.cardList}>
<span className={styles.cardListLabel}>{f.get('label')}:</span>{' '}
{ entry.getIn(['data', f.get('name')], '').toString() }
</p>
))
}

View File

@ -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 (
<div>
<div style={style}>
{fields.filter(isVisible).map(field => widgetFor(field.get('name')))}
</div>
);

View File

@ -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 = <div><strong>{field.get('label')}:</strong> {value}</div>;
}
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,

View File

@ -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;
}

View File

@ -107,7 +107,7 @@ class UnpublishedListing extends React.Component {
render() {
const columns = this.renderColumns(this.props.entries);
return (
<div className={styles.clear}>
<div>
<h5>Editorial Workflow</h5>
<div className={styles.container}>
{columns}

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function DatePreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{value ? value.toString() : null}</div>;
}
DatePreview.propTypes = {

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function DatePreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{value ? value.toString() : null}</div>;
}
DatePreview.propTypes = {

View File

@ -1,9 +1,16 @@
import React, { PropTypes } from 'react';
import previewStyle, { imagePreviewStyle } from './defaultPreviewStyle';
export default function ImagePreview({ value, getMedia }) {
return <span>
{value ? <img src={getMedia(value)}/> : null}
</span>;
return (<div style={previewStyle}>
{ value ?
<img
src={getMedia(value)}
style={imagePreviewStyle}
role="presentation"
/>
: null}
</div>);
}
ImagePreview.propTypes = {

View File

@ -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 ? (<div>{value.map((val, index) => <div key={index}>
{fields && fields.map(f => this.widgetFor(f, val))}
</div>)}</div>) : null;
return value ? (<div style={previewStyle}>
{value.map((val, index) => <div key={index}>
{fields && fields.map(f => this.widgetFor(f, val))}
</div>)}
</div>) : null;
}
return <span>{value ? value.join(', ') : null}</span>;
return <div style={previewStyle}>{value ? value.join(', ') : null}</div>;
}
}

View File

@ -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 (
<MarkupItReactRenderer
value={value}
syntax={markdown}
schema={schema}
/>
<div style={previewStyle}>
<MarkupItReactRenderer
value={value}
syntax={markdown}
schema={schema}
/>
</div>
);
};

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function NumberPreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{value}</div>;
}
NumberPreview.propTypes = {

View File

@ -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 (<div key={field.get('name')}>{React.createElement(widget.preview, {
key: field.get('name'),
value: value && value.get(field.get('name')),
field,
getMedia,
})}</div>);
return (
<div key={field.get('name')}>
{React.createElement(widget.preview, {
key: field.get('name'),
value: value && value.get(field.get('name')),
field,
getMedia,
})}
</div>
);
};
render() {
const { field } = this.props;
const fields = field && field.get('fields');
return <div>{fields ? fields.map(f => this.widgetFor(f)) : null}</div>;
return <div style={previewStyle}>{fields ? fields.map(f => this.widgetFor(f)) : null}</div>;
}
}

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function SelectPreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{value ? value.toString() : null}</div>;
}
SelectPreview.propTypes = {

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function StringPreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{ value }</div>;
}
StringPreview.propTypes = {

View File

@ -1,7 +1,8 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';
export default function TextPreview({ value }) {
return <span>{value ? value.toString() : null}</span>;
return <div style={previewStyle}>{value ? value.toString() : null}</div>;
}
TextPreview.propTypes = {

View File

@ -1,8 +1,9 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import previewStyle from './defaultPreviewStyle';
export default function UnknownPreview({ field }) {
return <div>No preview for widget '{field.get('widget')}'.</div>;
return <div style={previewStyle}>No preview for widget {field.get('widget')}.</div>;
}
UnknownPreview.propTypes = {

View File

@ -0,0 +1,10 @@
const defaultPrevieStyle = {
margin: '15px 2px',
};
export const imagePreviewStyle = {
width: '100%',
height: 'auto',
};
export default defaultPrevieStyle;

View File

@ -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 => <h1>{ value }</h1>,
fallbackToFirstField: true,
showError: true,
},
shortTitle: {
type: 'string',
secondaryTypes: [],
synonyms: ['short_title', 'shortTitle'],
defaultPreview: value => <h2>{ value }</h2>,
fallbackToFirstField: false,
showError: false,
},
author: {
type: 'string',
secondaryTypes: [],
synonyms: ['author', 'name', 'by'],
defaultPreview: value => <strong>{ value }</strong>,
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,
},
};

View File

@ -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 (
<div className={styles.root}>
<h1>Dashboard</h1>
<UnpublishedEntriesPanel />
</div>
);
class DashboardPage extends Component {
componentWillMount() {
if (this.props.publishMode === SIMPLE) {
history.push(`/collections/${ this.props.firstCollection }`);
}
}
render() {
return (
<div className={styles.root}>
<h1>Dashboard</h1>
<UnpublishedEntriesPanel />
</div>
);
}
}
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);

View File

@ -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,

View File

@ -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;