Single file collections (#132)

* Files based collections skeleton

* listing file based cards

* create new entry with collection

* moved lookupEntry to main backend

* Editing single page Collections file

* List widget basic implementation

* Adjustments for test-repo

* check if value exists before trying to iterate over
This commit is contained in:
Cássio Souza 2016-10-21 20:42:14 -02:00 committed by GitHub
parent cca338df79
commit 2496ec09a4
18 changed files with 190 additions and 102 deletions

View File

@ -37,13 +37,6 @@ collections: # A list of collections the CMS should be able to edit
description: "General Site Settings" description: "General Site Settings"
fields: fields:
- {label: "Global title", name: site_title, widget: "string"} - {label: "Global title", name: site_title, widget: "string"}
- label: "Post Settings"
name: posts
widget: "object"
fields:
- {label: "Number of posts on frontpage", name: front_limit, widget: number}
- {label: "Default Author", name: author, widget: string}
- {label: "Default Thumbnail", name: thumb, widget: image, class: "thumb"}
- name: "authors" - name: "authors"
label: "Authors" label: "Authors"

View File

@ -30,7 +30,7 @@
}, },
_data: { _data: {
"settings.json": { "settings.json": {
content: '{"site_title": "CMS Demo", "posts": {"front_limit": 5, "author": "Matt Biilmann"}}' content: '{"site_title": "CMS Demo"}'
}, },
"authors.yml": { "authors.yml": {
content: 'authors:\n - name: Mathias\n description: Co-founder @ Netlify\n' content: 'authors:\n - name: Mathias\n description: Co-founder @ Netlify\n'

View File

@ -3,6 +3,7 @@ import GitHubBackend from './github/implementation';
import NetlifyGitBackend from './netlify-git/implementation'; import NetlifyGitBackend from './netlify-git/implementation';
import { resolveFormat } from '../formats/formats'; import { resolveFormat } from '../formats/formats';
import { createEntry } from '../valueObjects/Entry'; import { createEntry } from '../valueObjects/Entry';
import { FILES, FOLDER } from '../constants/collectionTypes';
class LocalStorageAuthStore { class LocalStorageAuthStore {
storageKey = 'nf-cms-user'; storageKey = 'nf-cms-user';
@ -18,15 +19,15 @@ class LocalStorageAuthStore {
} }
const slugFormatter = (template, entryData) => { const slugFormatter = (template, entryData) => {
var date = new Date(); const date = new Date();
return template.replace(/\{\{([^\}]+)\}\}/g, function(_, name) { return template.replace(/\{\{([^\}]+)\}\}/g, (_, name) => {
switch (name) { switch (name) {
case 'year': case 'year':
return date.getFullYear(); return date.getFullYear();
case 'month': case 'month':
return ('0' + (date.getMonth() + 1)).slice(-2); return (`0${ date.getMonth() + 1 }`).slice(-2);
case 'day': case 'day':
return ('0' + date.getDate()).slice(-2); return (`0${ date.getDate() }`).slice(-2);
case 'slug': case 'slug':
const identifier = entryData.get('title', entryData.get('path')); const identifier = entryData.get('title', entryData.get('path'));
return identifier.trim().toLowerCase().replace(/[^a-z0-9\.\-\_]+/gi, '-'); return identifier.trim().toLowerCase().replace(/[^a-z0-9\.\-\_]+/gi, '-');
@ -65,13 +66,31 @@ class Backend {
}); });
} }
listEntries(collection, page, perPage) { listEntries(collection) {
return this.implementation.entries(collection, page, perPage).then((response) => { const type = collection.get('type');
return { if (type === FOLDER) {
pagination: response.pagination, return this.implementation.entriesByFolder(collection)
entries: response.entries.map(this.entryWithFormat(collection)) .then(loadedEntries => (
}; loadedEntries.map(loadedEntry => createEntry(collection.get('name'), loadedEntry.file.path.split('/').pop().replace(/\.[^\.]+$/, ''), loadedEntry.file.path, { raw: loadedEntry.data }))
}); ))
.then(entries => (
{
entries: entries.map(this.entryWithFormat(collection)),
}
));
} else if (type === FILES) {
const collectionFiles = collection.get('files').map(collectionFile => ({ path: collectionFile.get('file'), label: collectionFile.get('label') }));
return this.implementation.entriesByFiles(collection, collectionFiles)
.then(loadedEntries => (
loadedEntries.map(loadedEntry => createEntry(collection.get('name'), loadedEntry.file.path.split('/').pop().replace(/\.[^\.]+$/, ''), loadedEntry.file.path, { raw: loadedEntry.data, label: loadedEntry.file.label }))
))
.then(entries => (
{
entries: entries.map(this.entryWithFormat(collection)),
}
));
}
return Promise.reject(`Couldn't process collection type ${ type }`);
} }
// We have the file path. Fetch and parse the file. // We have the file path. Fetch and parse the file.
@ -82,12 +101,19 @@ class Backend {
// Will fetch the whole list of files from GitHub and load each file, then looks up for entry. // Will fetch the whole list of files from GitHub and load each file, then looks up for entry.
// (Files are persisted in local storage - only expensive on the first run for each file). // (Files are persisted in local storage - only expensive on the first run for each file).
lookupEntry(collection, slug) { lookupEntry(collection, slug) {
return this.implementation.lookupEntry(collection, slug).then(this.entryWithFormat(collection)); const type = collection.get('type');
if (type === FOLDER) {
return this.implementation.entriesByFolder(collection)
.then(loadedEntries => (
loadedEntries.map(loadedEntry => createEntry(collection.get('name'), loadedEntry.file.path.split('/').pop().replace(/\.[^\.]+$/, ''), loadedEntry.file.path, { raw: loadedEntry.data }))
))
.then(response => response.filter(entry => entry.slug === slug)[0])
.then(this.entryWithFormat(collection));
}
} }
newEntry(collection) { newEntry(collection) {
const newEntry = createEntry(); return createEntry(collection.get('name'));
return this.entryWithFormat(collection)(newEntry);
} }
entryWithFormat(collectionOrEntity) { entryWithFormat(collectionOrEntity) {
@ -95,8 +121,10 @@ class Backend {
const format = resolveFormat(collectionOrEntity, entry); const format = resolveFormat(collectionOrEntity, entry);
if (entry && entry.raw) { if (entry && entry.raw) {
entry.data = format && format.fromFile(entry.raw); entry.data = format && format.fromFile(entry.raw);
}
return entry; return entry;
} else {
return format.fromFile(entry);
}
}; };
} }
@ -104,7 +132,7 @@ class Backend {
return this.implementation.unpublishedEntries(page, perPage).then((response) => { return this.implementation.unpublishedEntries(page, perPage).then((response) => {
return { return {
pagination: response.pagination, pagination: response.pagination,
entries: response.entries.map(this.entryWithFormat('editorialWorkflow')) entries: response.entries.map(this.entryWithFormat('editorialWorkflow')),
}; };
}); });
} }
@ -127,27 +155,27 @@ class Backend {
const slug = slugFormatter(collection.get('slug'), entryDraft.getIn(['entry', 'data'])); const slug = slugFormatter(collection.get('slug'), entryDraft.getIn(['entry', 'data']));
entryObj = { entryObj = {
path: `${ collection.get('folder') }/${ slug }.md`, path: `${ collection.get('folder') }/${ slug }.md`,
slug: slug, slug,
raw: this.entryToRaw(collection, entryData) raw: this.entryToRaw(collection, entryData),
}; };
} else { } else {
entryObj = { entryObj = {
path: entryDraft.getIn(['entry', 'path']), path: entryDraft.getIn(['entry', 'path']),
slug: entryDraft.getIn(['entry', 'slug']), slug: entryDraft.getIn(['entry', 'slug']),
raw: this.entryToRaw(collection, entryData) raw: this.entryToRaw(collection, entryData),
}; };
} }
const commitMessage = (newEntry ? 'Created ' : 'Updated ') + const commitMessage = `${ (newEntry ? 'Created ' : 'Updated ') +
collection.get('label') + ' “' + collection.get('label') } ${
entryDraft.getIn(['entry', 'data', 'title']) + '”'; entryDraft.getIn(['entry', 'data', 'title']) }`;
const mode = config.get('publish_mode'); const mode = config.get('publish_mode');
const collectionName = collection.get('name'); const collectionName = collection.get('name');
return this.implementation.persistEntry(entryObj, MediaFiles, { return this.implementation.persistEntry(entryObj, MediaFiles, {
newEntry, parsedData, commitMessage, collectionName, mode, ...options newEntry, parsedData, commitMessage, collectionName, mode, ...options,
}); });
} }
@ -199,4 +227,4 @@ export const currentBackend = (function() {
return backend = resolveBackend(config); return backend = resolveBackend(config);
} }
}; };
})(); }());

View File

@ -112,13 +112,14 @@ export default class API {
const cache = LocalForage.getItem(`gh.meta.${ key }`); const cache = LocalForage.getItem(`gh.meta.${ key }`);
return cache.then((cached) => { return cache.then((cached) => {
if (cached && cached.expires > Date.now()) { return cached.data; } if (cached && cached.expires > Date.now()) { return cached.data; }
console.log("%c Checking for MetaData files", "line-height: 30px;text-align: center;font-weight: bold"); // eslint-disable-line
return this.request(`${ this.repoURL }/contents/${ key }.json`, { return this.request(`${ this.repoURL }/contents/${ key }.json`, {
params: { ref: 'refs/meta/_netlify_cms' }, params: { ref: 'refs/meta/_netlify_cms' },
headers: { Accept: 'application/vnd.github.VERSION.raw' }, headers: { Accept: 'application/vnd.github.VERSION.raw' },
cache: 'no-store', cache: 'no-store',
}) })
.then(response => JSON.parse(response)); .then(response => JSON.parse(response))
.catch(error => console.log("%c %s does not have metadata", "line-height: 30px;text-align: center;font-weight: bold", key)); // eslint-disable-line
}); });
} }

View File

@ -31,14 +31,22 @@ export default class GitHub {
}); });
} }
entries(collection) { entriesByFolder(collection) {
return this.api.listFiles(collection.get('folder')).then((files) => { return this.api.listFiles(collection.get('folder')).then(files => this.entriesByFiles(collection, files));
}
entriesByFiles(collection, files) {
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS); const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
const promises = []; const promises = [];
files.map((file) => { files.forEach((file) => {
promises.push(new Promise((resolve, reject) => { promises.push(new Promise((resolve, reject) => {
return sem.take(() => this.api.readFile(file.path, file.sha).then((data) => { return sem.take(() => this.api.readFile(file.path, file.sha).then((data) => {
resolve(createEntry(collection.get('name'), file.path.split('/').pop().replace(/\.[^\.]+$/, ''), file.path, { raw: data })); resolve(
{
file,
data,
}
);
sem.leave(); sem.leave();
}).catch((err) => { }).catch((err) => {
sem.leave(); sem.leave();
@ -47,17 +55,6 @@ export default class GitHub {
})); }));
}); });
return Promise.all(promises); return Promise.all(promises);
}).then(entries => ({
entries,
}));
}
// Will fetch the entire list of entries from github.
lookupEntry(collection, slug) {
return this.entries(collection).then(response => (
response.entries.filter(entry => entry.slug === slug)[0]
));
} }
// Fetches a single entry. // Fetches a single entry.

View File

@ -24,18 +24,25 @@ export default class TestRepo {
return Promise.resolve({ email: state.email }); return Promise.resolve({ email: state.email });
} }
entries(collection) { entriesByFolder(collection) {
const entries = []; const entries = [];
const folder = collection.get('folder'); const folder = collection.get('folder');
if (folder) { if (folder) {
for (const path in window.repoFiles[folder]) { for (const path in window.repoFiles[folder]) {
entries.push(createEntry(collection.get('name'), getSlug(path), `${ folder }/${ path }`, { raw: window.repoFiles[folder][path].content })); const file = { path: `${ folder }/${ path }` };
entries.push(
{
file,
data: window.repoFiles[folder][path].content,
} }
);
}
}
return Promise.resolve(entries);
} }
return Promise.resolve({ entriesByFiles(collection, files) {
entries, throw new Error('Not implemented yet');
});
} }
lookupEntry(collection, slug) { lookupEntry(collection, slug) {

View File

@ -6,7 +6,7 @@ import styles from './ControlPane.css';
export default class ControlPane extends Component { export default class ControlPane extends Component {
controlFor(field) { controlFor(field) {
const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props; const { entry, fields, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
const widget = resolveWidget(field.get('widget')); const widget = resolveWidget(field.get('widget'));
const fieldName = field.get('name'); const fieldName = field.get('name');
const value = entry.getIn(['data', fieldName]); const value = entry.getIn(['data', fieldName]);
@ -29,16 +29,15 @@ export default class ControlPane extends Component {
} }
render() { render() {
const { collection } = this.props; const { collection, fields } = this.props;
if (!collection) { if (!collection || !fields) {
return null; return null;
} }
return ( return (
<div> <div>
{ {
collection fields.map(field =>
.get('fields')
.map(field =>
<div <div
key={field.get('name')} key={field.get('name')}
className={styles.widget} className={styles.widget}
@ -55,6 +54,7 @@ export default class ControlPane extends Component {
ControlPane.propTypes = { ControlPane.propTypes = {
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
entry: ImmutablePropTypes.map.isRequired, entry: ImmutablePropTypes.map.isRequired,
fields: ImmutablePropTypes.list.isRequired,
getMedia: PropTypes.func.isRequired, getMedia: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired, onAddMedia: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,

View File

@ -10,6 +10,7 @@ export default function EntryEditor(
{ {
collection, collection,
entry, entry,
fields,
getMedia, getMedia,
onChange, onChange,
onAddMedia, onAddMedia,
@ -26,6 +27,7 @@ export default function EntryEditor(
<ControlPane <ControlPane
collection={collection} collection={collection}
entry={entry} entry={entry}
fields={fields}
getMedia={getMedia} getMedia={getMedia}
onChange={onChange} onChange={onChange}
onAddMedia={onAddMedia} onAddMedia={onAddMedia}
@ -37,6 +39,7 @@ export default function EntryEditor(
<PreviewPane <PreviewPane
collection={collection} collection={collection}
entry={entry} entry={entry}
fields={fields}
getMedia={getMedia} getMedia={getMedia}
/> />
</div> </div>
@ -56,6 +59,7 @@ export default function EntryEditor(
EntryEditor.propTypes = { EntryEditor.propTypes = {
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
entry: ImmutablePropTypes.map.isRequired, entry: ImmutablePropTypes.map.isRequired,
fields: ImmutablePropTypes.list.isRequired,
getMedia: PropTypes.func.isRequired, getMedia: PropTypes.func.isRequired,
onAddMedia: PropTypes.func.isRequired, onAddMedia: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,

View File

@ -21,7 +21,7 @@ export default class EntryListing extends React.Component {
{ mq: '1005px', columns: 4, gutter: 15 }, { mq: '1005px', columns: 4, gutter: 15 },
{ mq: '1515px', columns: 5, gutter: 15 }, { mq: '1515px', columns: 5, gutter: 15 },
{ mq: '1770px', columns: 6, gutter: 15 }, { mq: '1770px', columns: 6, gutter: 15 },
] ],
}; };
this.updateBricks = _.throttle(this.updateBricks.bind(this), 30); this.updateBricks = _.throttle(this.updateBricks.bind(this), 30);
@ -32,7 +32,7 @@ export default class EntryListing extends React.Component {
this.bricksInstance = Bricks({ this.bricksInstance = Bricks({
container: this._entries, container: this._entries,
packed: this.bricksConfig.packed, packed: this.bricksConfig.packed,
sizes: this.bricksConfig.sizes sizes: this.bricksConfig.sizes,
}); });
this.bricksInstance.resize(true); this.bricksInstance.resize(true);
@ -65,10 +65,10 @@ export default class EntryListing extends React.Component {
const card = Cards[cartType] || Cards._unknown; const card = Cards[cartType] || Cards._unknown;
return React.createElement(card, { return React.createElement(card, {
key: entry.get('slug'), key: entry.get('slug'),
collection: collection, collection,
onClick: history.push.bind(this, link), onClick: history.push.bind(this, link),
onImageLoaded: this.updateBricks, onImageLoaded: this.updateBricks,
text: entry.getIn(['data', collection.getIn(['card', 'text'])]), text: entry.get('label') ? entry.get('label') : entry.getIn(['data', collection.getIn(['card', 'text'])]),
description: entry.getIn(['data', collection.getIn(['card', 'description'])]), description: entry.getIn(['data', collection.getIn(['card', 'description'])]),
image: entry.getIn(['data', collection.getIn(['card', 'image'])]), image: entry.getIn(['data', collection.getIn(['card', 'image'])]),
}); });
@ -98,13 +98,13 @@ export default class EntryListing extends React.Component {
render() { render() {
const { children } = this.props; const { children } = this.props;
const cards = this.renderCards(); const cards = this.renderCards();
return <div> return (<div>
<h1>{children}</h1> <h1>{children}</h1>
<div ref={(c) => this._entries = c}> <div ref={c => this._entries = c}>
{cards} {cards}
<Waypoint onEnter={this.handleLoadMore} /> <Waypoint onEnter={this.handleLoadMore} />
</div> </div>
</div>; </div>);
} }
} }
@ -112,7 +112,7 @@ EntryListing.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
collections: PropTypes.oneOfType([ collections: PropTypes.oneOfType([
ImmutablePropTypes.map, ImmutablePropTypes.map,
ImmutablePropTypes.iterable ImmutablePropTypes.iterable,
]).isRequired, ]).isRequired,
entries: ImmutablePropTypes.list, entries: ImmutablePropTypes.list,
onPaginate: PropTypes.func.isRequired, onPaginate: PropTypes.func.isRequired,

View File

@ -1,14 +1,13 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
export default function Preview({ collection, widgetFor }) { export default function Preview({ collection, fields, widgetFor }) {
if (!collection) { if (!collection || !fields) {
return null; return null;
} }
return ( return (
<div> <div>
{collection.get('fields').map(field => widgetFor(field.get('name')))} {fields.map(field => widgetFor(field.get('name')))}
</div> </div>
); );
} }
@ -16,6 +15,7 @@ export default function Preview({ collection, widgetFor }) {
Preview.propTypes = { Preview.propTypes = {
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
entry: ImmutablePropTypes.map.isRequired, entry: ImmutablePropTypes.map.isRequired,
fields: ImmutablePropTypes.list.isRequired,
getMedia: PropTypes.func.isRequired, getMedia: PropTypes.func.isRequired,
widgetFor: PropTypes.func.isRequired, widgetFor: PropTypes.func.isRequired,
}; };

View File

@ -14,8 +14,8 @@ export default class PreviewPane extends React.Component {
} }
widgetFor = (name) => { widgetFor = (name) => {
const { collection, entry, getMedia } = this.props; const { fields, entry, getMedia } = this.props;
const field = collection.get('fields').find(field => field.get('name') === name); const field = fields.find(field => field.get('name') === name);
const widget = resolveWidget(field.get('widget')); const widget = resolveWidget(field.get('widget'));
return React.createElement(widget.preview, { return React.createElement(widget.preview, {
key: field.get('name'), key: field.get('name'),
@ -67,6 +67,7 @@ export default class PreviewPane extends React.Component {
PreviewPane.propTypes = { PreviewPane.propTypes = {
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
fields: ImmutablePropTypes.list.isRequired,
entry: ImmutablePropTypes.map.isRequired, entry: ImmutablePropTypes.map.isRequired,
getMedia: PropTypes.func.isRequired, getMedia: PropTypes.func.isRequired,
scrollTop: PropTypes.number, scrollTop: PropTypes.number,

View File

@ -3,6 +3,8 @@ import UnknownControl from './Widgets/UnknownControl';
import UnknownPreview from './Widgets/UnknownPreview'; import UnknownPreview from './Widgets/UnknownPreview';
import StringControl from './Widgets/StringControl'; import StringControl from './Widgets/StringControl';
import StringPreview from './Widgets/StringPreview'; import StringPreview from './Widgets/StringPreview';
import ListControl from './Widgets/ListControl';
import ListPreview from './Widgets/ListPreview';
import TextControl from './Widgets/TextControl'; import TextControl from './Widgets/TextControl';
import TextPreview from './Widgets/TextPreview'; import TextPreview from './Widgets/TextPreview';
import MarkdownControl from './Widgets/MarkdownControl'; import MarkdownControl from './Widgets/MarkdownControl';
@ -14,6 +16,7 @@ import DateTimePreview from './Widgets/DateTimePreview';
registry.registerWidget('string', StringControl, StringPreview); registry.registerWidget('string', StringControl, StringPreview);
registry.registerWidget('text', TextControl, TextPreview); registry.registerWidget('text', TextControl, TextPreview);
registry.registerWidget('list', ListControl, ListPreview);
registry.registerWidget('markdown', MarkdownControl, MarkdownPreview); registry.registerWidget('markdown', MarkdownControl, MarkdownPreview);
registry.registerWidget('image', ImageControl, ImagePreview); registry.registerWidget('image', ImageControl, ImagePreview);
registry.registerWidget('datetime', DateTimeControl, DateTimePreview); registry.registerWidget('datetime', DateTimeControl, DateTimePreview);

View File

@ -0,0 +1,17 @@
import React, { Component, PropTypes } from 'react';
export default class ListControl extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
value: PropTypes.node,
};
handleChange = (e) => {
this.props.onChange(e.target.value.split(',').map(item => item.trim()));
};
render() {
const { value } = this.props;
return <input type="text" value={value ? value.join(', ') : ''} onChange={this.handleChange} />;
}
}

View File

@ -0,0 +1,11 @@
import React, { PropTypes } from 'react';
export default function ListPreview({ value }) {
return (<ul>
{ value && value.map(item => <li key={item}>{item}</li>) }
</ul>);
}
ListPreview.propTypes = {
value: PropTypes.node,
};

View File

@ -0,0 +1,2 @@
export const FILES = 'file_based_collection';
export const FOLDER = 'folder_based_collection';

View File

@ -12,6 +12,7 @@ import {
import { cancelEdit } from '../actions/editor'; import { cancelEdit } from '../actions/editor';
import { addMedia, removeMedia } from '../actions/media'; import { addMedia, removeMedia } from '../actions/media';
import { selectEntry, getMedia } from '../reducers'; import { selectEntry, getMedia } from '../reducers';
import { FOLDER, FILES } from '../constants/collectionTypes';
import EntryEditor from '../components/EntryEditor/EntryEditor'; import EntryEditor from '../components/EntryEditor/EntryEditor';
import entryPageHOC from './editorialWorkflow/EntryPageHOC'; import entryPageHOC from './editorialWorkflow/EntryPageHOC';
import { Loader } from '../components/UI'; import { Loader } from '../components/UI';
@ -31,6 +32,7 @@ class EntryPage extends React.Component {
persistEntry: PropTypes.func.isRequired, persistEntry: PropTypes.func.isRequired,
removeMedia: PropTypes.func.isRequired, removeMedia: PropTypes.func.isRequired,
cancelEdit: PropTypes.func.isRequired, cancelEdit: PropTypes.func.isRequired,
fields: ImmutablePropTypes.list.isRequired,
slug: PropTypes.string, slug: PropTypes.string,
newEntry: PropTypes.bool.isRequired, newEntry: PropTypes.bool.isRequired,
}; };
@ -41,7 +43,7 @@ class EntryPage extends React.Component {
if (newEntry) { if (newEntry) {
createEmptyDraft(collection); createEmptyDraft(collection);
} else { } else {
loadEntry(entry, collection, slug); if (collection.get('type') === FOLDER) loadEntry(entry, collection, slug);
this.createDraft(entry); this.createDraft(entry);
} }
} }
@ -72,6 +74,7 @@ class EntryPage extends React.Component {
const { const {
entry, entry,
entryDraft, entryDraft,
fields,
boundGetMedia, boundGetMedia,
collection, collection,
changeDraft, changeDraft,
@ -90,6 +93,7 @@ class EntryPage extends React.Component {
entry={entryDraft.get('entry')} entry={entryDraft.get('entry')}
getMedia={boundGetMedia} getMedia={boundGetMedia}
collection={collection} collection={collection}
fields={fields}
onChange={changeDraft} onChange={changeDraft}
onAddMedia={addMedia} onAddMedia={addMedia}
onRemoveMedia={removeMedia} onRemoveMedia={removeMedia}
@ -102,9 +106,19 @@ class EntryPage extends React.Component {
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { collections, entryDraft } = state; const { collections, entryDraft } = state;
const collection = collections.get(ownProps.params.name);
const newEntry = ownProps.route && ownProps.route.newRecord === true;
const slug = ownProps.params.slug; const slug = ownProps.params.slug;
const collection = collections.get(ownProps.params.name);
let fields;
if (collection.get('type') === FOLDER) {
fields = collection.get('fields');
} else {
const files = collection.get('files');
const file = files.filter(f => f.get('name') === slug);
fields = file.getIn([0, 'fields']);
}
const newEntry = ownProps.route && ownProps.route.newRecord === true;
const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug); const entry = newEntry ? null : selectEntry(state, collection.get('name'), slug);
const boundGetMedia = getMedia.bind(null, state); const boundGetMedia = getMedia.bind(null, state);
return { return {
@ -113,6 +127,7 @@ function mapStateToProps(state, ownProps) {
newEntry, newEntry,
entryDraft, entryDraft,
boundGetMedia, boundGetMedia,
fields,
slug, slug,
entry, entry,
}; };

View File

@ -1,13 +1,21 @@
import { OrderedMap, fromJS } from 'immutable'; import { OrderedMap, fromJS } from 'immutable';
import { CONFIG_SUCCESS } from '../actions/config'; import { CONFIG_SUCCESS } from '../actions/config';
import { FILES, FOLDER } from '../constants/collectionTypes';
const hasProperty = (config, property) => ({}.hasOwnProperty.call(config, property));
const collections = (state = null, action) => { const collections = (state = null, action) => {
switch (action.type) { switch (action.type) {
case CONFIG_SUCCESS: case CONFIG_SUCCESS:
const collections = action.payload && action.payload.collections; const configCollections = action.payload && action.payload.collections;
return OrderedMap().withMutations((map) => { return OrderedMap().withMutations((map) => {
(collections || []).forEach(function(collection) { (configCollections || []).forEach((configCollection) => {
map.set(collection.name, fromJS(collection)); if (hasProperty(configCollection, 'folder')) {
configCollection.type = FOLDER; // eslint-disable-line no-param-reassign
} else if (hasProperty(configCollection, 'files')) {
configCollection.type = FILES; // eslint-disable-line no-param-reassign
}
map.set(configCollection.name, fromJS(configCollection));
}); });
}); });
default: default:

View File

@ -6,6 +6,7 @@ export function createEntry(collection, slug = '', path = '', options = {}) {
returnObj.partial = options.partial || false; returnObj.partial = options.partial || false;
returnObj.raw = options.raw || ''; returnObj.raw = options.raw || '';
returnObj.data = options.data || {}; returnObj.data = options.data || {};
returnObj.label = options.label || null;
returnObj.metaData = options.metaData || null; returnObj.metaData = options.metaData || null;
return returnObj; return returnObj;
} }