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:
parent
fed0066ca5
commit
b1a5ea95d3
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
))
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
10
src/components/Widgets/defaultPreviewStyle.js
Normal file
10
src/components/Widgets/defaultPreviewStyle.js
Normal file
@ -0,0 +1,10 @@
|
||||
const defaultPrevieStyle = {
|
||||
margin: '15px 2px',
|
||||
};
|
||||
|
||||
export const imagePreviewStyle = {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
};
|
||||
|
||||
export default defaultPrevieStyle;
|
45
src/constants/fieldInference.js
Normal file
45
src/constants/fieldInference.js
Normal 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,
|
||||
},
|
||||
};
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user