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 assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
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;
return persistAction.call(backend, state.config, collection, transformedEntryDraft, assetProxies.toJS())
return persistAction.call(backend, state.config, collection, serializedEntryDraft, assetProxies.toJS())
.then(() => {
dispatch(notifSend({
message: 'Entry saved',
kind: 'success',
dismissAfter: 4000,
}));
return dispatch(unpublishedEntryPersisted(collection, transformedEntry, transactionID));
return dispatch(unpublishedEntryPersisted(collection, serializedEntry, transactionID));
})
.catch((error) => {
dispatch(notifSend({

View File

@ -271,19 +271,24 @@ export function persistEntry(collection) {
const backend = currentBackend(state.config);
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
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(entryPersisting(collection, transformedEntry));
/**
* 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(entryPersisting(collection, serializedEntry));
return backend
.persistEntry(state.config, collection, transformedEntryDraft, assetProxies.toJS())
.persistEntry(state.config, collection, serializedEntryDraft, assetProxies.toJS())
.then(() => {
dispatch(notifSend({
message: 'Entry saved',
kind: 'success',
dismissAfter: 4000,
}));
return dispatch(entryPersisted(collection, transformedEntry));
return dispatch(entryPersisted(collection, serializedEntry));
})
.catch((error) => {
console.error(error);
@ -292,7 +297,7 @@ export function persistEntry(collection) {
kind: 'danger',
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',
};
/**
* Use a stateful component so that child components can effectively utilize
* `shouldComponentUpdate`.
*/
export default class Preview extends React.Component {
render() {
const { collection, fields, widgetFor } = this.props;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,13 @@
import React from 'react';
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) {
// 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'));
return isWidgetContainer || this.props.value !== nextProps.value;
}

View File

@ -68,6 +68,11 @@ class EntryPage extends React.Component {
const { entry, newEntry, fields, collection } = nextProps;
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 deserializedEntry = entry.set('data', values);
this.createDraft(deserializedEntry);

View File

@ -20,22 +20,40 @@ import registry from './registry';
* registered deserialization handlers run on entry load, and serialization
* handlers run on persist.
*/
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) => {
const fieldName = field.get('name');
const value = values.get(fieldName);
const serializer = registry.getWidgetValueSerializer(field.get('widget'));
const nestedFields = field.get('fields');
// Call recursively for fields within lists
if (nestedFields && List.isList(value)) {
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));
} 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));
} 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;
}, 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);
case ENTRY_SUCCESS:
const result = state.setIn(
return state.setIn(
['entities', `${ action.payload.collection }.${ action.payload.entry.slug }`],
fromJS(action.payload.entry)
);
return result;
case ENTRIES_REQUEST:
return state.setIn(['pages', action.payload.collection, 'isFetching'], true);