Load, display and edit entries from test repo and github
This commit is contained in:
parent
7601d3f5a1
commit
32e54cdbdc
@ -61,6 +61,7 @@
|
||||
"draft-js": "^0.7.0",
|
||||
"draft-js-export-markdown": "^0.2.0",
|
||||
"draft-js-import-markdown": "^0.1.6",
|
||||
"json-loader": "^0.5.4"
|
||||
"json-loader": "^0.5.4",
|
||||
"localforage": "^1.4.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,40 @@
|
||||
import { currentBackend } from '../backends/backend';
|
||||
|
||||
export const ENTRY_REQUEST = 'ENTRY_REQUEST';
|
||||
export const ENTRY_SUCCESS = 'ENTRY_SUCCESS';
|
||||
export const ENTRY_FAILURE = 'ENTRY_FAILURE';
|
||||
|
||||
export const ENTRIES_REQUEST = 'ENTRIES_REQUEST';
|
||||
export const ENTRIES_SUCCESS = 'ENTRIES_SUCCESS';
|
||||
export const ENTRIES_FAILURE = 'ENTRIES_FAILURE';
|
||||
|
||||
export function entriesLoaded(collection, entries) {
|
||||
export function entryLoading(collection, slug) {
|
||||
return {
|
||||
type: ENTRY_REQUEST,
|
||||
payload: {
|
||||
collection: collection.get('name'),
|
||||
slug: slug
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function entryLoaded(collection, entry) {
|
||||
return {
|
||||
type: ENTRY_SUCCESS,
|
||||
payload: {
|
||||
collection: collection.get('name'),
|
||||
entry: entry
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function entriesLoaded(collection, entries, pagination) {
|
||||
return {
|
||||
type: ENTRIES_SUCCESS,
|
||||
payload: {
|
||||
collection: collection.get('name'),
|
||||
entries: entries
|
||||
entries: entries,
|
||||
pages: pagination
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -32,6 +57,17 @@ export function entriesFailed(collection, error) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadEntry(collection, slug) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const backend = currentBackend(state.config);
|
||||
|
||||
dispatch(entryLoading(collection, slug));
|
||||
backend.entry(collection, slug)
|
||||
.then((entry) => dispatch(entryLoaded(collection, entry)));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadEntries(collection) {
|
||||
return (dispatch, getState) => {
|
||||
if (collection.get('isFetching')) { return; }
|
||||
@ -40,10 +76,6 @@ export function loadEntries(collection) {
|
||||
|
||||
dispatch(entriesLoading(collection));
|
||||
backend.entries(collection)
|
||||
.then((entries) => dispatch(entriesLoaded(collection, entries)))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return dispatch(entriesFailed(collection, err));
|
||||
});
|
||||
.then((response) => dispatch(entriesLoaded(collection, response.entries, response.pagination)))
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import TestRepoBackend from './test-repo/Implementation';
|
||||
import GitHubBackend from './github/Implementation';
|
||||
import { resolveFormat } from '../formats/formats';
|
||||
|
||||
class LocalStorageAuthStore {
|
||||
@ -25,7 +26,11 @@ class Backend {
|
||||
|
||||
currentUser() {
|
||||
if (this.user) { return this.user; }
|
||||
return this.authStore && this.authStore.retrieve();
|
||||
const stored = this.authStore && this.authStore.retrieve();
|
||||
if (stored) {
|
||||
this.implementation.setUser(stored);
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
|
||||
authComponent() {
|
||||
@ -39,20 +44,27 @@ class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
entries(collection) {
|
||||
return this.implementation.entries(collection).then((entries = []) => (
|
||||
entries.map((entry) => {
|
||||
const format = resolveFormat(collection, entry);
|
||||
if (entry && entry.raw) {
|
||||
entry.data = format && format.fromFile(entry.raw);
|
||||
}
|
||||
return entry;
|
||||
})
|
||||
));
|
||||
entries(collection, page, perPage) {
|
||||
return this.implementation.entries(collection, page, perPage).then((response) => {
|
||||
return {
|
||||
pagination: response.pagination,
|
||||
entries: response.entries.map(this.entryWithFormat(collection))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
entry(collection, slug) {
|
||||
return this.implementation.entry(collection, slug);
|
||||
return this.implementation.entry(collection, slug).then(this.entryWithFormat(collection));
|
||||
}
|
||||
|
||||
entryWithFormat(collection) {
|
||||
return (entry) => {
|
||||
const format = resolveFormat(collection, entry);
|
||||
if (entry && entry.raw) {
|
||||
entry.data = format && format.fromFile(entry.raw);
|
||||
}
|
||||
return entry;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +79,8 @@ export function resolveBackend(config) {
|
||||
switch (name) {
|
||||
case 'test-repo':
|
||||
return new Backend(new TestRepoBackend(config), authStore);
|
||||
case 'github':
|
||||
return new Backend(new GitHubBackend(config), authStore);
|
||||
default:
|
||||
throw `Backend not found: ${name}`;
|
||||
}
|
||||
|
36
src/backends/github/AuthenticationPage.js
Normal file
36
src/backends/github/AuthenticationPage.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import Authenticator from '../../lib/netlify-auth';
|
||||
|
||||
export default class AuthenticationPage extends React.Component {
|
||||
static propTypes = {
|
||||
onLogin: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.handleLogin = this.handleLogin.bind(this);
|
||||
}
|
||||
|
||||
handleLogin(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const auth = new Authenticator({site_id: 'cms.netlify.com'});
|
||||
auth.authenticate({provider: 'github', scope: 'user'}, (err, data) => {
|
||||
if (err) {
|
||||
this.setState({loginError: err.toString()});
|
||||
return;
|
||||
}
|
||||
this.props.onLogin(data);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loginError } = this.state;
|
||||
|
||||
return <div>
|
||||
{loginError && <p>{loginError}</p>}
|
||||
<p><a href="#" onClick={this.handleLogin}>Login with GitHub</a></p>
|
||||
</div>;
|
||||
}
|
||||
}
|
118
src/backends/github/implementation.js
Normal file
118
src/backends/github/implementation.js
Normal file
@ -0,0 +1,118 @@
|
||||
import LocalForage from 'localforage';
|
||||
import AuthenticationPage from './AuthenticationPage';
|
||||
|
||||
const API_ROOT = 'https://api.github.com';
|
||||
|
||||
class API {
|
||||
constructor(token, repo, branch) {
|
||||
this.token = token;
|
||||
this.repo = repo;
|
||||
this.branch = branch;
|
||||
this.baseURL = API_ROOT + `/repos/${this.repo}`;
|
||||
}
|
||||
|
||||
user() {
|
||||
return this.request('/user');
|
||||
}
|
||||
|
||||
readFile(path, sha) {
|
||||
const cache = sha ? LocalForage.getItem(`gh.${sha}`) : Promise.resolve(null);
|
||||
return cache.then((cached) => {
|
||||
if (cached) { return cached; }
|
||||
|
||||
return this.request(`/contents/${path}`, {
|
||||
headers: {Accept: 'application/vnd.github.VERSION.raw'},
|
||||
data: {ref: this.branch},
|
||||
cache: false
|
||||
}).then((result) => {
|
||||
if (sha) {
|
||||
LocalForage.setItem(`gh.${sha}`, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
listFiles(path) {
|
||||
return this.request(`/contents/${path}`, {
|
||||
data: {ref: this.branch}
|
||||
});
|
||||
}
|
||||
|
||||
requestHeaders(headers = {}) {
|
||||
return {
|
||||
Authorization: `token ${this.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
};
|
||||
}
|
||||
|
||||
parseJsonResponse(response) {
|
||||
return response.json().then((json) => {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(json);
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
request(path, options = {}) {
|
||||
const headers = this.requestHeaders(options.headers || {});
|
||||
return fetch(this.baseURL + path, {...options, headers: headers}).then((response) => {
|
||||
if (response.headers.get('Content-Type').match(/json/)) {
|
||||
return this.parseJsonResponse(response);
|
||||
}
|
||||
|
||||
return response.text();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class GitHub {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
if (config.getIn(['backend', 'repo']) == null) {
|
||||
throw 'The GitHub backend needs a "repo" in the backend configuration.';
|
||||
}
|
||||
this.repo = config.getIn(['backend', 'repo']);
|
||||
}
|
||||
|
||||
authComponent() {
|
||||
return AuthenticationPage;
|
||||
}
|
||||
|
||||
setUser(user) {
|
||||
this.api = new API(user.token, this.repo, this.branch || 'master');
|
||||
}
|
||||
|
||||
authenticate(state) {
|
||||
this.api = new API(state.token, this.repo, this.branch || 'master');
|
||||
return this.api.user().then((user) => {
|
||||
user.token = state.token;
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
entries(collection) {
|
||||
return this.api.listFiles(collection.get('folder')).then((files) => (
|
||||
Promise.all(files.map((file) => (
|
||||
this.api.readFile(file.path, file.sha).then((data) => {
|
||||
file.slug = file.path.split('/').pop().replace(/\.[^\.]+$/, '');
|
||||
file.raw = data;
|
||||
return file;
|
||||
})
|
||||
)))
|
||||
)).then((entries) => ({
|
||||
pagination: {},
|
||||
entries
|
||||
}));
|
||||
}
|
||||
|
||||
entry(collection, slug) {
|
||||
return this.entries(collection).then((response) => (
|
||||
response.entries.filter((entry) => entry.slug === slug)[0]
|
||||
));
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ export default class TestRepo {
|
||||
}
|
||||
}
|
||||
|
||||
setUser() {}
|
||||
|
||||
authComponent() {
|
||||
return AuthenticationPage;
|
||||
}
|
||||
@ -34,12 +36,15 @@ export default class TestRepo {
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(entries);
|
||||
return Promise.resolve({
|
||||
pagination: {},
|
||||
entries
|
||||
});
|
||||
}
|
||||
|
||||
entry(collection, slug) {
|
||||
return this.entries(collection).then((entries) => (
|
||||
entries.filter((entry) => entry.slug === slug)[0]
|
||||
return this.entries(collection).then((response) => (
|
||||
response.entries.filter((entry) => entry.slug === slug)[0]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ export default class ControlPane extends React.Component {
|
||||
return React.createElement(widget.Control, {
|
||||
key: field.get('name'),
|
||||
field: field,
|
||||
value: entry.get(field.get('name')),
|
||||
onChange: (value) => this.props.onChange(entry.set(field.get('name'), value))
|
||||
value: entry.getIn(['data', field.get('name')]),
|
||||
onChange: (value) => this.props.onChange(entry.setIn(['data', field.get('name')], value))
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export default class ControlPane extends React.Component {
|
||||
if (!collection) { return null; }
|
||||
|
||||
return <div>
|
||||
{collection.get('fields').map((field) => <div>{this.controlFor(field)}</div>)}
|
||||
{collection.get('fields').map((field) => <div key={field.get('names ')}>{this.controlFor(field)}</div>)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,23 @@ export default class EntryEditor extends React.Component {
|
||||
return <div>
|
||||
<h1>Entry in {collection.get('label')}</h1>
|
||||
<h2>{entry && entry.get('title')}</h2>
|
||||
<div className="cms-container">
|
||||
<div className="cms-control-pane">
|
||||
<div className="cms-container" style={styles.container}>
|
||||
<div className="cms-control-pane" style={styles.pane}>
|
||||
<ControlPane collection={collection} entry={this.state.entry} onChange={this.handleChange}/>
|
||||
</div>
|
||||
<div className="cms-preview-pane">
|
||||
<div className="cms-preview-pane" style={styles.pane}>
|
||||
<PreviewPane collection={collection} entry={this.state.entry}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex'
|
||||
},
|
||||
pane: {
|
||||
width: '50%'
|
||||
}
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ export default class PreviewPane extends React.Component {
|
||||
return React.createElement(widget.Preview, {
|
||||
key: field.get('name'),
|
||||
field: field,
|
||||
value: entry.get(field.get('name'))
|
||||
value: entry.getIn(['data', field.get('name')])
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import React from 'react';
|
||||
export default class UnknownControl extends React.Component {
|
||||
render() {
|
||||
const { field } = this.props;
|
||||
console.log('field: %o', field.toObject());
|
||||
|
||||
return <div>No control for widget '{field.get('widget')}'.</div>;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { loadEntries } from '../actions/entries';
|
||||
import { selectEntries } from '../reducers/entries';
|
||||
import EntryListing from '../components/EntryListing';
|
||||
|
||||
class DashboardPage extends React.Component {
|
||||
@ -21,14 +22,12 @@ class DashboardPage extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collections, collection } = this.props;
|
||||
const { collections, collection, entries } = this.props;
|
||||
|
||||
if (collections == null) {
|
||||
return <h1>No collections defined in your config.yml</h1>;
|
||||
}
|
||||
|
||||
const entries = collection.get('entries');
|
||||
|
||||
return <div>
|
||||
<h1>Dashboard</h1>
|
||||
<div>
|
||||
@ -39,7 +38,7 @@ class DashboardPage extends React.Component {
|
||||
)).toArray()}
|
||||
</div>
|
||||
<div>
|
||||
{entries ? <EntryListing collection={collection} entries={entries}/> : 'No entries...'}
|
||||
{entries ? <EntryListing collection={collection} entries={entries}/> : 'Loading entries...'}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@ -49,8 +48,9 @@ function mapStateToProps(state, ownProps) {
|
||||
const { collections } = state;
|
||||
const { name, slug } = ownProps.params;
|
||||
const collection = name ? collections.get(name) : collections.first();
|
||||
const entries = selectEntries(state, collection.get('name'));
|
||||
|
||||
return {slug, collection, collections};
|
||||
return {slug, collection, collections, entries};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(DashboardPage);
|
||||
|
@ -1,21 +1,38 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Map } from 'immutable';
|
||||
import { loadEntry } from '../actions/entries';
|
||||
import { selectEntry } from '../reducers/entries';
|
||||
import EntryEditor from '../components/EntryEditor';
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
render() {
|
||||
const { collection, entry } = this.props;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.props.dispatch(loadEntry(props.collection, props.slug));
|
||||
}
|
||||
|
||||
return <EntryEditor entry={entry || new Map()} collection={collection}/>;
|
||||
render() {
|
||||
const { entry, collection } = this.props;
|
||||
if (entry == null || entry.get('isFetching')) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EntryEditor
|
||||
entry={entry || new Map()}
|
||||
collection={collection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const { collections, media } = state;
|
||||
const { collections } = state;
|
||||
const collection = collections.get(ownProps.params.name);
|
||||
const slug = ownProps.params.slug;
|
||||
const entry = selectEntry(state, collection.get('name'), slug);
|
||||
|
||||
return {media, collection, collections};
|
||||
return {collection, collections, slug, entry};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(EntryPage);
|
||||
|
119
src/lib/netlify-auth.js
Normal file
119
src/lib/netlify-auth.js
Normal file
@ -0,0 +1,119 @@
|
||||
const NETLIFY_API = 'https://api.netlify.com';
|
||||
|
||||
class NetlifyError {
|
||||
constructor(err) {
|
||||
this.err = err;
|
||||
}
|
||||
toString() {
|
||||
return this.err && this.err.message;
|
||||
}
|
||||
}
|
||||
|
||||
const PROVIDERS = {
|
||||
github: {
|
||||
width: 960,
|
||||
height: 600
|
||||
},
|
||||
gitlab: {
|
||||
width: 960,
|
||||
height: 600
|
||||
},
|
||||
bitbucket: {
|
||||
width: 960,
|
||||
height: 500
|
||||
},
|
||||
email: {
|
||||
width: 500,
|
||||
height: 400
|
||||
}
|
||||
};
|
||||
|
||||
class Authenticator {
|
||||
constructor(config) {
|
||||
this.site_id = config.site_id;
|
||||
this.base_url = config.base_url || NETLIFY_API;
|
||||
}
|
||||
|
||||
handshakeCallback(options, cb) {
|
||||
const fn = (e) => {
|
||||
if (e.data === ('authorizing:' + options.provider) && e.origin === this.base_url) {
|
||||
window.removeEventListener('message', fn, false);
|
||||
window.addEventListener('message', this.authorizeCallback(options, cb), false);
|
||||
return this.authWindow.postMessage(e.data, e.origin);
|
||||
}
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
authorizeCallback(options, cb) {
|
||||
const fn = (e) => {
|
||||
var data, err;
|
||||
if (e.origin !== this.base_url) { return; }
|
||||
if (e.data.indexOf('authorization:' + options.provider + ':success:') === 0) {
|
||||
data = JSON.parse(e.data.match(new RegExp('^authorization:' + options.provider + ':success:(.+)$'))[1]);
|
||||
window.removeEventListener('message', fn, false);
|
||||
this.authWindow.close();
|
||||
cb(null, data);
|
||||
}
|
||||
if (e.data.indexOf('authorization:' + options.provider + ':error:') === 0) {
|
||||
console.log('Got authorization error');
|
||||
err = JSON.parse(e.data.match(new RegExp('^authorization:' + options.provider + ':error:(.+)$'))[1]);
|
||||
window.removeEventListener('message', fn, false);
|
||||
this.authWindow.close();
|
||||
cb(new NetlifyError(err));
|
||||
}
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
getSiteID() {
|
||||
if (this.site_id) {
|
||||
return this.site_id;
|
||||
}
|
||||
const host = document.location.host.split(':')[0];
|
||||
return host === 'localhost' ? null : host;
|
||||
}
|
||||
|
||||
authenticate(options, cb) {
|
||||
var left, top, url,
|
||||
siteID = this.getSiteID(),
|
||||
provider = options.provider;
|
||||
if (!provider) {
|
||||
return cb(new NetlifyError({
|
||||
message: 'You must specify a provider when calling netlify.authenticate'
|
||||
}));
|
||||
}
|
||||
if (!siteID) {
|
||||
return cb(new NetlifyError({
|
||||
message: 'You must set a site_id with netlify.configure({site_id: \'your-site-id\'}) to make authentication work from localhost'
|
||||
}));
|
||||
}
|
||||
|
||||
const conf = PROVIDERS[provider] || PROVIDERS.github;
|
||||
left = (screen.width / 2) - (conf.width / 2);
|
||||
top = (screen.height / 2) - (conf.height / 2);
|
||||
window.addEventListener('message', this.handshakeCallback(options, cb), false);
|
||||
url = this.base_url + '/auth?provider=' + options.provider + '&site_id=' + siteID;
|
||||
if (options.scope) {
|
||||
url += '&scope=' + options.scope;
|
||||
}
|
||||
if (options.login === true) {
|
||||
url += '&login=true';
|
||||
}
|
||||
if (options.beta_invite) {
|
||||
url += '&beta_invite=' + options.beta_invite;
|
||||
}
|
||||
if (options.invite_code) {
|
||||
url += '&invite_code=' + options.invite_code;
|
||||
}
|
||||
this.authWindow = window.open(
|
||||
url,
|
||||
'Netlify Authorization',
|
||||
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, ' +
|
||||
('width=' + conf.width + ', height=' + conf.height + ', top=' + top + ', left=' + left + ');')
|
||||
);
|
||||
this.authWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export default Authenticator;
|
@ -1,6 +1,5 @@
|
||||
import { OrderedMap, fromJS } from 'immutable';
|
||||
import { CONFIG_SUCCESS } from '../actions/config';
|
||||
import { ENTRIES_REQUEST, ENTRIES_SUCCESS } from '../actions/entries';
|
||||
|
||||
export function collections(state = null, action) {
|
||||
switch (action.type) {
|
||||
@ -11,10 +10,6 @@ export function collections(state = null, action) {
|
||||
map.set(collection.name, fromJS(collection));
|
||||
});
|
||||
});
|
||||
case ENTRIES_REQUEST:
|
||||
return state && state.setIn([action.payload.collection, 'isFetching'], true);
|
||||
case ENTRIES_SUCCESS:
|
||||
return state && state.setIn([action.payload.collection, 'entries'], fromJS(action.payload.entries));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
40
src/reducers/entries.js
Normal file
40
src/reducers/entries.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import {
|
||||
ENTRY_REQUEST, ENTRY_SUCCESS, ENTRIES_REQUEST, ENTRIES_SUCCESS
|
||||
} from '../actions/entries';
|
||||
|
||||
export function entries(state = Map({entities: Map(), pages: Map()}), action) {
|
||||
switch (action.type) {
|
||||
case ENTRY_REQUEST:
|
||||
return state.setIn(['entities', `${action.payload.collection}.${action.payload.slug}`, 'isFetching'], true);
|
||||
case ENTRY_SUCCESS:
|
||||
return state.setIn(
|
||||
['entities', `${action.payload.collection}.${action.payload.entry.slug}`],
|
||||
fromJS(action.payload.entry)
|
||||
);
|
||||
case ENTRIES_REQUEST:
|
||||
return state.setIn(['pages', action.payload.collection, 'isFetching'], true);
|
||||
case ENTRIES_SUCCESS:
|
||||
const { collection, entries, pages } = action.payload;
|
||||
return state.withMutations((map) => {
|
||||
entries.forEach((entry) => (
|
||||
map.setIn(['entities', `${collection}.${entry.slug}`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
map.setIn(['pages', collection], Map({
|
||||
...pages,
|
||||
ids: List(entries.map((entry) => entry.slug))
|
||||
}));
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function selectEntry(state, collection, slug) {
|
||||
return state.entries.getIn(['entities', `${collection}.${slug}`]);
|
||||
}
|
||||
|
||||
export function selectEntries(state, collection) {
|
||||
const slugs = state.entries.getIn(['pages', collection, 'ids']);
|
||||
return slugs && slugs.map((slug) => selectEntry(state, collection, slug));
|
||||
}
|
@ -4,12 +4,14 @@ import { browserHistory } from 'react-router';
|
||||
import { syncHistory, routeReducer } from 'react-router-redux';
|
||||
import { auth } from '../reducers/auth';
|
||||
import { config } from '../reducers/config';
|
||||
import { entries } from '../reducers/entries';
|
||||
import { collections } from '../reducers/collections';
|
||||
|
||||
const reducer = combineReducers({
|
||||
auth,
|
||||
config,
|
||||
collections,
|
||||
entries,
|
||||
router: routeReducer
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user