refactor: remove immutable from 'config' state slice (#4960)
This commit is contained in:
parent
133689247b
commit
6623740a8c
@ -175,7 +175,7 @@ describe('gitlab backend', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
backendName: 'gitlab',
|
backendName: 'gitlab',
|
||||||
config: fromJS(config),
|
config,
|
||||||
authStore,
|
authStore,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -401,7 +401,7 @@ describe('gitlab backend', () => {
|
|||||||
|
|
||||||
const entry = await backend.getEntry(
|
const entry = await backend.getEntry(
|
||||||
{
|
{
|
||||||
config: fromJS({}),
|
config: {},
|
||||||
integrations: fromJS([]),
|
integrations: fromJS([]),
|
||||||
entryDraft: fromJS({}),
|
entryDraft: fromJS({}),
|
||||||
mediaLibrary: fromJS({}),
|
mediaLibrary: fromJS({}),
|
||||||
|
16
packages/netlify-cms-core/index.d.ts
vendored
16
packages/netlify-cms-core/index.d.ts
vendored
@ -355,6 +355,14 @@ declare module 'netlify-cms-core' {
|
|||||||
cms_label_prefix?: string;
|
cms_label_prefix?: string;
|
||||||
squash_merges?: boolean;
|
squash_merges?: boolean;
|
||||||
proxy_url?: string;
|
proxy_url?: string;
|
||||||
|
commit_messages?: {
|
||||||
|
create?: string;
|
||||||
|
update?: string;
|
||||||
|
delete?: string;
|
||||||
|
uploadMedia?: string;
|
||||||
|
deleteMedia?: string;
|
||||||
|
openAuthoring?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsSlug {
|
export interface CmsSlug {
|
||||||
@ -382,6 +390,14 @@ declare module 'netlify-cms-core' {
|
|||||||
media_library?: CmsMediaLibrary;
|
media_library?: CmsMediaLibrary;
|
||||||
publish_mode?: CmsPublishMode;
|
publish_mode?: CmsPublishMode;
|
||||||
load_config_file?: boolean;
|
load_config_file?: boolean;
|
||||||
|
integrations?: {
|
||||||
|
hooks: string[];
|
||||||
|
provider: string;
|
||||||
|
collections?: '*' | string[];
|
||||||
|
applicationID?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
getSignedFormURL?: string;
|
||||||
|
}[];
|
||||||
slug?: CmsSlug;
|
slug?: CmsSlug;
|
||||||
i18n?: CmsI18nConfig;
|
i18n?: CmsI18nConfig;
|
||||||
local_backend?: boolean | CmsLocalBackend;
|
local_backend?: boolean | CmsLocalBackend;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Map, List, fromJS } from 'immutable';
|
||||||
import {
|
import {
|
||||||
resolveBackend,
|
resolveBackend,
|
||||||
Backend,
|
Backend,
|
||||||
@ -5,12 +6,10 @@ import {
|
|||||||
expandSearchEntries,
|
expandSearchEntries,
|
||||||
mergeExpandedEntries,
|
mergeExpandedEntries,
|
||||||
} from '../backend';
|
} from '../backend';
|
||||||
import registry from 'Lib/registry';
|
import registry from '../lib/registry';
|
||||||
import { FOLDER } from 'Constants/collectionTypes';
|
import { FOLDER, FILES } from '../constants/collectionTypes';
|
||||||
import { Map, List, fromJS } from 'immutable';
|
|
||||||
import { FILES } from '../constants/collectionTypes';
|
|
||||||
|
|
||||||
jest.mock('Lib/registry');
|
jest.mock('../lib/registry');
|
||||||
jest.mock('netlify-cms-lib-util');
|
jest.mock('netlify-cms-lib-util');
|
||||||
jest.mock('../lib/urlHelper');
|
jest.mock('../lib/urlHelper');
|
||||||
|
|
||||||
@ -22,13 +21,11 @@ describe('Backend', () => {
|
|||||||
registry.getBackend.mockReturnValue({
|
registry.getBackend.mockReturnValue({
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
});
|
});
|
||||||
backend = resolveBackend(
|
backend = resolveBackend({
|
||||||
Map({
|
backend: {
|
||||||
backend: Map({
|
name: 'git-gateway',
|
||||||
name: 'git-gateway',
|
},
|
||||||
}),
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('filters string values', () => {
|
it('filters string values', () => {
|
||||||
@ -133,9 +130,8 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
const collection = Map({
|
const collection = Map({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -155,9 +151,7 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
|
||||||
|
|
||||||
const collection = Map({
|
const collection = Map({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -177,9 +171,8 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
const collection = Map({
|
const collection = Map({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -218,9 +211,8 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
const collection = Map({
|
const collection = Map({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -268,9 +260,8 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
backend.entryToRaw = jest.fn().mockReturnValue('');
|
backend.entryToRaw = jest.fn().mockReturnValue('');
|
||||||
|
|
||||||
@ -295,9 +286,8 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
backend.entryToRaw = jest.fn().mockReturnValue('content');
|
backend.entryToRaw = jest.fn().mockReturnValue('content');
|
||||||
|
|
||||||
@ -334,10 +324,10 @@ describe('Backend', () => {
|
|||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
persistMedia: jest.fn().mockResolvedValue(persistMediaResult),
|
persistMedia: jest.fn().mockResolvedValue(persistMediaResult),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
const config = { backend: { name: 'github' } };
|
||||||
|
|
||||||
|
const backend = new Backend(implementation, { config, backendName: config.backend.name });
|
||||||
const user = { login: 'login', name: 'name' };
|
const user = { login: 'login', name: 'name' };
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
|
||||||
backend.currentUser = jest.fn().mockResolvedValue(user);
|
backend.currentUser = jest.fn().mockResolvedValue(user);
|
||||||
|
|
||||||
const file = { path: 'static/media/image.png' };
|
const file = { path: 'static/media/image.png' };
|
||||||
@ -365,7 +355,9 @@ describe('Backend', () => {
|
|||||||
.mockResolvedValueOnce('---\ntitle: "Hello World"\n---\n'),
|
.mockResolvedValueOnce('---\ntitle: "Hello World"\n---\n'),
|
||||||
unpublishedEntryMediaFile: jest.fn().mockResolvedValueOnce({ id: '1' }),
|
unpublishedEntryMediaFile: jest.fn().mockResolvedValueOnce({ id: '1' }),
|
||||||
};
|
};
|
||||||
const config = Map({ media_folder: 'static/images' });
|
const config = {
|
||||||
|
media_folder: 'static/images',
|
||||||
|
};
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config, backendName: 'github' });
|
||||||
|
|
||||||
@ -412,8 +404,6 @@ describe('Backend', () => {
|
|||||||
const { sanitizeSlug } = require('../lib/urlHelper');
|
const { sanitizeSlug } = require('../lib/urlHelper');
|
||||||
sanitizeSlug.mockReturnValue('some-post-title');
|
sanitizeSlug.mockReturnValue('some-post-title');
|
||||||
|
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
getEntry: jest.fn(() => Promise.resolve()),
|
getEntry: jest.fn(() => Promise.resolve()),
|
||||||
@ -436,7 +426,7 @@ describe('Backend', () => {
|
|||||||
title: 'some post title',
|
title: 'some post title',
|
||||||
});
|
});
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
await expect(backend.generateUniqueSlug(collection, entry, Map({}), [])).resolves.toBe(
|
await expect(backend.generateUniqueSlug(collection, entry, Map({}), [])).resolves.toBe(
|
||||||
'sub_dir/some-post-title',
|
'sub_dir/some-post-title',
|
||||||
@ -448,8 +438,6 @@ describe('Backend', () => {
|
|||||||
sanitizeSlug.mockReturnValue('some-post-title');
|
sanitizeSlug.mockReturnValue('some-post-title');
|
||||||
sanitizeChar.mockReturnValue('-');
|
sanitizeChar.mockReturnValue('-');
|
||||||
|
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
getEntry: jest.fn(),
|
getEntry: jest.fn(),
|
||||||
@ -475,7 +463,7 @@ describe('Backend', () => {
|
|||||||
title: 'some post title',
|
title: 'some post title',
|
||||||
});
|
});
|
||||||
|
|
||||||
const backend = new Backend(implementation, { config, backendName: 'github' });
|
const backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
|
|
||||||
await expect(backend.generateUniqueSlug(collection, entry, Map({}), [])).resolves.toBe(
|
await expect(backend.generateUniqueSlug(collection, entry, Map({}), [])).resolves.toBe(
|
||||||
'sub_dir/some-post-title-1',
|
'sub_dir/some-post-title-1',
|
||||||
@ -585,11 +573,10 @@ describe('Backend', () => {
|
|||||||
const implementation = {
|
const implementation = {
|
||||||
init: jest.fn(() => implementation),
|
init: jest.fn(() => implementation),
|
||||||
};
|
};
|
||||||
const config = Map({});
|
|
||||||
|
|
||||||
let backend;
|
let backend;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backend = new Backend(implementation, { config, backendName: 'github' });
|
backend = new Backend(implementation, { config: {}, backendName: 'github' });
|
||||||
backend.listAllEntries = jest.fn(collection => {
|
backend.listAllEntries = jest.fn(collection => {
|
||||||
if (collection.get('name') === 'posts') {
|
if (collection.get('name') === 'posts') {
|
||||||
return Promise.resolve(posts);
|
return Promise.resolve(posts);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
import {
|
import {
|
||||||
loadConfig,
|
loadConfig,
|
||||||
parseConfig,
|
parseConfig,
|
||||||
@ -932,13 +931,13 @@ describe('config', () => {
|
|||||||
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
type: 'CONFIG_SUCCESS',
|
type: 'CONFIG_SUCCESS',
|
||||||
payload: fromJS({
|
payload: {
|
||||||
backend: { repo: 'test-repo' },
|
backend: { repo: 'test-repo' },
|
||||||
collections: [],
|
collections: [],
|
||||||
publish_mode: 'simple',
|
publish_mode: 'simple',
|
||||||
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
||||||
public_folder: '/',
|
public_folder: '/',
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -965,13 +964,13 @@ describe('config', () => {
|
|||||||
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
type: 'CONFIG_SUCCESS',
|
type: 'CONFIG_SUCCESS',
|
||||||
payload: fromJS({
|
payload: {
|
||||||
backend: { repo: 'github' },
|
backend: { repo: 'github' },
|
||||||
collections: [],
|
collections: [],
|
||||||
publish_mode: 'simple',
|
publish_mode: 'simple',
|
||||||
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
||||||
public_folder: '/',
|
public_folder: '/',
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -395,13 +395,13 @@ describe('entries', () => {
|
|||||||
|
|
||||||
describe('validateMetaField', () => {
|
describe('validateMetaField', () => {
|
||||||
const state = {
|
const state = {
|
||||||
config: fromJS({
|
config: {
|
||||||
slug: {
|
slug: {
|
||||||
encoding: 'unicode',
|
encoding: 'unicode',
|
||||||
clean_accents: false,
|
clean_accents: false,
|
||||||
sanitize_replacement: '-',
|
sanitize_replacement: '-',
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
entries: fromJS([]),
|
entries: fromJS([]),
|
||||||
};
|
};
|
||||||
const collection = fromJS({
|
const collection = fromJS({
|
||||||
|
@ -3,7 +3,7 @@ import thunk from 'redux-thunk';
|
|||||||
import { List, Map } from 'immutable';
|
import { List, Map } from 'immutable';
|
||||||
import { insertMedia, persistMedia, deleteMedia } from '../mediaLibrary';
|
import { insertMedia, persistMedia, deleteMedia } from '../mediaLibrary';
|
||||||
|
|
||||||
jest.mock('coreSrc/backend');
|
jest.mock('../../backend');
|
||||||
jest.mock('../waitUntil');
|
jest.mock('../waitUntil');
|
||||||
jest.mock('netlify-cms-lib-util', () => {
|
jest.mock('netlify-cms-lib-util', () => {
|
||||||
const lib = jest.requireActual('netlify-cms-lib-util');
|
const lib = jest.requireActual('netlify-cms-lib-util');
|
||||||
@ -20,9 +20,9 @@ describe('mediaLibrary', () => {
|
|||||||
describe('insertMedia', () => {
|
describe('insertMedia', () => {
|
||||||
it('should return mediaPath as string when string is given', () => {
|
it('should return mediaPath as string when string is given', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
public_folder: '/media',
|
public_folder: '/media',
|
||||||
}),
|
},
|
||||||
collections: Map({
|
collections: Map({
|
||||||
posts: Map({ name: 'posts' }),
|
posts: Map({ name: 'posts' }),
|
||||||
}),
|
}),
|
||||||
@ -40,9 +40,9 @@ describe('mediaLibrary', () => {
|
|||||||
|
|
||||||
it('should return mediaPath as array of strings when array of strings is given', () => {
|
it('should return mediaPath as array of strings when array of strings is given', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
public_folder: '/media',
|
public_folder: '/media',
|
||||||
}),
|
},
|
||||||
collections: Map({
|
collections: Map({
|
||||||
posts: Map({ name: 'posts' }),
|
posts: Map({ name: 'posts' }),
|
||||||
}),
|
}),
|
||||||
@ -81,14 +81,14 @@ describe('mediaLibrary', () => {
|
|||||||
getBlobSHA.mockReturnValue('000000000000000');
|
getBlobSHA.mockReturnValue('000000000000000');
|
||||||
|
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
media_folder: 'static/media',
|
media_folder: 'static/media',
|
||||||
slug: Map({
|
slug: {
|
||||||
encoding: 'unicode',
|
encoding: 'unicode',
|
||||||
clean_accents: false,
|
clean_accents: false,
|
||||||
sanitize_replacement: '-',
|
sanitize_replacement: '-',
|
||||||
}),
|
},
|
||||||
}),
|
},
|
||||||
collections: Map({
|
collections: Map({
|
||||||
posts: Map({ name: 'posts' }),
|
posts: Map({ name: 'posts' }),
|
||||||
}),
|
}),
|
||||||
@ -132,14 +132,14 @@ describe('mediaLibrary', () => {
|
|||||||
|
|
||||||
it('should persist media when not editing draft', () => {
|
it('should persist media when not editing draft', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
media_folder: 'static/media',
|
media_folder: 'static/media',
|
||||||
slug: Map({
|
slug: {
|
||||||
encoding: 'unicode',
|
encoding: 'unicode',
|
||||||
clean_accents: false,
|
clean_accents: false,
|
||||||
sanitize_replacement: '-',
|
sanitize_replacement: '-',
|
||||||
}),
|
},
|
||||||
}),
|
},
|
||||||
collections: Map({
|
collections: Map({
|
||||||
posts: Map({ name: 'posts' }),
|
posts: Map({ name: 'posts' }),
|
||||||
}),
|
}),
|
||||||
@ -186,14 +186,14 @@ describe('mediaLibrary', () => {
|
|||||||
|
|
||||||
it('should sanitize media name if needed when persisting', () => {
|
it('should sanitize media name if needed when persisting', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
media_folder: 'static/media',
|
media_folder: 'static/media',
|
||||||
slug: Map({
|
slug: {
|
||||||
encoding: 'ascii',
|
encoding: 'ascii',
|
||||||
clean_accents: true,
|
clean_accents: true,
|
||||||
sanitize_replacement: '_',
|
sanitize_replacement: '_',
|
||||||
}),
|
},
|
||||||
}),
|
},
|
||||||
collections: Map({
|
collections: Map({
|
||||||
posts: Map({ name: 'posts' }),
|
posts: Map({ name: 'posts' }),
|
||||||
}),
|
}),
|
||||||
@ -247,9 +247,9 @@ describe('mediaLibrary', () => {
|
|||||||
|
|
||||||
it('should delete non draft file', () => {
|
it('should delete non draft file', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
publish_mode: 'editorial_workflow',
|
publish_mode: 'editorial_workflow',
|
||||||
}),
|
},
|
||||||
collections: Map(),
|
collections: Map(),
|
||||||
integrations: Map(),
|
integrations: Map(),
|
||||||
mediaLibrary: Map({
|
mediaLibrary: Map({
|
||||||
@ -290,9 +290,9 @@ describe('mediaLibrary', () => {
|
|||||||
|
|
||||||
it('should not delete a draft file', () => {
|
it('should not delete a draft file', () => {
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
config: Map({
|
config: {
|
||||||
publish_mode: 'editorial_workflow',
|
publish_mode: 'editorial_workflow',
|
||||||
}),
|
},
|
||||||
collections: Map(),
|
collections: Map(),
|
||||||
integrations: Map(),
|
integrations: Map(),
|
||||||
mediaLibrary: Map({
|
mediaLibrary: Map({
|
||||||
|
@ -240,8 +240,7 @@ export function applyDefaults(originalConfig: CmsConfig) {
|
|||||||
|
|
||||||
throwOnMissingDefaultLocale(i18n);
|
throwOnMissingDefaultLocale(i18n);
|
||||||
|
|
||||||
// TODO remove fromJS when Immutable is removed from backend
|
const backend = resolveBackend(config);
|
||||||
const backend = resolveBackend(fromJS(config));
|
|
||||||
|
|
||||||
for (const collection of config.collections) {
|
for (const collection of config.collections) {
|
||||||
if (!('publish' in collection)) {
|
if (!('publish' in collection)) {
|
||||||
@ -399,13 +398,13 @@ export function configLoaded(config: CmsConfig) {
|
|||||||
return {
|
return {
|
||||||
type: CONFIG_SUCCESS,
|
type: CONFIG_SUCCESS,
|
||||||
payload: config,
|
payload: config,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configLoading() {
|
export function configLoading() {
|
||||||
return {
|
return {
|
||||||
type: CONFIG_REQUEST,
|
type: CONFIG_REQUEST,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configFailed(err: Error) {
|
export function configFailed(err: Error) {
|
||||||
@ -413,7 +412,7 @@ export function configFailed(err: Error) {
|
|||||||
type: CONFIG_FAILURE,
|
type: CONFIG_FAILURE,
|
||||||
error: 'Error loading config',
|
error: 'Error loading config',
|
||||||
payload: err,
|
payload: err,
|
||||||
};
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function detectProxyServer(localBackend?: boolean | CmsLocalBackend) {
|
export async function detectProxyServer(localBackend?: boolean | CmsLocalBackend) {
|
||||||
@ -495,7 +494,7 @@ export async function handleLocalBackend(originalConfig: CmsConfig) {
|
|||||||
|
|
||||||
export function loadConfig(manualConfig: Partial<CmsConfig> = {}, onLoad: () => unknown) {
|
export function loadConfig(manualConfig: Partial<CmsConfig> = {}, onLoad: () => unknown) {
|
||||||
if (window.CMS_CONFIG) {
|
if (window.CMS_CONFIG) {
|
||||||
return configLoaded(fromJS(window.CMS_CONFIG));
|
return configLoaded(window.CMS_CONFIG);
|
||||||
}
|
}
|
||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>) => {
|
||||||
dispatch(configLoading());
|
dispatch(configLoading());
|
||||||
@ -518,7 +517,7 @@ export function loadConfig(manualConfig: Partial<CmsConfig> = {}, onLoad: () =>
|
|||||||
|
|
||||||
const config = applyDefaults(normalizedConfig);
|
const config = applyDefaults(normalizedConfig);
|
||||||
|
|
||||||
dispatch(configLoaded(fromJS(config)));
|
dispatch(configLoaded(config));
|
||||||
|
|
||||||
if (typeof onLoad === 'function') {
|
if (typeof onLoad === 'function') {
|
||||||
onLoad();
|
onLoad();
|
||||||
@ -529,3 +528,7 @@ export function loadConfig(manualConfig: Partial<CmsConfig> = {}, onLoad: () =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ConfigAction = ReturnType<
|
||||||
|
typeof configLoading | typeof configLoaded | typeof configFailed
|
||||||
|
>;
|
||||||
|
@ -286,7 +286,10 @@ export function loadUnpublishedEntries(collections: Collections) {
|
|||||||
const state = getState();
|
const state = getState();
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
const entriesLoaded = get(state.editorialWorkflow.toJS(), 'pages.ids', false);
|
const entriesLoaded = get(state.editorialWorkflow.toJS(), 'pages.ids', false);
|
||||||
if (state.config.get('publish_mode') !== EDITORIAL_WORKFLOW || entriesLoaded) return;
|
|
||||||
|
if (state.config.publish_mode !== EDITORIAL_WORKFLOW || entriesLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(unpublishedEntriesLoading());
|
dispatch(unpublishedEntriesLoading());
|
||||||
backend
|
backend
|
||||||
|
@ -1020,7 +1020,7 @@ export function validateMetaField(
|
|||||||
}
|
}
|
||||||
const sanitizedPath = (value as string)
|
const sanitizedPath = (value as string)
|
||||||
.split('/')
|
.split('/')
|
||||||
.map(getProcessSegment(state.config.get('slug')))
|
.map(getProcessSegment(state.config.slug))
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
if (value !== sanitizedPath) {
|
if (value !== sanitizedPath) {
|
||||||
|
@ -215,7 +215,7 @@ export function persistMedia(file: File, opts: MediaOptions = {}) {
|
|||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
const integration = selectIntegration(state, null, 'assetStore');
|
const integration = selectIntegration(state, null, 'assetStore');
|
||||||
const files: MediaFile[] = selectMediaFiles(state, field);
|
const files: MediaFile[] = selectMediaFiles(state, field);
|
||||||
const fileName = sanitizeSlug(file.name.toLowerCase(), state.config.get('slug'));
|
const fileName = sanitizeSlug(file.name.toLowerCase(), state.config.slug);
|
||||||
const existingFile = files.find(existingFile => existingFile.name.toLowerCase() === fileName);
|
const existingFile = files.find(existingFile => existingFile.name.toLowerCase() === fileName);
|
||||||
|
|
||||||
const editingDraft = selectEditingDraft(state.entryDraft);
|
const editingDraft = selectEditingDraft(state.entryDraft);
|
||||||
|
@ -1,6 +1,26 @@
|
|||||||
import { attempt, flatten, isError, uniq, trim, sortBy, get, set } from 'lodash';
|
import { attempt, flatten, isError, uniq, trim, sortBy, get, set } from 'lodash';
|
||||||
import { List, Map, fromJS, Set } from 'immutable';
|
import { List, Map, fromJS, Set } from 'immutable';
|
||||||
import * as fuzzy from 'fuzzy';
|
import * as fuzzy from 'fuzzy';
|
||||||
|
import {
|
||||||
|
localForage,
|
||||||
|
Cursor,
|
||||||
|
CURSOR_COMPATIBILITY_SYMBOL,
|
||||||
|
EditorialWorkflowError,
|
||||||
|
Implementation as BackendImplementation,
|
||||||
|
DisplayURL,
|
||||||
|
ImplementationEntry,
|
||||||
|
Credentials,
|
||||||
|
User,
|
||||||
|
getPathDepth,
|
||||||
|
blobToFileObj,
|
||||||
|
asyncLock,
|
||||||
|
AsyncLock,
|
||||||
|
UnpublishedEntry,
|
||||||
|
DataFile,
|
||||||
|
UnpublishedEntryDiff,
|
||||||
|
} from 'netlify-cms-lib-util';
|
||||||
|
import { basename, join, extname, dirname } from 'path';
|
||||||
|
import { stringTemplate } from 'netlify-cms-lib-widgets';
|
||||||
import { resolveFormat } from './formats/formats';
|
import { resolveFormat } from './formats/formats';
|
||||||
import { selectUseWorkflow } from './reducers/config';
|
import { selectUseWorkflow } from './reducers/config';
|
||||||
import { selectMediaFilePath, selectEntry } from './reducers/entries';
|
import { selectMediaFilePath, selectEntry } from './reducers/entries';
|
||||||
@ -21,35 +41,14 @@ import { createEntry, EntryValue } from './valueObjects/Entry';
|
|||||||
import { sanitizeChar } from './lib/urlHelper';
|
import { sanitizeChar } from './lib/urlHelper';
|
||||||
import { getBackend, invokeEvent } from './lib/registry';
|
import { getBackend, invokeEvent } from './lib/registry';
|
||||||
import { commitMessageFormatter, slugFormatter, previewUrlFormatter } from './lib/formatters';
|
import { commitMessageFormatter, slugFormatter, previewUrlFormatter } from './lib/formatters';
|
||||||
import {
|
|
||||||
localForage,
|
|
||||||
Cursor,
|
|
||||||
CURSOR_COMPATIBILITY_SYMBOL,
|
|
||||||
EditorialWorkflowError,
|
|
||||||
Implementation as BackendImplementation,
|
|
||||||
DisplayURL,
|
|
||||||
ImplementationEntry,
|
|
||||||
Credentials,
|
|
||||||
User,
|
|
||||||
getPathDepth,
|
|
||||||
Config as ImplementationConfig,
|
|
||||||
blobToFileObj,
|
|
||||||
asyncLock,
|
|
||||||
AsyncLock,
|
|
||||||
UnpublishedEntry,
|
|
||||||
DataFile,
|
|
||||||
UnpublishedEntryDiff,
|
|
||||||
} from 'netlify-cms-lib-util';
|
|
||||||
import { basename, join, extname, dirname } from 'path';
|
|
||||||
import { status } from './constants/publishModes';
|
import { status } from './constants/publishModes';
|
||||||
import { stringTemplate } from 'netlify-cms-lib-widgets';
|
|
||||||
import {
|
import {
|
||||||
Collection,
|
CmsConfig,
|
||||||
EntryMap,
|
EntryMap,
|
||||||
Config,
|
|
||||||
FilterRule,
|
FilterRule,
|
||||||
Collections,
|
|
||||||
EntryDraft,
|
EntryDraft,
|
||||||
|
Collection,
|
||||||
|
Collections,
|
||||||
CollectionFile,
|
CollectionFile,
|
||||||
State,
|
State,
|
||||||
EntryField,
|
EntryField,
|
||||||
@ -73,7 +72,7 @@ const { extractTemplateVars, dateParsers, expandPath } = stringTemplate;
|
|||||||
|
|
||||||
function updateAssetProxies(
|
function updateAssetProxies(
|
||||||
assetProxies: AssetProxy[],
|
assetProxies: AssetProxy[],
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryDraft: EntryDraft,
|
entryDraft: EntryDraft,
|
||||||
path: string,
|
path: string,
|
||||||
@ -238,9 +237,9 @@ interface AuthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BackendOptions {
|
interface BackendOptions {
|
||||||
backendName?: string;
|
backendName: string;
|
||||||
authStore?: AuthStore | null;
|
config: CmsConfig;
|
||||||
config?: Config;
|
authStore?: AuthStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaFile {
|
export interface MediaFile {
|
||||||
@ -263,7 +262,7 @@ interface BackupEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PersistArgs {
|
interface PersistArgs {
|
||||||
config: Config;
|
config: CmsConfig;
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
entryDraft: EntryDraft;
|
entryDraft: EntryDraft;
|
||||||
assetProxies: AssetProxy[];
|
assetProxies: AssetProxy[];
|
||||||
@ -279,7 +278,7 @@ interface ImplementationInitOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Implementation = BackendImplementation & {
|
type Implementation = BackendImplementation & {
|
||||||
init: (config: ImplementationConfig, options: ImplementationInitOptions) => Implementation;
|
init: (config: CmsConfig, options: ImplementationInitOptions) => Implementation;
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepareMetaPath(path: string, collection: Collection) {
|
function prepareMetaPath(path: string, collection: Collection) {
|
||||||
@ -305,24 +304,21 @@ function collectionDepth(collection: Collection) {
|
|||||||
export class Backend {
|
export class Backend {
|
||||||
implementation: Implementation;
|
implementation: Implementation;
|
||||||
backendName: string;
|
backendName: string;
|
||||||
authStore: AuthStore | null;
|
config: CmsConfig;
|
||||||
config: Config;
|
authStore?: AuthStore;
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
backupSync: AsyncLock;
|
backupSync: AsyncLock;
|
||||||
|
|
||||||
constructor(
|
constructor(implementation: Implementation, { backendName, authStore, config }: BackendOptions) {
|
||||||
implementation: Implementation,
|
|
||||||
{ backendName, authStore = null, config }: BackendOptions = {},
|
|
||||||
) {
|
|
||||||
// We can't reliably run this on exit, so we do cleanup on load.
|
// We can't reliably run this on exit, so we do cleanup on load.
|
||||||
this.deleteAnonymousBackup();
|
this.deleteAnonymousBackup();
|
||||||
this.config = config as Config;
|
this.config = config;
|
||||||
this.implementation = implementation.init(this.config.toJS(), {
|
this.implementation = implementation.init(this.config, {
|
||||||
useWorkflow: selectUseWorkflow(this.config),
|
useWorkflow: selectUseWorkflow(this.config),
|
||||||
updateUserCredentials: this.updateUserCredentials,
|
updateUserCredentials: this.updateUserCredentials,
|
||||||
initialWorkflowStatus: status.first(),
|
initialWorkflowStatus: status.first(),
|
||||||
});
|
});
|
||||||
this.backendName = backendName as string;
|
this.backendName = backendName;
|
||||||
this.authStore = authStore;
|
this.authStore = authStore;
|
||||||
if (this.implementation === null) {
|
if (this.implementation === null) {
|
||||||
throw new Error('Cannot instantiate a Backend with no implementation');
|
throw new Error('Cannot instantiate a Backend with no implementation');
|
||||||
@ -436,11 +432,11 @@ export class Backend {
|
|||||||
async generateUniqueSlug(
|
async generateUniqueSlug(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryData: Map<string, unknown>,
|
entryData: Map<string, unknown>,
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
usedSlugs: List<string>,
|
usedSlugs: List<string>,
|
||||||
customPath: string | undefined,
|
customPath: string | undefined,
|
||||||
) {
|
) {
|
||||||
const slugConfig = config.get('slug');
|
const slugConfig = config.slug;
|
||||||
let slug: string;
|
let slug: string;
|
||||||
if (customPath) {
|
if (customPath) {
|
||||||
slug = slugFromCustomPath(collection, customPath);
|
slug = slugFromCustomPath(collection, customPath);
|
||||||
@ -944,7 +940,7 @@ export class Backend {
|
|||||||
|
|
||||||
async processEntry(state: State, collection: Collection, entry: EntryValue) {
|
async processEntry(state: State, collection: Collection, entry: EntryValue) {
|
||||||
const integration = selectIntegration(state.integrations, null, 'assetStore');
|
const integration = selectIntegration(state.integrations, null, 'assetStore');
|
||||||
const mediaFolders = selectMediaFolders(state, collection, fromJS(entry));
|
const mediaFolders = selectMediaFolders(state.config, collection, fromJS(entry));
|
||||||
if (mediaFolders.length > 0 && !integration) {
|
if (mediaFolders.length > 0 && !integration) {
|
||||||
const files = await Promise.all(
|
const files = await Promise.all(
|
||||||
mediaFolders.map(folder => this.implementation.getMedia(folder)),
|
mediaFolders.map(folder => this.implementation.getMedia(folder)),
|
||||||
@ -978,14 +974,14 @@ export class Backend {
|
|||||||
* If `site_url` is undefined or `show_preview_links` in the config is set to false, do nothing.
|
* If `site_url` is undefined or `show_preview_links` in the config is set to false, do nothing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const baseUrl = this.config.get('site_url');
|
const baseUrl = this.config.site_url;
|
||||||
|
|
||||||
if (!baseUrl || this.config.get('show_preview_links') === false) {
|
if (!baseUrl || this.config.show_preview_links === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: previewUrlFormatter(baseUrl, collection, slug, this.config.get('slug'), entry),
|
url: previewUrlFormatter(baseUrl, collection, slug, entry, this.config.slug),
|
||||||
status: 'SUCCESS',
|
status: 'SUCCESS',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1005,7 +1001,7 @@ export class Backend {
|
|||||||
* If the registered backend does not provide a `getDeployPreview` method, or
|
* If the registered backend does not provide a `getDeployPreview` method, or
|
||||||
* `show_preview_links` in the config is set to false, do nothing.
|
* `show_preview_links` in the config is set to false, do nothing.
|
||||||
*/
|
*/
|
||||||
if (!this.implementation.getDeployPreview || this.config.get('show_preview_links') === false) {
|
if (!this.implementation.getDeployPreview || this.config.show_preview_links === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1019,7 +1015,7 @@ export class Backend {
|
|||||||
count++;
|
count++;
|
||||||
deployPreview = await this.implementation.getDeployPreview(collection.get('name'), slug);
|
deployPreview = await this.implementation.getDeployPreview(collection.get('name'), slug);
|
||||||
if (!deployPreview) {
|
if (!deployPreview) {
|
||||||
await new Promise(resolve => setTimeout(() => resolve(), interval));
|
await new Promise(resolve => setTimeout(() => resolve(undefined), interval));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1034,7 +1030,7 @@ export class Backend {
|
|||||||
/**
|
/**
|
||||||
* Create a URL using the collection `preview_path`, if provided.
|
* Create a URL using the collection `preview_path`, if provided.
|
||||||
*/
|
*/
|
||||||
url: previewUrlFormatter(deployPreview.url, collection, slug, this.config.get('slug'), entry),
|
url: previewUrlFormatter(deployPreview.url, collection, slug, entry, this.config.slug),
|
||||||
/**
|
/**
|
||||||
* Always capitalize the status for consistency.
|
* Always capitalize the status for consistency.
|
||||||
*/
|
*/
|
||||||
@ -1182,7 +1178,7 @@ export class Backend {
|
|||||||
await this.invokeEventWithEntry('postSave', entry);
|
await this.invokeEventWithEntry('postSave', entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
async persistMedia(config: Config, file: AssetProxy) {
|
async persistMedia(config: CmsConfig, file: AssetProxy) {
|
||||||
const user = (await this.currentUser()) as User;
|
const user = (await this.currentUser()) as User;
|
||||||
const options = {
|
const options = {
|
||||||
commitMessage: commitMessageFormatter(
|
commitMessage: commitMessageFormatter(
|
||||||
@ -1233,7 +1229,7 @@ export class Backend {
|
|||||||
await this.invokePostUnpublishEvent(entry);
|
await this.invokePostUnpublishEvent(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(config: Config, path: string) {
|
async deleteMedia(config: CmsConfig, path: string) {
|
||||||
const user = (await this.currentUser()) as User;
|
const user = (await this.currentUser()) as User;
|
||||||
const commitMessage = commitMessageFormatter(
|
const commitMessage = commitMessageFormatter(
|
||||||
'deleteMedia',
|
'deleteMedia',
|
||||||
@ -1310,12 +1306,12 @@ export class Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveBackend(config: Config) {
|
export function resolveBackend(config: CmsConfig) {
|
||||||
const name = config.getIn(['backend', 'name']);
|
if (!config.backend.name) {
|
||||||
if (name == null) {
|
|
||||||
throw new Error('No backend defined in configuration');
|
throw new Error('No backend defined in configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { name } = config.backend;
|
||||||
const authStore = new LocalStorageAuthStore();
|
const authStore = new LocalStorageAuthStore();
|
||||||
|
|
||||||
const backend = getBackend(name);
|
const backend = getBackend(name);
|
||||||
@ -1329,7 +1325,7 @@ export function resolveBackend(config: Config) {
|
|||||||
export const currentBackend = (function() {
|
export const currentBackend = (function() {
|
||||||
let backend: Backend;
|
let backend: Backend;
|
||||||
|
|
||||||
return (config: Config) => {
|
return (config: CmsConfig) => {
|
||||||
if (backend) {
|
if (backend) {
|
||||||
return backend;
|
return backend;
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ function RouteInCollection({ collections, render, ...props }) {
|
|||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
auth: PropTypes.object.isRequired,
|
auth: PropTypes.object.isRequired,
|
||||||
config: ImmutablePropTypes.map,
|
config: PropTypes.object.isRequired,
|
||||||
collections: ImmutablePropTypes.orderedMap,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
loginUser: PropTypes.func.isRequired,
|
loginUser: PropTypes.func.isRequired,
|
||||||
logoutUser: PropTypes.func.isRequired,
|
logoutUser: PropTypes.func.isRequired,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
@ -94,7 +94,7 @@ class App extends React.Component {
|
|||||||
<h1>{t('app.app.errorHeader')}</h1>
|
<h1>{t('app.app.errorHeader')}</h1>
|
||||||
<div>
|
<div>
|
||||||
<strong>{t('app.app.configErrors')}:</strong>
|
<strong>{t('app.app.configErrors')}:</strong>
|
||||||
<ErrorCodeBlock>{config.get('error')}</ErrorCodeBlock>
|
<ErrorCodeBlock>{config.error}</ErrorCodeBlock>
|
||||||
<span>{t('app.app.checkConfigYml')}</span>
|
<span>{t('app.app.checkConfigYml')}</span>
|
||||||
</div>
|
</div>
|
||||||
</ErrorContainer>
|
</ErrorContainer>
|
||||||
@ -124,10 +124,10 @@ class App extends React.Component {
|
|||||||
onLogin: this.handleLogin.bind(this),
|
onLogin: this.handleLogin.bind(this),
|
||||||
error: auth.error,
|
error: auth.error,
|
||||||
inProgress: auth.isFetching,
|
inProgress: auth.isFetching,
|
||||||
siteId: this.props.config.getIn(['backend', 'site_domain']),
|
siteId: this.props.config.backend.site_domain,
|
||||||
base_url: this.props.config.getIn(['backend', 'base_url'], null),
|
base_url: this.props.config.backend.base_url,
|
||||||
authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']),
|
authEndpoint: this.props.config.backend.auth_endpoint,
|
||||||
config: this.props.config.toJS(),
|
config: this.props.config,
|
||||||
clearHash: () => history.replace('/'),
|
clearHash: () => history.replace('/'),
|
||||||
t,
|
t,
|
||||||
})}
|
})}
|
||||||
@ -158,11 +158,11 @@ class App extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.get('error')) {
|
if (config.error) {
|
||||||
return this.configError(config);
|
return this.configError(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.get('isFetching')) {
|
if (config.isFetching) {
|
||||||
return <Loader active>{t('app.app.loadingConfig')}</Loader>;
|
return <Loader active>{t('app.app.loadingConfig')}</Loader>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +183,8 @@ class App extends React.Component {
|
|||||||
onLogoutClick={logoutUser}
|
onLogoutClick={logoutUser}
|
||||||
openMediaLibrary={openMediaLibrary}
|
openMediaLibrary={openMediaLibrary}
|
||||||
hasWorkflow={hasWorkflow}
|
hasWorkflow={hasWorkflow}
|
||||||
displayUrl={config.get('display_url')}
|
displayUrl={config.display_url}
|
||||||
isTestRepo={config.getIn(['backend', 'name']) === 'test-repo'}
|
isTestRepo={config.backend.name === 'test-repo'}
|
||||||
showMediaButton={showMediaButton}
|
showMediaButton={showMediaButton}
|
||||||
/>
|
/>
|
||||||
<AppMainContainer>
|
<AppMainContainer>
|
||||||
@ -256,7 +256,7 @@ function mapStateToProps(state) {
|
|||||||
const { auth, config, collections, globalUI, mediaLibrary } = state;
|
const { auth, config, collections, globalUI, mediaLibrary } = state;
|
||||||
const user = auth.user;
|
const user = auth.user;
|
||||||
const isFetching = globalUI.get('isFetching');
|
const isFetching = globalUI.get('isFetching');
|
||||||
const publishMode = config && config.get('publish_mode');
|
const publishMode = config.publish_mode;
|
||||||
const useMediaLibrary = !mediaLibrary.get('externalLibrary');
|
const useMediaLibrary = !mediaLibrary.get('externalLibrary');
|
||||||
const showMediaButton = mediaLibrary.get('showMediaButton');
|
const showMediaButton = mediaLibrary.get('showMediaButton');
|
||||||
return {
|
return {
|
||||||
|
@ -116,7 +116,7 @@ const AppHeaderNavList = styled.ul`
|
|||||||
class Header extends React.Component {
|
class Header extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
onCreateEntryClick: PropTypes.func.isRequired,
|
onCreateEntryClick: PropTypes.func.isRequired,
|
||||||
onLogoutClick: PropTypes.func.isRequired,
|
onLogoutClick: PropTypes.func.isRequired,
|
||||||
openMediaLibrary: PropTypes.func.isRequired,
|
openMediaLibrary: PropTypes.func.isRequired,
|
||||||
|
@ -48,7 +48,7 @@ export class Collection extends React.Component {
|
|||||||
isSearchResults: PropTypes.bool,
|
isSearchResults: PropTypes.bool,
|
||||||
isSingleSearchResult: PropTypes.bool,
|
isSingleSearchResult: PropTypes.bool,
|
||||||
collection: ImmutablePropTypes.map.isRequired,
|
collection: ImmutablePropTypes.map.isRequired,
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
sortableFields: PropTypes.array,
|
sortableFields: PropTypes.array,
|
||||||
sort: ImmutablePropTypes.orderedMap,
|
sort: ImmutablePropTypes.orderedMap,
|
||||||
onSortClick: PropTypes.func.isRequired,
|
onSortClick: PropTypes.func.isRequired,
|
||||||
|
@ -88,7 +88,7 @@ const SuggestionDivider = styled.div`
|
|||||||
|
|
||||||
class CollectionSearch extends React.Component {
|
class CollectionSearch extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
collection: ImmutablePropTypes.map,
|
collection: ImmutablePropTypes.map,
|
||||||
searchTerm: PropTypes.string.isRequired,
|
searchTerm: PropTypes.string.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
@ -67,7 +67,7 @@ const SidebarNavLink = styled(NavLink)`
|
|||||||
|
|
||||||
export class Sidebar extends React.Component {
|
export class Sidebar extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
collection: ImmutablePropTypes.map,
|
collection: ImmutablePropTypes.map,
|
||||||
searchTerm: PropTypes.string,
|
searchTerm: PropTypes.string,
|
||||||
filterTerm: PropTypes.string,
|
filterTerm: PropTypes.string,
|
||||||
|
@ -434,8 +434,8 @@ function mapStateToProps(state, ownProps) {
|
|||||||
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
|
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
|
||||||
const user = auth.user;
|
const user = auth.user;
|
||||||
const hasChanged = entryDraft.get('hasChanged');
|
const hasChanged = entryDraft.get('hasChanged');
|
||||||
const displayUrl = config.get('display_url');
|
const displayUrl = config.display_url;
|
||||||
const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW;
|
const hasWorkflow = config.publish_mode === EDITORIAL_WORKFLOW;
|
||||||
const useOpenAuthoring = globalUI.get('useOpenAuthoring', false);
|
const useOpenAuthoring = globalUI.get('useOpenAuthoring', false);
|
||||||
const isModification = entryDraft.getIn(['entry', 'isModification']);
|
const isModification = entryDraft.getIn(['entry', 'isModification']);
|
||||||
const collectionEntriesLoaded = !!entries.getIn(['pages', collectionName]);
|
const collectionEntriesLoaded = !!entries.getIn(['pages', collectionName]);
|
||||||
|
@ -7,7 +7,7 @@ import { loadUnpublishedEntry, persistUnpublishedEntry } from 'Actions/editorial
|
|||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
const { collections } = state;
|
const { collections } = state;
|
||||||
const isEditorialWorkflow = state.config.get('publish_mode') === EDITORIAL_WORKFLOW;
|
const isEditorialWorkflow = state.config.publish_mode === EDITORIAL_WORKFLOW;
|
||||||
const collection = collections.get(ownProps.match.params.name);
|
const collection = collections.get(ownProps.match.params.name);
|
||||||
const returnObj = {
|
const returnObj = {
|
||||||
isEditorialWorkflow,
|
isEditorialWorkflow,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
@ -44,9 +43,9 @@ function buildIssueTemplate({ config }) {
|
|||||||
}
|
}
|
||||||
const template = getIssueTemplate({
|
const template = getIssueTemplate({
|
||||||
version,
|
version,
|
||||||
provider: config.getIn(['backend', 'name']),
|
provider: config.backend.name,
|
||||||
browser: navigator.userAgent,
|
browser: navigator.userAgent,
|
||||||
config: yaml.stringify(config.toJS()),
|
config: yaml.stringify(config),
|
||||||
});
|
});
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
@ -131,7 +130,7 @@ export class ErrorBoundary extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
config: ImmutablePropTypes.map.isRequired,
|
config: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ErrorBoundary } from '../ErrorBoundary';
|
import { ErrorBoundary } from '../ErrorBoundary';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
import { oneLineTrim } from 'common-tags';
|
import { oneLineTrim } from 'common-tags';
|
||||||
|
|
||||||
function WithError() {
|
function WithError() {
|
||||||
@ -24,7 +23,7 @@ Object.defineProperty(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('Editor', () => {
|
describe('Editor', () => {
|
||||||
const config = fromJS({ backend: { name: 'github' } });
|
const config = { backend: { name: 'github' } };
|
||||||
|
|
||||||
const props = { t: jest.fn(key => key), config };
|
const props = { t: jest.fn(key => key), config };
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ const WorkflowTopDescription = styled.p`
|
|||||||
|
|
||||||
class Workflow extends Component {
|
class Workflow extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
isEditorialWorkflow: PropTypes.bool.isRequired,
|
isEditorialWorkflow: PropTypes.bool.isRequired,
|
||||||
isOpenAuthoring: PropTypes.bool,
|
isOpenAuthoring: PropTypes.bool,
|
||||||
isFetching: PropTypes.bool,
|
isFetching: PropTypes.bool,
|
||||||
@ -137,7 +137,7 @@ class Workflow extends Component {
|
|||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const { collections, config, globalUI } = state;
|
const { collections, config, globalUI } = state;
|
||||||
const isEditorialWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW;
|
const isEditorialWorkflow = config.publish_mode === EDITORIAL_WORKFLOW;
|
||||||
const isOpenAuthoring = globalUI.get('useOpenAuthoring', false);
|
const isOpenAuthoring = globalUI.get('useOpenAuthoring', false);
|
||||||
const returnObj = { collections, isEditorialWorkflow, isOpenAuthoring };
|
const returnObj = { collections, isEditorialWorkflow, isOpenAuthoring };
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ class WorkflowList extends React.Component {
|
|||||||
handleDelete: PropTypes.func.isRequired,
|
handleDelete: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
isOpenAuthoring: PropTypes.bool,
|
isOpenAuthoring: PropTypes.bool,
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChangeStatus = (newStatus, dragProps) => {
|
handleChangeStatus = (newStatus, dragProps) => {
|
||||||
|
2
packages/netlify-cms-core/src/constants/commitProps.ts
Normal file
2
packages/netlify-cms-core/src/constants/commitProps.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const COMMIT_AUTHOR = 'commit_author';
|
||||||
|
export const COMMIT_DATE = 'commit_date';
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const IDENTIFIER_FIELDS = ['title', 'path'];
|
export const IDENTIFIER_FIELDS = ['title', 'path'] as const;
|
||||||
|
|
||||||
export const SORTABLE_FIELDS = ['title', 'date', 'author', 'description'];
|
export const SORTABLE_FIELDS = ['title', 'date', 'author', 'description'] as const;
|
||||||
|
|
||||||
export const INFERABLE_FIELDS = {
|
export const INFERABLE_FIELDS = {
|
||||||
title: {
|
title: {
|
||||||
|
@ -14,18 +14,18 @@ jest.mock('../../reducers/collections');
|
|||||||
describe('formatters', () => {
|
describe('formatters', () => {
|
||||||
describe('commitMessageFormatter', () => {
|
describe('commitMessageFormatter', () => {
|
||||||
const config = {
|
const config = {
|
||||||
getIn: jest.fn(),
|
backend: {
|
||||||
};
|
name: 'git-gateway',
|
||||||
|
},
|
||||||
const collection = {
|
|
||||||
get: jest.fn().mockReturnValue('Collection'),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return default commit message on create', () => {
|
it('should return default commit message on create, label_singular', () => {
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('create', config, {
|
commitMessageFormatter('create', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -35,9 +35,8 @@ describe('formatters', () => {
|
|||||||
).toEqual('Create Collection “doc-slug”');
|
).toEqual('Create Collection “doc-slug”');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return default commit message on create', () => {
|
it('should return default commit message on create, label', () => {
|
||||||
collection.get.mockReturnValueOnce(undefined);
|
const collection = Map({ label: 'Collections' });
|
||||||
collection.get.mockReturnValueOnce('Collections');
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('update', config, {
|
commitMessageFormatter('update', config, {
|
||||||
@ -49,6 +48,8 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return default commit message on delete', () => {
|
it('should return default commit message on delete', () => {
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('delete', config, {
|
commitMessageFormatter('delete', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -59,6 +60,8 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return default commit message on uploadMedia', () => {
|
it('should return default commit message on uploadMedia', () => {
|
||||||
|
const collection = Map({});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('uploadMedia', config, {
|
commitMessageFormatter('uploadMedia', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -69,6 +72,8 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return default commit message on deleteMedia', () => {
|
it('should return default commit message on deleteMedia', () => {
|
||||||
|
const collection = Map({});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('deleteMedia', config, {
|
commitMessageFormatter('deleteMedia', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -79,11 +84,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log warning on unknown variable', () => {
|
it('should log warning on unknown variable', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
create: 'Create {{collection}} “{{slug}}” with "{{unknown variable}}"',
|
commit_messages: {
|
||||||
}),
|
create: 'Create {{collection}} “{{slug}}” with "{{unknown variable}}"',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('create', config, {
|
commitMessageFormatter('create', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -98,12 +106,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return custom commit message on update', () => {
|
it('should return custom commit message on update', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
update: 'Custom commit message',
|
commit_messages: {
|
||||||
}),
|
update: 'Custom commit message',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({});
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter('update', config, {
|
commitMessageFormatter('update', config, {
|
||||||
slug: 'doc-slug',
|
slug: 'doc-slug',
|
||||||
@ -114,12 +124,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should use empty values if "authorLogin" and "authorName" are missing in commit message', () => {
|
it('should use empty values if "authorLogin" and "authorName" are missing in commit message', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
update: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
|
commit_messages: {
|
||||||
}),
|
update: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter(
|
commitMessageFormatter(
|
||||||
'update',
|
'update',
|
||||||
@ -135,12 +147,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return custom create message with author information', () => {
|
it('should return custom create message with author information', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
create: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
|
commit_messages: {
|
||||||
}),
|
create: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter(
|
commitMessageFormatter(
|
||||||
'create',
|
'create',
|
||||||
@ -158,12 +172,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return custom open authoring message', () => {
|
it('should return custom open authoring message', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
|
commit_messages: {
|
||||||
}),
|
openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter(
|
commitMessageFormatter(
|
||||||
'create',
|
'create',
|
||||||
@ -181,12 +197,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should use empty values if "authorLogin" and "authorName" are missing in open authoring message', () => {
|
it('should use empty values if "authorLogin" and "authorName" are missing in open authoring message', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
|
commit_messages: {
|
||||||
}),
|
openAuthoring: '{{author-login}} - {{author-name}}: {{message}}',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
expect(
|
expect(
|
||||||
commitMessageFormatter(
|
commitMessageFormatter(
|
||||||
'create',
|
'create',
|
||||||
@ -202,12 +220,14 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log warning on unknown variable in open authoring template', () => {
|
it('should log warning on unknown variable in open authoring template', () => {
|
||||||
config.getIn.mockReturnValueOnce(
|
const config = {
|
||||||
Map({
|
backend: {
|
||||||
openAuthoring: '{{author-email}}: {{message}}',
|
commit_messages: {
|
||||||
}),
|
openAuthoring: '{{author-email}}: {{message}}',
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const collection = Map({ label_singular: 'Collection' });
|
||||||
commitMessageFormatter(
|
commitMessageFormatter(
|
||||||
'create',
|
'create',
|
||||||
config,
|
config,
|
||||||
@ -246,11 +266,11 @@ describe('formatters', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const slugConfig = Map({
|
const slugConfig = {
|
||||||
encoding: 'unicode',
|
encoding: 'unicode',
|
||||||
clean_accents: false,
|
clean_accents: false,
|
||||||
sanitize_replacement: '-',
|
sanitize_replacement: '-',
|
||||||
});
|
};
|
||||||
|
|
||||||
describe('slugFormatter', () => {
|
describe('slugFormatter', () => {
|
||||||
const date = new Date('2020-01-01');
|
const date = new Date('2020-01-01');
|
||||||
@ -363,8 +383,8 @@ describe('formatters', () => {
|
|||||||
preview_path_date_field: 'customDateField',
|
preview_path_date_field: 'customDateField',
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({ customDateField: date, slug: 'entrySlug', title: 'title' }) }),
|
Map({ data: Map({ customDateField: date, slug: 'entrySlug', title: 'title' }) }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/2020/backendslug/title/entryslug');
|
).toBe('https://www.example.com/2020/backendslug/title/entryslug');
|
||||||
});
|
});
|
||||||
@ -384,8 +404,8 @@ describe('formatters', () => {
|
|||||||
files: List([file]),
|
files: List([file]),
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/backendslug/about-the-project/title');
|
).toBe('https://www.example.com/backendslug/about-the-project/title');
|
||||||
});
|
});
|
||||||
@ -404,8 +424,8 @@ describe('formatters', () => {
|
|||||||
files: List([file]),
|
files: List([file]),
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/backendslug/about-the-project/title');
|
).toBe('https://www.example.com/backendslug/about-the-project/title');
|
||||||
});
|
});
|
||||||
@ -425,8 +445,8 @@ describe('formatters', () => {
|
|||||||
files: List([file]),
|
files: List([file]),
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/backendslug/title/about-the-project');
|
).toBe('https://www.example.com/backendslug/title/about-the-project');
|
||||||
});
|
});
|
||||||
@ -444,8 +464,8 @@ describe('formatters', () => {
|
|||||||
preview_path: '{{year}}/{{month}}/{{slug}}/{{title}}/{{fields.slug}}',
|
preview_path: '{{year}}/{{month}}/{{slug}}/{{title}}/{{fields.slug}}',
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({ date, slug: 'entrySlug', title: 'title' }) }),
|
Map({ data: Map({ date, slug: 'entrySlug', title: 'title' }) }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/2020/01/backendslug/title/entryslug');
|
).toBe('https://www.example.com/2020/01/backendslug/title/entryslug');
|
||||||
});
|
});
|
||||||
@ -458,8 +478,8 @@ describe('formatters', () => {
|
|||||||
preview_path: 'posts/{{filename}}.{{extension}}',
|
preview_path: 'posts/{{filename}}.{{extension}}',
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({}), path: 'src/content/posts/title.md' }),
|
Map({ data: Map({}), path: 'src/content/posts/title.md' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/posts/title.md');
|
).toBe('https://www.example.com/posts/title.md');
|
||||||
});
|
});
|
||||||
@ -473,8 +493,8 @@ describe('formatters', () => {
|
|||||||
preview_path: 'portfolio/{{dirname}}',
|
preview_path: 'portfolio/{{dirname}}',
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({}), path: '_portfolio/i-am-the-slug.md' }),
|
Map({ data: Map({}), path: '_portfolio/i-am-the-slug.md' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/portfolio/');
|
).toBe('https://www.example.com/portfolio/');
|
||||||
});
|
});
|
||||||
@ -490,8 +510,8 @@ describe('formatters', () => {
|
|||||||
meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
|
meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } },
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({}), path: '_portfolio/drawing/i-am-the-slug/index.md' }),
|
Map({ data: Map({}), path: '_portfolio/drawing/i-am-the-slug/index.md' }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com/portfolio/drawing/i-am-the-slug');
|
).toBe('https://www.example.com/portfolio/drawing/i-am-the-slug');
|
||||||
});
|
});
|
||||||
@ -507,8 +527,8 @@ describe('formatters', () => {
|
|||||||
preview_path_date_field: 'date',
|
preview_path_date_field: 'date',
|
||||||
}),
|
}),
|
||||||
'backendSlug',
|
'backendSlug',
|
||||||
slugConfig,
|
|
||||||
Map({ data: Map({}) }),
|
Map({ data: Map({}) }),
|
||||||
|
slugConfig,
|
||||||
),
|
),
|
||||||
).toBe('https://www.example.com');
|
).toBe('https://www.example.com');
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Map } from 'immutable';
|
|
||||||
import { sanitizeURI, sanitizeSlug, sanitizeChar } from '../urlHelper';
|
import { sanitizeURI, sanitizeSlug, sanitizeChar } from '../urlHelper';
|
||||||
|
|
||||||
describe('sanitizeURI', () => {
|
describe('sanitizeURI', () => {
|
||||||
@ -60,79 +59,80 @@ describe('sanitizeSlug', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error for non-string replacements', () => {
|
it('throws an error for non-string replacements', () => {
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: {} }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: {} })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: [] }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: [] })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: false }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: false })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: null }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: null })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: 11232 }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: 11232 })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
// do not test undefined for this variant since a default is set in the constructor.
|
// do not test undefined for this variant since a default is set in the constructor.
|
||||||
//expect(() => sanitizeSlug('test', { sanitize_replacement: undefined })).toThrowError("`options.replacement` must be a string.");
|
//expect(() => sanitizeSlug('test', { sanitize_replacement: undefined })).toThrowError("`options.replacement` must be a string.");
|
||||||
expect(() => sanitizeSlug('test', Map({ sanitize_replacement: () => {} }))).toThrowError(
|
expect(() => sanitizeSlug('test', { sanitize_replacement: () => {} })).toThrowError(
|
||||||
'`options.replacement` must be a string.',
|
'`options.replacement` must be a string.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should keep valid URI chars (letters digits _ - . ~)', () => {
|
it('should keep valid URI chars (letters digits _ - . ~)', () => {
|
||||||
expect(sanitizeSlug('This, that-one_or.the~other 123!', Map(slugConfig))).toEqual(
|
expect(sanitizeSlug('This, that-one_or.the~other 123!', slugConfig)).toEqual(
|
||||||
'This-that-one_or.the~other-123',
|
'This-that-one_or.the~other-123',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove accents with `clean_accents` set', () => {
|
it('should remove accents with `clean_accents` set', () => {
|
||||||
expect(sanitizeSlug('ěščřžý', Map({ ...slugConfig, clean_accents: true }))).toEqual('escrzy');
|
expect(sanitizeSlug('ěščřžý', { ...slugConfig, clean_accents: true })).toEqual('escrzy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove non-latin chars in "ascii" mode', () => {
|
it('should remove non-latin chars in "ascii" mode', () => {
|
||||||
expect(
|
expect(sanitizeSlug('ěščřžý日本語のタイトル', { ...slugConfig, encoding: 'ascii' })).toEqual(
|
||||||
sanitizeSlug('ěščřžý日本語のタイトル', Map({ ...slugConfig, encoding: 'ascii' })),
|
'',
|
||||||
).toEqual('');
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clean accents and strip non-latin chars in "ascii" mode with `clean_accents` set', () => {
|
it('should clean accents and strip non-latin chars in "ascii" mode with `clean_accents` set', () => {
|
||||||
expect(
|
expect(
|
||||||
sanitizeSlug(
|
sanitizeSlug('ěščřžý日本語のタイトル', {
|
||||||
'ěščřžý日本語のタイトル',
|
...slugConfig,
|
||||||
Map({ ...slugConfig, encoding: 'ascii', clean_accents: true }),
|
encoding: 'ascii',
|
||||||
),
|
clean_accents: true,
|
||||||
|
}),
|
||||||
).toEqual('escrzy');
|
).toEqual('escrzy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes double replacements', () => {
|
it('removes double replacements', () => {
|
||||||
expect(sanitizeSlug('test--test', Map(slugConfig))).toEqual('test-test');
|
expect(sanitizeSlug('test--test', slugConfig)).toEqual('test-test');
|
||||||
expect(sanitizeSlug('test test', Map(slugConfig))).toEqual('test-test');
|
expect(sanitizeSlug('test test', slugConfig)).toEqual('test-test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes trailing replacements', () => {
|
it('removes trailing replacements', () => {
|
||||||
expect(sanitizeSlug('test test ', Map(slugConfig))).toEqual('test-test');
|
expect(sanitizeSlug('test test ', slugConfig)).toEqual('test-test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes leading replacements', () => {
|
it('removes leading replacements', () => {
|
||||||
expect(sanitizeSlug('"test" test', Map(slugConfig))).toEqual('test-test');
|
expect(sanitizeSlug('"test" test', slugConfig)).toEqual('test-test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses alternate replacements', () => {
|
it('uses alternate replacements', () => {
|
||||||
expect(
|
expect(sanitizeSlug('test test ', { ...slugConfig, sanitize_replacement: '_' })).toEqual(
|
||||||
sanitizeSlug('test test ', Map({ ...slugConfig, sanitize_replacement: '_' })),
|
'test_test',
|
||||||
).toEqual('test_test');
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sanitizeChar', () => {
|
describe('sanitizeChar', () => {
|
||||||
it('should sanitize whitespace with default replacement', () => {
|
it('should sanitize whitespace with default replacement', () => {
|
||||||
expect(sanitizeChar(' ', Map(slugConfig))).toBe('-');
|
expect(sanitizeChar(' ', slugConfig)).toBe('-');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sanitize whitespace with custom replacement', () => {
|
it('should sanitize whitespace with custom replacement', () => {
|
||||||
expect(sanitizeChar(' ', Map({ ...slugConfig, sanitize_replacement: '_' }))).toBe('_');
|
expect(sanitizeChar(' ', { ...slugConfig, sanitize_replacement: '_' })).toBe('_');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,14 +5,13 @@ import { stringTemplate } from 'netlify-cms-lib-widgets';
|
|||||||
import {
|
import {
|
||||||
selectIdentifier,
|
selectIdentifier,
|
||||||
selectField,
|
selectField,
|
||||||
COMMIT_AUTHOR,
|
|
||||||
COMMIT_DATE,
|
|
||||||
selectInferedField,
|
selectInferedField,
|
||||||
getFileFromSlug,
|
getFileFromSlug,
|
||||||
} from '../reducers/collections';
|
} from '../reducers/collections';
|
||||||
import { Collection, SlugConfig, Config, EntryMap } from '../types/redux';
|
import { Collection, CmsConfig, CmsSlug, EntryMap } from '../types/redux';
|
||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
import { FILES } from '../constants/collectionTypes';
|
import { FILES } from '../constants/collectionTypes';
|
||||||
|
import { COMMIT_AUTHOR, COMMIT_DATE } from '../constants/commitProps';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
compileStringTemplate,
|
compileStringTemplate,
|
||||||
@ -22,14 +21,14 @@ const {
|
|||||||
addFileTemplateFields,
|
addFileTemplateFields,
|
||||||
} = stringTemplate;
|
} = stringTemplate;
|
||||||
|
|
||||||
const commitMessageTemplates = Map({
|
const commitMessageTemplates = {
|
||||||
create: 'Create {{collection}} “{{slug}}”',
|
create: 'Create {{collection}} “{{slug}}”',
|
||||||
update: 'Update {{collection}} “{{slug}}”',
|
update: 'Update {{collection}} “{{slug}}”',
|
||||||
delete: 'Delete {{collection}} “{{slug}}”',
|
delete: 'Delete {{collection}} “{{slug}}”',
|
||||||
uploadMedia: 'Upload “{{path}}”',
|
uploadMedia: 'Upload “{{path}}”',
|
||||||
deleteMedia: 'Delete “{{path}}”',
|
deleteMedia: 'Delete “{{path}}”',
|
||||||
openAuthoring: '{{message}}',
|
openAuthoring: '{{message}}',
|
||||||
});
|
} as const;
|
||||||
|
|
||||||
const variableRegex = /\{\{([^}]+)\}\}/g;
|
const variableRegex = /\{\{([^}]+)\}\}/g;
|
||||||
|
|
||||||
@ -42,16 +41,14 @@ type Options = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function commitMessageFormatter(
|
export function commitMessageFormatter(
|
||||||
type: string,
|
type: keyof typeof commitMessageTemplates,
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
{ slug, path, collection, authorLogin, authorName }: Options,
|
{ slug, path, collection, authorLogin, authorName }: Options,
|
||||||
isOpenAuthoring?: boolean,
|
isOpenAuthoring?: boolean,
|
||||||
) {
|
) {
|
||||||
const templates = commitMessageTemplates.merge(
|
const templates = { ...commitMessageTemplates, ...(config.backend.commit_messages || {}) };
|
||||||
config.getIn(['backend', 'commit_messages'], Map<string, string>()),
|
|
||||||
);
|
|
||||||
|
|
||||||
const commitMessage = templates.get(type).replace(variableRegex, (_, variable) => {
|
const commitMessage = templates[type].replace(variableRegex, (_, variable) => {
|
||||||
switch (variable) {
|
switch (variable) {
|
||||||
case 'slug':
|
case 'slug':
|
||||||
return slug || '';
|
return slug || '';
|
||||||
@ -73,7 +70,7 @@ export function commitMessageFormatter(
|
|||||||
return commitMessage;
|
return commitMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = templates.get('openAuthoring').replace(variableRegex, (_, variable) => {
|
const message = templates.openAuthoring.replace(variableRegex, (_, variable) => {
|
||||||
switch (variable) {
|
switch (variable) {
|
||||||
case 'message':
|
case 'message':
|
||||||
return commitMessage;
|
return commitMessage;
|
||||||
@ -105,9 +102,9 @@ export function prepareSlug(slug: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProcessSegment(slugConfig: SlugConfig, ignoreValues: string[] = []) {
|
export function getProcessSegment(slugConfig?: CmsSlug, ignoreValues?: string[]) {
|
||||||
return (value: string) =>
|
return (value: string) =>
|
||||||
ignoreValues.includes(value)
|
ignoreValues && ignoreValues.includes(value)
|
||||||
? value
|
? value
|
||||||
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
|
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
|
||||||
}
|
}
|
||||||
@ -115,7 +112,7 @@ export function getProcessSegment(slugConfig: SlugConfig, ignoreValues: string[]
|
|||||||
export function slugFormatter(
|
export function slugFormatter(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryData: Map<string, unknown>,
|
entryData: Map<string, unknown>,
|
||||||
slugConfig: SlugConfig,
|
slugConfig?: CmsSlug,
|
||||||
) {
|
) {
|
||||||
const slugTemplate = collection.get('slug') || '{{slug}}';
|
const slugTemplate = collection.get('slug') || '{{slug}}';
|
||||||
|
|
||||||
@ -144,8 +141,8 @@ export function previewUrlFormatter(
|
|||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
slug: string,
|
slug: string,
|
||||||
slugConfig: SlugConfig,
|
|
||||||
entry: EntryMap,
|
entry: EntryMap,
|
||||||
|
slugConfig?: CmsSlug,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Preview URL can't be created without `baseUrl`. This makes preview URLs
|
* Preview URL can't be created without `baseUrl`. This makes preview URLs
|
||||||
@ -239,7 +236,7 @@ export function folderFormatter(
|
|||||||
collection: Collection,
|
collection: Collection,
|
||||||
defaultFolder: string,
|
defaultFolder: string,
|
||||||
folderKey: string,
|
folderKey: string,
|
||||||
slugConfig: SlugConfig,
|
slugConfig?: CmsSlug,
|
||||||
) {
|
) {
|
||||||
if (!entry || !entry.get('data')) {
|
if (!entry || !entry.get('data')) {
|
||||||
return folderTemplate;
|
return folderTemplate;
|
||||||
|
@ -2,7 +2,7 @@ import url from 'url';
|
|||||||
import diacritics from 'diacritics';
|
import diacritics from 'diacritics';
|
||||||
import sanitizeFilename from 'sanitize-filename';
|
import sanitizeFilename from 'sanitize-filename';
|
||||||
import { isString, escapeRegExp, flow, partialRight } from 'lodash';
|
import { isString, escapeRegExp, flow, partialRight } from 'lodash';
|
||||||
import { SlugConfig } from '../types/redux';
|
import { CmsSlug } from '../types/redux';
|
||||||
|
|
||||||
function getUrl(urlString: string, direct?: boolean) {
|
function getUrl(urlString: string, direct?: boolean) {
|
||||||
return `${direct ? '/#' : ''}${urlString}`;
|
return `${direct ? '/#' : ''}${urlString}`;
|
||||||
@ -16,7 +16,7 @@ export function getNewEntryUrl(collectionName: string, direct?: boolean) {
|
|||||||
return getUrl(`/collections/${collectionName}/new`, direct);
|
return getUrl(`/collections/${collectionName}/new`, direct);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addParams(urlString: string, params: {}) {
|
export function addParams(urlString: string, params: Record<string, string>) {
|
||||||
const parsedUrl = url.parse(urlString, true);
|
const parsedUrl = url.parse(urlString, true);
|
||||||
parsedUrl.query = { ...parsedUrl.query, ...params };
|
parsedUrl.query = { ...parsedUrl.query, ...params };
|
||||||
return url.format(parsedUrl);
|
return url.format(parsedUrl);
|
||||||
@ -64,7 +64,12 @@ export function getCharReplacer(encoding: string, replacement: string) {
|
|||||||
return (char: string) => (validChar(char) ? char : replacement);
|
return (char: string) => (validChar(char) ? char : replacement);
|
||||||
}
|
}
|
||||||
// `sanitizeURI` does not actually URI-encode the chars (that is the browser's and server's job), just removes the ones that are not allowed.
|
// `sanitizeURI` does not actually URI-encode the chars (that is the browser's and server's job), just removes the ones that are not allowed.
|
||||||
export function sanitizeURI(str: string, { replacement = '', encoding = 'unicode' } = {}) {
|
export function sanitizeURI(
|
||||||
|
str: string,
|
||||||
|
options?: { replacement: CmsSlug['sanitize_replacement']; encoding: CmsSlug['encoding'] },
|
||||||
|
) {
|
||||||
|
const { replacement = '', encoding = 'unicode' } = options || {};
|
||||||
|
|
||||||
if (!isString(str)) {
|
if (!isString(str)) {
|
||||||
throw new Error('The input slug must be a string.');
|
throw new Error('The input slug must be a string.');
|
||||||
}
|
}
|
||||||
@ -79,21 +84,18 @@ export function sanitizeURI(str: string, { replacement = '', encoding = 'unicode
|
|||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeChar(char: string, options: SlugConfig) {
|
export function sanitizeChar(char: string, options?: CmsSlug) {
|
||||||
const encoding = options.get('encoding');
|
const { encoding = 'unicode', sanitize_replacement: replacement = '' } = options || {};
|
||||||
const replacement = options.get('sanitize_replacement');
|
|
||||||
|
|
||||||
return getCharReplacer(encoding, replacement)(char);
|
return getCharReplacer(encoding, replacement)(char);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeSlug(str: string, options: SlugConfig) {
|
export function sanitizeSlug(str: string, options?: CmsSlug) {
|
||||||
if (!isString(str)) {
|
if (!isString(str)) {
|
||||||
throw new Error('The input slug must be a string.');
|
throw new Error('The input slug must be a string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const encoding = options.get('encoding');
|
const { encoding, clean_accents: stripDiacritics, sanitize_replacement: replacement } =
|
||||||
const stripDiacritics = options.get('clean_accents');
|
options || {};
|
||||||
const replacement = options.get('sanitize_replacement');
|
|
||||||
|
|
||||||
const sanitizedSlug = flow([
|
const sanitizedSlug = flow([
|
||||||
...(stripDiacritics ? [diacritics.remove] : []),
|
...(stripDiacritics ? [diacritics.remove] : []),
|
||||||
|
@ -7,7 +7,7 @@ import { getMediaLibrary } from './lib/registry';
|
|||||||
import store from './redux';
|
import store from './redux';
|
||||||
import { configFailed } from './actions/config';
|
import { configFailed } from './actions/config';
|
||||||
import { createMediaLibrary, insertMedia } from './actions/mediaLibrary';
|
import { createMediaLibrary, insertMedia } from './actions/mediaLibrary';
|
||||||
import { MediaLibraryInstance, State } from './types/redux';
|
import { MediaLibraryInstance } from './types/redux';
|
||||||
|
|
||||||
type MediaLibraryOptions = {};
|
type MediaLibraryOptions = {};
|
||||||
|
|
||||||
@ -38,10 +38,12 @@ const initializeMediaLibrary = once(async function initializeMediaLibrary(name,
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
const state = store.getState() as State;
|
const state = store.getState();
|
||||||
const mediaLibraryName = state.config.getIn(['media_library', 'name']);
|
if (state) {
|
||||||
if (mediaLibraryName && !state.mediaLibrary.get('externalLibrary')) {
|
const mediaLibraryName = state.config.media_library?.name;
|
||||||
const mediaLibraryConfig = state.config.get('media_library').toJS();
|
if (mediaLibraryName && !state.mediaLibrary.get('externalLibrary')) {
|
||||||
initializeMediaLibrary(mediaLibraryName, mediaLibraryConfig);
|
const mediaLibraryConfig = state.config.media_library;
|
||||||
|
initializeMediaLibrary(mediaLibraryName, mediaLibraryConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { OrderedMap, fromJS } from 'immutable';
|
import { fromJS, Map } from 'immutable';
|
||||||
import { configLoaded } from 'Actions/config';
|
import { configLoaded } from '../../actions/config';
|
||||||
import collections, {
|
import collections, {
|
||||||
selectAllowDeletion,
|
selectAllowDeletion,
|
||||||
selectEntryPath,
|
selectEntryPath,
|
||||||
@ -11,39 +11,51 @@ import collections, {
|
|||||||
selectField,
|
selectField,
|
||||||
updateFieldByKey,
|
updateFieldByKey,
|
||||||
} from '../collections';
|
} from '../collections';
|
||||||
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
import { FILES, FOLDER } from '../../constants/collectionTypes';
|
||||||
|
|
||||||
describe('collections', () => {
|
describe('collections', () => {
|
||||||
it('should handle an empty state', () => {
|
it('should handle an empty state', () => {
|
||||||
expect(collections(undefined, {})).toEqual(null);
|
expect(collections(undefined, {})).toEqual(Map());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the collections from the config', () => {
|
it('should load the collections from the config', () => {
|
||||||
expect(
|
expect(
|
||||||
collections(
|
collections(
|
||||||
undefined,
|
undefined,
|
||||||
configLoaded(
|
configLoaded({
|
||||||
fromJS({
|
collections: [
|
||||||
collections: [
|
{
|
||||||
{
|
name: 'posts',
|
||||||
name: 'posts',
|
folder: '_posts',
|
||||||
folder: '_posts',
|
fields: [{ name: 'title', widget: 'string' }],
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
},
|
||||||
},
|
],
|
||||||
],
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).toEqual(
|
|
||||||
OrderedMap({
|
|
||||||
posts: fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: '_posts',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
type: FOLDER,
|
|
||||||
}),
|
}),
|
||||||
|
).toJS(),
|
||||||
|
).toEqual({
|
||||||
|
posts: {
|
||||||
|
name: 'posts',
|
||||||
|
folder: '_posts',
|
||||||
|
fields: [{ name: 'title', widget: 'string' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should maintain config collections order', () => {
|
||||||
|
const collectionsData = new Array(1000).fill(0).map((_, index) => ({
|
||||||
|
name: `collection_${index}`,
|
||||||
|
folder: `collection_${index}`,
|
||||||
|
fields: [{ name: 'title', widget: 'string' }],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newState = collections(
|
||||||
|
undefined,
|
||||||
|
configLoaded({
|
||||||
|
collections: collectionsData,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const keyArray = newState.keySeq().toArray();
|
||||||
|
expect(keyArray).toEqual(collectionsData.map(({ name }) => name));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('selectAllowDeletions', () => {
|
describe('selectAllowDeletions', () => {
|
||||||
@ -234,11 +246,11 @@ describe('collections', () => {
|
|||||||
sanitize_replacement: '-',
|
sanitize_replacement: '-',
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = fromJS({ slug, media_folder: '/static/img' });
|
const config = { slug, media_folder: '/static/img' };
|
||||||
it('should return fields and collection folders', () => {
|
it('should return fields and collection folders', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolders(
|
selectMediaFolders(
|
||||||
{ config },
|
config,
|
||||||
fromJS({
|
fromJS({
|
||||||
folder: 'posts',
|
folder: 'posts',
|
||||||
media_folder: '{{media_folder}}/general/',
|
media_folder: '{{media_folder}}/general/',
|
||||||
@ -265,7 +277,7 @@ describe('collections', () => {
|
|||||||
it('should return fields, file and collection folders', () => {
|
it('should return fields, file and collection folders', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolders(
|
selectMediaFolders(
|
||||||
{ config },
|
config,
|
||||||
fromJS({
|
fromJS({
|
||||||
media_folder: '{{media_folder}}/general/',
|
media_folder: '{{media_folder}}/general/',
|
||||||
files: [
|
files: [
|
||||||
|
@ -1,29 +1,38 @@
|
|||||||
import { Map } from 'immutable';
|
import { configLoaded, configLoading, configFailed } from '../../actions/config';
|
||||||
import { configLoaded, configLoading, configFailed } from 'Actions/config';
|
import config, { selectLocale } from '../config';
|
||||||
import config, { selectLocale } from 'Reducers/config';
|
|
||||||
|
|
||||||
describe('config', () => {
|
describe('config', () => {
|
||||||
it('should handle an empty state', () => {
|
it('should handle an empty state', () => {
|
||||||
expect(config(undefined, {})).toEqual(Map({ isFetching: true }));
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||||
|
// @ts-ignore config reducer doesn't accept empty action
|
||||||
|
expect(config(undefined, {})).toEqual({ isFetching: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an update', () => {
|
it('should handle an update', () => {
|
||||||
expect(config(Map({ a: 'b', c: 'd' }), configLoaded(Map({ a: 'changed', e: 'new' })))).toEqual(
|
expect(
|
||||||
Map({ a: 'changed', e: 'new' }),
|
config({ isFetching: true }, configLoaded({ locale: 'fr', backend: { name: 'proxy' } })),
|
||||||
);
|
).toEqual({
|
||||||
|
locale: 'fr',
|
||||||
|
backend: { name: 'proxy' },
|
||||||
|
isFetching: false,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should mark the config as loading', () => {
|
it('should mark the config as loading', () => {
|
||||||
expect(config(undefined, configLoading())).toEqual(Map({ isFetching: true }));
|
expect(config({ isFetching: false }, configLoading())).toEqual({ isFetching: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an error', () => {
|
it('should handle an error', () => {
|
||||||
expect(config(Map(), configFailed(new Error('Config could not be loaded')))).toEqual(
|
expect(
|
||||||
Map({ error: 'Error: Config could not be loaded' }),
|
config({ isFetching: true }, configFailed(new Error('Config could not be loaded'))),
|
||||||
);
|
).toEqual({
|
||||||
|
error: 'Error: Config could not be loaded',
|
||||||
|
isFetching: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default to "en" locale', () => {
|
it('should default to "en" locale', () => {
|
||||||
expect(selectLocale(Map())).toEqual('en');
|
expect(selectLocale({})).toEqual('en');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { OrderedMap, fromJS } from 'immutable';
|
import { OrderedMap, fromJS } from 'immutable';
|
||||||
import * as actions from 'Actions/entries';
|
import * as actions from '../../actions/entries';
|
||||||
import reducer, {
|
import reducer, {
|
||||||
selectMediaFolder,
|
selectMediaFolder,
|
||||||
selectMediaFilePath,
|
selectMediaFilePath,
|
||||||
@ -76,7 +76,7 @@ describe('entries', () => {
|
|||||||
it("should return global media folder when collection doesn't specify media_folder", () => {
|
it("should return global media folder when collection doesn't specify media_folder", () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts' }),
|
fromJS({ name: 'posts' }),
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@ -87,7 +87,7 @@ describe('entries', () => {
|
|||||||
it('should return draft media folder when collection specifies media_folder and entry is undefined', () => {
|
it('should return draft media folder when collection specifies media_folder and entry is undefined', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@ -98,7 +98,7 @@ describe('entries', () => {
|
|||||||
it('should return relative media folder when collection specifies media_folder and entry path is not null', () => {
|
it('should return relative media folder when collection specifies media_folder and entry path is not null', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
fromJS({ path: 'posts/title/index.md' }),
|
||||||
undefined,
|
undefined,
|
||||||
@ -121,7 +121,7 @@ describe('entries', () => {
|
|||||||
const field = fromJS({ media_folder: '' });
|
const field = fromJS({ media_folder: '' });
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: '/static/img' }),
|
{ media_folder: '/static/img' },
|
||||||
fromJS({
|
fromJS({
|
||||||
name: 'other',
|
name: 'other',
|
||||||
folder: 'other',
|
folder: 'other',
|
||||||
@ -137,7 +137,7 @@ describe('entries', () => {
|
|||||||
it('should return collection absolute media folder without leading slash', () => {
|
it('should return collection absolute media folder without leading slash', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: '/static/Images' }),
|
{ media_folder: '/static/Images' },
|
||||||
fromJS({
|
fromJS({
|
||||||
name: 'getting-started',
|
name: 'getting-started',
|
||||||
folder: 'src/docs/getting-started',
|
folder: 'src/docs/getting-started',
|
||||||
@ -169,7 +169,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media', slug: slugConfig }),
|
{ media_folder: 'static/media', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
undefined,
|
undefined,
|
||||||
@ -196,7 +196,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: '/static/images', slug: slugConfig }),
|
{ media_folder: '/static/images', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
undefined,
|
undefined,
|
||||||
@ -229,7 +229,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media', slug: slugConfig }),
|
{ media_folder: 'static/media', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
collection.get('fields').get(0),
|
collection.get('fields').get(0),
|
||||||
@ -258,7 +258,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: '/static/img/', slug: slugConfig }),
|
{ media_folder: '/static/img/', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
undefined,
|
undefined,
|
||||||
@ -267,7 +267,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/img/', slug: slugConfig }),
|
{ media_folder: 'static/img/', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
undefined,
|
undefined,
|
||||||
@ -278,7 +278,7 @@ describe('entries', () => {
|
|||||||
it('should handle file media_folder', () => {
|
it('should handle file media_folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFolder(
|
selectMediaFolder(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', files: [{ name: 'index', media_folder: '/static/images/' }] }),
|
fromJS({ name: 'posts', files: [{ name: 'index', media_folder: '/static/images/' }] }),
|
||||||
fromJS({ path: 'posts/title/index.md', slug: 'index' }),
|
fromJS({ path: 'posts/title/index.md', slug: 'index' }),
|
||||||
undefined,
|
undefined,
|
||||||
@ -302,7 +302,7 @@ describe('entries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
fromJS({ media_folder: '/static/img' }),
|
{ media_folder: '/static/img' },
|
||||||
fromJS({
|
fromJS({
|
||||||
name: 'general',
|
name: 'general',
|
||||||
media_folder: '{{media_folder}}/general/',
|
media_folder: '{{media_folder}}/general/',
|
||||||
@ -345,7 +345,7 @@ describe('entries', () => {
|
|||||||
it('should resolve path from global media folder for collection with no media folder', () => {
|
it('should resolve path from global media folder for collection with no media folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePath(
|
selectMediaFilePath(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts' }),
|
fromJS({ name: 'posts', folder: 'posts' }),
|
||||||
undefined,
|
undefined,
|
||||||
'image.png',
|
'image.png',
|
||||||
@ -357,7 +357,7 @@ describe('entries', () => {
|
|||||||
it('should resolve path from collection media folder for collection with media folder', () => {
|
it('should resolve path from collection media folder for collection with media folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePath(
|
selectMediaFilePath(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
||||||
undefined,
|
undefined,
|
||||||
'image.png',
|
'image.png',
|
||||||
@ -369,7 +369,7 @@ describe('entries', () => {
|
|||||||
it('should handle relative media_folder', () => {
|
it('should handle relative media_folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePath(
|
selectMediaFilePath(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '../../static/media/' }),
|
fromJS({ name: 'posts', folder: 'posts', media_folder: '../../static/media/' }),
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
fromJS({ path: 'posts/title/index.md' }),
|
||||||
'image.png',
|
'image.png',
|
||||||
@ -382,7 +382,7 @@ describe('entries', () => {
|
|||||||
const field = fromJS({ media_folder: '../../static/media/' });
|
const field = fromJS({ media_folder: '../../static/media/' });
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePath(
|
selectMediaFilePath(
|
||||||
fromJS({ media_folder: 'static/media' }),
|
{ media_folder: 'static/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', fields: [field] }),
|
fromJS({ name: 'posts', folder: 'posts', fields: [field] }),
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
fromJS({ path: 'posts/title/index.md' }),
|
||||||
'image.png',
|
'image.png',
|
||||||
@ -402,7 +402,7 @@ describe('entries', () => {
|
|||||||
it('should resolve path from public folder for collection with no media folder', () => {
|
it('should resolve path from public folder for collection with no media folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: '/media' }),
|
{ public_folder: '/media' },
|
||||||
null,
|
null,
|
||||||
'/media/image.png',
|
'/media/image.png',
|
||||||
undefined,
|
undefined,
|
||||||
@ -414,7 +414,7 @@ describe('entries', () => {
|
|||||||
it('should resolve path from collection public folder for collection with public folder', () => {
|
it('should resolve path from collection public folder for collection with public folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: '/media' }),
|
{ public_folder: '/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', public_folder: '' }),
|
fromJS({ name: 'posts', folder: 'posts', public_folder: '' }),
|
||||||
'image.png',
|
'image.png',
|
||||||
undefined,
|
undefined,
|
||||||
@ -426,7 +426,7 @@ describe('entries', () => {
|
|||||||
it('should handle relative public_folder', () => {
|
it('should handle relative public_folder', () => {
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: '/media' }),
|
{ public_folder: '/media' },
|
||||||
fromJS({ name: 'posts', folder: 'posts', public_folder: '../../static/media/' }),
|
fromJS({ name: 'posts', folder: 'posts', public_folder: '../../static/media/' }),
|
||||||
'image.png',
|
'image.png',
|
||||||
undefined,
|
undefined,
|
||||||
@ -455,7 +455,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: 'static/media', slug: slugConfig }),
|
{ public_folder: 'static/media', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
'image.png',
|
'image.png',
|
||||||
entry,
|
entry,
|
||||||
@ -489,7 +489,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: 'static/media', slug: slugConfig }),
|
{ public_folder: 'static/media', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
'image.png',
|
'image.png',
|
||||||
entry,
|
entry,
|
||||||
@ -523,7 +523,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: 'static/media/', slug: slugConfig }),
|
{ public_folder: 'static/media/', slug: slugConfig },
|
||||||
collection,
|
collection,
|
||||||
'image.png',
|
'image.png',
|
||||||
entry,
|
entry,
|
||||||
@ -551,7 +551,7 @@ describe('entries', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
selectMediaFilePublicPath(
|
selectMediaFilePublicPath(
|
||||||
fromJS({ public_folder: 'static/media/' }),
|
{ public_folder: 'static/media/' },
|
||||||
collection,
|
collection,
|
||||||
'image.png',
|
'image.png',
|
||||||
entry,
|
entry,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { fromJS } from 'immutable';
|
|
||||||
import integrations from '../integrations';
|
import integrations from '../integrations';
|
||||||
import { CONFIG_SUCCESS } from '../../actions/config';
|
import { CONFIG_SUCCESS, ConfigAction } from '../../actions/config';
|
||||||
|
import { FOLDER } from '../../constants/collectionTypes';
|
||||||
|
|
||||||
describe('integrations', () => {
|
describe('integrations', () => {
|
||||||
it('should return default state when no integrations', () => {
|
it('should return default state when no integrations', () => {
|
||||||
const result = integrations(null, {
|
const result = integrations(null, {
|
||||||
type: CONFIG_SUCCESS,
|
type: CONFIG_SUCCESS,
|
||||||
payload: fromJS({ integrations: [] }),
|
payload: { integrations: [] },
|
||||||
});
|
} as ConfigAction);
|
||||||
expect(result && result.toJS()).toEqual({
|
expect(result && result.toJS()).toEqual({
|
||||||
providers: {},
|
providers: {},
|
||||||
hooks: {},
|
hooks: {},
|
||||||
@ -17,7 +17,7 @@ describe('integrations', () => {
|
|||||||
it('should return hooks and providers map when has integrations', () => {
|
it('should return hooks and providers map when has integrations', () => {
|
||||||
const result = integrations(null, {
|
const result = integrations(null, {
|
||||||
type: CONFIG_SUCCESS,
|
type: CONFIG_SUCCESS,
|
||||||
payload: fromJS({
|
payload: {
|
||||||
integrations: [
|
integrations: [
|
||||||
{
|
{
|
||||||
hooks: ['listEntries'],
|
hooks: ['listEntries'],
|
||||||
@ -39,9 +39,13 @@ describe('integrations', () => {
|
|||||||
getSignedFormURL: 'https://asset.store.com/signedUrl',
|
getSignedFormURL: 'https://asset.store.com/signedUrl',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
collections: [{ name: 'posts' }, { name: 'pages' }, { name: 'faq' }],
|
collections: [
|
||||||
}),
|
{ name: 'posts', label: 'Posts', type: FOLDER },
|
||||||
});
|
{ name: 'pages', label: 'Pages', type: FOLDER },
|
||||||
|
{ name: 'faq', label: 'FAQ', type: FOLDER },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as ConfigAction);
|
||||||
|
|
||||||
expect(result && result.toJS()).toEqual({
|
expect(result && result.toJS()).toEqual({
|
||||||
providers: {
|
providers: {
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { List, Set } from 'immutable';
|
import { List, Set, fromJS, OrderedMap } from 'immutable';
|
||||||
import { get, escapeRegExp } from 'lodash';
|
import { get, escapeRegExp } from 'lodash';
|
||||||
import consoleError from '../lib/consoleError';
|
import consoleError from '../lib/consoleError';
|
||||||
import { CONFIG_SUCCESS } from '../actions/config';
|
import { CONFIG_SUCCESS, ConfigAction } from '../actions/config';
|
||||||
import { FILES, FOLDER } from '../constants/collectionTypes';
|
import { FILES, FOLDER } from '../constants/collectionTypes';
|
||||||
|
import { COMMIT_DATE, COMMIT_AUTHOR } from '../constants/commitProps';
|
||||||
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS, SORTABLE_FIELDS } from '../constants/fieldInference';
|
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS, SORTABLE_FIELDS } from '../constants/fieldInference';
|
||||||
import { formatExtensions } from '../formats/formats';
|
import { formatExtensions } from '../formats/formats';
|
||||||
import {
|
import {
|
||||||
CollectionsAction,
|
|
||||||
Collection,
|
Collection,
|
||||||
|
Collections,
|
||||||
CollectionFiles,
|
CollectionFiles,
|
||||||
EntryField,
|
EntryField,
|
||||||
State,
|
|
||||||
EntryMap,
|
EntryMap,
|
||||||
ViewFilter,
|
ViewFilter,
|
||||||
ViewGroup,
|
ViewGroup,
|
||||||
|
CmsConfig,
|
||||||
} from '../types/redux';
|
} from '../types/redux';
|
||||||
import { selectMediaFolder } from './entries';
|
import { selectMediaFolder } from './entries';
|
||||||
import { stringTemplate } from 'netlify-cms-lib-widgets';
|
import { stringTemplate } from 'netlify-cms-lib-widgets';
|
||||||
@ -22,29 +23,17 @@ import { Backend } from '../backend';
|
|||||||
|
|
||||||
const { keyToPathArray } = stringTemplate;
|
const { keyToPathArray } = stringTemplate;
|
||||||
|
|
||||||
function collections(state = null, action: CollectionsAction) {
|
const defaultState: Collections = fromJS({});
|
||||||
|
|
||||||
|
function collections(state = defaultState, action: ConfigAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS: {
|
case CONFIG_SUCCESS: {
|
||||||
const configCollections = action.payload
|
const collections = action.payload.collections;
|
||||||
? action.payload.get('collections')
|
let newState = OrderedMap({});
|
||||||
: List<Collection>();
|
collections.forEach(collection => {
|
||||||
|
newState = newState.set(collection.name, fromJS(collection));
|
||||||
return (
|
});
|
||||||
configCollections
|
return newState;
|
||||||
.toOrderedMap()
|
|
||||||
.map(item => {
|
|
||||||
const collection = item as Collection;
|
|
||||||
if (collection.has('folder')) {
|
|
||||||
return collection.set('type', FOLDER);
|
|
||||||
}
|
|
||||||
if (collection.has('files')) {
|
|
||||||
return collection.set('type', FILES);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
||||||
// @ts-ignore
|
|
||||||
.mapKeys((key: string, collection: Collection) => collection.get('name'))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
@ -165,19 +154,19 @@ export function selectFieldsWithMediaFolders(collection: Collection, slug: strin
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectMediaFolders(state: State, collection: Collection, entry: EntryMap) {
|
export function selectMediaFolders(config: CmsConfig, collection: Collection, entry: EntryMap) {
|
||||||
const fields = selectFieldsWithMediaFolders(collection, entry.get('slug'));
|
const fields = selectFieldsWithMediaFolders(collection, entry.get('slug'));
|
||||||
const folders = fields.map(f => selectMediaFolder(state.config, collection, entry, f));
|
const folders = fields.map(f => selectMediaFolder(config, collection, entry, f));
|
||||||
if (collection.has('files')) {
|
if (collection.has('files')) {
|
||||||
const file = getFileFromSlug(collection, entry.get('slug'));
|
const file = getFileFromSlug(collection, entry.get('slug'));
|
||||||
if (file) {
|
if (file) {
|
||||||
folders.unshift(selectMediaFolder(state.config, collection, entry, undefined));
|
folders.unshift(selectMediaFolder(config, collection, entry, undefined));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (collection.has('media_folder')) {
|
if (collection.has('media_folder')) {
|
||||||
// stop evaluating media folders at collection level
|
// stop evaluating media folders at collection level
|
||||||
collection = collection.delete('files');
|
collection = collection.delete('files');
|
||||||
folders.unshift(selectMediaFolder(state.config, collection, entry, undefined));
|
folders.unshift(selectMediaFolder(config, collection, entry, undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Set(folders).toArray();
|
return Set(folders).toArray();
|
||||||
@ -317,10 +306,10 @@ export function updateFieldByKey(
|
|||||||
|
|
||||||
export function selectIdentifier(collection: Collection) {
|
export function selectIdentifier(collection: Collection) {
|
||||||
const identifier = collection.get('identifier_field');
|
const identifier = collection.get('identifier_field');
|
||||||
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : IDENTIFIER_FIELDS;
|
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : [...IDENTIFIER_FIELDS];
|
||||||
const fieldNames = getFieldsNames(collection.get('fields', List<EntryField>()).toArray());
|
const fieldNames = getFieldsNames(collection.get('fields', List()).toArray());
|
||||||
return identifierFields.find(id =>
|
return identifierFields.find(id =>
|
||||||
fieldNames.find(name => name?.toLowerCase().trim() === id.toLowerCase().trim()),
|
fieldNames.find(name => name.toLowerCase().trim() === id.toLowerCase().trim()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,9 +379,6 @@ export function selectEntryCollectionTitle(collection: Collection, entry: EntryM
|
|||||||
return titleField && entryData.getIn(keyToPathArray(titleField));
|
return titleField && entryData.getIn(keyToPathArray(titleField));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const COMMIT_AUTHOR = 'commit_author';
|
|
||||||
export const COMMIT_DATE = 'commit_date';
|
|
||||||
|
|
||||||
export function selectDefaultSortableFields(
|
export function selectDefaultSortableFields(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
|
@ -1,37 +1,35 @@
|
|||||||
import { Map } from 'immutable';
|
import { produce } from 'immer';
|
||||||
import { CONFIG_REQUEST, CONFIG_SUCCESS, CONFIG_FAILURE } from '../actions/config';
|
import { CONFIG_REQUEST, CONFIG_SUCCESS, CONFIG_FAILURE, ConfigAction } from '../actions/config';
|
||||||
import { Config, ConfigAction } from '../types/redux';
|
|
||||||
import { EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
import { EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
||||||
|
import { CmsConfig } from '../types/redux';
|
||||||
|
|
||||||
const defaultState: Map<string, boolean | string> = Map({ isFetching: true });
|
const defaultState = {
|
||||||
|
isFetching: true,
|
||||||
|
};
|
||||||
|
|
||||||
function config(state = defaultState, action: ConfigAction) {
|
const config = produce((state: CmsConfig, action: ConfigAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_REQUEST:
|
case CONFIG_REQUEST:
|
||||||
return state.set('isFetching', true);
|
state.isFetching = true;
|
||||||
|
break;
|
||||||
case CONFIG_SUCCESS:
|
case CONFIG_SUCCESS:
|
||||||
/**
|
return {
|
||||||
* The loadConfig action merges any existing config into the loaded config
|
...action.payload,
|
||||||
* before firing this action (so the resulting config can be validated),
|
isFetching: false,
|
||||||
* so we don't have to merge it here.
|
error: undefined,
|
||||||
*/
|
};
|
||||||
return action.payload;
|
|
||||||
case CONFIG_FAILURE:
|
case CONFIG_FAILURE:
|
||||||
return state.withMutations(s => {
|
state.isFetching = false;
|
||||||
s.delete('isFetching');
|
state.error = action.payload.toString();
|
||||||
s.set('error', action.payload.toString());
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
}, defaultState);
|
||||||
|
|
||||||
|
export function selectLocale(state: CmsConfig) {
|
||||||
|
return state.locale || 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectLocale(state: Config) {
|
export function selectUseWorkflow(state: CmsConfig) {
|
||||||
return state.get('locale', 'en') as string;
|
return state.publish_mode === EDITORIAL_WORKFLOW;
|
||||||
}
|
|
||||||
|
|
||||||
export function selectUseWorkflow(state: Config) {
|
|
||||||
return state.get('publish_mode') === EDITORIAL_WORKFLOW;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -24,7 +24,7 @@ import { EditorialWorkflowAction, EditorialWorkflow, Entities } from '../types/r
|
|||||||
function unpublishedEntries(state = Map(), action: EditorialWorkflowAction) {
|
function unpublishedEntries(state = Map(), action: EditorialWorkflowAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS: {
|
case CONFIG_SUCCESS: {
|
||||||
const publishMode = action.payload && action.payload.get('publish_mode');
|
const publishMode = action.payload && action.payload.publish_mode;
|
||||||
if (publishMode === EDITORIAL_WORKFLOW) {
|
if (publishMode === EDITORIAL_WORKFLOW) {
|
||||||
// Editorial workflow state is explicitly initiated after the config.
|
// Editorial workflow state is explicitly initiated after the config.
|
||||||
return Map({ entities: Map(), pages: Map() });
|
return Map({ entities: Map(), pages: Map() });
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
EntriesSuccessPayload,
|
EntriesSuccessPayload,
|
||||||
EntryObject,
|
EntryObject,
|
||||||
Entries,
|
Entries,
|
||||||
Config,
|
CmsConfig,
|
||||||
Collection,
|
Collection,
|
||||||
EntryFailurePayload,
|
EntryFailurePayload,
|
||||||
EntryDeletePayload,
|
EntryDeletePayload,
|
||||||
@ -564,7 +564,7 @@ function hasCustomFolder(
|
|||||||
|
|
||||||
function traverseFields(
|
function traverseFields(
|
||||||
folderKey: 'media_folder' | 'public_folder',
|
folderKey: 'media_folder' | 'public_folder',
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
field: EntryField,
|
field: EntryField,
|
||||||
@ -579,7 +579,7 @@ function traverseFields(
|
|||||||
collection,
|
collection,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
folderKey,
|
folderKey,
|
||||||
config.get('slug'),
|
config.slug,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,7 +594,7 @@ function traverseFields(
|
|||||||
collection,
|
collection,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
folderKey,
|
folderKey,
|
||||||
config.get('slug'),
|
config.slug,
|
||||||
);
|
);
|
||||||
let fieldFolder = null;
|
let fieldFolder = null;
|
||||||
if (f.has('fields')) {
|
if (f.has('fields')) {
|
||||||
@ -638,12 +638,12 @@ function traverseFields(
|
|||||||
|
|
||||||
function evaluateFolder(
|
function evaluateFolder(
|
||||||
folderKey: 'media_folder' | 'public_folder',
|
folderKey: 'media_folder' | 'public_folder',
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) {
|
) {
|
||||||
let currentFolder = config.get(folderKey);
|
let currentFolder = config[folderKey]!;
|
||||||
|
|
||||||
// add identity template if doesn't exist
|
// add identity template if doesn't exist
|
||||||
if (!collection.has(folderKey)) {
|
if (!collection.has(folderKey)) {
|
||||||
@ -659,7 +659,7 @@ function evaluateFolder(
|
|||||||
collection,
|
collection,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
folderKey,
|
folderKey,
|
||||||
config.get('slug'),
|
config.slug,
|
||||||
);
|
);
|
||||||
|
|
||||||
let file = getFileField(collection.get('files')!, entryMap?.get('slug'));
|
let file = getFileField(collection.get('files')!, entryMap?.get('slug'));
|
||||||
@ -676,7 +676,7 @@ function evaluateFolder(
|
|||||||
collection,
|
collection,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
folderKey,
|
folderKey,
|
||||||
config.get('slug'),
|
config.slug,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
@ -704,7 +704,7 @@ function evaluateFolder(
|
|||||||
collection,
|
collection,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
folderKey,
|
folderKey,
|
||||||
config.get('slug'),
|
config.slug,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
@ -728,13 +728,13 @@ function evaluateFolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectMediaFolder(
|
export function selectMediaFolder(
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) {
|
) {
|
||||||
const name = 'media_folder';
|
const name = 'media_folder';
|
||||||
let mediaFolder = config.get(name);
|
let mediaFolder = config[name];
|
||||||
|
|
||||||
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
|
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
|
||||||
|
|
||||||
@ -755,7 +755,7 @@ export function selectMediaFolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectMediaFilePath(
|
export function selectMediaFilePath(
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
mediaPath: string,
|
mediaPath: string,
|
||||||
@ -771,7 +771,7 @@ export function selectMediaFilePath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectMediaFilePublicPath(
|
export function selectMediaFilePublicPath(
|
||||||
config: Config,
|
config: CmsConfig,
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
mediaPath: string,
|
mediaPath: string,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
@ -782,7 +782,7 @@ export function selectMediaFilePublicPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = 'public_folder';
|
const name = 'public_folder';
|
||||||
let publicFolder = config.get(name);
|
let publicFolder = config[name]!;
|
||||||
|
|
||||||
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
|
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { fromJS, List } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { CONFIG_SUCCESS } from '../actions/config';
|
import { CONFIG_SUCCESS, ConfigAction } from '../actions/config';
|
||||||
import { Integrations, IntegrationsAction, Integration, Config } from '../types/redux';
|
import { Integrations, CmsConfig } from '../types/redux';
|
||||||
|
|
||||||
interface Acc {
|
interface Acc {
|
||||||
providers: Record<string, {}>;
|
providers: Record<string, {}>;
|
||||||
hooks: Record<string, string | Record<string, string>>;
|
hooks: Record<string, string | Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIntegrations(config: Config) {
|
export function getIntegrations(config: CmsConfig) {
|
||||||
const integrations: Integration[] = config.get('integrations', List()).toJS() || [];
|
const integrations = config.integrations || [];
|
||||||
const newState = integrations.reduce(
|
const newState = integrations.reduce(
|
||||||
(acc, integration) => {
|
(acc, integration) => {
|
||||||
const { hooks, collections, provider, ...providerData } = integration;
|
const { hooks, collections, provider, ...providerData } = integration;
|
||||||
@ -20,12 +20,7 @@ export function getIntegrations(config: Config) {
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
const integrationCollections =
|
const integrationCollections =
|
||||||
collections === '*'
|
collections === '*' ? config.collections.map(collection => collection.name) : collections;
|
||||||
? config
|
|
||||||
.get('collections')
|
|
||||||
.map(collection => collection!.get('name'))
|
|
||||||
.toArray()
|
|
||||||
: (collections as string[]);
|
|
||||||
integrationCollections.forEach(collection => {
|
integrationCollections.forEach(collection => {
|
||||||
hooks.forEach(hook => {
|
hooks.forEach(hook => {
|
||||||
acc.hooks[collection]
|
acc.hooks[collection]
|
||||||
@ -40,7 +35,9 @@ export function getIntegrations(config: Config) {
|
|||||||
return fromJS(newState);
|
return fromJS(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
function integrations(state = null, action: IntegrationsAction): Integrations | null {
|
const defaultState = fromJS({ providers: {}, hooks: {} });
|
||||||
|
|
||||||
|
function integrations(state = defaultState, action: ConfigAction): Integrations | null {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS: {
|
case CONFIG_SUCCESS: {
|
||||||
return getIntegrations(action.payload);
|
return getIntegrations(action.payload);
|
||||||
|
@ -362,6 +362,14 @@ export interface CmsBackend {
|
|||||||
cms_label_prefix?: string;
|
cms_label_prefix?: string;
|
||||||
squash_merges?: boolean;
|
squash_merges?: boolean;
|
||||||
proxy_url?: string;
|
proxy_url?: string;
|
||||||
|
commit_messages?: {
|
||||||
|
create?: string;
|
||||||
|
update?: string;
|
||||||
|
delete?: string;
|
||||||
|
uploadMedia?: string;
|
||||||
|
deleteMedia?: string;
|
||||||
|
openAuthoring?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsSlug {
|
export interface CmsSlug {
|
||||||
@ -389,12 +397,22 @@ export interface CmsConfig {
|
|||||||
media_library?: CmsMediaLibrary;
|
media_library?: CmsMediaLibrary;
|
||||||
publish_mode?: CmsPublishMode;
|
publish_mode?: CmsPublishMode;
|
||||||
load_config_file?: boolean;
|
load_config_file?: boolean;
|
||||||
|
integrations?: {
|
||||||
|
hooks: string[];
|
||||||
|
provider: string;
|
||||||
|
collections?: '*' | string[];
|
||||||
|
applicationID?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
getSignedFormURL?: string;
|
||||||
|
}[];
|
||||||
slug?: CmsSlug;
|
slug?: CmsSlug;
|
||||||
i18n?: CmsI18nConfig;
|
i18n?: CmsI18nConfig;
|
||||||
local_backend?: boolean | CmsLocalBackend;
|
local_backend?: boolean | CmsLocalBackend;
|
||||||
editor?: {
|
editor?: {
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
};
|
};
|
||||||
|
error: string | undefined;
|
||||||
|
isFetching: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CmsMediaLibraryOptions = unknown; // TODO: type properly
|
export type CmsMediaLibraryOptions = unknown; // TODO: type properly
|
||||||
@ -675,7 +693,7 @@ export type Cursors = StaticallyTypedRecord<{}>;
|
|||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
auth: Auth;
|
auth: Auth;
|
||||||
config: Config;
|
config: CmsConfig;
|
||||||
cursors: Cursors;
|
cursors: Cursors;
|
||||||
collections: Collections;
|
collections: Collections;
|
||||||
deploys: Deploys;
|
deploys: Deploys;
|
||||||
@ -690,20 +708,12 @@ export interface State {
|
|||||||
status: Status;
|
status: Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigAction extends Action<string> {
|
|
||||||
payload: Map<string, boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Integration {
|
export interface Integration {
|
||||||
hooks: string[];
|
hooks: string[];
|
||||||
collections?: string | string[];
|
collections?: string | string[];
|
||||||
provider: string;
|
provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IntegrationsAction extends Action<string> {
|
|
||||||
payload: Config;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EntryPayload {
|
interface EntryPayload {
|
||||||
collection: string;
|
collection: string;
|
||||||
}
|
}
|
||||||
@ -785,12 +795,8 @@ export interface EntriesAction extends Action<string> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionsAction extends Action<string> {
|
|
||||||
payload?: StaticallyTypedRecord<{ collections: List<Collection> }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditorialWorkflowAction extends Action<string> {
|
export interface EditorialWorkflowAction extends Action<string> {
|
||||||
payload?: StaticallyTypedRecord<{ publish_mode: string }> & {
|
payload?: CmsConfig & {
|
||||||
collection: string;
|
collection: string;
|
||||||
entry: { slug: string };
|
entry: { slug: string };
|
||||||
} & {
|
} & {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user