Allow the creation of new entries
This commit is contained in:
parent
fd79381160
commit
b717874e7b
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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) => ({
|
||||||
|
@ -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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
8
src/valueObjects/Entry.js
Normal file
8
src/valueObjects/Entry.js
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user