Allow the creation of new entries

This commit is contained in:
Cássio Zen 2016-08-24 21:36:44 -03:00
parent fd79381160
commit b717874e7b
9 changed files with 73 additions and 29 deletions

View File

@ -13,6 +13,7 @@ export const ENTRIES_SUCCESS = 'ENTRIES_SUCCESS';
export const ENTRIES_FAILURE = 'ENTRIES_FAILURE'; export const ENTRIES_FAILURE = 'ENTRIES_FAILURE';
export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY'; export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY';
export const DRAFT_CREATE_EMPTY = 'DRAFT_CREATE_EMPTY';
export const DRAFT_DISCARD = 'DRAFT_DISCARD'; export const DRAFT_DISCARD = 'DRAFT_DISCARD';
export const DRAFT_CHANGE = 'DRAFT_CHANGE'; export const DRAFT_CHANGE = 'DRAFT_CHANGE';
@ -102,6 +103,13 @@ function entryPersistFail(collection, entry, error) {
}; };
} }
function emmptyDraftCreated(entry) {
return {
type: DRAFT_CREATE_EMPTY,
payload: entry
};
}
/* /*
* Exported simple Action Creators * Exported simple Action Creators
*/ */
@ -153,6 +161,15 @@ export function loadEntries(collection) {
}; };
} }
export function createEmptyDraft(collection) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const newEntry = backend.newEntry(collection);
dispatch(emmptyDraftCreated(newEntry));
};
}
export function persistEntry(collection, entry) { export function persistEntry(collection, entry) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();

View File

@ -1,6 +1,7 @@
import TestRepoBackend from './test-repo/implementation'; import TestRepoBackend from './test-repo/implementation';
import GitHubBackend from './github/implementation'; import GitHubBackend from './github/implementation';
import { resolveFormat } from '../formats/formats'; import { resolveFormat } from '../formats/formats';
import { createEntry } from '../valueObjects/Entry';
class LocalStorageAuthStore { class LocalStorageAuthStore {
storageKey = 'nf-cms-user'; storageKey = 'nf-cms-user';
@ -57,6 +58,11 @@ class Backend {
return this.implementation.entry(collection, slug).then(this.entryWithFormat(collection)); return this.implementation.entry(collection, slug).then(this.entryWithFormat(collection));
} }
newEntry(collection) {
const newEntry = createEntry();
return this.entryWithFormat(collection)(newEntry);
}
entryWithFormat(collection) { entryWithFormat(collection) {
return (entry) => { return (entry) => {
const format = resolveFormat(collection, entry); const format = resolveFormat(collection, entry);

View File

@ -1,5 +1,6 @@
import LocalForage from 'localforage'; import LocalForage from 'localforage';
import MediaProxy from '../../valueObjects/MediaProxy'; import MediaProxy from '../../valueObjects/MediaProxy';
import { createEntry } from '../../valueObjects/Entry';
import AuthenticationPage from './AuthenticationPage'; import AuthenticationPage from './AuthenticationPage';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
@ -210,9 +211,7 @@ export default class GitHub {
return this.api.listFiles(collection.get('folder')).then((files) => ( return this.api.listFiles(collection.get('folder')).then((files) => (
Promise.all(files.map((file) => ( Promise.all(files.map((file) => (
this.api.readFile(file.path, file.sha).then((data) => { this.api.readFile(file.path, file.sha).then((data) => {
file.slug = file.path.split('/').pop().replace(/\.[^\.]+$/, ''); return createEntry(file.path, file.path.split('/').pop().replace(/\.[^\.]+$/, ''), data);
file.raw = data;
return file;
}) })
))) )))
)).then((entries) => ({ )).then((entries) => ({

View File

@ -1,4 +1,5 @@
import AuthenticationPage from './AuthenticationPage'; import AuthenticationPage from './AuthenticationPage';
import { createEntry } from '../../valueObjects/Entry';
function getSlug(path) { function getSlug(path) {
const m = path.match(/([^\/]+?)(\.[^\/\.]+)?$/); const m = path.match(/([^\/]+?)(\.[^\/\.]+)?$/);
@ -28,11 +29,7 @@ export default class TestRepo {
const folder = collection.get('folder'); const folder = collection.get('folder');
if (folder) { if (folder) {
for (var path in window.repoFiles[folder]) { for (var path in window.repoFiles[folder]) {
entries.push({ entries.push(createEntry(folder + '/' + path, getSlug(path), window.repoFiles[folder][path].content));
path: folder + '/' + path,
slug: getSlug(path),
raw: window.repoFiles[folder][path].content
});
} }
} }

View File

@ -11,7 +11,7 @@ export default class StringControl extends React.Component {
} }
render() { render() {
return <input value={this.props.value} onChange={this.handleChange}/>; return <input type="text" value={this.props.value || ''} onChange={this.handleChange}/>;
} }
} }

View File

@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { import {
loadEntry, loadEntry,
createDraftFromEntry, createDraftFromEntry,
createEmptyDraft,
discardDraft, discardDraft,
changeDraft, changeDraft,
persistEntry persistEntry
@ -15,19 +16,27 @@ import EntryEditor from '../components/EntryEditor';
class EntryPage extends React.Component { class EntryPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.props.loadEntry(props.collection, props.slug); this.createDraft = this.createDraft.bind(this);
this.handlePersistEntry = this.handlePersistEntry.bind(this); this.handlePersistEntry = this.handlePersistEntry.bind(this);
} }
componentDidMount() { componentDidMount() {
if (this.props.entry) { if (!this.props.newEntry) {
this.props.createDraftFromEntry(this.props.entry); this.props.loadEntry(this.props.collection, this.props.slug);
this.createDraft(this.props.entry);
} else {
this.props.createEmptyDraft(this.props.collection);
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.entry !== nextProps.entry && !nextProps.entry.get('isFetching')) { if (this.props.entry === nextProps.entry) return;
this.props.createDraftFromEntry(nextProps.entry);
if (nextProps.entry && !nextProps.entry.get('isFetching')) {
this.createDraft(nextProps.entry);
} else if (nextProps.newEntry) {
this.props.createEmptyDraft(nextProps.collection);
} }
} }
@ -35,17 +44,19 @@ class EntryPage extends React.Component {
this.props.discardDraft(); this.props.discardDraft();
} }
createDraft(entry) {
if (entry) this.props.createDraftFromEntry(entry);
}
handlePersistEntry() { handlePersistEntry() {
this.props.persistEntry(this.props.collection, this.props.entryDraft); this.props.persistEntry(this.props.collection, this.props.entryDraft);
} }
render() { render() {
const { const {
entry, entryDraft, boundGetMedia, collection, changeDraft, addMedia, removeMedia entry, entryDraft, boundGetMedia, collection, changeDraft, addMedia, removeMedia
} = this.props; } = this.props;
if (entryDraft == null || entryDraft.get('entry') == undefined || entry && entry.get('isFetching')) {
if (entry == null || entryDraft.get('entry') == undefined || entry.get('isFetching')) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return ( return (
@ -68,22 +79,25 @@ EntryPage.propTypes = {
changeDraft: PropTypes.func.isRequired, changeDraft: PropTypes.func.isRequired,
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
createDraftFromEntry: PropTypes.func.isRequired, createDraftFromEntry: PropTypes.func.isRequired,
createEmptyDraft: PropTypes.func.isRequired,
discardDraft: PropTypes.func.isRequired, discardDraft: PropTypes.func.isRequired,
entry: ImmutablePropTypes.map.isRequired, entry: ImmutablePropTypes.map,
entryDraft: ImmutablePropTypes.map.isRequired, entryDraft: ImmutablePropTypes.map.isRequired,
loadEntry: PropTypes.func.isRequired, loadEntry: PropTypes.func.isRequired,
persistEntry: PropTypes.func.isRequired, persistEntry: PropTypes.func.isRequired,
removeMedia: PropTypes.func.isRequired, removeMedia: PropTypes.func.isRequired,
slug: PropTypes.string.isRequired, slug: PropTypes.string,
newEntry: PropTypes.bool.isRequired,
}; };
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { collections, entryDraft } = state; const { collections, entryDraft } = state;
const collection = collections.get(ownProps.params.name); 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 entry = 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 { collection, collections, entryDraft, boundGetMedia, slug, entry }; return { collection, collections, newEntry, entryDraft, boundGetMedia, slug, entry };
} }
export default connect( export default connect(
@ -94,6 +108,7 @@ export default connect(
removeMedia, removeMedia,
loadEntry, loadEntry,
createDraftFromEntry, createDraftFromEntry,
createEmptyDraft,
discardDraft, discardDraft,
persistEntry persistEntry
} }

View File

@ -1,5 +1,5 @@
import { Map, List } from 'immutable'; import { Map, List, fromJS } from 'immutable';
import { DRAFT_CREATE_FROM_ENTRY, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries'; import { DRAFT_CREATE_FROM_ENTRY, DRAFT_CREATE_EMPTY, DRAFT_DISCARD, DRAFT_CHANGE } from '../actions/entries';
import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media'; import { ADD_MEDIA, REMOVE_MEDIA } from '../actions/media';
const initialState = Map({ entry: Map(), mediaFiles: List() }); const initialState = Map({ entry: Map(), mediaFiles: List() });
@ -7,14 +7,15 @@ const initialState = Map({ entry: Map(), mediaFiles: List() });
const entryDraft = (state = Map(), action) => { const entryDraft = (state = Map(), action) => {
switch (action.type) { switch (action.type) {
case DRAFT_CREATE_FROM_ENTRY: case DRAFT_CREATE_FROM_ENTRY:
if (!action.payload) {
// New entry
return initialState;
}
// Existing Entry // Existing Entry
return state.withMutations((state) => { return state.withMutations((state) => {
state.set('entry', action.payload); state.set('entry', action.payload);
state.setIn(['entry', 'newRecord'], false); state.set('mediaFiles', List());
});
case DRAFT_CREATE_EMPTY:
// New Entry
return state.withMutations((state) => {
state.set('entry', fromJS(action.payload));
state.set('mediaFiles', List()); state.set('mediaFiles', List());
}); });
case DRAFT_DISCARD: case DRAFT_DISCARD:

View File

@ -10,7 +10,8 @@ export default (
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRoute component={CollectionPage}/> <IndexRoute component={CollectionPage}/>
<Route path="/collections/:name" component={CollectionPage}/> <Route path="/collections/:name" component={CollectionPage}/>
<Route path="/collections/:name/entries/:slug" component={EntryPage}/> <Route path="/collections/:name/entries/new" component={EntryPage} newRecord />
<Route path="/collections/:name/entries/:slug" component={EntryPage} />
<Route path="/search" component={SearchPage}/> <Route path="/search" component={SearchPage}/>
<Route path="*" component={NotFoundPage}/> <Route path="*" component={NotFoundPage}/>
</Route> </Route>

View File

@ -0,0 +1,8 @@
export function createEntry(path = '', slug = '', raw = '') {
const returnObj = {};
returnObj.path = path;
returnObj.slug = slug;
returnObj.raw = raw;
returnObj.data = {};
return returnObj;
}