begin scaffolding for lerna
This commit is contained in:
@ -0,0 +1,45 @@
|
||||
import Immutable from 'immutable';
|
||||
import { authenticating, authenticate, authError, logout } from 'Actions/auth';
|
||||
import auth from '../auth';
|
||||
|
||||
describe('auth', () => {
|
||||
it('should handle an empty state', () => {
|
||||
expect(
|
||||
auth(undefined, {})
|
||||
).toEqual(
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle an authentication request', () => {
|
||||
expect(
|
||||
auth(undefined, authenticating())
|
||||
).toEqual(
|
||||
Immutable.Map({ isFetching: true })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle authentication', () => {
|
||||
expect(
|
||||
auth(undefined, authenticate({ email: 'joe@example.com' }))
|
||||
).toEqual(
|
||||
Immutable.fromJS({ user: { email: 'joe@example.com' } })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle an authentication error', () => {
|
||||
expect(
|
||||
auth(undefined, authError(new Error('Bad credentials')))
|
||||
).toEqual(
|
||||
Immutable.Map({
|
||||
error: 'Error: Bad credentials',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle logout', () => {
|
||||
const initialState = Immutable.fromJS({ user: { email: 'joe@example.com' } });
|
||||
const newState = auth(initialState, logout());
|
||||
expect(newState.get('user')).toBeUndefined();
|
||||
});
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
import { OrderedMap, fromJS } from 'immutable';
|
||||
import { configLoaded } from 'Actions/config';
|
||||
import collections from '../collections';
|
||||
|
||||
describe('collections', () => {
|
||||
it('should handle an empty state', () => {
|
||||
expect(
|
||||
collections(undefined, {})
|
||||
).toEqual(
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it('should load the collections from the config', () => {
|
||||
expect(
|
||||
collections(undefined, configLoaded(fromJS({
|
||||
collections: [
|
||||
{
|
||||
name: 'posts',
|
||||
folder: '_posts',
|
||||
fields: [{ name: 'title', widget: 'string' }],
|
||||
},
|
||||
],
|
||||
})))
|
||||
).toEqual(
|
||||
OrderedMap({
|
||||
posts: fromJS({
|
||||
name: 'posts',
|
||||
folder: '_posts',
|
||||
fields: [{ name: 'title', widget: 'string' }],
|
||||
type: 'folder_based_collection',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
import { Map } from 'immutable';
|
||||
import { configLoaded, configLoading, configFailed } from 'Actions/config';
|
||||
import config from 'Reducers/config';
|
||||
|
||||
describe('config', () => {
|
||||
it('should handle an empty state', () => {
|
||||
expect(
|
||||
config(undefined, {})
|
||||
).toEqual(
|
||||
Map({ isFetching: true })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle an update', () => {
|
||||
expect(
|
||||
config(Map({ a: 'b', c: 'd' }), configLoaded(Map({ a: 'changed', e: 'new' })))
|
||||
).toEqual(
|
||||
Map({ a: 'changed', e: 'new' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should mark the config as loading', () => {
|
||||
expect(
|
||||
config(undefined, configLoading())
|
||||
).toEqual(
|
||||
Map({ isFetching: true })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle an error', () => {
|
||||
expect(
|
||||
config(Map(), configFailed(new Error('Config could not be loaded')))
|
||||
).toEqual(
|
||||
Map({ error: 'Error: Config could not be loaded' })
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
import { Map, OrderedMap, fromJS } from 'immutable';
|
||||
import * as actions from 'Actions/entries';
|
||||
import reducer from '../entries';
|
||||
|
||||
const initialState = OrderedMap({
|
||||
posts: Map({ name: 'posts' }),
|
||||
});
|
||||
|
||||
describe('entries', () => {
|
||||
it('should mark entries as fetching', () => {
|
||||
expect(
|
||||
reducer(initialState, actions.entriesLoading(Map({ name: 'posts' })))
|
||||
).toEqual(
|
||||
OrderedMap(fromJS({
|
||||
posts: { name: 'posts' },
|
||||
pages: {
|
||||
posts: { isFetching: true },
|
||||
},
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle loaded entries', () => {
|
||||
const entries = [{ slug: 'a', path: '' }, { slug: 'b', title: 'B' }];
|
||||
expect(
|
||||
reducer(initialState, actions.entriesLoaded(Map({ name: 'posts' }), entries, 0))
|
||||
).toEqual(
|
||||
OrderedMap(fromJS(
|
||||
{
|
||||
posts: { name: 'posts' },
|
||||
entities: {
|
||||
'posts.a': { slug: 'a', path: '', isFetching: false },
|
||||
'posts.b': { slug: 'b', title: 'B', isFetching: false },
|
||||
},
|
||||
pages: {
|
||||
posts: {
|
||||
page: 0,
|
||||
ids: ['a', 'b'],
|
||||
},
|
||||
},
|
||||
}
|
||||
))
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,139 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import * as actions from 'Actions/entries';
|
||||
import reducer from '../entryDraft';
|
||||
|
||||
let initialState = Map({
|
||||
entry: Map(),
|
||||
mediaFiles: List(),
|
||||
fieldsMetaData: Map(),
|
||||
fieldsErrors: Map(),
|
||||
hasChanged: false,
|
||||
});
|
||||
|
||||
const entry = {
|
||||
collection: 'posts',
|
||||
slug: 'slug',
|
||||
path: 'content/blog/art-and-wine-festival.md',
|
||||
partial: false,
|
||||
raw: '',
|
||||
data: {},
|
||||
metaData: null,
|
||||
};
|
||||
|
||||
describe('entryDraft reducer', () => {
|
||||
describe('DRAFT_CREATE_FROM_ENTRY', () => {
|
||||
it('should create draft from the entry', () => {
|
||||
expect(
|
||||
reducer(
|
||||
initialState,
|
||||
actions.createDraftFromEntry(fromJS(entry))
|
||||
)
|
||||
).toEqual(
|
||||
fromJS({
|
||||
entry: {
|
||||
...entry,
|
||||
newRecord: false,
|
||||
},
|
||||
mediaFiles: [],
|
||||
fieldsMetaData: Map(),
|
||||
fieldsErrors: Map(),
|
||||
hasChanged: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DRAFT_CREATE_EMPTY', () => {
|
||||
it('should create a new draft ', () => {
|
||||
expect(
|
||||
reducer(
|
||||
initialState,
|
||||
actions.emptyDraftCreated(fromJS(entry))
|
||||
)
|
||||
).toEqual(
|
||||
fromJS({
|
||||
entry: {
|
||||
...entry,
|
||||
newRecord: true,
|
||||
},
|
||||
mediaFiles: [],
|
||||
fieldsMetaData: Map(),
|
||||
fieldsErrors: Map(),
|
||||
hasChanged: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DRAFT_DISCARD', () => {
|
||||
it('should discard the draft and return initial state', () => {
|
||||
expect(reducer(initialState, actions.discardDraft()))
|
||||
.toEqual(initialState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DRAFT_CHANGE', () => {
|
||||
it.skip('should update the draft', () => {
|
||||
const newEntry = {
|
||||
...entry,
|
||||
raw: 'updated',
|
||||
};
|
||||
expect(reducer(initialState, actions.changeDraft(newEntry)))
|
||||
.toEqual(fromJS({
|
||||
entry: {
|
||||
...entry,
|
||||
raw: 'updated',
|
||||
},
|
||||
mediaFiles: [],
|
||||
hasChanged: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('persisting', () => {
|
||||
beforeEach(() => {
|
||||
initialState = fromJS({
|
||||
entities: {
|
||||
'posts.slug': {
|
||||
collection: 'posts',
|
||||
slug: 'slug',
|
||||
path: 'content/blog/art-and-wine-festival.md',
|
||||
partial: false,
|
||||
raw: '',
|
||||
data: {},
|
||||
metaData: null,
|
||||
},
|
||||
},
|
||||
pages: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle persisting request', () => {
|
||||
const newState = reducer(
|
||||
initialState,
|
||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' }))
|
||||
);
|
||||
expect(newState.getIn(['entry', 'isPersisting'])).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle persisting success', () => {
|
||||
let newState = reducer(initialState,
|
||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' }))
|
||||
);
|
||||
newState = reducer(newState,
|
||||
actions.entryPersisted(Map({ name: 'posts' }), Map({ slug: 'slug' }))
|
||||
);
|
||||
expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle persisting error', () => {
|
||||
let newState = reducer(initialState,
|
||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' }))
|
||||
);
|
||||
newState = reducer(newState,
|
||||
actions.entryPersistFail(Map({ name: 'posts' }), Map({ slug: 'slug' }), 'Error message')
|
||||
);
|
||||
expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
21
packages/netlify-cms-core/src/reducers/auth.js
Normal file
21
packages/netlify-cms-core/src/reducers/auth.js
Normal file
@ -0,0 +1,21 @@
|
||||
import Immutable from 'immutable';
|
||||
import { AUTH_REQUEST, AUTH_SUCCESS, AUTH_FAILURE, AUTH_REQUEST_DONE, LOGOUT } from 'Actions/auth';
|
||||
|
||||
const auth = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case AUTH_REQUEST:
|
||||
return Immutable.Map({ isFetching: true });
|
||||
case AUTH_SUCCESS:
|
||||
return Immutable.fromJS({ user: action.payload });
|
||||
case AUTH_FAILURE:
|
||||
return Immutable.Map({ error: action.payload && action.payload.toString() });
|
||||
case AUTH_REQUEST_DONE:
|
||||
return state.remove('isFetching');
|
||||
case LOGOUT:
|
||||
return state.remove('user').remove('isFetching');
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default auth;
|
131
packages/netlify-cms-core/src/reducers/collections.js
Normal file
131
packages/netlify-cms-core/src/reducers/collections.js
Normal file
@ -0,0 +1,131 @@
|
||||
import { List } from 'immutable';
|
||||
import { has, get, escapeRegExp } from 'lodash';
|
||||
import consoleError from 'Lib/consoleError';
|
||||
import { CONFIG_SUCCESS } from 'Actions/config';
|
||||
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
||||
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS } from 'Constants/fieldInference';
|
||||
import { formatToExtension } from 'Formats/formats';
|
||||
|
||||
const collections = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case CONFIG_SUCCESS:
|
||||
const configCollections = action.payload ? action.payload.get('collections') : List();
|
||||
return configCollections
|
||||
.toOrderedMap()
|
||||
.map(collection => {
|
||||
if (collection.has('folder')) {
|
||||
return collection.set('type', FOLDER);
|
||||
}
|
||||
if (collection.has('files')) {
|
||||
return collection.set('type', FILES);
|
||||
}
|
||||
})
|
||||
.mapKeys((key, collection) => collection.get('name'));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const selectors = {
|
||||
[FOLDER]: {
|
||||
entryExtension(collection) {
|
||||
return (collection.get('extension') || formatToExtension(collection.get('format') || 'frontmatter')).replace(/^\./, '');
|
||||
},
|
||||
fields(collection) {
|
||||
return collection.get('fields');
|
||||
},
|
||||
entryPath(collection, slug) {
|
||||
return `${ collection.get('folder').replace(/\/$/, '') }/${ slug }.${ this.entryExtension(collection) }`;
|
||||
},
|
||||
entrySlug(collection, path) {
|
||||
return path.split('/').pop().replace(new RegExp(`\.${ escapeRegExp(this.entryExtension(collection)) }$`), '');
|
||||
},
|
||||
listMethod() {
|
||||
return 'entriesByFolder';
|
||||
},
|
||||
allowNewEntries(collection) {
|
||||
return collection.get('create');
|
||||
},
|
||||
allowDeletion(collection) {
|
||||
return collection.get('delete', true);
|
||||
},
|
||||
templateName(collection) {
|
||||
return collection.get('name');
|
||||
},
|
||||
},
|
||||
[FILES]: {
|
||||
fileForEntry(collection, slug) {
|
||||
const files = collection.get('files');
|
||||
return files.filter(f => f.get('name') === slug).get(0);
|
||||
},
|
||||
fields(collection, slug) {
|
||||
const file = this.fileForEntry(collection, slug);
|
||||
return file && file.get('fields');
|
||||
},
|
||||
entryPath(collection, slug) {
|
||||
const file = this.fileForEntry(collection, slug);
|
||||
return file && file.get('file');
|
||||
},
|
||||
entrySlug(collection, path) {
|
||||
const file = collection.get('files').filter(f => f.get('file') === path).get(0);
|
||||
return file && file.get('name');
|
||||
},
|
||||
listMethod() {
|
||||
return 'entriesByFiles';
|
||||
},
|
||||
allowNewEntries() {
|
||||
return false;
|
||||
},
|
||||
allowDeletion(collection) {
|
||||
return collection.get('delete', true);
|
||||
},
|
||||
templateName(collection, slug) {
|
||||
return slug;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const selectFields = (collection, slug) => selectors[collection.get('type')].fields(collection, slug);
|
||||
export const selectFolderEntryExtension = (collection) => selectors[FOLDER].entryExtension(collection);
|
||||
export const selectEntryPath = (collection, slug) => selectors[collection.get('type')].entryPath(collection, slug);
|
||||
export const selectEntrySlug = (collection, path) => selectors[collection.get('type')].entrySlug(collection, path);
|
||||
export const selectListMethod = collection => selectors[collection.get('type')].listMethod();
|
||||
export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(collection);
|
||||
export const selectAllowDeletion = collection => selectors[collection.get('type')].allowDeletion(collection);
|
||||
export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug);
|
||||
export const selectIdentifier = collection => {
|
||||
const fieldNames = collection.get('fields').map(field => field.get('name'));
|
||||
return IDENTIFIER_FIELDS.find(id => fieldNames.find(name => name.toLowerCase().trim() === id));
|
||||
};
|
||||
export const selectInferedField = (collection, fieldName) => {
|
||||
const inferableField = INFERABLE_FIELDS[fieldName];
|
||||
const fields = collection.get('fields');
|
||||
let field;
|
||||
|
||||
// If colllection has no fields or fieldName is not defined within inferables list, return null
|
||||
if (!fields || !inferableField) return null;
|
||||
// Try to return a field of the specified type with one of the synonyms
|
||||
const mainTypeFields = fields.filter(f => f.get('widget', 'string') === inferableField.type).map(f => f.get('name'));
|
||||
field = mainTypeFields.filter(f => inferableField.synonyms.indexOf(f) !== -1);
|
||||
if (field && field.size > 0) return field.first();
|
||||
|
||||
// Try to return a field for each of the specified secondary types
|
||||
const secondaryTypeFields = fields.filter(f => inferableField.secondaryTypes.indexOf(f.get('widget', 'string')) !== -1).map(f => f.get('name'));
|
||||
field = secondaryTypeFields.filter(f => inferableField.synonyms.indexOf(f) !== -1);
|
||||
if (field && field.size > 0) return field.first();
|
||||
|
||||
// Try to return the first field of the specified type
|
||||
if (inferableField.fallbackToFirstField && mainTypeFields.size > 0) return mainTypeFields.first();
|
||||
|
||||
// Coundn't infer the field. Show error and return null.
|
||||
if (inferableField.showError) {
|
||||
consoleError(
|
||||
`The Field ${ fieldName } is missing for the collection “${ collection.get('name') }”`,
|
||||
`Netlify CMS tries to infer the entry ${ fieldName } automatically, but one couldn\'t be found for entries of the collection “${ collection.get('name') }”. Please check your site configuration.`
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default collections;
|
11
packages/netlify-cms-core/src/reducers/combinedReducer.js
Normal file
11
packages/netlify-cms-core/src/reducers/combinedReducer.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { routerReducer } from 'react-router-redux';
|
||||
import { reducer as notifReducer } from 'redux-notifications';
|
||||
import optimist from 'redux-optimist';
|
||||
import reducers from '.';
|
||||
|
||||
export default optimist(combineReducers({
|
||||
...reducers,
|
||||
notifs: notifReducer,
|
||||
routing: routerReducer,
|
||||
}));
|
24
packages/netlify-cms-core/src/reducers/config.js
Normal file
24
packages/netlify-cms-core/src/reducers/config.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { Map } from 'immutable';
|
||||
import { CONFIG_REQUEST, CONFIG_SUCCESS, CONFIG_FAILURE, CONFIG_MERGE } from 'Actions/config';
|
||||
|
||||
const config = (state = Map({ isFetching: true }), action) => {
|
||||
switch (action.type) {
|
||||
case CONFIG_MERGE:
|
||||
return state.mergeDeep(action.payload);
|
||||
case CONFIG_REQUEST:
|
||||
return state.set('isFetching', true);
|
||||
case CONFIG_SUCCESS:
|
||||
/**
|
||||
* The loadConfig action merges any existing config into the loaded config
|
||||
* before firing this action (so the resulting config can be validated),
|
||||
* so we don't have to merge it here.
|
||||
*/
|
||||
return action.payload.delete('isFetching');
|
||||
case CONFIG_FAILURE:
|
||||
return Map({ error: action.payload.toString() });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
27
packages/netlify-cms-core/src/reducers/cursors.js
Normal file
27
packages/netlify-cms-core/src/reducers/cursors.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { fromJS, Map } from 'immutable';
|
||||
import Cursor from "ValueObjects/Cursor";
|
||||
import {
|
||||
ENTRIES_SUCCESS,
|
||||
} from 'Actions/entries';
|
||||
|
||||
// Since pagination can be used for a variety of views (collections
|
||||
// and searches are the most common examples), we namespace cursors by
|
||||
// their type before storing them in the state.
|
||||
export const selectCollectionEntriesCursor = (state, collectionName) =>
|
||||
new Cursor(state.getIn(["cursorsByType", "collectionEntries", collectionName]));
|
||||
|
||||
const cursors = (state = fromJS({ cursorsByType: { collectionEntries: {} } }), action) => {
|
||||
switch (action.type) {
|
||||
case ENTRIES_SUCCESS: {
|
||||
return state.setIn(
|
||||
["cursorsByType", "collectionEntries", action.payload.collection],
|
||||
Cursor.create(action.payload.cursor).store
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default cursors;
|
106
packages/netlify-cms-core/src/reducers/editorialWorkflow.js
Normal file
106
packages/netlify-cms-core/src/reducers/editorialWorkflow.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import { EDITORIAL_WORKFLOW } from 'Constants/publishModes';
|
||||
import {
|
||||
UNPUBLISHED_ENTRY_REQUEST,
|
||||
UNPUBLISHED_ENTRY_REDIRECT,
|
||||
UNPUBLISHED_ENTRY_SUCCESS,
|
||||
UNPUBLISHED_ENTRIES_REQUEST,
|
||||
UNPUBLISHED_ENTRIES_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_PERSIST_REQUEST,
|
||||
UNPUBLISHED_ENTRY_PERSIST_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST,
|
||||
UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE,
|
||||
UNPUBLISHED_ENTRY_PUBLISH_REQUEST,
|
||||
UNPUBLISHED_ENTRY_PUBLISH_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_PUBLISH_FAILURE,
|
||||
UNPUBLISHED_ENTRY_DELETE_REQUEST,
|
||||
UNPUBLISHED_ENTRY_DELETE_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_DELETE_FAILURE,
|
||||
} from 'Actions/editorialWorkflow';
|
||||
import { CONFIG_SUCCESS } from 'Actions/config';
|
||||
|
||||
const unpublishedEntries = (state = Map(), action) => {
|
||||
switch (action.type) {
|
||||
case CONFIG_SUCCESS:
|
||||
const publishMode = action.payload && action.payload.get('publish_mode');
|
||||
if (publishMode === EDITORIAL_WORKFLOW) {
|
||||
// Editorial workflow state is explicetelly initiated after the config.
|
||||
return Map({ entities: Map(), pages: Map() });
|
||||
}
|
||||
return state;
|
||||
case UNPUBLISHED_ENTRY_REQUEST:
|
||||
return state.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isFetching'], true);
|
||||
|
||||
case UNPUBLISHED_ENTRY_REDIRECT:
|
||||
return state.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`]);
|
||||
|
||||
case UNPUBLISHED_ENTRY_SUCCESS:
|
||||
return state.setIn(
|
||||
['entities', `${ action.payload.collection }.${ action.payload.entry.slug }`],
|
||||
fromJS(action.payload.entry)
|
||||
);
|
||||
|
||||
case UNPUBLISHED_ENTRIES_REQUEST:
|
||||
return state.setIn(['pages', 'isFetching'], true);
|
||||
|
||||
case UNPUBLISHED_ENTRIES_SUCCESS:
|
||||
return state.withMutations((map) => {
|
||||
action.payload.entries.forEach(entry => (
|
||||
map.setIn(['entities', `${ entry.collection }.${ entry.slug }`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
map.set('pages', Map({
|
||||
...action.payload.pages,
|
||||
ids: List(action.payload.entries.map(entry => entry.slug)),
|
||||
}));
|
||||
});
|
||||
|
||||
case UNPUBLISHED_ENTRY_PERSIST_REQUEST:
|
||||
// Update Optimistically
|
||||
return state.withMutations((map) => {
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.entry.get('slug') }`], fromJS(action.payload.entry));
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.entry.get('slug') }`, 'isPersisting'], true);
|
||||
map.updateIn(['pages', 'ids'], List(), list => list.push(action.payload.entry.get('slug')));
|
||||
});
|
||||
|
||||
case UNPUBLISHED_ENTRY_PERSIST_SUCCESS:
|
||||
// Update Optimistically
|
||||
return state.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.entry.get('slug') }`, 'isPersisting']);
|
||||
|
||||
case UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST:
|
||||
// Update Optimistically
|
||||
return state.withMutations((map) => {
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'metaData', 'status'], action.payload.newStatus);
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isUpdatingStatus'], true);
|
||||
});
|
||||
|
||||
case UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS:
|
||||
case UNPUBLISHED_ENTRY_STATUS_CHANGE_FAILURE:
|
||||
return state.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isUpdatingStatus'], false);
|
||||
|
||||
case UNPUBLISHED_ENTRY_PUBLISH_REQUEST:
|
||||
return state.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isPublishing'], true);
|
||||
|
||||
case UNPUBLISHED_ENTRY_PUBLISH_SUCCESS:
|
||||
case UNPUBLISHED_ENTRY_PUBLISH_FAILURE:
|
||||
return state.withMutations(map => {
|
||||
map.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`]);
|
||||
});
|
||||
|
||||
case UNPUBLISHED_ENTRY_DELETE_SUCCESS:
|
||||
return state.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`]);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const selectUnpublishedEntry = (state, collection, slug) => state && state.getIn(['entities', `${ collection }.${ slug }`]);
|
||||
|
||||
export const selectUnpublishedEntriesByStatus = (state, status) => {
|
||||
if (!state) return null;
|
||||
return state.get('entities').filter(entry => entry.getIn(['metaData', 'status']) === status).valueSeq();
|
||||
};
|
||||
|
||||
|
||||
export default unpublishedEntries;
|
90
packages/netlify-cms-core/src/reducers/entries.js
Normal file
90
packages/netlify-cms-core/src/reducers/entries.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import {
|
||||
ENTRY_REQUEST,
|
||||
ENTRY_SUCCESS,
|
||||
ENTRY_FAILURE,
|
||||
ENTRIES_REQUEST,
|
||||
ENTRIES_SUCCESS,
|
||||
ENTRIES_FAILURE,
|
||||
ENTRY_DELETE_SUCCESS,
|
||||
} from 'Actions/entries';
|
||||
|
||||
import { SEARCH_ENTRIES_SUCCESS } from 'Actions/search';
|
||||
|
||||
let collection;
|
||||
let loadedEntries;
|
||||
let append;
|
||||
let page;
|
||||
|
||||
const 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:
|
||||
collection = action.payload.collection;
|
||||
loadedEntries = action.payload.entries;
|
||||
append = action.payload.append;
|
||||
page = action.payload.page;
|
||||
return state.withMutations((map) => {
|
||||
loadedEntries.forEach(entry => (
|
||||
map.setIn(['entities', `${ collection }.${ entry.slug }`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
|
||||
const ids = List(loadedEntries.map(entry => entry.slug));
|
||||
map.setIn(['pages', collection], Map({
|
||||
page,
|
||||
ids: append
|
||||
? map.getIn(['pages', collection, 'ids'], List()).concat(ids)
|
||||
: ids,
|
||||
}));
|
||||
});
|
||||
|
||||
case ENTRIES_FAILURE:
|
||||
return state.setIn(['pages', action.meta.collection, 'isFetching'], false);
|
||||
|
||||
case ENTRY_FAILURE:
|
||||
return state.withMutations((map) => {
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'isFetching'], false);
|
||||
map.setIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`, 'error'], action.payload.error.message);
|
||||
});
|
||||
|
||||
case SEARCH_ENTRIES_SUCCESS:
|
||||
loadedEntries = action.payload.entries;
|
||||
return state.withMutations((map) => {
|
||||
loadedEntries.forEach(entry => (
|
||||
map.setIn(['entities', `${ entry.collection }.${ entry.slug }`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
});
|
||||
|
||||
case ENTRY_DELETE_SUCCESS:
|
||||
return state.withMutations((map) => {
|
||||
map.deleteIn(['entities', `${ action.payload.collectionName }.${ action.payload.entrySlug }`]);
|
||||
map.updateIn(['pages', action.payload.collectionName, 'ids'],
|
||||
ids => ids.filter(id => id !== action.payload.entrySlug));
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const selectEntry = (state, collection, slug) => (
|
||||
state.getIn(['entities', `${ collection }.${ slug }`])
|
||||
);
|
||||
|
||||
export const selectEntries = (state, collection) => {
|
||||
const slugs = state.getIn(['pages', collection, 'ids']);
|
||||
return slugs && slugs.map(slug => selectEntry(state, collection, slug));
|
||||
};
|
||||
|
||||
export default entries;
|
112
packages/netlify-cms-core/src/reducers/entryDraft.js
Normal file
112
packages/netlify-cms-core/src/reducers/entryDraft.js
Normal file
@ -0,0 +1,112 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import {
|
||||
DRAFT_CREATE_FROM_ENTRY,
|
||||
DRAFT_CREATE_EMPTY,
|
||||
DRAFT_DISCARD,
|
||||
DRAFT_CHANGE_FIELD,
|
||||
DRAFT_VALIDATION_ERRORS,
|
||||
ENTRY_PERSIST_REQUEST,
|
||||
ENTRY_PERSIST_SUCCESS,
|
||||
ENTRY_PERSIST_FAILURE,
|
||||
ENTRY_DELETE_SUCCESS,
|
||||
} from 'Actions/entries';
|
||||
import {
|
||||
UNPUBLISHED_ENTRY_PERSIST_REQUEST,
|
||||
UNPUBLISHED_ENTRY_PERSIST_SUCCESS,
|
||||
UNPUBLISHED_ENTRY_PERSIST_FAILURE,
|
||||
} from 'Actions/editorialWorkflow';
|
||||
import {
|
||||
ADD_ASSET,
|
||||
REMOVE_ASSET,
|
||||
} from 'Actions/media';
|
||||
|
||||
const initialState = Map({
|
||||
entry: Map(),
|
||||
mediaFiles: List(),
|
||||
fieldsMetaData: Map(),
|
||||
fieldsErrors: Map(),
|
||||
hasChanged: false,
|
||||
});
|
||||
|
||||
const entryDraftReducer = (state = Map(), action) => {
|
||||
switch (action.type) {
|
||||
case DRAFT_CREATE_FROM_ENTRY:
|
||||
// Existing Entry
|
||||
return state.withMutations((state) => {
|
||||
state.set('entry', action.payload.entry);
|
||||
state.setIn(['entry', 'newRecord'], false);
|
||||
state.set('mediaFiles', List());
|
||||
// An existing entry may already have metadata. If we surfed away and back to its
|
||||
// editor page, the metadata will have been fetched already, so we shouldn't
|
||||
// clear it as to not break relation lists.
|
||||
state.set('fieldsMetaData', action.payload.metadata || Map());
|
||||
state.set('fieldsErrors', Map());
|
||||
state.set('hasChanged', false);
|
||||
});
|
||||
case DRAFT_CREATE_EMPTY:
|
||||
// New Entry
|
||||
return state.withMutations((state) => {
|
||||
state.set('entry', fromJS(action.payload));
|
||||
state.setIn(['entry', 'newRecord'], true);
|
||||
state.set('mediaFiles', List());
|
||||
state.set('fieldsMetaData', Map());
|
||||
state.set('fieldsErrors', Map());
|
||||
state.set('hasChanged', false);
|
||||
});
|
||||
case DRAFT_DISCARD:
|
||||
return initialState;
|
||||
case DRAFT_CHANGE_FIELD:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['entry', 'data', action.payload.field], action.payload.value);
|
||||
state.mergeDeepIn(['fieldsMetaData'], fromJS(action.payload.metadata));
|
||||
state.set('hasChanged', true);
|
||||
});
|
||||
|
||||
case DRAFT_VALIDATION_ERRORS:
|
||||
if (action.payload.errors.length === 0) {
|
||||
return state.deleteIn(['fieldsErrors', action.payload.field]);
|
||||
} else {
|
||||
return state.setIn(['fieldsErrors', action.payload.field], action.payload.errors);
|
||||
}
|
||||
|
||||
case ENTRY_PERSIST_REQUEST:
|
||||
case UNPUBLISHED_ENTRY_PERSIST_REQUEST: {
|
||||
return state.setIn(['entry', 'isPersisting'], true);
|
||||
}
|
||||
|
||||
case ENTRY_PERSIST_FAILURE:
|
||||
case UNPUBLISHED_ENTRY_PERSIST_FAILURE: {
|
||||
return state.deleteIn(['entry', 'isPersisting']);
|
||||
}
|
||||
|
||||
case ENTRY_PERSIST_SUCCESS:
|
||||
case UNPUBLISHED_ENTRY_PERSIST_SUCCESS:
|
||||
return state.withMutations((state) => {
|
||||
state.deleteIn(['entry', 'isPersisting']);
|
||||
state.set('hasChanged', false);
|
||||
if (!state.getIn(['entry', 'slug'])) {
|
||||
state.setIn(['entry', 'slug'], action.payload.slug);
|
||||
}
|
||||
});
|
||||
|
||||
case ENTRY_DELETE_SUCCESS:
|
||||
return state.withMutations((state) => {
|
||||
state.deleteIn(['entry', 'isPersisting']);
|
||||
state.set('hasChanged', false);
|
||||
});
|
||||
|
||||
case ADD_ASSET:
|
||||
if (state.has('mediaFiles')) {
|
||||
return state.update('mediaFiles', list => list.push(action.payload.public_path));
|
||||
}
|
||||
return state;
|
||||
|
||||
case REMOVE_ASSET:
|
||||
return state.update('mediaFiles', list => list.filterNot(path => path === action.payload));
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default entryDraftReducer;
|
18
packages/netlify-cms-core/src/reducers/globalUI.js
Normal file
18
packages/netlify-cms-core/src/reducers/globalUI.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { Map } from 'immutable';
|
||||
/*
|
||||
* Reducer for some global UI state that we want to share between components
|
||||
* */
|
||||
const globalUI = (state = Map({ isFetching: false }), action) => {
|
||||
// Generic, global loading indicator
|
||||
if ((action.type.indexOf('REQUEST') > -1)) {
|
||||
return state.set('isFetching', true);
|
||||
} else if (
|
||||
(action.type.indexOf('SUCCESS') > -1) ||
|
||||
(action.type.indexOf('FAILURE') > -1)
|
||||
) {
|
||||
return state.set('isFetching', false);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default globalUI;
|
55
packages/netlify-cms-core/src/reducers/index.js
Normal file
55
packages/netlify-cms-core/src/reducers/index.js
Normal file
@ -0,0 +1,55 @@
|
||||
import auth from './auth';
|
||||
import config from './config';
|
||||
import integrations, * as fromIntegrations from './integrations';
|
||||
import entries, * as fromEntries from './entries';
|
||||
import cursors from './cursors';
|
||||
import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow';
|
||||
import entryDraft from './entryDraft';
|
||||
import collections from './collections';
|
||||
import search from './search';
|
||||
import mediaLibrary from './mediaLibrary';
|
||||
import medias, * as fromMedias from './medias';
|
||||
import globalUI from './globalUI';
|
||||
|
||||
const reducers = {
|
||||
auth,
|
||||
config,
|
||||
collections,
|
||||
search,
|
||||
integrations,
|
||||
entries,
|
||||
cursors,
|
||||
editorialWorkflow,
|
||||
entryDraft,
|
||||
mediaLibrary,
|
||||
medias,
|
||||
globalUI,
|
||||
};
|
||||
|
||||
export default reducers;
|
||||
|
||||
/*
|
||||
* Selectors
|
||||
*/
|
||||
export const selectEntry = (state, collection, slug) =>
|
||||
fromEntries.selectEntry(state.entries, collection, slug);
|
||||
|
||||
export const selectEntries = (state, collection) =>
|
||||
fromEntries.selectEntries(state.entries, collection);
|
||||
|
||||
export const selectSearchedEntries = (state) => {
|
||||
const searchItems = state.search.get('entryIds');
|
||||
return searchItems && searchItems.map(({ collection, slug }) => fromEntries.selectEntry(state.entries, collection, slug));
|
||||
};
|
||||
|
||||
export const selectUnpublishedEntry = (state, collection, slug) =>
|
||||
fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, collection, slug);
|
||||
|
||||
export const selectUnpublishedEntriesByStatus = (state, status) =>
|
||||
fromEditorialWorkflow.selectUnpublishedEntriesByStatus(state.editorialWorkflow, status);
|
||||
|
||||
export const selectIntegration = (state, collection, hook) =>
|
||||
fromIntegrations.selectIntegration(state.integrations, collection, hook);
|
||||
|
||||
export const getAsset = (state, path) =>
|
||||
fromMedias.getAsset(state.config.get('public_folder'), state.medias, path);
|
36
packages/netlify-cms-core/src/reducers/integrations.js
Normal file
36
packages/netlify-cms-core/src/reducers/integrations.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { fromJS, List } from 'immutable';
|
||||
import { CONFIG_SUCCESS } from 'Actions/config';
|
||||
|
||||
const integrations = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case CONFIG_SUCCESS:
|
||||
const integrations = action.payload.get('integrations', List()).toJS() || [];
|
||||
const newState = integrations.reduce((acc, integration) => {
|
||||
const { hooks, collections, provider, ...providerData } = integration;
|
||||
acc.providers[provider] = { ...providerData };
|
||||
if (!collections) {
|
||||
hooks.forEach((hook) => {
|
||||
acc.hooks[hook] = provider;
|
||||
});
|
||||
return acc;
|
||||
}
|
||||
const integrationCollections = collections === "*" ? action.payload.collections.map(collection => collection.name) : collections;
|
||||
integrationCollections.forEach((collection) => {
|
||||
hooks.forEach((hook) => {
|
||||
acc.hooks[collection] ? acc.hooks[collection][hook] = provider : acc.hooks[collection] = { [hook]: provider };
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, { providers:{}, hooks: {} });
|
||||
return fromJS(newState);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const selectIntegration = (state, collection, hook) => (
|
||||
collection? state.getIn(['hooks', collection, hook], false) : state.getIn(['hooks', hook], false)
|
||||
);
|
||||
|
||||
|
||||
export default integrations;
|
130
packages/netlify-cms-core/src/reducers/mediaLibrary.js
Normal file
130
packages/netlify-cms-core/src/reducers/mediaLibrary.js
Normal file
@ -0,0 +1,130 @@
|
||||
import { get } from 'lodash';
|
||||
import { Map } from 'immutable';
|
||||
import uuid from 'uuid/v4';
|
||||
import {
|
||||
MEDIA_LIBRARY_OPEN,
|
||||
MEDIA_LIBRARY_CLOSE,
|
||||
MEDIA_INSERT,
|
||||
MEDIA_REMOVE_INSERTED,
|
||||
MEDIA_LOAD_REQUEST,
|
||||
MEDIA_LOAD_SUCCESS,
|
||||
MEDIA_LOAD_FAILURE,
|
||||
MEDIA_PERSIST_REQUEST,
|
||||
MEDIA_PERSIST_SUCCESS,
|
||||
MEDIA_PERSIST_FAILURE,
|
||||
MEDIA_DELETE_REQUEST,
|
||||
MEDIA_DELETE_SUCCESS,
|
||||
MEDIA_DELETE_FAILURE,
|
||||
} from 'Actions/mediaLibrary';
|
||||
|
||||
const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), action) => {
|
||||
const privateUploadChanged = state.get('privateUpload') !== get(action, ['payload', 'privateUpload']);
|
||||
switch (action.type) {
|
||||
case MEDIA_LIBRARY_OPEN: {
|
||||
const { controlID, forImage, privateUpload } = action.payload || {};
|
||||
if (privateUploadChanged) {
|
||||
return Map({
|
||||
isVisible: true,
|
||||
forImage,
|
||||
controlID,
|
||||
canInsert: !!controlID,
|
||||
privateUpload,
|
||||
controlMedia: Map(),
|
||||
});
|
||||
}
|
||||
return state.withMutations(map => {
|
||||
map.set('isVisible', true);
|
||||
map.set('forImage', forImage);
|
||||
map.set('controlID', controlID);
|
||||
map.set('canInsert', !!controlID);
|
||||
map.set('privateUpload', privateUpload);
|
||||
});
|
||||
}
|
||||
case MEDIA_LIBRARY_CLOSE:
|
||||
return state.set('isVisible', false);
|
||||
case MEDIA_INSERT: {
|
||||
const controlID = state.get('controlID');
|
||||
const mediaPath = get(action, ['payload', 'mediaPath']);
|
||||
return state.setIn(['controlMedia', controlID], mediaPath);
|
||||
}
|
||||
case MEDIA_REMOVE_INSERTED: {
|
||||
const controlID = get(action, ['payload', 'controlID']);
|
||||
return state.setIn(['controlMedia', controlID], '');
|
||||
}
|
||||
case MEDIA_LOAD_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isLoading', true);
|
||||
map.set('isPaginating', action.payload.page > 1);
|
||||
});
|
||||
case MEDIA_LOAD_SUCCESS: {
|
||||
const { files = [], page, canPaginate, dynamicSearch, dynamicSearchQuery, privateUpload } = action.payload;
|
||||
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const filesWithKeys = files.map(file => ({ ...file, key: uuid() }));
|
||||
return state.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('isPaginating', false);
|
||||
map.set('page', page);
|
||||
map.set('hasNextPage', canPaginate && files.length > 0);
|
||||
map.set('dynamicSearch', dynamicSearch);
|
||||
map.set('dynamicSearchQuery', dynamicSearchQuery);
|
||||
map.set('dynamicSearchActive', !!dynamicSearchQuery);
|
||||
if (page && page > 1) {
|
||||
const updatedFiles = map.get('files').concat(filesWithKeys);
|
||||
map.set('files', updatedFiles);
|
||||
} else {
|
||||
map.set('files', filesWithKeys);
|
||||
}
|
||||
});
|
||||
}
|
||||
case MEDIA_LOAD_FAILURE:
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
return state.set('isLoading', false);
|
||||
case MEDIA_PERSIST_REQUEST:
|
||||
return state.set('isPersisting', true);
|
||||
case MEDIA_PERSIST_SUCCESS: {
|
||||
const { file } = action.payload;
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
return state.withMutations(map => {
|
||||
const fileWithKey = { ...file, key: uuid() };
|
||||
const updatedFiles = [fileWithKey, ...map.get('files')];
|
||||
map.set('files', updatedFiles);
|
||||
map.set('isPersisting', false);
|
||||
});
|
||||
}
|
||||
case MEDIA_PERSIST_FAILURE:
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
return state.set('isPersisting', false);
|
||||
case MEDIA_DELETE_REQUEST:
|
||||
return state.set('isDeleting', true);
|
||||
case MEDIA_DELETE_SUCCESS: {
|
||||
const { key } = action.payload.file;
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
return state.withMutations(map => {
|
||||
const updatedFiles = map.get('files').filter(file => file.key !== key);
|
||||
map.set('files', updatedFiles);
|
||||
map.set('isDeleting', false);
|
||||
});
|
||||
}
|
||||
case MEDIA_DELETE_FAILURE:
|
||||
if (privateUploadChanged) {
|
||||
return state;
|
||||
}
|
||||
return state.set('isDeleting', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default mediaLibrary;
|
34
packages/netlify-cms-core/src/reducers/medias.js
Normal file
34
packages/netlify-cms-core/src/reducers/medias.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { Map } from 'immutable';
|
||||
import { resolvePath } from 'netlify-cms-lib-util';
|
||||
import { ADD_ASSET, REMOVE_ASSET } from 'Actions/media';
|
||||
import AssetProxy from 'ValueObjects/AssetProxy';
|
||||
|
||||
const medias = (state = Map(), action) => {
|
||||
switch (action.type) {
|
||||
case ADD_ASSET:
|
||||
return state.set(action.payload.public_path, action.payload);
|
||||
case REMOVE_ASSET:
|
||||
return state.delete(action.payload);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default medias;
|
||||
|
||||
const memoizedProxies = {};
|
||||
export const getAsset = (publicFolder, state, path) => {
|
||||
// No path provided, skip
|
||||
if (!path) return null;
|
||||
|
||||
let proxy = state.get(path) || memoizedProxies[path];
|
||||
if (proxy) {
|
||||
// There is already an AssetProxy in memmory for this path. Use it.
|
||||
return proxy;
|
||||
}
|
||||
|
||||
// Create a new AssetProxy (for consistency) and return it.
|
||||
proxy = memoizedProxies[path] = new AssetProxy(resolvePath(path, publicFolder), null, true);
|
||||
return proxy;
|
||||
};
|
67
packages/netlify-cms-core/src/reducers/search.js
Normal file
67
packages/netlify-cms-core/src/reducers/search.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { Map, List } from 'immutable';
|
||||
|
||||
import {
|
||||
SEARCH_ENTRIES_REQUEST,
|
||||
SEARCH_ENTRIES_SUCCESS,
|
||||
QUERY_REQUEST,
|
||||
QUERY_SUCCESS,
|
||||
SEARCH_CLEAR,
|
||||
} from 'Actions/search';
|
||||
|
||||
let loadedEntries;
|
||||
let response;
|
||||
let page;
|
||||
let searchTerm;
|
||||
|
||||
const defaultState = Map({ isFetching: false, term: null, page: 0, entryIds: List([]), queryHits: Map({}) });
|
||||
|
||||
const entries = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case SEARCH_CLEAR:
|
||||
return defaultState;
|
||||
|
||||
case SEARCH_ENTRIES_REQUEST:
|
||||
if (action.payload.searchTerm !== state.get('term')) {
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', true);
|
||||
map.set('term', action.payload.searchTerm);
|
||||
});
|
||||
}
|
||||
return state;
|
||||
|
||||
case SEARCH_ENTRIES_SUCCESS:
|
||||
loadedEntries = action.payload.entries;
|
||||
page = action.payload.page;
|
||||
searchTerm = action.payload.searchTerm;
|
||||
return state.withMutations((map) => {
|
||||
const entryIds = List(loadedEntries.map(entry => ({ collection: entry.collection, slug: entry.slug })));
|
||||
map.set('isFetching', false);
|
||||
map.set('page', page);
|
||||
map.set('term', searchTerm);
|
||||
map.set('entryIds', (!page || isNaN(page) || page === 0) ? entryIds : map.get('entryIds', List()).concat(entryIds));
|
||||
});
|
||||
|
||||
case QUERY_REQUEST:
|
||||
if (action.payload.searchTerm !== state.get('term')) {
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', action.payload.namespace);
|
||||
map.set('term', action.payload.searchTerm);
|
||||
});
|
||||
}
|
||||
return state;
|
||||
|
||||
case QUERY_SUCCESS:
|
||||
searchTerm = action.payload.searchTerm;
|
||||
response = action.payload.response;
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', false);
|
||||
map.set('term', searchTerm);
|
||||
map.mergeIn(['queryHits'], Map({ [action.payload.namespace]: response.hits }));
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default entries;
|
Reference in New Issue
Block a user