fix small code issues in RTE implementation

This commit is contained in:
Shawn Erquhart 2017-08-02 13:11:43 -04:00
parent 3d83325afc
commit 9c0b7262ef
11 changed files with 70 additions and 27 deletions

View File

@ -231,20 +231,25 @@ export function persistUnpublishedEntry(collection, existingUnpublishedEntry) {
const transactionID = uuid.v4(); const transactionID = uuid.v4();
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path)); const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
const entry = entryDraft.get('entry'); const entry = entryDraft.get('entry');
const transformedData = serializeValues(entryDraft.getIn(['entry', 'data']), collection.get('fields'));
const transformedEntry = entry.set('data', transformedData);
const transformedEntryDraft = entryDraft.set('entry', transformedEntry);
dispatch(unpublishedEntryPersisting(collection, transformedEntry, transactionID)); /**
* Serialize the values of any fields with registered serializers, and
* update the entry and entryDraft with the serialized values.
*/
const serializedData = serializeValues(entryDraft.getIn(['entry', 'data']), collection.get('fields'));
const serializedEntry = entry.set('data', serializedData);
const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
dispatch(unpublishedEntryPersisting(collection, serializedEntry, transactionID));
const persistAction = existingUnpublishedEntry ? backend.persistUnpublishedEntry : backend.persistEntry; const persistAction = existingUnpublishedEntry ? backend.persistUnpublishedEntry : backend.persistEntry;
return persistAction.call(backend, state.config, collection, transformedEntryDraft, assetProxies.toJS()) return persistAction.call(backend, state.config, collection, serializedEntryDraft, assetProxies.toJS())
.then(() => { .then(() => {
dispatch(notifSend({ dispatch(notifSend({
message: 'Entry saved', message: 'Entry saved',
kind: 'success', kind: 'success',
dismissAfter: 4000, dismissAfter: 4000,
})); }));
return dispatch(unpublishedEntryPersisted(collection, transformedEntry, transactionID)); return dispatch(unpublishedEntryPersisted(collection, serializedEntry, transactionID));
}) })
.catch((error) => { .catch((error) => {
dispatch(notifSend({ dispatch(notifSend({

View File

@ -271,19 +271,24 @@ export function persistEntry(collection) {
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path)); const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
const entry = entryDraft.get('entry'); const entry = entryDraft.get('entry');
const transformedData = serializeValues(entryDraft.getIn(['entry', 'data']), collection.get('fields'));
const transformedEntry = entry.set('data', transformedData); /**
const transformedEntryDraft = entryDraft.set('entry', transformedEntry); * Serialize the values of any fields with registered serializers, and
dispatch(entryPersisting(collection, transformedEntry)); * update the entry and entryDraft with the serialized values.
*/
const serializedData = serializeValues(entryDraft.getIn(['entry', 'data']), collection.get('fields'));
const serializedEntry = entry.set('data', serializedData);
const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
dispatch(entryPersisting(collection, serializedEntry));
return backend return backend
.persistEntry(state.config, collection, transformedEntryDraft, assetProxies.toJS()) .persistEntry(state.config, collection, serializedEntryDraft, assetProxies.toJS())
.then(() => { .then(() => {
dispatch(notifSend({ dispatch(notifSend({
message: 'Entry saved', message: 'Entry saved',
kind: 'success', kind: 'success',
dismissAfter: 4000, dismissAfter: 4000,
})); }));
return dispatch(entryPersisted(collection, transformedEntry)); return dispatch(entryPersisted(collection, serializedEntry));
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.error(error);
@ -292,7 +297,7 @@ export function persistEntry(collection) {
kind: 'danger', kind: 'danger',
dismissAfter: 8000, dismissAfter: 8000,
})); }));
return dispatch(entryPersistFail(collection, transformedEntry, error)); return dispatch(entryPersistFail(collection, serializedEntry, error));
}); });
}; };
} }

View File

@ -9,6 +9,10 @@ const style = {
fontFamily: 'Roboto, "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif', fontFamily: 'Roboto, "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif',
}; };
/**
* Use a stateful component so that child components can effectively utilize
* `shouldComponentUpdate`.
*/
export default class Preview extends React.Component { export default class Preview extends React.Component {
render() { render() {
const { collection, fields, widgetFor } = this.props; const { collection, fields, widgetFor } = this.props;

View File

@ -1,9 +1,11 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { ScrollSyncPane } from '../ScrollSync'; import { ScrollSyncPane } from '../ScrollSync';
// We need to create a lightweight component here so that we can /**
// access the context within the Frame. This allows us to attach * We need to create a lightweight component here so that we can access the
// the ScrollSyncPane to the body. * context within the Frame. This allows us to attach the ScrollSyncPane to the
* body.
*/
class PreviewContent extends React.Component { class PreviewContent extends React.Component {
render() { render() {
const { previewComponent, previewProps } = this.props; const { previewComponent, previewProps } = this.props;

View File

@ -17,6 +17,9 @@ export default class PreviewPane extends React.Component {
const { fieldsMetaData, getAsset, entry } = props; const { fieldsMetaData, getAsset, entry } = props;
const widget = resolveWidget(field.get('widget')); const widget = resolveWidget(field.get('widget'));
/**
* Use an HOC to provide conditional updates for all previews.
*/
return !widget.preview ? null : ( return !widget.preview ? null : (
<PreviewHOC <PreviewHOC
previewComponent={widget.preview} previewComponent={widget.preview}

View File

@ -1,6 +1,5 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ImmutablePropTypes from "react-immutable-proptypes"; import ImmutablePropTypes from "react-immutable-proptypes";
import isEqual from 'lodash/isEqual';
const truthy = () => ({ error: false }); const truthy = () => ({ error: false });

View File

@ -19,7 +19,7 @@ import mdastDefinitions from 'mdast-util-definitions';
* Yields: * Yields:
* *
* ``` * ```
* ![alpha][http://example.com/example.jpg] * ![alpha](http://example.com/example.jpg)
* ``` * ```
* *
*/ */

View File

@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
class PreviewHOC extends React.Component { class PreviewHOC extends React.Component {
/**
* Only re-render on value change, but always re-render objects and lists.
* Their child widgets will each also be wrapped with this component, and
* will only be updated on value change.
*/
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
// Only re-render on value change, but always re-render objects and lists.
// Their child widgets will each also be wrapped with this component, and
// will only be updated on value change.
const isWidgetContainer = ['object', 'list'].includes(nextProps.field.get('widget')); const isWidgetContainer = ['object', 'list'].includes(nextProps.field.get('widget'));
return isWidgetContainer || this.props.value !== nextProps.value; return isWidgetContainer || this.props.value !== nextProps.value;
} }

View File

@ -68,6 +68,11 @@ class EntryPage extends React.Component {
const { entry, newEntry, fields, collection } = nextProps; const { entry, newEntry, fields, collection } = nextProps;
if (entry && !entry.get('isFetching') && !entry.get('error')) { if (entry && !entry.get('isFetching') && !entry.get('error')) {
/**
* Deserialize entry values for widgets with registered serializers before
* creating the entry draft.
*/
const values = deserializeValues(entry.get('data'), fields); const values = deserializeValues(entry.get('data'), fields);
const deserializedEntry = entry.set('data', values); const deserializedEntry = entry.set('data', values);
this.createDraft(deserializedEntry); this.createDraft(deserializedEntry);

View File

@ -20,22 +20,40 @@ import registry from './registry';
* registered deserialization handlers run on entry load, and serialization * registered deserialization handlers run on entry load, and serialization
* handlers run on persist. * handlers run on persist.
*/ */
const runSerializer = (values, fields, method) => { const runSerializer = (values, fields, method) => {
/**
* Reduce the list of fields to a map where keys are field names and values
* are field values, serializing the values of fields whose widgets have
* registered serializers. If the field is a list or object, call recursively
* for nested fields.
*/
return fields.reduce((acc, field) => { return fields.reduce((acc, field) => {
const fieldName = field.get('name'); const fieldName = field.get('name');
const value = values.get(fieldName); const value = values.get(fieldName);
const serializer = registry.getWidgetValueSerializer(field.get('widget')); const serializer = registry.getWidgetValueSerializer(field.get('widget'));
const nestedFields = field.get('fields'); const nestedFields = field.get('fields');
// Call recursively for fields within lists
if (nestedFields && List.isList(value)) { if (nestedFields && List.isList(value)) {
return acc.set(fieldName, value.map(val => runSerializer(val, nestedFields, method))); return acc.set(fieldName, value.map(val => runSerializer(val, nestedFields, method)));
} else if (nestedFields && Map.isMap(value)) { }
// Call recursively for fields within objects
if (nestedFields && Map.isMap(value)) {
return acc.set(fieldName, runSerializer(value, nestedFields, method)); return acc.set(fieldName, runSerializer(value, nestedFields, method));
} else if (serializer && !isNil(value)) { }
// Run serialization method on value if not null or undefined
if (serializer && !isNil(value)) {
return acc.set(fieldName, serializer[method](value)); return acc.set(fieldName, serializer[method](value));
} else if (!isNil(value)) { }
// If no serializer is registered for the field's widget, use the field as is
if (!isNil(value)) {
return acc.set(fieldName, value); return acc.set(fieldName, value);
} }
return acc; return acc;
}, Map()); }, Map());
}; };

View File

@ -21,11 +21,10 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
return state.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isFetching'], true); return state.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isFetching'], true);
case ENTRY_SUCCESS: case ENTRY_SUCCESS:
const result = state.setIn( return state.setIn(
['entities', `${ action.payload.collection }.${ action.payload.entry.slug }`], ['entities', `${ action.payload.collection }.${ action.payload.entry.slug }`],
fromJS(action.payload.entry) fromJS(action.payload.entry)
); );
return result;
case ENTRIES_REQUEST: case ENTRIES_REQUEST:
return state.setIn(['pages', action.payload.collection, 'isFetching'], true); return state.setIn(['pages', action.payload.collection, 'isFetching'], true);