refactor: convert function expressions to declarations (#4926)
This commit is contained in:
parent
c0236536dd
commit
141a2eba56
@ -30,6 +30,7 @@ module.exports = {
|
|||||||
'@emotion/styled-import': 'error',
|
'@emotion/styled-import': 'error',
|
||||||
'require-atomic-updates': [0],
|
'require-atomic-updates': [0],
|
||||||
'object-shorthand': ['error', 'always'],
|
'object-shorthand': ['error', 'always'],
|
||||||
|
'func-style': ['error', 'declaration'],
|
||||||
'prefer-const': [
|
'prefer-const': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
@ -74,7 +74,7 @@ const defaultPlugins = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
const presets = () => {
|
function presets() {
|
||||||
return [
|
return [
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
'@babel/preset-env',
|
'@babel/preset-env',
|
||||||
@ -86,9 +86,9 @@ const presets = () => {
|
|||||||
],
|
],
|
||||||
'@babel/typescript',
|
'@babel/typescript',
|
||||||
];
|
];
|
||||||
};
|
}
|
||||||
|
|
||||||
const plugins = () => {
|
function plugins() {
|
||||||
if (isESM) {
|
if (isESM) {
|
||||||
return [
|
return [
|
||||||
...defaultPlugins,
|
...defaultPlugins,
|
||||||
@ -129,7 +129,7 @@ const plugins = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return defaultPlugins;
|
return defaultPlugins;
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: presets(),
|
presets: presets(),
|
||||||
|
@ -161,7 +161,7 @@ function delay(ms: number) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChangeItem = (item: AzureCommitItem) => {
|
function getChangeItem(item: AzureCommitItem) {
|
||||||
switch (item.action) {
|
switch (item.action) {
|
||||||
case AzureCommitChangeType.ADD:
|
case AzureCommitChangeType.ADD:
|
||||||
return {
|
return {
|
||||||
@ -195,7 +195,7 @@ const getChangeItem = (item: AzureCommitItem) => {
|
|||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
type AzureCommitItem = {
|
type AzureCommitItem = {
|
||||||
action: AzureCommitChangeType;
|
action: AzureCommitChangeType;
|
||||||
|
@ -33,7 +33,7 @@ import {
|
|||||||
|
|
||||||
const MAX_CONCURRENT_DOWNLOADS = 10;
|
const MAX_CONCURRENT_DOWNLOADS = 10;
|
||||||
|
|
||||||
const parseAzureRepo = (config: Config) => {
|
function parseAzureRepo(config: Config) {
|
||||||
const { repo } = config.backend;
|
const { repo } = config.backend;
|
||||||
|
|
||||||
if (typeof repo !== 'string') {
|
if (typeof repo !== 'string') {
|
||||||
@ -51,7 +51,7 @@ const parseAzureRepo = (config: Config) => {
|
|||||||
project,
|
project,
|
||||||
repoName,
|
repoName,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class Azure implements Implementation {
|
export default class Azure implements Implementation {
|
||||||
lock: AsyncLock;
|
lock: AsyncLock;
|
||||||
|
@ -186,14 +186,14 @@ export const API_NAME = 'Bitbucket';
|
|||||||
|
|
||||||
const APPLICATION_JSON = 'application/json; charset=utf-8';
|
const APPLICATION_JSON = 'application/json; charset=utf-8';
|
||||||
|
|
||||||
const replace404WithEmptyResponse = (err: FetchError) => {
|
function replace404WithEmptyResponse(err: FetchError) {
|
||||||
if (err && err.status === 404) {
|
if (err && err.status === 404) {
|
||||||
console.log('This 404 was expected and handled appropriately.');
|
console.log('This 404 was expected and handled appropriately.');
|
||||||
return { size: 0, values: [] as BitBucketFile[] } as BitBucketSrcResult;
|
return { size: 0, values: [] as BitBucketFile[] } as BitBucketSrcResult;
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
apiRoot: string;
|
apiRoot: string;
|
||||||
|
@ -121,11 +121,11 @@ interface NetlifyUser extends Credentials {
|
|||||||
user_metadata: { full_name: string; avatar_url: string };
|
user_metadata: { full_name: string; avatar_url: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiGet = async (path: string) => {
|
async function apiGet(path: string) {
|
||||||
const apiRoot = 'https://api.netlify.com/api/v1/sites';
|
const apiRoot = 'https://api.netlify.com/api/v1/sites';
|
||||||
const response = await fetch(`${apiRoot}/${path}`).then(res => res.json());
|
const response = await fetch(`${apiRoot}/${path}`).then(res => res.json());
|
||||||
return response;
|
return response;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class GitGateway implements Implementation {
|
export default class GitGateway implements Implementation {
|
||||||
config: Config;
|
config: Config;
|
||||||
|
@ -15,8 +15,9 @@ type ClientConfig = {
|
|||||||
transformImages: ImageTransformations | boolean;
|
transformImages: ImageTransformations | boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const matchPath = ({ patterns }: ClientConfig, path: string) =>
|
export function matchPath({ patterns }: ClientConfig, path: string) {
|
||||||
patterns.some(pattern => minimatch(path, pattern, { matchBase: true }));
|
return patterns.some(pattern => minimatch(path, pattern, { matchBase: true }));
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// API interactions
|
// API interactions
|
||||||
@ -26,10 +27,10 @@ const defaultContentHeaders = {
|
|||||||
['Content-Type']: 'application/vnd.git-lfs+json',
|
['Content-Type']: 'application/vnd.git-lfs+json',
|
||||||
};
|
};
|
||||||
|
|
||||||
const resourceExists = async (
|
async function resourceExists(
|
||||||
{ rootURL, makeAuthorizedRequest }: ClientConfig,
|
{ rootURL, makeAuthorizedRequest }: ClientConfig,
|
||||||
{ sha, size }: PointerFile,
|
{ sha, size }: PointerFile,
|
||||||
) => {
|
) {
|
||||||
const response = await makeAuthorizedRequest({
|
const response = await makeAuthorizedRequest({
|
||||||
url: `${rootURL}/verify`,
|
url: `${rootURL}/verify`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -45,20 +46,20 @@ const resourceExists = async (
|
|||||||
|
|
||||||
// TODO: what kind of error to throw here? APIError doesn't seem
|
// TODO: what kind of error to throw here? APIError doesn't seem
|
||||||
// to fit
|
// to fit
|
||||||
};
|
}
|
||||||
|
|
||||||
const getTransofrmationsParams = (t: boolean | ImageTransformations) => {
|
function getTransofrmationsParams(t: boolean | ImageTransformations) {
|
||||||
if (isPlainObject(t) && !isEmpty(t)) {
|
if (isPlainObject(t) && !isEmpty(t)) {
|
||||||
const { nf_resize: resize, w, h } = t as ImageTransformations;
|
const { nf_resize: resize, w, h } = t as ImageTransformations;
|
||||||
return `?nf_resize=${resize}&w=${w}&h=${h}`;
|
return `?nf_resize=${resize}&w=${w}&h=${h}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
}
|
||||||
|
|
||||||
const getDownloadURL = async (
|
async function getDownloadURL(
|
||||||
{ rootURL, transformImages: t, makeAuthorizedRequest }: ClientConfig,
|
{ rootURL, transformImages: t, makeAuthorizedRequest }: ClientConfig,
|
||||||
{ sha }: PointerFile,
|
{ sha }: PointerFile,
|
||||||
) => {
|
) {
|
||||||
try {
|
try {
|
||||||
const transformation = getTransofrmationsParams(t);
|
const transformation = getTransofrmationsParams(t);
|
||||||
const transformedPromise = makeAuthorizedRequest(`${rootURL}/origin/${sha}${transformation}`);
|
const transformedPromise = makeAuthorizedRequest(`${rootURL}/origin/${sha}${transformation}`);
|
||||||
@ -81,21 +82,23 @@ const getDownloadURL = async (
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
return { url: '', blob: new Blob() };
|
return { url: '', blob: new Blob() };
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const uploadOperation = (objects: PointerFile[]) => ({
|
function uploadOperation(objects: PointerFile[]) {
|
||||||
|
return {
|
||||||
operation: 'upload',
|
operation: 'upload',
|
||||||
transfers: ['basic'],
|
transfers: ['basic'],
|
||||||
objects: objects.map(({ sha, ...rest }) => ({ ...rest, oid: sha })),
|
objects: objects.map(({ sha, ...rest }) => ({ ...rest, oid: sha })),
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const getResourceUploadURLs = async (
|
async function getResourceUploadURLs(
|
||||||
{
|
{
|
||||||
rootURL,
|
rootURL,
|
||||||
makeAuthorizedRequest,
|
makeAuthorizedRequest,
|
||||||
}: { rootURL: string; makeAuthorizedRequest: MakeAuthorizedRequest },
|
}: { rootURL: string; makeAuthorizedRequest: MakeAuthorizedRequest },
|
||||||
pointerFiles: PointerFile[],
|
pointerFiles: PointerFile[],
|
||||||
) => {
|
) {
|
||||||
const response = await makeAuthorizedRequest({
|
const response = await makeAuthorizedRequest({
|
||||||
url: `${rootURL}/objects/batch`,
|
url: `${rootURL}/objects/batch`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -113,19 +116,20 @@ const getResourceUploadURLs = async (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return uploadUrls;
|
return uploadUrls;
|
||||||
};
|
}
|
||||||
|
|
||||||
const uploadBlob = (uploadURL: string, blob: Blob) =>
|
function uploadBlob(uploadURL: string, blob: Blob) {
|
||||||
unsentRequest.fetchWithTimeout(uploadURL, {
|
return unsentRequest.fetchWithTimeout(uploadURL, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: blob,
|
body: blob,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const uploadResource = async (
|
async function uploadResource(
|
||||||
clientConfig: ClientConfig,
|
clientConfig: ClientConfig,
|
||||||
{ sha, size }: PointerFile,
|
{ sha, size }: PointerFile,
|
||||||
resource: Blob,
|
resource: Blob,
|
||||||
) => {
|
) {
|
||||||
const existingFile = await resourceExists(clientConfig, { sha, size });
|
const existingFile = await resourceExists(clientConfig, { sha, size });
|
||||||
if (existingFile) {
|
if (existingFile) {
|
||||||
return sha;
|
return sha;
|
||||||
@ -133,13 +137,14 @@ const uploadResource = async (
|
|||||||
const [uploadURL] = await getResourceUploadURLs(clientConfig, [{ sha, size }]);
|
const [uploadURL] = await getResourceUploadURLs(clientConfig, [{ sha, size }]);
|
||||||
await uploadBlob(uploadURL, resource);
|
await uploadBlob(uploadURL, resource);
|
||||||
return sha;
|
return sha;
|
||||||
};
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Create Large Media client
|
// Create Large Media client
|
||||||
|
|
||||||
const configureFn = (config: ClientConfig, fn: Function) => (...args: unknown[]) =>
|
function configureFn(config: ClientConfig, fn: Function) {
|
||||||
fn(config, ...args);
|
return (...args: unknown[]) => fn(config, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
const clientFns: Record<string, Function> = {
|
const clientFns: Record<string, Function> = {
|
||||||
resourceExists,
|
resourceExists,
|
||||||
@ -159,7 +164,7 @@ export type Client = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClient = (clientConfig: ClientConfig) => {
|
export function getClient(clientConfig: ClientConfig) {
|
||||||
return flow([
|
return flow([
|
||||||
Object.keys,
|
Object.keys,
|
||||||
map((key: string) => [key, configureFn(clientConfig, clientFns[key])]),
|
map((key: string) => [key, configureFn(clientConfig, clientFns[key])]),
|
||||||
@ -170,4 +175,4 @@ export const getClient = (clientConfig: ClientConfig) => {
|
|||||||
enabled: clientConfig.enabled,
|
enabled: clientConfig.enabled,
|
||||||
}),
|
}),
|
||||||
])(clientFns);
|
])(clientFns);
|
||||||
};
|
}
|
||||||
|
@ -129,12 +129,15 @@ type MediaFile = {
|
|||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const withCmsLabel = (pr: GitHubPull, cmsLabelPrefix: string) =>
|
function withCmsLabel(pr: GitHubPull, cmsLabelPrefix: string) {
|
||||||
pr.labels.some(l => isCMSLabel(l.name, cmsLabelPrefix));
|
return pr.labels.some(l => isCMSLabel(l.name, cmsLabelPrefix));
|
||||||
const withoutCmsLabel = (pr: GitHubPull, cmsLabelPrefix: string) =>
|
}
|
||||||
pr.labels.every(l => !isCMSLabel(l.name, cmsLabelPrefix));
|
|
||||||
|
|
||||||
const getTreeFiles = (files: GitHubCompareFiles) => {
|
function withoutCmsLabel(pr: GitHubPull, cmsLabelPrefix: string) {
|
||||||
|
return pr.labels.every(l => !isCMSLabel(l.name, cmsLabelPrefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTreeFiles(files: GitHubCompareFiles) {
|
||||||
const treeFiles = files.reduce((arr, file) => {
|
const treeFiles = files.reduce((arr, file) => {
|
||||||
if (file.status === 'removed') {
|
if (file.status === 'removed') {
|
||||||
// delete the file
|
// delete the file
|
||||||
@ -152,7 +155,7 @@ const getTreeFiles = (files: GitHubCompareFiles) => {
|
|||||||
}, [] as { sha: string | null; path: string }[]);
|
}, [] as { sha: string | null; path: string }[]);
|
||||||
|
|
||||||
return treeFiles;
|
return treeFiles;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type Diff = {
|
export type Diff = {
|
||||||
path: string;
|
path: string;
|
||||||
@ -446,7 +449,7 @@ export default class API {
|
|||||||
headers: { Accept: 'application/vnd.github.v3.raw' },
|
headers: { Accept: 'application/vnd.github.v3.raw' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorHandler = (err: Error) => {
|
function errorHandler(err: Error) {
|
||||||
if (err.message === 'Not Found') {
|
if (err.message === 'Not Found') {
|
||||||
console.log(
|
console.log(
|
||||||
'%c %s does not have metadata',
|
'%c %s does not have metadata',
|
||||||
@ -455,7 +458,7 @@ export default class API {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (!this.useOpenAuthoring) {
|
if (!this.useOpenAuthoring) {
|
||||||
const result = await this.request(
|
const result = await this.request(
|
||||||
|
@ -69,14 +69,14 @@ type GraphQLPullRequest = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformPullRequest = (pr: GraphQLPullRequest) => {
|
function transformPullRequest(pr: GraphQLPullRequest) {
|
||||||
return {
|
return {
|
||||||
...pr,
|
...pr,
|
||||||
labels: pr.labels.nodes,
|
labels: pr.labels.nodes,
|
||||||
head: { ref: pr.headRefName, sha: pr.headRefOid, repo: { fork: pr.repository.isFork } },
|
head: { ref: pr.headRefName, sha: pr.headRefOid, repo: { fork: pr.repository.isFork } },
|
||||||
base: { ref: pr.baseRefName, sha: pr.baseRefOid },
|
base: { ref: pr.baseRefName, sha: pr.baseRefOid },
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
type Error = GraphQLError & { type: string };
|
type Error = GraphQLError & { type: string };
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ describe('github API', () => {
|
|||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockAPI = (api, responses) => {
|
function mockAPI(api, responses) {
|
||||||
api.request = jest.fn().mockImplementation((path, options = {}) => {
|
api.request = jest.fn().mockImplementation((path, options = {}) => {
|
||||||
const normalizedPath = path.indexOf('?') !== -1 ? path.substr(0, path.indexOf('?')) : path;
|
const normalizedPath = path.indexOf('?') !== -1 ? path.substr(0, path.indexOf('?')) : path;
|
||||||
const response = responses[normalizedPath];
|
const response = responses[normalizedPath];
|
||||||
@ -16,7 +16,7 @@ describe('github API', () => {
|
|||||||
? Promise.resolve(response(options))
|
? Promise.resolve(response(options))
|
||||||
: Promise.reject(new Error(`No response for path '${normalizedPath}'`));
|
: Promise.reject(new Error(`No response for path '${normalizedPath}'`));
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('editorialWorkflowGit', () => {
|
describe('editorialWorkflowGit', () => {
|
||||||
it('should create PR with correct base branch name when publishing with editorial workflow', () => {
|
it('should create PR with correct base branch name when publishing with editorial workflow', () => {
|
||||||
|
@ -62,7 +62,7 @@ export const statues = gql`
|
|||||||
${fragments.object}
|
${fragments.object}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const buildFilesQuery = (depth = 1) => {
|
function buildFilesQuery(depth = 1) {
|
||||||
const PLACE_HOLDER = 'PLACE_HOLDER';
|
const PLACE_HOLDER = 'PLACE_HOLDER';
|
||||||
let query = oneLine`
|
let query = oneLine`
|
||||||
...ObjectParts
|
...ObjectParts
|
||||||
@ -93,9 +93,10 @@ const buildFilesQuery = (depth = 1) => {
|
|||||||
query = query.replace(PLACE_HOLDER, '');
|
query = query.replace(PLACE_HOLDER, '');
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const files = (depth: number) => gql`
|
export function files(depth: number) {
|
||||||
|
return gql`
|
||||||
query files($owner: String!, $name: String!, $expression: String!) {
|
query files($owner: String!, $name: String!, $expression: String!) {
|
||||||
repository(owner: $owner, name: $name) {
|
repository(owner: $owner, name: $name) {
|
||||||
...RepositoryParts
|
...RepositoryParts
|
||||||
@ -107,7 +108,8 @@ export const files = (depth: number) => gql`
|
|||||||
${fragments.repository}
|
${fragments.repository}
|
||||||
${fragments.object}
|
${fragments.object}
|
||||||
${fragments.fileEntry}
|
${fragments.fileEntry}
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
const branchQueryPart = `
|
const branchQueryPart = `
|
||||||
branch: ref(qualifiedName: $qualifiedName) {
|
branch: ref(qualifiedName: $qualifiedName) {
|
||||||
|
@ -171,14 +171,14 @@ type GitLabCommit = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMaxAccess = (groups: { group_access_level: number }[]) => {
|
export function getMaxAccess(groups: { group_access_level: number }[]) {
|
||||||
return groups.reduce((previous, current) => {
|
return groups.reduce((previous, current) => {
|
||||||
if (current.group_access_level > previous.group_access_level) {
|
if (current.group_access_level > previous.group_access_level) {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
return previous;
|
return previous;
|
||||||
}, groups[0]);
|
}, groups[0]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
apiRoot: string;
|
apiRoot: string;
|
||||||
|
@ -8,7 +8,7 @@ import AuthenticationPage from '../AuthenticationPage';
|
|||||||
|
|
||||||
const { Backend, LocalStorageAuthStore } = jest.requireActual('netlify-cms-core/src/backend');
|
const { Backend, LocalStorageAuthStore } = jest.requireActual('netlify-cms-core/src/backend');
|
||||||
|
|
||||||
const generateEntries = (path, length) => {
|
function generateEntries(path, length) {
|
||||||
const entries = Array.from({ length }, (val, idx) => {
|
const entries = Array.from({ length }, (val, idx) => {
|
||||||
const count = idx + 1;
|
const count = idx + 1;
|
||||||
const id = `00${count}`.slice(-3);
|
const id = `00${count}`.slice(-3);
|
||||||
@ -37,7 +37,7 @@ const generateEntries = (path, length) => {
|
|||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const manyEntries = generateEntries('many-entries', 500);
|
const manyEntries = generateEntries('many-entries', 500);
|
||||||
|
|
||||||
@ -222,8 +222,10 @@ describe('gitlab backend', () => {
|
|||||||
const pageNum = parseInt(page, 10);
|
const pageNum = parseInt(page, 10);
|
||||||
const pageCountNum = parseInt(pageCount, 10);
|
const pageCountNum = parseInt(pageCount, 10);
|
||||||
const url = `${backend.implementation.apiRoot}${basePath}`;
|
const url = `${backend.implementation.apiRoot}${basePath}`;
|
||||||
const link = linkPage =>
|
|
||||||
`<${url}?id=${expectedRepo}&page=${linkPage}&path=${path}&per_page=${perPage}&recursive=false>`;
|
function link(linkPage) {
|
||||||
|
return `<${url}?id=${expectedRepo}&page=${linkPage}&path=${path}&per_page=${perPage}&recursive=false>`;
|
||||||
|
}
|
||||||
|
|
||||||
const linkHeader = oneLine`
|
const linkHeader = oneLine`
|
||||||
${link(1)}; rel="first",
|
${link(1)}; rel="first",
|
||||||
|
@ -14,11 +14,10 @@ import {
|
|||||||
} from 'netlify-cms-lib-util';
|
} from 'netlify-cms-lib-util';
|
||||||
import AuthenticationPage from './AuthenticationPage';
|
import AuthenticationPage from './AuthenticationPage';
|
||||||
|
|
||||||
const serializeAsset = async (assetProxy: AssetProxy) => {
|
async function serializeAsset(assetProxy: AssetProxy) {
|
||||||
const base64content = await assetProxy.toBase64!();
|
const base64content = await assetProxy.toBase64!();
|
||||||
|
|
||||||
return { path: assetProxy.path, content: base64content, encoding: 'base64' };
|
return { path: assetProxy.path, content: base64content, encoding: 'base64' };
|
||||||
};
|
}
|
||||||
|
|
||||||
type MediaFile = {
|
type MediaFile = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -28,7 +27,7 @@ type MediaFile = {
|
|||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deserializeMediaFile = ({ id, content, encoding, path, name }: MediaFile) => {
|
function deserializeMediaFile({ id, content, encoding, path, name }: MediaFile) {
|
||||||
let byteArray = new Uint8Array(0);
|
let byteArray = new Uint8Array(0);
|
||||||
if (encoding !== 'base64') {
|
if (encoding !== 'base64') {
|
||||||
console.error(`Unsupported encoding '${encoding}' for file '${path}'`);
|
console.error(`Unsupported encoding '${encoding}' for file '${path}'`);
|
||||||
@ -43,7 +42,7 @@ const deserializeMediaFile = ({ id, content, encoding, path, name }: MediaFile)
|
|||||||
const file = blobToFileObj(name, blob);
|
const file = blobToFileObj(name, blob);
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
return { id, name, path, file, size: file.size, url, displayURL: url };
|
return { id, name, path, file, size: file.size, url, displayURL: url };
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class ProxyBackend implements Implementation {
|
export default class ProxyBackend implements Implementation {
|
||||||
proxyUrl: string;
|
proxyUrl: string;
|
||||||
|
@ -74,13 +74,13 @@ function deleteFile(path: string, tree: RepoTree) {
|
|||||||
|
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
|
|
||||||
const getCursor = (
|
function getCursor(
|
||||||
folder: string,
|
folder: string,
|
||||||
extension: string,
|
extension: string,
|
||||||
entries: ImplementationEntry[],
|
entries: ImplementationEntry[],
|
||||||
index: number,
|
index: number,
|
||||||
depth: number,
|
depth: number,
|
||||||
) => {
|
) {
|
||||||
const count = entries.length;
|
const count = entries.length;
|
||||||
const pageCount = Math.floor(count / pageSize);
|
const pageCount = Math.floor(count / pageSize);
|
||||||
return Cursor.create({
|
return Cursor.create({
|
||||||
@ -91,16 +91,16 @@ const getCursor = (
|
|||||||
meta: { index, count, pageSize, pageCount },
|
meta: { index, count, pageSize, pageCount },
|
||||||
data: { folder, extension, index, pageCount, depth },
|
data: { folder, extension, index, pageCount, depth },
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getFolderFiles = (
|
export function getFolderFiles(
|
||||||
tree: RepoTree,
|
tree: RepoTree,
|
||||||
folder: string,
|
folder: string,
|
||||||
extension: string,
|
extension: string,
|
||||||
depth: number,
|
depth: number,
|
||||||
files = [] as RepoFile[],
|
files = [] as RepoFile[],
|
||||||
path = folder,
|
path = folder,
|
||||||
) => {
|
) {
|
||||||
if (depth <= 0) {
|
if (depth <= 0) {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ export const getFolderFiles = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class TestBackend implements Implementation {
|
export default class TestBackend implements Implementation {
|
||||||
mediaFolder: string;
|
mediaFolder: string;
|
||||||
|
@ -817,14 +817,14 @@ describe('config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('detectProxyServer', () => {
|
describe('detectProxyServer', () => {
|
||||||
const assetFetchCalled = (url = 'http://localhost:8081/api/v1') => {
|
function assetFetchCalled(url = 'http://localhost:8081/api/v1') {
|
||||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
expect(global.fetch).toHaveBeenCalledWith(url, {
|
expect(global.fetch).toHaveBeenCalledWith(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ action: 'info' }),
|
body: JSON.stringify({ action: 'info' }),
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
delete window.location;
|
delete window.location;
|
||||||
|
@ -13,7 +13,7 @@ export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
|||||||
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
||||||
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
||||||
|
|
||||||
const traverseFieldsJS = (fields, updater) => {
|
function traverseFieldsJS(fields, updater) {
|
||||||
return fields.map(field => {
|
return fields.map(field => {
|
||||||
let newField = updater(field);
|
let newField = updater(field);
|
||||||
if (newField.fields) {
|
if (newField.fields) {
|
||||||
@ -26,9 +26,9 @@ const traverseFieldsJS = (fields, updater) => {
|
|||||||
|
|
||||||
return newField;
|
return newField;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const getConfigUrl = () => {
|
function getConfigUrl() {
|
||||||
const validTypes = { 'text/yaml': 'yaml', 'application/x-yaml': 'yaml' };
|
const validTypes = { 'text/yaml': 'yaml', 'application/x-yaml': 'yaml' };
|
||||||
const configLinkEl = document.querySelector('link[rel="cms-config-url"]');
|
const configLinkEl = document.querySelector('link[rel="cms-config-url"]');
|
||||||
const isValidLink = configLinkEl && validTypes[configLinkEl.type] && get(configLinkEl, 'href');
|
const isValidLink = configLinkEl && validTypes[configLinkEl.type] && get(configLinkEl, 'href');
|
||||||
@ -38,14 +38,14 @@ const getConfigUrl = () => {
|
|||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
return 'config.yml';
|
return 'config.yml';
|
||||||
};
|
}
|
||||||
|
|
||||||
const setDefaultPublicFolder = map => {
|
function setDefaultPublicFolder(map) {
|
||||||
if (map.has('media_folder') && !map.has('public_folder')) {
|
if (map.has('media_folder') && !map.has('public_folder')) {
|
||||||
map = map.set('public_folder', map.get('media_folder'));
|
map = map.set('public_folder', map.get('media_folder'));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Mapping between existing camelCase and its snake_case counterpart
|
// Mapping between existing camelCase and its snake_case counterpart
|
||||||
const WIDGET_KEY_MAP = {
|
const WIDGET_KEY_MAP = {
|
||||||
@ -60,7 +60,7 @@ const WIDGET_KEY_MAP = {
|
|||||||
optionsLength: 'options_length',
|
optionsLength: 'options_length',
|
||||||
};
|
};
|
||||||
|
|
||||||
const setSnakeCaseConfig = field => {
|
function setSnakeCaseConfig(field) {
|
||||||
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(camel => camel in field);
|
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(camel => camel in field);
|
||||||
const snakeValues = deprecatedKeys.map(camel => {
|
const snakeValues = deprecatedKeys.map(camel => {
|
||||||
const snake = WIDGET_KEY_MAP[camel];
|
const snake = WIDGET_KEY_MAP[camel];
|
||||||
@ -71,18 +71,18 @@ const setSnakeCaseConfig = field => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return Object.assign({}, field, ...snakeValues);
|
return Object.assign({}, field, ...snakeValues);
|
||||||
};
|
}
|
||||||
|
|
||||||
const setI18nField = field => {
|
function setI18nField(field) {
|
||||||
if (field.get(I18N) === true) {
|
if (field.get(I18N) === true) {
|
||||||
field = field.set(I18N, I18N_FIELD.TRANSLATE);
|
field = field.set(I18N, I18N_FIELD.TRANSLATE);
|
||||||
} else if (field.get(I18N) === false || !field.has(I18N)) {
|
} else if (field.get(I18N) === false || !field.has(I18N)) {
|
||||||
field = field.set(I18N, I18N_FIELD.NONE);
|
field = field.set(I18N, I18N_FIELD.NONE);
|
||||||
}
|
}
|
||||||
return field;
|
return field;
|
||||||
};
|
}
|
||||||
|
|
||||||
const setI18nDefaults = (defaultI18n, collectionOrFile) => {
|
function setI18nDefaults(defaultI18n, collectionOrFile) {
|
||||||
if (defaultI18n && collectionOrFile.has(I18N)) {
|
if (defaultI18n && collectionOrFile.has(I18N)) {
|
||||||
const collectionOrFileI18n = collectionOrFile.get(I18N);
|
const collectionOrFileI18n = collectionOrFile.get(I18N);
|
||||||
if (collectionOrFileI18n === true) {
|
if (collectionOrFileI18n === true) {
|
||||||
@ -121,17 +121,17 @@ const setI18nDefaults = (defaultI18n, collectionOrFile) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collectionOrFile;
|
return collectionOrFile;
|
||||||
};
|
}
|
||||||
|
|
||||||
const throwOnInvalidFileCollectionStructure = i18n => {
|
function throwOnInvalidFileCollectionStructure(i18n) {
|
||||||
if (i18n && i18n.get('structure') !== I18N_STRUCTURE.SINGLE_FILE) {
|
if (i18n && i18n.get('structure') !== I18N_STRUCTURE.SINGLE_FILE) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`,
|
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const throwOnMissingDefaultLocale = i18n => {
|
function throwOnMissingDefaultLocale(i18n) {
|
||||||
if (i18n && !i18n.get('locales').includes(i18n.get('default_locale'))) {
|
if (i18n && !i18n.get('locales').includes(i18n.get('default_locale'))) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`i18n locales '${i18n.get('locales').join(', ')}' are missing the default locale ${i18n.get(
|
`i18n locales '${i18n.get('locales').join(', ')}' are missing the default locale ${i18n.get(
|
||||||
@ -139,9 +139,9 @@ const throwOnMissingDefaultLocale = i18n => {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const setViewPatternsDefaults = (key, collection) => {
|
function setViewPatternsDefaults(key, collection) {
|
||||||
if (!collection.has(key)) {
|
if (!collection.has(key)) {
|
||||||
collection = collection.set(key, fromJS([]));
|
collection = collection.set(key, fromJS([]));
|
||||||
} else {
|
} else {
|
||||||
@ -152,17 +152,17 @@ const setViewPatternsDefaults = (key, collection) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
};
|
}
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
publish_mode: SIMPLE_PUBLISH_MODE,
|
publish_mode: SIMPLE_PUBLISH_MODE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasIntegration = (config, collection) => {
|
function hasIntegration(config, collection) {
|
||||||
const integrations = getIntegrations(config);
|
const integrations = getIntegrations(config);
|
||||||
const integration = selectIntegration(integrations, collection.get('name'), 'listEntries');
|
const integration = selectIntegration(integrations, collection.get('name'), 'listEntries');
|
||||||
return !!integration;
|
return !!integration;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function normalizeConfig(config) {
|
export function normalizeConfig(config) {
|
||||||
const { collections = [] } = config;
|
const { collections = [] } = config;
|
||||||
@ -390,7 +390,7 @@ export async function detectProxyServer(localBackend) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPublishMode = (config, publishModes, backendType) => {
|
function getPublishMode(config, publishModes, backendType) {
|
||||||
if (config.publish_mode && publishModes && !publishModes.includes(config.publish_mode)) {
|
if (config.publish_mode && publishModes && !publishModes.includes(config.publish_mode)) {
|
||||||
const newPublishMode = publishModes[0];
|
const newPublishMode = publishModes[0];
|
||||||
console.log(
|
console.log(
|
||||||
@ -400,9 +400,9 @@ const getPublishMode = (config, publishModes, backendType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return config.publish_mode;
|
return config.publish_mode;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const handleLocalBackend = async config => {
|
export async function handleLocalBackend(config) {
|
||||||
if (!config.local_backend) {
|
if (!config.local_backend) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@ -421,7 +421,7 @@ export const handleLocalBackend = async config => {
|
|||||||
...(publishMode && { publish_mode: publishMode }),
|
...(publishMode && { publish_mode: publishMode }),
|
||||||
backend: { ...config.backend, name: 'proxy', proxy_url: proxyUrl },
|
backend: { ...config.backend, name: 'proxy', proxy_url: proxyUrl },
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export function loadConfig(manualConfig = {}, onLoad) {
|
export function loadConfig(manualConfig = {}, onLoad) {
|
||||||
if (window.CMS_CONFIG) {
|
if (window.CMS_CONFIG) {
|
||||||
|
@ -154,7 +154,7 @@ export function entriesFailed(collection: Collection, error: Error) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllEntries = async (state: State, collection: Collection) => {
|
async function getAllEntries(state: State, collection: Collection) {
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
const integration = selectIntegration(state, collection.get('name'), 'listEntries');
|
const integration = selectIntegration(state, collection.get('name'), 'listEntries');
|
||||||
const provider: Backend = integration
|
const provider: Backend = integration
|
||||||
@ -162,7 +162,7 @@ const getAllEntries = async (state: State, collection: Collection) => {
|
|||||||
: backend;
|
: backend;
|
||||||
const entries = await provider.listAllEntries(collection);
|
const entries = await provider.listAllEntries(collection);
|
||||||
return entries;
|
return entries;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function sortByField(
|
export function sortByField(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
@ -555,7 +555,7 @@ const appendActions = fromJS({
|
|||||||
['append_next']: { action: 'next', append: true },
|
['append_next']: { action: 'next', append: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const addAppendActionsToCursor = (cursor: Cursor) => {
|
function addAppendActionsToCursor(cursor: Cursor) {
|
||||||
return Cursor.create(cursor).updateStore('actions', (actions: Set<string>) => {
|
return Cursor.create(cursor).updateStore('actions', (actions: Set<string>) => {
|
||||||
return actions.union(
|
return actions.union(
|
||||||
appendActions
|
appendActions
|
||||||
@ -563,7 +563,7 @@ const addAppendActionsToCursor = (cursor: Cursor) => {
|
|||||||
.keySeq(),
|
.keySeq(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export function loadEntries(collection: Collection, page = 0) {
|
export function loadEntries(collection: Collection, page = 0) {
|
||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
@ -692,16 +692,16 @@ export function traverseCollectionCursor(collection: Collection, action: string)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const escapeHtml = (unsafe: string) => {
|
function escapeHtml(unsafe: string) {
|
||||||
return unsafe
|
return unsafe
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
}
|
||||||
|
|
||||||
const processValue = (unsafe: string) => {
|
function processValue(unsafe: string) {
|
||||||
if (['true', 'True', 'TRUE'].includes(unsafe)) {
|
if (['true', 'True', 'TRUE'].includes(unsafe)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -710,10 +710,15 @@ const processValue = (unsafe: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return escapeHtml(unsafe);
|
return escapeHtml(unsafe);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getDataFields = (fields: EntryFields) => fields.filter(f => !f!.get('meta')).toList();
|
function getDataFields(fields: EntryFields) {
|
||||||
const getMetaFields = (fields: EntryFields) => fields.filter(f => f!.get('meta') === true).toList();
|
return fields.filter(f => !f!.get('meta')).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMetaFields(fields: EntryFields) {
|
||||||
|
return fields.filter(f => f!.get('meta') === true).toList();
|
||||||
|
}
|
||||||
|
|
||||||
export function createEmptyDraft(collection: Collection, search: string) {
|
export function createEmptyDraft(collection: Collection, search: string) {
|
||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
@ -785,7 +790,10 @@ export function createEmptyDraftData(
|
|||||||
const list = item.get('widget') == 'list';
|
const list = item.get('widget') == 'list';
|
||||||
const name = item.get('name');
|
const name = item.get('name');
|
||||||
const defaultValue = item.get('default', null);
|
const defaultValue = item.get('default', null);
|
||||||
const isEmptyDefaultValue = (val: unknown) => [[{}], {}].some(e => isEqual(val, e));
|
|
||||||
|
function isEmptyDefaultValue(val: unknown) {
|
||||||
|
return [[{}], {}].some(e => isEqual(val, e));
|
||||||
|
}
|
||||||
|
|
||||||
if (List.isList(subfields)) {
|
if (List.isList(subfields)) {
|
||||||
const subDefaultValue = list
|
const subDefaultValue = list
|
||||||
@ -831,9 +839,9 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: EntryField
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const skipField = (field: EntryField) => {
|
function skipField(field: EntryField) {
|
||||||
return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE;
|
return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE;
|
||||||
};
|
}
|
||||||
|
|
||||||
const i18nData = createEmptyDraftData(dataFields, true, skipField);
|
const i18nData = createEmptyDraftData(dataFields, true, skipField);
|
||||||
return duplicateDefaultI18nFields(collection, i18nData);
|
return duplicateDefaultI18nFields(collection, i18nData);
|
||||||
@ -850,23 +858,25 @@ export function getMediaAssets({ entry }: { entry: EntryMap }) {
|
|||||||
return assets;
|
return assets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSerializedEntry = (collection: Collection, entry: Entry) => {
|
export function getSerializedEntry(collection: Collection, entry: Entry) {
|
||||||
/**
|
/**
|
||||||
* Serialize the values of any fields with registered serializers, and
|
* Serialize the values of any fields with registered serializers, and
|
||||||
* update the entry and entryDraft with the serialized values.
|
* update the entry and entryDraft with the serialized values.
|
||||||
*/
|
*/
|
||||||
const fields = selectFields(collection, entry.get('slug'));
|
const fields = selectFields(collection, entry.get('slug'));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const serializeData = (data: any) => {
|
function serializeData(data: any) {
|
||||||
return serializeValues(data, fields);
|
return serializeValues(data, fields);
|
||||||
};
|
}
|
||||||
|
|
||||||
const serializedData = serializeData(entry.get('data'));
|
const serializedData = serializeData(entry.get('data'));
|
||||||
let serializedEntry = entry.set('data', serializedData);
|
let serializedEntry = entry.set('data', serializedData);
|
||||||
if (hasI18n(collection)) {
|
if (hasI18n(collection)) {
|
||||||
serializedEntry = serializeI18n(collection, serializedEntry, serializeData);
|
serializedEntry = serializeI18n(collection, serializedEntry, serializeData);
|
||||||
}
|
}
|
||||||
return serializedEntry;
|
return serializedEntry;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function persistEntry(collection: Collection) {
|
export function persistEntry(collection: Collection) {
|
||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
@ -982,11 +992,11 @@ export function deleteEntry(collection: Collection, slug: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPathError = (
|
function getPathError(
|
||||||
path: string | undefined,
|
path: string | undefined,
|
||||||
key: string,
|
key: string,
|
||||||
t: (key: string, args: Record<string, unknown>) => string,
|
t: (key: string, args: Record<string, unknown>) => string,
|
||||||
) => {
|
) {
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
type: ValidationErrorTypes.CUSTOM,
|
type: ValidationErrorTypes.CUSTOM,
|
||||||
@ -995,7 +1005,7 @@ const getPathError = (
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export function validateMetaField(
|
export function validateMetaField(
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -82,10 +82,10 @@ export function boundGetAsset(
|
|||||||
collection: Collection,
|
collection: Collection,
|
||||||
entry: EntryMap,
|
entry: EntryMap,
|
||||||
) {
|
) {
|
||||||
const bound = (path: string, field: EntryField) => {
|
function bound(path: string, field: EntryField) {
|
||||||
const asset = dispatch(getAsset({ collection, entry, path, field }));
|
const asset = dispatch(getAsset({ collection, entry, path, field }));
|
||||||
return asset;
|
return asset;
|
||||||
};
|
}
|
||||||
|
|
||||||
return bound;
|
return bound;
|
||||||
}
|
}
|
||||||
|
@ -158,8 +158,8 @@ export function loadMedia(
|
|||||||
}
|
}
|
||||||
dispatch(mediaLoading(page));
|
dispatch(mediaLoading(page));
|
||||||
|
|
||||||
const loadFunction = () =>
|
function loadFunction() {
|
||||||
backend
|
return backend
|
||||||
.getMedia()
|
.getMedia()
|
||||||
.then(files => dispatch(mediaLoaded(files)))
|
.then(files => dispatch(mediaLoaded(files)))
|
||||||
.catch((error: { status?: number }) => {
|
.catch((error: { status?: number }) => {
|
||||||
@ -171,6 +171,7 @@ export function loadMedia(
|
|||||||
dispatch(mediaLoadFailed());
|
dispatch(mediaLoadFailed());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
@ -3,19 +3,19 @@ import { ThunkDispatch } from 'redux-thunk';
|
|||||||
import { AnyAction } from 'redux';
|
import { AnyAction } from 'redux';
|
||||||
import { State } from '../types/redux';
|
import { State } from '../types/redux';
|
||||||
|
|
||||||
export const waitUntil = ({ predicate, run }: WaitActionArgs) => {
|
export function waitUntil({ predicate, run }: WaitActionArgs) {
|
||||||
return {
|
return {
|
||||||
type: WAIT_UNTIL_ACTION,
|
type: WAIT_UNTIL_ACTION,
|
||||||
predicate,
|
predicate,
|
||||||
run,
|
run,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export const waitUntilWithTimeout = async <T>(
|
export async function waitUntilWithTimeout<T>(
|
||||||
dispatch: ThunkDispatch<State, {}, AnyAction>,
|
dispatch: ThunkDispatch<State, {}, AnyAction>,
|
||||||
waitActionArgs: (resolve: (value?: T) => void) => WaitActionArgs,
|
waitActionArgs: (resolve: (value?: T) => void) => WaitActionArgs,
|
||||||
timeout = 30000,
|
timeout = 30000,
|
||||||
): Promise<T | null> => {
|
): Promise<T | null> {
|
||||||
let waitDone = false;
|
let waitDone = false;
|
||||||
|
|
||||||
const waitPromise = new Promise<T>(resolve => {
|
const waitPromise = new Promise<T>(resolve => {
|
||||||
@ -44,4 +44,4 @@ export const waitUntilWithTimeout = async <T>(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
@ -71,13 +71,13 @@ import {
|
|||||||
|
|
||||||
const { extractTemplateVars, dateParsers, expandPath } = stringTemplate;
|
const { extractTemplateVars, dateParsers, expandPath } = stringTemplate;
|
||||||
|
|
||||||
const updateAssetProxies = (
|
function updateAssetProxies(
|
||||||
assetProxies: AssetProxy[],
|
assetProxies: AssetProxy[],
|
||||||
config: Config,
|
config: Config,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryDraft: EntryDraft,
|
entryDraft: EntryDraft,
|
||||||
path: string,
|
path: string,
|
||||||
) => {
|
) {
|
||||||
assetProxies.map(asset => {
|
assetProxies.map(asset => {
|
||||||
// update media files path based on entry path
|
// update media files path based on entry path
|
||||||
const oldPath = asset.path;
|
const oldPath = asset.path;
|
||||||
@ -90,7 +90,7 @@ const updateAssetProxies = (
|
|||||||
);
|
);
|
||||||
asset.path = newPath;
|
asset.path = newPath;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export class LocalStorageAuthStore {
|
export class LocalStorageAuthStore {
|
||||||
storageKey = 'netlify-cms-user';
|
storageKey = 'netlify-cms-user';
|
||||||
@ -118,7 +118,7 @@ function getEntryBackupKey(collectionName?: string, slug?: string) {
|
|||||||
return `${baseKey}.${collectionName}${suffix}`;
|
return `${baseKey}.${collectionName}${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEntryField = (field: string, entry: EntryValue) => {
|
function getEntryField(field: string, entry: EntryValue) {
|
||||||
const value = get(entry.data, field);
|
const value = get(entry.data, field);
|
||||||
if (value) {
|
if (value) {
|
||||||
return String(value);
|
return String(value);
|
||||||
@ -131,9 +131,10 @@ const getEntryField = (field: string, entry: EntryValue) => {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const extractSearchFields = (searchFields: string[]) => (entry: EntryValue) =>
|
export function extractSearchFields(searchFields: string[]) {
|
||||||
|
return (entry: EntryValue) =>
|
||||||
searchFields.reduce((acc, field) => {
|
searchFields.reduce((acc, field) => {
|
||||||
const value = getEntryField(field, entry);
|
const value = getEntryField(field, entry);
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -142,8 +143,9 @@ export const extractSearchFields = (searchFields: string[]) => (entry: EntryValu
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
}, '');
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
export const expandSearchEntries = (entries: EntryValue[], searchFields: string[]) => {
|
export function expandSearchEntries(entries: EntryValue[], searchFields: string[]) {
|
||||||
// expand the entries for the purpose of the search
|
// expand the entries for the purpose of the search
|
||||||
const expandedEntries = entries.reduce((acc, e) => {
|
const expandedEntries = entries.reduce((acc, e) => {
|
||||||
const expandedFields = searchFields.reduce((acc, f) => {
|
const expandedFields = searchFields.reduce((acc, f) => {
|
||||||
@ -160,9 +162,9 @@ export const expandSearchEntries = (entries: EntryValue[], searchFields: string[
|
|||||||
}, [] as (EntryValue & { field: string })[]);
|
}, [] as (EntryValue & { field: string })[]);
|
||||||
|
|
||||||
return expandedEntries;
|
return expandedEntries;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const mergeExpandedEntries = (entries: (EntryValue & { field: string })[]) => {
|
export function mergeExpandedEntries(entries: (EntryValue & { field: string })[]) {
|
||||||
// merge the search results by slug and only keep data that matched the search
|
// merge the search results by slug and only keep data that matched the search
|
||||||
const fields = entries.map(f => f.field);
|
const fields = entries.map(f => f.field);
|
||||||
const arrayPaths: Record<string, Set<string>> = {};
|
const arrayPaths: Record<string, Set<string>> = {};
|
||||||
@ -214,20 +216,20 @@ export const mergeExpandedEntries = (entries: (EntryValue & { field: string })[]
|
|||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(merged);
|
return Object.values(merged);
|
||||||
};
|
}
|
||||||
|
|
||||||
const sortByScore = (a: fuzzy.FilterResult<EntryValue>, b: fuzzy.FilterResult<EntryValue>) => {
|
function sortByScore(a: fuzzy.FilterResult<EntryValue>, b: fuzzy.FilterResult<EntryValue>) {
|
||||||
if (a.score > b.score) return -1;
|
if (a.score > b.score) return -1;
|
||||||
if (a.score < b.score) return 1;
|
if (a.score < b.score) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const slugFromCustomPath = (collection: Collection, customPath: string) => {
|
export function slugFromCustomPath(collection: Collection, customPath: string) {
|
||||||
const folderPath = collection.get('folder', '') as string;
|
const folderPath = collection.get('folder', '') as string;
|
||||||
const entryPath = customPath.toLowerCase().replace(folderPath.toLowerCase(), '');
|
const entryPath = customPath.toLowerCase().replace(folderPath.toLowerCase(), '');
|
||||||
const slug = join(dirname(trim(entryPath, '/')), basename(entryPath, extname(customPath)));
|
const slug = join(dirname(trim(entryPath, '/')), basename(entryPath, extname(customPath)));
|
||||||
return slug;
|
return slug;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface AuthStore {
|
interface AuthStore {
|
||||||
retrieve: () => User;
|
retrieve: () => User;
|
||||||
@ -280,15 +282,15 @@ type Implementation = BackendImplementation & {
|
|||||||
init: (config: ImplementationConfig, options: ImplementationInitOptions) => Implementation;
|
init: (config: ImplementationConfig, options: ImplementationInitOptions) => Implementation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepareMetaPath = (path: string, collection: Collection) => {
|
function prepareMetaPath(path: string, collection: Collection) {
|
||||||
if (!selectHasMetaPath(collection)) {
|
if (!selectHasMetaPath(collection)) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
const dir = dirname(path);
|
const dir = dirname(path);
|
||||||
return dir.substr(collection.get('folder')!.length + 1) || '/';
|
return dir.substr(collection.get('folder')!.length + 1) || '/';
|
||||||
};
|
}
|
||||||
|
|
||||||
const collectionDepth = (collection: Collection) => {
|
function collectionDepth(collection: Collection) {
|
||||||
let depth;
|
let depth;
|
||||||
depth =
|
depth =
|
||||||
collection.get('nested')?.get('depth') || getPathDepth(collection.get('path', '') as string);
|
collection.get('nested')?.get('depth') || getPathDepth(collection.get('path', '') as string);
|
||||||
@ -298,7 +300,7 @@ const collectionDepth = (collection: Collection) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return depth;
|
return depth;
|
||||||
};
|
}
|
||||||
|
|
||||||
export class Backend {
|
export class Backend {
|
||||||
implementation: Implementation;
|
implementation: Implementation;
|
||||||
|
12
packages/netlify-cms-core/src/bootstrap.js
vendored
12
packages/netlify-cms-core/src/bootstrap.js
vendored
@ -19,7 +19,7 @@ import 'what-input';
|
|||||||
|
|
||||||
const ROOT_ID = 'nc-root';
|
const ROOT_ID = 'nc-root';
|
||||||
|
|
||||||
const TranslatedApp = ({ locale, config }) => {
|
function TranslatedApp({ locale, config }) {
|
||||||
return (
|
return (
|
||||||
<I18n locale={locale} messages={getPhrases(locale)}>
|
<I18n locale={locale} messages={getPhrases(locale)}>
|
||||||
<ErrorBoundary showBackup config={config}>
|
<ErrorBoundary showBackup config={config}>
|
||||||
@ -29,11 +29,11 @@ const TranslatedApp = ({ locale, config }) => {
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</I18n>
|
</I18n>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = state => {
|
function mapDispatchToProps(state) {
|
||||||
return { locale: selectLocale(state.config), config: state.config };
|
return { locale: selectLocale(state.config), config: state.config };
|
||||||
};
|
}
|
||||||
|
|
||||||
const ConnectedTranslatedApp = connect(mapDispatchToProps)(TranslatedApp);
|
const ConnectedTranslatedApp = connect(mapDispatchToProps)(TranslatedApp);
|
||||||
|
|
||||||
@ -82,7 +82,8 @@ function bootstrap(opts = {}) {
|
|||||||
/**
|
/**
|
||||||
* Create connected root component.
|
* Create connected root component.
|
||||||
*/
|
*/
|
||||||
const Root = () => (
|
function Root() {
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalStyles />
|
<GlobalStyles />
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@ -90,6 +91,7 @@ function bootstrap(opts = {}) {
|
|||||||
</Provider>
|
</Provider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render application root.
|
* Render application root.
|
||||||
|
@ -48,16 +48,16 @@ const ErrorCodeBlock = styled.pre`
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getDefaultPath = collections => {
|
function getDefaultPath(collections) {
|
||||||
const first = collections.filter(collection => collection.get('hide') !== true).first();
|
const first = collections.filter(collection => collection.get('hide') !== true).first();
|
||||||
if (first) {
|
if (first) {
|
||||||
return `/collections/${first.get('name')}`;
|
return `/collections/${first.get('name')}`;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Could not find a non hidden collection');
|
throw new Error('Could not find a non hidden collection');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const RouteInCollection = ({ collections, render, ...props }) => {
|
function RouteInCollection({ collections, render, ...props }) {
|
||||||
const defaultPath = getDefaultPath(collections);
|
const defaultPath = getDefaultPath(collections);
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
@ -68,7 +68,7 @@ const RouteInCollection = ({ collections, render, ...props }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -26,7 +26,8 @@ const styles = {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppHeader = props => (
|
function AppHeader(props) {
|
||||||
|
return (
|
||||||
<header
|
<header
|
||||||
css={css`
|
css={css`
|
||||||
${shadows.dropMain};
|
${shadows.dropMain};
|
||||||
@ -39,7 +40,8 @@ const AppHeader = props => (
|
|||||||
`}
|
`}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const AppHeaderContent = styled.div`
|
const AppHeaderContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -8,11 +8,13 @@ const NotFoundContainer = styled.div`
|
|||||||
margin: ${lengths.pageMargin};
|
margin: ${lengths.pageMargin};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NotFoundPage = ({ t }) => (
|
function NotFoundPage({ t }) {
|
||||||
|
return (
|
||||||
<NotFoundContainer>
|
<NotFoundContainer>
|
||||||
<h2>{t('app.notFoundPage.header')}</h2>
|
<h2>{t('app.notFoundPage.header')}</h2>
|
||||||
</NotFoundContainer>
|
</NotFoundContainer>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
NotFoundPage.propTypes = {
|
NotFoundPage.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
@ -183,7 +183,7 @@ const mapDispatchToProps = {
|
|||||||
groupByField,
|
groupByField,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||||
return {
|
return {
|
||||||
...stateProps,
|
...stateProps,
|
||||||
...ownProps,
|
...ownProps,
|
||||||
@ -193,7 +193,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
onGroupClick: group => dispatchProps.groupByField(stateProps.collection, group),
|
onGroupClick: group => dispatchProps.groupByField(stateProps.collection, group),
|
||||||
onChangeViewStyle: viewStyle => dispatchProps.changeViewStyle(viewStyle),
|
onChangeViewStyle: viewStyle => dispatchProps.changeViewStyle(viewStyle),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const ConnectedCollection = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Collection);
|
const ConnectedCollection = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Collection);
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ const CollectionControlsContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CollectionControls = ({
|
function CollectionControls({
|
||||||
viewStyle,
|
viewStyle,
|
||||||
onChangeViewStyle,
|
onChangeViewStyle,
|
||||||
sortableFields,
|
sortableFields,
|
||||||
@ -32,7 +32,7 @@ const CollectionControls = ({
|
|||||||
t,
|
t,
|
||||||
filter,
|
filter,
|
||||||
group,
|
group,
|
||||||
}) => {
|
}) {
|
||||||
return (
|
return (
|
||||||
<CollectionControlsContainer>
|
<CollectionControlsContainer>
|
||||||
<ViewStyleControl viewStyle={viewStyle} onChangeViewStyle={onChangeViewStyle} />
|
<ViewStyleControl viewStyle={viewStyle} onChangeViewStyle={onChangeViewStyle} />
|
||||||
@ -52,6 +52,6 @@ const CollectionControls = ({
|
|||||||
)}
|
)}
|
||||||
</CollectionControlsContainer>
|
</CollectionControlsContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default CollectionControls;
|
export default CollectionControls;
|
||||||
|
@ -35,7 +35,7 @@ const CollectionTopDescription = styled.p`
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getCollectionProps = collection => {
|
function getCollectionProps(collection) {
|
||||||
const collectionLabel = collection.get('label');
|
const collectionLabel = collection.get('label');
|
||||||
const collectionLabelSingular = collection.get('label_singular');
|
const collectionLabelSingular = collection.get('label_singular');
|
||||||
const collectionDescription = collection.get('description');
|
const collectionDescription = collection.get('description');
|
||||||
@ -45,9 +45,9 @@ const getCollectionProps = collection => {
|
|||||||
collectionLabelSingular,
|
collectionLabelSingular,
|
||||||
collectionDescription,
|
collectionDescription,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const CollectionTop = ({ collection, newEntryUrl, t }) => {
|
function CollectionTop({ collection, newEntryUrl, t }) {
|
||||||
const { collectionLabel, collectionLabelSingular, collectionDescription } = getCollectionProps(
|
const { collectionLabel, collectionLabelSingular, collectionDescription } = getCollectionProps(
|
||||||
collection,
|
collection,
|
||||||
t,
|
t,
|
||||||
@ -70,7 +70,7 @@ const CollectionTop = ({ collection, newEntryUrl, t }) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</CollectionTopContainer>
|
</CollectionTopContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
CollectionTop.propTypes = {
|
CollectionTop.propTypes = {
|
||||||
collection: ImmutablePropTypes.map.isRequired,
|
collection: ImmutablePropTypes.map.isRequired,
|
||||||
|
@ -14,7 +14,7 @@ const Button = styled(StyledDropdownButton)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ControlButton = ({ active, title }) => {
|
export function ControlButton({ active, title }) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
css={css`
|
css={css`
|
||||||
@ -24,4 +24,4 @@ export const ControlButton = ({ active, title }) => {
|
|||||||
{title}
|
{title}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -16,7 +16,7 @@ const NoEntriesMessage = styled(PaginationMessage)`
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Entries = ({
|
function Entries({
|
||||||
collections,
|
collections,
|
||||||
entries,
|
entries,
|
||||||
isFetching,
|
isFetching,
|
||||||
@ -25,7 +25,7 @@ const Entries = ({
|
|||||||
handleCursorActions,
|
handleCursorActions,
|
||||||
t,
|
t,
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) {
|
||||||
const loadingMessages = [
|
const loadingMessages = [
|
||||||
t('collection.entries.loadingEntries'),
|
t('collection.entries.loadingEntries'),
|
||||||
t('collection.entries.cachingEntries'),
|
t('collection.entries.cachingEntries'),
|
||||||
@ -56,7 +56,7 @@ const Entries = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <NoEntriesMessage>{t('collection.entries.noEntries')}</NoEntriesMessage>;
|
return <NoEntriesMessage>{t('collection.entries.noEntries')}</NoEntriesMessage>;
|
||||||
};
|
}
|
||||||
|
|
||||||
Entries.propTypes = {
|
Entries.propTypes = {
|
||||||
collections: ImmutablePropTypes.iterable.isRequired,
|
collections: ImmutablePropTypes.iterable.isRequired,
|
||||||
|
@ -28,11 +28,11 @@ const GroupHeading = styled.h2`
|
|||||||
|
|
||||||
const GroupContainer = styled.div``;
|
const GroupContainer = styled.div``;
|
||||||
|
|
||||||
const getGroupEntries = (entries, paths) => {
|
function getGroupEntries(entries, paths) {
|
||||||
return entries.filter(entry => paths.has(entry.get('path')));
|
return entries.filter(entry => paths.has(entry.get('path')));
|
||||||
};
|
}
|
||||||
|
|
||||||
const getGroupTitle = (group, t) => {
|
function getGroupTitle(group, t) {
|
||||||
const { label, value } = group;
|
const { label, value } = group;
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return t('collection.groups.other');
|
return t('collection.groups.other');
|
||||||
@ -41,9 +41,9 @@ const getGroupTitle = (group, t) => {
|
|||||||
return value ? label : t('collection.groups.negateLabel', { label });
|
return value ? label : t('collection.groups.negateLabel', { label });
|
||||||
}
|
}
|
||||||
return `${label} ${value}`.trim();
|
return `${label} ${value}`.trim();
|
||||||
};
|
}
|
||||||
|
|
||||||
const withGroups = (groups, entries, EntriesToRender, t) => {
|
function withGroups(groups, entries, EntriesToRender, t) {
|
||||||
return groups.map(group => {
|
return groups.map(group => {
|
||||||
const title = getGroupTitle(group, t);
|
const title = getGroupTitle(group, t);
|
||||||
return (
|
return (
|
||||||
@ -53,7 +53,7 @@ const withGroups = (groups, entries, EntriesToRender, t) => {
|
|||||||
</GroupContainer>
|
</GroupContainer>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export class EntriesCollection extends React.Component {
|
export class EntriesCollection extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -114,7 +114,7 @@ export class EntriesCollection extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterNestedEntries = (path, collectionFolder, entries) => {
|
export function filterNestedEntries(path, collectionFolder, entries) {
|
||||||
const filtered = entries.filter(e => {
|
const filtered = entries.filter(e => {
|
||||||
const entryPath = e.get('path').substring(collectionFolder.length + 1);
|
const entryPath = e.get('path').substring(collectionFolder.length + 1);
|
||||||
if (!entryPath.startsWith(path)) {
|
if (!entryPath.startsWith(path)) {
|
||||||
@ -132,7 +132,7 @@ export const filterNestedEntries = (path, collectionFolder, entries) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
};
|
}
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
const { collection, viewStyle, filterTerm } = ownProps;
|
const { collection, viewStyle, filterTerm } = ownProps;
|
||||||
|
@ -84,7 +84,7 @@ const CardImage = styled.div`
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EntryCard = ({
|
function EntryCard({
|
||||||
path,
|
path,
|
||||||
summary,
|
summary,
|
||||||
image,
|
image,
|
||||||
@ -92,7 +92,7 @@ const EntryCard = ({
|
|||||||
collectionLabel,
|
collectionLabel,
|
||||||
viewStyle = VIEW_STYLE_LIST,
|
viewStyle = VIEW_STYLE_LIST,
|
||||||
getAsset,
|
getAsset,
|
||||||
}) => {
|
}) {
|
||||||
if (viewStyle === VIEW_STYLE_LIST) {
|
if (viewStyle === VIEW_STYLE_LIST) {
|
||||||
return (
|
return (
|
||||||
<ListCard>
|
<ListCard>
|
||||||
@ -117,9 +117,9 @@ const EntryCard = ({
|
|||||||
</GridCard>
|
</GridCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
function mapStateToProps(state, ownProps) {
|
||||||
const { entry, inferedFields, collection } = ownProps;
|
const { entry, inferedFields, collection } = ownProps;
|
||||||
const entryData = entry.get('data');
|
const entryData = entry.get('data');
|
||||||
const summary = selectEntryCollectionTitle(collection, entry);
|
const summary = selectEntryCollectionTitle(collection, entry);
|
||||||
@ -140,22 +140,22 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
?.find(f => f.get('name') === inferedFields.imageField && f.get('widget') === 'image'),
|
?.find(f => f.get('name') === inferedFields.imageField && f.get('widget') === 'image'),
|
||||||
isLoadingAsset,
|
isLoadingAsset,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||||
return {
|
return {
|
||||||
...stateProps,
|
...stateProps,
|
||||||
...dispatchProps,
|
...dispatchProps,
|
||||||
...ownProps,
|
...ownProps,
|
||||||
getAsset: dispatchProps.boundGetAsset(ownProps.collection, ownProps.entry),
|
getAsset: dispatchProps.boundGetAsset(ownProps.collection, ownProps.entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const ConnectedEntryCard = connect(mapStateToProps, mapDispatchToProps, mergeProps)(EntryCard);
|
const ConnectedEntryCard = connect(mapStateToProps, mapDispatchToProps, mergeProps)(EntryCard);
|
||||||
|
|
||||||
|
@ -13,14 +13,15 @@ jest.mock('../Entries', () => 'mock-entries');
|
|||||||
const middlewares = [];
|
const middlewares = [];
|
||||||
const mockStore = configureStore(middlewares);
|
const mockStore = configureStore(middlewares);
|
||||||
|
|
||||||
const renderWithRedux = (component, { store } = {}) => {
|
function renderWithRedux(component, { store } = {}) {
|
||||||
function Wrapper({ children }) {
|
function Wrapper({ children }) {
|
||||||
return <Provider store={store}>{children}</Provider>;
|
return <Provider store={store}>{children}</Provider>;
|
||||||
}
|
}
|
||||||
return render(component, { wrapper: Wrapper });
|
|
||||||
};
|
|
||||||
|
|
||||||
const toEntriesState = (collection, entriesArray) => {
|
return render(component, { wrapper: Wrapper });
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEntriesState(collection, entriesArray) {
|
||||||
const entries = entriesArray.reduce(
|
const entries = entriesArray.reduce(
|
||||||
(acc, entry) => {
|
(acc, entry) => {
|
||||||
acc.entities[`${collection.get('name')}.${entry.slug}`] = entry;
|
acc.entities[`${collection.get('name')}.${entry.slug}`] = entry;
|
||||||
@ -30,7 +31,7 @@ const toEntriesState = (collection, entriesArray) => {
|
|||||||
{ pages: { [collection.get('name')]: { ids: [] } }, entities: {} },
|
{ pages: { [collection.get('name')]: { ids: [] } }, entities: {} },
|
||||||
);
|
);
|
||||||
return fromJS(entries);
|
return fromJS(entries);
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('filterNestedEntries', () => {
|
describe('filterNestedEntries', () => {
|
||||||
it('should return only immediate children for non root path', () => {
|
it('should return only immediate children for non root path', () => {
|
||||||
|
@ -3,7 +3,7 @@ import { translate } from 'react-polyglot';
|
|||||||
import { Dropdown, DropdownCheckedItem } from 'netlify-cms-ui-default';
|
import { Dropdown, DropdownCheckedItem } from 'netlify-cms-ui-default';
|
||||||
import { ControlButton } from './ControlButton';
|
import { ControlButton } from './ControlButton';
|
||||||
|
|
||||||
const FilterControl = ({ viewFilters, t, onFilterClick, filter }) => {
|
function FilterControl({ viewFilters, t, onFilterClick, filter }) {
|
||||||
const hasActiveFilter = filter
|
const hasActiveFilter = filter
|
||||||
?.valueSeq()
|
?.valueSeq()
|
||||||
.toJS()
|
.toJS()
|
||||||
@ -33,6 +33,6 @@ const FilterControl = ({ viewFilters, t, onFilterClick, filter }) => {
|
|||||||
})}
|
})}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default translate()(FilterControl);
|
export default translate()(FilterControl);
|
||||||
|
@ -3,7 +3,7 @@ import { translate } from 'react-polyglot';
|
|||||||
import { Dropdown, DropdownItem } from 'netlify-cms-ui-default';
|
import { Dropdown, DropdownItem } from 'netlify-cms-ui-default';
|
||||||
import { ControlButton } from './ControlButton';
|
import { ControlButton } from './ControlButton';
|
||||||
|
|
||||||
const GroupControl = ({ viewGroups, t, onGroupClick, group }) => {
|
function GroupControl({ viewGroups, t, onGroupClick, group }) {
|
||||||
const hasActiveGroup = group
|
const hasActiveGroup = group
|
||||||
?.valueSeq()
|
?.valueSeq()
|
||||||
.toJS()
|
.toJS()
|
||||||
@ -33,6 +33,6 @@ const GroupControl = ({ viewGroups, t, onGroupClick, group }) => {
|
|||||||
})}
|
})}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default translate()(GroupControl);
|
export default translate()(GroupControl);
|
||||||
|
@ -66,14 +66,14 @@ const TreeNavLink = styled(NavLink)`
|
|||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getNodeTitle = node => {
|
function getNodeTitle(node) {
|
||||||
const title = node.isRoot
|
const title = node.isRoot
|
||||||
? node.title
|
? node.title
|
||||||
: node.children.find(c => !c.isDir && c.title)?.title || node.title;
|
: node.children.find(c => !c.isDir && c.title)?.title || node.title;
|
||||||
return title;
|
return title;
|
||||||
};
|
}
|
||||||
|
|
||||||
const TreeNode = props => {
|
function TreeNode(props) {
|
||||||
const { collection, treeData, depth = 0, onToggle } = props;
|
const { collection, treeData, depth = 0, onToggle } = props;
|
||||||
const collectionName = collection.get('name');
|
const collectionName = collection.get('name');
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ const TreeNode = props => {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
TreeNode.propTypes = {
|
TreeNode.propTypes = {
|
||||||
collection: ImmutablePropTypes.map.isRequired,
|
collection: ImmutablePropTypes.map.isRequired,
|
||||||
@ -127,18 +127,18 @@ TreeNode.propTypes = {
|
|||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const walk = (treeData, callback) => {
|
export function walk(treeData, callback) {
|
||||||
const traverse = children => {
|
function traverse(children) {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
callback(child);
|
callback(child);
|
||||||
traverse(child.children);
|
traverse(child.children);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return traverse(treeData);
|
return traverse(treeData);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getTreeData = (collection, entries) => {
|
export function getTreeData(collection, entries) {
|
||||||
const collectionFolder = collection.get('folder');
|
const collectionFolder = collection.get('folder');
|
||||||
const rootFolder = '/';
|
const rootFolder = '/';
|
||||||
const entriesObj = entries
|
const entriesObj = entries
|
||||||
@ -200,7 +200,7 @@ export const getTreeData = (collection, entries) => {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const reducer = (acc, value) => {
|
function reducer(acc, value) {
|
||||||
const node = value;
|
const node = value;
|
||||||
let children = [];
|
let children = [];
|
||||||
if (parentsToChildren[node.path]) {
|
if (parentsToChildren[node.path]) {
|
||||||
@ -209,17 +209,17 @@ export const getTreeData = (collection, entries) => {
|
|||||||
|
|
||||||
acc.push({ ...node, children });
|
acc.push({ ...node, children });
|
||||||
return acc;
|
return acc;
|
||||||
};
|
}
|
||||||
|
|
||||||
const treeData = parentsToChildren[''].reduce(reducer, []);
|
const treeData = parentsToChildren[''].reduce(reducer, []);
|
||||||
|
|
||||||
return treeData;
|
return treeData;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const updateNode = (treeData, node, callback) => {
|
export function updateNode(treeData, node, callback) {
|
||||||
let stop = false;
|
let stop = false;
|
||||||
|
|
||||||
const updater = nodes => {
|
function updater(nodes) {
|
||||||
if (stop) {
|
if (stop) {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
@ -232,10 +232,10 @@ export const updateNode = (treeData, node, callback) => {
|
|||||||
}
|
}
|
||||||
nodes.forEach(node => updater(node.children));
|
nodes.forEach(node => updater(node.children));
|
||||||
return nodes;
|
return nodes;
|
||||||
};
|
}
|
||||||
|
|
||||||
return updater([...treeData]);
|
return updater([...treeData]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export class NestedCollection extends React.Component {
|
export class NestedCollection extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -28,7 +28,7 @@ const sortIconDirections = {
|
|||||||
[SortDirection.Descending]: 'down',
|
[SortDirection.Descending]: 'down',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortControl = ({ t, fields, onSortClick, sort }) => {
|
function SortControl({ t, fields, onSortClick, sort }) {
|
||||||
const hasActiveSort = sort
|
const hasActiveSort = sort
|
||||||
?.valueSeq()
|
?.valueSeq()
|
||||||
.toJS()
|
.toJS()
|
||||||
@ -62,6 +62,6 @@ const SortControl = ({ t, fields, onSortClick, sort }) => {
|
|||||||
})}
|
})}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default translate()(SortControl);
|
export default translate()(SortControl);
|
||||||
|
@ -27,7 +27,7 @@ const ViewControlsButton = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ViewStyleControl = ({ viewStyle, onChangeViewStyle }) => {
|
function ViewStyleControl({ viewStyle, onChangeViewStyle }) {
|
||||||
return (
|
return (
|
||||||
<ViewControlsSection>
|
<ViewControlsSection>
|
||||||
<ViewControlsButton
|
<ViewControlsButton
|
||||||
@ -44,6 +44,6 @@ const ViewStyleControl = ({ viewStyle, onChangeViewStyle }) => {
|
|||||||
</ViewControlsButton>
|
</ViewControlsButton>
|
||||||
</ViewControlsSection>
|
</ViewControlsSection>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ViewStyleControl;
|
export default ViewStyleControl;
|
||||||
|
@ -13,12 +13,13 @@ jest.mock('../Sidebar', () => 'mock-sidebar');
|
|||||||
const middlewares = [];
|
const middlewares = [];
|
||||||
const mockStore = configureStore(middlewares);
|
const mockStore = configureStore(middlewares);
|
||||||
|
|
||||||
const renderWithRedux = (component, { store } = {}) => {
|
function renderWithRedux(component, { store } = {}) {
|
||||||
function Wrapper({ children }) {
|
function Wrapper({ children }) {
|
||||||
return <Provider store={store}>{children}</Provider>;
|
return <Provider store={store}>{children}</Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(component, { wrapper: Wrapper });
|
return render(component, { wrapper: Wrapper });
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('Collection', () => {
|
describe('Collection', () => {
|
||||||
const collection = fromJS({
|
const collection = fromJS({
|
||||||
|
@ -22,12 +22,13 @@ jest.mock('netlify-cms-ui-default', () => {
|
|||||||
const middlewares = [];
|
const middlewares = [];
|
||||||
const mockStore = configureStore(middlewares);
|
const mockStore = configureStore(middlewares);
|
||||||
|
|
||||||
const renderWithRedux = (component, { store } = {}) => {
|
function renderWithRedux(component, { store } = {}) {
|
||||||
function Wrapper({ children }) {
|
function Wrapper({ children }) {
|
||||||
return <Provider store={store}>{children}</Provider>;
|
return <Provider store={store}>{children}</Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(component, { wrapper: Wrapper });
|
return render(component, { wrapper: Wrapper });
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('NestedCollection', () => {
|
describe('NestedCollection', () => {
|
||||||
const collection = fromJS({
|
const collection = fromJS({
|
||||||
|
@ -95,7 +95,7 @@ export const ControlHint = styled.p`
|
|||||||
transition: color ${transitions.main};
|
transition: color ${transitions.main};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LabelComponent = ({ field, isActive, hasErrors, uniqueFieldId, isFieldOptional, t }) => {
|
function LabelComponent({ field, isActive, hasErrors, uniqueFieldId, isFieldOptional, t }) {
|
||||||
const label = `${field.get('label', field.get('name'))}`;
|
const label = `${field.get('label', field.get('name'))}`;
|
||||||
const labelComponent = (
|
const labelComponent = (
|
||||||
<FieldLabel isActive={isActive} hasErrors={hasErrors} htmlFor={uniqueFieldId}>
|
<FieldLabel isActive={isActive} hasErrors={hasErrors} htmlFor={uniqueFieldId}>
|
||||||
@ -104,7 +104,7 @@ const LabelComponent = ({ field, isActive, hasErrors, uniqueFieldId, isFieldOpti
|
|||||||
);
|
);
|
||||||
|
|
||||||
return labelComponent;
|
return labelComponent;
|
||||||
};
|
}
|
||||||
|
|
||||||
class EditorControl extends React.Component {
|
class EditorControl extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -334,13 +334,13 @@ class EditorControl extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
function mapStateToProps(state) {
|
||||||
const { collections, entryDraft } = state;
|
const { collections, entryDraft } = state;
|
||||||
const entry = entryDraft.get('entry');
|
const entry = entryDraft.get('entry');
|
||||||
const collection = collections.get(entryDraft.getIn(['entry', 'collection']));
|
const collection = collections.get(entryDraft.getIn(['entry', 'collection']));
|
||||||
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
||||||
|
|
||||||
const loadEntry = async (collectionName, slug) => {
|
async function loadEntry(collectionName, slug) {
|
||||||
const targetCollection = collections.get(collectionName);
|
const targetCollection = collections.get(collectionName);
|
||||||
if (targetCollection) {
|
if (targetCollection) {
|
||||||
const loadedEntry = await tryLoadEntry(state, targetCollection, slug);
|
const loadedEntry = await tryLoadEntry(state, targetCollection, slug);
|
||||||
@ -348,7 +348,7 @@ const mapStateToProps = state => {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(`Can't find collection '${collectionName}'`);
|
throw new Error(`Can't find collection '${collectionName}'`);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaPaths: state.mediaLibrary.get('controlMedia'),
|
mediaPaths: state.mediaLibrary.get('controlMedia'),
|
||||||
@ -361,9 +361,9 @@ const mapStateToProps = state => {
|
|||||||
loadEntry,
|
loadEntry,
|
||||||
validateMetaField: (field, value, t) => validateMetaField(state, collection, field, value, t),
|
validateMetaField: (field, value, t) => validateMetaField(state, collection, field, value, t),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
function mapDispatchToProps(dispatch) {
|
||||||
const creators = bindActionCreators(
|
const creators = bindActionCreators(
|
||||||
{
|
{
|
||||||
openMediaLibrary,
|
openMediaLibrary,
|
||||||
@ -381,16 +381,16 @@ const mapDispatchToProps = dispatch => {
|
|||||||
...creators,
|
...creators,
|
||||||
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||||
return {
|
return {
|
||||||
...stateProps,
|
...stateProps,
|
||||||
...dispatchProps,
|
...dispatchProps,
|
||||||
...ownProps,
|
...ownProps,
|
||||||
boundGetAsset: dispatchProps.boundGetAsset(stateProps.collection, stateProps.entry),
|
boundGetAsset: dispatchProps.boundGetAsset(stateProps.collection, stateProps.entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const ConnectedEditorControl = connect(
|
const ConnectedEditorControl = connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
|
@ -50,7 +50,7 @@ const StyledDropdown = styled(Dropdown)`
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LocaleDropdown = ({ locales, selectedLocale, onLocaleChange, t }) => {
|
function LocaleDropdown({ locales, selectedLocale, onLocaleChange, t }) {
|
||||||
return (
|
return (
|
||||||
<StyledDropdown
|
<StyledDropdown
|
||||||
renderButton={() => {
|
renderButton={() => {
|
||||||
@ -77,9 +77,9 @@ const LocaleDropdown = ({ locales, selectedLocale, onLocaleChange, t }) => {
|
|||||||
))}
|
))}
|
||||||
</StyledDropdown>
|
</StyledDropdown>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getFieldValue = ({ field, entry, isTranslatable, locale }) => {
|
function getFieldValue({ field, entry, isTranslatable, locale }) {
|
||||||
if (field.get('meta')) {
|
if (field.get('meta')) {
|
||||||
return entry.getIn(['meta', field.get('name')]);
|
return entry.getIn(['meta', field.get('name')]);
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ const getFieldValue = ({ field, entry, isTranslatable, locale }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entry.getIn(['data', field.get('name')]);
|
return entry.getIn(['data', field.get('name')]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class ControlPane extends React.Component {
|
export default class ControlPane extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
|
@ -5,14 +5,19 @@ import { Map, List } from 'immutable';
|
|||||||
import { oneLine } from 'common-tags';
|
import { oneLine } from 'common-tags';
|
||||||
import ValidationErrorTypes from 'Constants/validationErrorTypes';
|
import ValidationErrorTypes from 'Constants/validationErrorTypes';
|
||||||
|
|
||||||
const truthy = () => ({ error: false });
|
function truthy() {
|
||||||
|
return { error: false };
|
||||||
|
}
|
||||||
|
|
||||||
const isEmpty = value =>
|
function isEmpty(value) {
|
||||||
|
return (
|
||||||
value === null ||
|
value === null ||
|
||||||
value === undefined ||
|
value === undefined ||
|
||||||
(Object.prototype.hasOwnProperty.call(value, 'length') && value.length === 0) ||
|
(Object.prototype.hasOwnProperty.call(value, 'length') && value.length === 0) ||
|
||||||
(value.constructor === Object && Object.keys(value).length === 0) ||
|
(value.constructor === Object && Object.keys(value).length === 0) ||
|
||||||
(List.isList(value) && value.size === 0);
|
(List.isList(value) && value.size === 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default class Widget extends Component {
|
export default class Widget extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -41,7 +41,8 @@ const EditorToggle = styled(IconButton)`
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ReactSplitPaneGlobalStyles = () => (
|
function ReactSplitPaneGlobalStyles() {
|
||||||
|
return (
|
||||||
<Global
|
<Global
|
||||||
styles={css`
|
styles={css`
|
||||||
.Resizer.vertical {
|
.Resizer.vertical {
|
||||||
@ -67,7 +68,8 @@ const ReactSplitPaneGlobalStyles = () => (
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const StyledSplitPane = styled(SplitPane)`
|
const StyledSplitPane = styled(SplitPane)`
|
||||||
${styles.splitPane};
|
${styles.splitPane};
|
||||||
@ -121,13 +123,13 @@ const ViewControls = styled.div`
|
|||||||
z-index: ${zIndex.zIndex299};
|
z-index: ${zIndex.zIndex299};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EditorContent = ({
|
function EditorContent({
|
||||||
i18nVisible,
|
i18nVisible,
|
||||||
previewVisible,
|
previewVisible,
|
||||||
editor,
|
editor,
|
||||||
editorWithEditor,
|
editorWithEditor,
|
||||||
editorWithPreview,
|
editorWithPreview,
|
||||||
}) => {
|
}) {
|
||||||
if (i18nVisible) {
|
if (i18nVisible) {
|
||||||
return editorWithEditor;
|
return editorWithEditor;
|
||||||
} else if (previewVisible) {
|
} else if (previewVisible) {
|
||||||
@ -135,7 +137,7 @@ const EditorContent = ({
|
|||||||
} else {
|
} else {
|
||||||
return <NoPreviewContainer>{editor}</NoPreviewContainer>;
|
return <NoPreviewContainer>{editor}</NoPreviewContainer>;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function isPreviewEnabled(collection, entry) {
|
function isPreviewEnabled(collection, entry) {
|
||||||
if (collection.get('type') === FILES) {
|
if (collection.get('type') === FILES) {
|
||||||
|
@ -248,24 +248,24 @@ PreviewPane.propTypes = {
|
|||||||
getAsset: PropTypes.func.isRequired,
|
getAsset: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
function mapStateToProps(state) {
|
||||||
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
||||||
return { isLoadingAsset, config: state.config };
|
return { isLoadingAsset, config: state.config };
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
boundGetAsset: (collection, entry) => boundGetAsset(dispatch, collection, entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||||
return {
|
return {
|
||||||
...stateProps,
|
...stateProps,
|
||||||
...dispatchProps,
|
...dispatchProps,
|
||||||
...ownProps,
|
...ownProps,
|
||||||
getAsset: dispatchProps.boundGetAsset(ownProps.collection, ownProps.entry),
|
getAsset: dispatchProps.boundGetAsset(ownProps.collection, ownProps.entry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(PreviewPane);
|
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(PreviewPane);
|
||||||
|
@ -3,11 +3,11 @@ import { translate } from 'react-polyglot';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const UnknownControl = ({ field, t }) => {
|
function UnknownControl({ field, t }) {
|
||||||
return (
|
return (
|
||||||
<div>{t('editor.editorWidgets.unknownControl.noControl', { widget: field.get('widget') })}</div>
|
<div>{t('editor.editorWidgets.unknownControl.noControl', { widget: field.get('widget') })}</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
UnknownControl.propTypes = {
|
UnknownControl.propTypes = {
|
||||||
field: ImmutablePropTypes.map,
|
field: ImmutablePropTypes.map,
|
||||||
|
@ -3,13 +3,13 @@ import { translate } from 'react-polyglot';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const UnknownPreview = ({ field, t }) => {
|
function UnknownPreview({ field, t }) {
|
||||||
return (
|
return (
|
||||||
<div className="nc-widgetPreview">
|
<div className="nc-widgetPreview">
|
||||||
{t('editor.editorWidgets.unknownPreview.noPreview', { widget: field.get('widget') })}
|
{t('editor.editorWidgets.unknownPreview.noPreview', { widget: field.get('widget') })}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
UnknownPreview.propTypes = {
|
UnknownPreview.propTypes = {
|
||||||
field: ImmutablePropTypes.map,
|
field: ImmutablePropTypes.map,
|
||||||
|
@ -12,11 +12,13 @@ const EmptyMessageContainer = styled.div`
|
|||||||
color: ${props => props.isPrivate && colors.textFieldBorder};
|
color: ${props => props.isPrivate && colors.textFieldBorder};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmptyMessage = ({ content, isPrivate }) => (
|
function EmptyMessage({ content, isPrivate }) {
|
||||||
|
return (
|
||||||
<EmptyMessageContainer isPrivate={isPrivate}>
|
<EmptyMessageContainer isPrivate={isPrivate}>
|
||||||
<h1>{content}</h1>
|
<h1>{content}</h1>
|
||||||
</EmptyMessageContainer>
|
</EmptyMessageContainer>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
EmptyMessage.propTypes = {
|
EmptyMessage.propTypes = {
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
|
@ -354,7 +354,7 @@ class MediaLibrary extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
function mapStateToProps(state) {
|
||||||
const { mediaLibrary } = state;
|
const { mediaLibrary } = state;
|
||||||
const field = mediaLibrary.get('field');
|
const field = mediaLibrary.get('field');
|
||||||
const mediaLibraryProps = {
|
const mediaLibraryProps = {
|
||||||
@ -377,7 +377,7 @@ const mapStateToProps = state => {
|
|||||||
field,
|
field,
|
||||||
};
|
};
|
||||||
return { ...mediaLibraryProps };
|
return { ...mediaLibraryProps };
|
||||||
};
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
loadMedia: loadMediaAction,
|
loadMedia: loadMediaAction,
|
||||||
|
@ -8,7 +8,7 @@ import { colors } from 'netlify-cms-ui-default';
|
|||||||
import { FixedSizeGrid as Grid } from 'react-window';
|
import { FixedSizeGrid as Grid } from 'react-window';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
const CardWrapper = props => {
|
function CardWrapper(props) {
|
||||||
const {
|
const {
|
||||||
rowIndex,
|
rowIndex,
|
||||||
columnIndex,
|
columnIndex,
|
||||||
@ -61,9 +61,9 @@ const CardWrapper = props => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const VirtualizedGrid = props => {
|
function VirtualizedGrid(props) {
|
||||||
const { mediaItems, setScrollContainerRef } = props;
|
const { mediaItems, setScrollContainerRef } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -94,9 +94,9 @@ const VirtualizedGrid = props => {
|
|||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
</CardGridContainer>
|
</CardGridContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const PaginatedGrid = ({
|
function PaginatedGrid({
|
||||||
setScrollContainerRef,
|
setScrollContainerRef,
|
||||||
mediaItems,
|
mediaItems,
|
||||||
isSelectedFile,
|
isSelectedFile,
|
||||||
@ -112,7 +112,7 @@ const PaginatedGrid = ({
|
|||||||
onLoadMore,
|
onLoadMore,
|
||||||
isPaginating,
|
isPaginating,
|
||||||
paginatingMessage,
|
paginatingMessage,
|
||||||
}) => {
|
}) {
|
||||||
return (
|
return (
|
||||||
<CardGridContainer ref={setScrollContainerRef}>
|
<CardGridContainer ref={setScrollContainerRef}>
|
||||||
<CardGrid>
|
<CardGrid>
|
||||||
@ -141,7 +141,7 @@ const PaginatedGrid = ({
|
|||||||
)}
|
)}
|
||||||
</CardGridContainer>
|
</CardGridContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const CardGridContainer = styled.div`
|
const CardGridContainer = styled.div`
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -160,13 +160,13 @@ const PaginatingMessage = styled.h1`
|
|||||||
color: ${props => props.isPrivate && colors.textFieldBorder};
|
color: ${props => props.isPrivate && colors.textFieldBorder};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibraryCardGrid = props => {
|
function MediaLibraryCardGrid(props) {
|
||||||
const { canLoadMore, isPaginating } = props;
|
const { canLoadMore, isPaginating } = props;
|
||||||
if (canLoadMore || isPaginating) {
|
if (canLoadMore || isPaginating) {
|
||||||
return <PaginatedGrid {...props} />;
|
return <PaginatedGrid {...props} />;
|
||||||
}
|
}
|
||||||
return <VirtualizedGrid {...props} />;
|
return <VirtualizedGrid {...props} />;
|
||||||
};
|
}
|
||||||
|
|
||||||
MediaLibraryCardGrid.propTypes = {
|
MediaLibraryCardGrid.propTypes = {
|
||||||
setScrollContainerRef: PropTypes.func.isRequired,
|
setScrollContainerRef: PropTypes.func.isRequired,
|
||||||
|
@ -28,14 +28,16 @@ const LibraryTitle = styled.h1`
|
|||||||
color: ${props => props.isPrivate && colors.textFieldBorder};
|
color: ${props => props.isPrivate && colors.textFieldBorder};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibraryHeader = ({ onClose, title, isPrivate }) => (
|
function MediaLibraryHeader({ onClose, title, isPrivate }) {
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CloseButton onClick={onClose}>
|
<CloseButton onClick={onClose}>
|
||||||
<Icon type="close" />
|
<Icon type="close" />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
<LibraryTitle isPrivate={isPrivate}>{title}</LibraryTitle>
|
<LibraryTitle isPrivate={isPrivate}>{title}</LibraryTitle>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
MediaLibraryHeader.propTypes = {
|
MediaLibraryHeader.propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
@ -60,7 +60,7 @@ const StyledModal = styled(Modal)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibraryModal = ({
|
function MediaLibraryModal({
|
||||||
isVisible,
|
isVisible,
|
||||||
canInsert,
|
canInsert,
|
||||||
files,
|
files,
|
||||||
@ -91,7 +91,7 @@ const MediaLibraryModal = ({
|
|||||||
loadDisplayURL,
|
loadDisplayURL,
|
||||||
displayURLs,
|
displayURLs,
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) {
|
||||||
const filteredFiles = forImage ? handleFilter(files) : files;
|
const filteredFiles = forImage ? handleFilter(files) : files;
|
||||||
const queriedFiles = !dynamicSearch && query ? handleQuery(query, filteredFiles) : filteredFiles;
|
const queriedFiles = !dynamicSearch && query ? handleQuery(query, filteredFiles) : filteredFiles;
|
||||||
const tableData = toTableData(queriedFiles);
|
const tableData = toTableData(queriedFiles);
|
||||||
@ -152,7 +152,7 @@ const MediaLibraryModal = ({
|
|||||||
/>
|
/>
|
||||||
</StyledModal>
|
</StyledModal>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const fileShape = {
|
export const fileShape = {
|
||||||
displayURL: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
displayURL: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
|
@ -35,7 +35,8 @@ const SearchIcon = styled(Icon)`
|
|||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibrarySearch = ({ value, onChange, onKeyDown, placeholder, disabled }) => (
|
function MediaLibrarySearch({ value, onChange, onKeyDown, placeholder, disabled }) {
|
||||||
|
return (
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
<SearchIcon type="search" size="small" />
|
<SearchIcon type="search" size="small" />
|
||||||
<SearchInput
|
<SearchInput
|
||||||
@ -46,7 +47,8 @@ const MediaLibrarySearch = ({ value, onChange, onKeyDown, placeholder, disabled
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</SearchContainer>
|
</SearchContainer>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
MediaLibrarySearch.propTypes = {
|
MediaLibrarySearch.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
@ -26,7 +26,7 @@ const ButtonsContainer = styled.div`
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibraryTop = ({
|
function MediaLibraryTop({
|
||||||
t,
|
t,
|
||||||
onClose,
|
onClose,
|
||||||
privateUpload,
|
privateUpload,
|
||||||
@ -44,7 +44,7 @@ const MediaLibraryTop = ({
|
|||||||
isPersisting,
|
isPersisting,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
selectedFile,
|
selectedFile,
|
||||||
}) => {
|
}) {
|
||||||
const shouldShowButtonLoader = isPersisting || isDeleting;
|
const shouldShowButtonLoader = isPersisting || isDeleting;
|
||||||
const uploadEnabled = !shouldShowButtonLoader;
|
const uploadEnabled = !shouldShowButtonLoader;
|
||||||
const deleteEnabled = !shouldShowButtonLoader && hasSelection;
|
const deleteEnabled = !shouldShowButtonLoader && hasSelection;
|
||||||
@ -110,7 +110,7 @@ const MediaLibraryTop = ({
|
|||||||
</RowContainer>
|
</RowContainer>
|
||||||
</LibraryTop>
|
</LibraryTop>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
MediaLibraryTop.propTypes = {
|
MediaLibraryTop.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const DragSource = ({ namespace, ...props }) => {
|
export function DragSource({ namespace, ...props }) {
|
||||||
const DragComponent = ReactDNDDragSource(
|
const DragComponent = ReactDNDDragSource(
|
||||||
namespace,
|
namespace,
|
||||||
{
|
{
|
||||||
@ -23,13 +23,14 @@ export const DragSource = ({ namespace, ...props }) => {
|
|||||||
)(({ children, connectDragComponent }) => children(connectDragComponent));
|
)(({ children, connectDragComponent }) => children(connectDragComponent));
|
||||||
|
|
||||||
return React.createElement(DragComponent, props, props.children);
|
return React.createElement(DragComponent, props, props.children);
|
||||||
};
|
}
|
||||||
|
|
||||||
DragSource.propTypes = {
|
DragSource.propTypes = {
|
||||||
namespace: PropTypes.any.isRequired,
|
namespace: PropTypes.any.isRequired,
|
||||||
children: PropTypes.func.isRequired,
|
children: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropTarget = ({ onDrop, namespace, ...props }) => {
|
export function DropTarget({ onDrop, namespace, ...props }) {
|
||||||
const DropComponent = ReactDNDDropTarget(
|
const DropComponent = ReactDNDDropTarget(
|
||||||
namespace,
|
namespace,
|
||||||
{
|
{
|
||||||
@ -44,11 +45,14 @@ export const DropTarget = ({ onDrop, namespace, ...props }) => {
|
|||||||
)(({ children, connectDropTarget, isHovered }) => children(connectDropTarget, { isHovered }));
|
)(({ children, connectDropTarget, isHovered }) => children(connectDropTarget, { isHovered }));
|
||||||
|
|
||||||
return React.createElement(DropComponent, props, props.children);
|
return React.createElement(DropComponent, props, props.children);
|
||||||
};
|
}
|
||||||
|
|
||||||
DropTarget.propTypes = {
|
DropTarget.propTypes = {
|
||||||
onDrop: PropTypes.func.isRequired,
|
onDrop: PropTypes.func.isRequired,
|
||||||
namespace: PropTypes.any.isRequired,
|
namespace: PropTypes.any.isRequired,
|
||||||
children: PropTypes.func.isRequired,
|
children: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HTML5DragDrop = component => ReactDNDDragDropContext(ReactDNDHTML5Backend)(component);
|
export function HTML5DragDrop(component) {
|
||||||
|
return ReactDNDDragDropContext(ReactDNDHTML5Backend)(component);
|
||||||
|
}
|
||||||
|
@ -10,7 +10,9 @@ import { localForage } from 'netlify-cms-lib-util';
|
|||||||
import { buttons, colors } from 'netlify-cms-ui-default';
|
import { buttons, colors } from 'netlify-cms-ui-default';
|
||||||
|
|
||||||
const ISSUE_URL = 'https://github.com/netlify/netlify-cms/issues/new?';
|
const ISSUE_URL = 'https://github.com/netlify/netlify-cms/issues/new?';
|
||||||
const getIssueTemplate = ({ version, provider, browser, config }) => `
|
|
||||||
|
function getIssueTemplate({ version, provider, browser, config }) {
|
||||||
|
return `
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
@ -31,8 +33,9 @@ ${config}
|
|||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
const buildIssueTemplate = ({ config }) => {
|
function buildIssueTemplate({ config }) {
|
||||||
let version = '';
|
let version = '';
|
||||||
if (typeof NETLIFY_CMS_VERSION === 'string') {
|
if (typeof NETLIFY_CMS_VERSION === 'string') {
|
||||||
version = `netlify-cms@${NETLIFY_CMS_VERSION}`;
|
version = `netlify-cms@${NETLIFY_CMS_VERSION}`;
|
||||||
@ -47,9 +50,9 @@ const buildIssueTemplate = ({ config }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
};
|
}
|
||||||
|
|
||||||
const buildIssueUrl = ({ title, config }) => {
|
function buildIssueUrl({ title, config }) {
|
||||||
try {
|
try {
|
||||||
const body = buildIssueTemplate({ config });
|
const body = buildIssueTemplate({ config });
|
||||||
|
|
||||||
@ -63,7 +66,7 @@ const buildIssueUrl = ({ title, config }) => {
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
return `${ISSUE_URL}template=bug_report.md`;
|
return `${ISSUE_URL}template=bug_report.md`;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const ErrorBoundaryContainer = styled.div`
|
const ErrorBoundaryContainer = styled.div`
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
@ -107,7 +110,7 @@ const CopyButton = styled.button`
|
|||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RecoveredEntry = ({ entry, t }) => {
|
function RecoveredEntry({ entry, t }) {
|
||||||
console.log(entry);
|
console.log(entry);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -122,7 +125,7 @@ const RecoveredEntry = ({ entry, t }) => {
|
|||||||
</pre>
|
</pre>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export class ErrorBoundary extends React.Component {
|
export class ErrorBoundary extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const FileUploadButton = ({ label, imagesOnly, onChange, disabled, className }) => (
|
export function FileUploadButton({ label, imagesOnly, onChange, disabled, className }) {
|
||||||
|
return (
|
||||||
<label className={`nc-fileUploadButton ${className || ''}`}>
|
<label className={`nc-fileUploadButton ${className || ''}`}>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
<input
|
<input
|
||||||
@ -11,7 +12,8 @@ export const FileUploadButton = ({ label, imagesOnly, onChange, disabled, classN
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
FileUploadButton.propTypes = {
|
FileUploadButton.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
@ -4,7 +4,8 @@ import { css, Global, ClassNames } from '@emotion/core';
|
|||||||
import ReactModal from 'react-modal';
|
import ReactModal from 'react-modal';
|
||||||
import { transitions, shadows, lengths, zIndex } from 'netlify-cms-ui-default';
|
import { transitions, shadows, lengths, zIndex } from 'netlify-cms-ui-default';
|
||||||
|
|
||||||
const ReactModalGlobalStyles = () => (
|
function ReactModalGlobalStyles() {
|
||||||
|
return (
|
||||||
<Global
|
<Global
|
||||||
styles={css`
|
styles={css`
|
||||||
.ReactModal__Body--open {
|
.ReactModal__Body--open {
|
||||||
@ -12,7 +13,8 @@ const ReactModalGlobalStyles = () => (
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const styleStrings = {
|
const styleStrings = {
|
||||||
modalBody: `
|
modalBody: `
|
||||||
|
@ -46,14 +46,20 @@ const AppHeaderTestRepoIndicator = styled.a`
|
|||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Avatar = ({ imageUrl }) =>
|
function Avatar({ imageUrl }) {
|
||||||
imageUrl ? <AvatarImage src={imageUrl} /> : <AvatarPlaceholderIcon type="user" size="large" />;
|
return imageUrl ? (
|
||||||
|
<AvatarImage src={imageUrl} />
|
||||||
|
) : (
|
||||||
|
<AvatarPlaceholderIcon type="user" size="large" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Avatar.propTypes = {
|
Avatar.propTypes = {
|
||||||
imageUrl: PropTypes.string,
|
imageUrl: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsDropdown = ({ displayUrl, isTestRepo, imageUrl, onLogoutClick, t }) => (
|
function SettingsDropdown({ displayUrl, isTestRepo, imageUrl, onLogoutClick, t }) {
|
||||||
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{isTestRepo && (
|
{isTestRepo && (
|
||||||
<AppHeaderTestRepoIndicator
|
<AppHeaderTestRepoIndicator
|
||||||
@ -82,7 +88,8 @@ const SettingsDropdown = ({ displayUrl, isTestRepo, imageUrl, onLogoutClick, t }
|
|||||||
<DropdownItem label={t('ui.settingsDropdown.logOut')} onClick={onLogoutClick} />
|
<DropdownItem label={t('ui.settingsDropdown.logOut')} onClick={onLogoutClick} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdown.propTypes = {
|
SettingsDropdown.propTypes = {
|
||||||
displayUrl: PropTypes.string,
|
displayUrl: PropTypes.string,
|
||||||
|
@ -6,7 +6,8 @@ import { translate } from 'react-polyglot';
|
|||||||
import reduxNotificationsStyles from 'redux-notifications/lib/styles.css';
|
import reduxNotificationsStyles from 'redux-notifications/lib/styles.css';
|
||||||
import { shadows, colors, lengths, zIndex } from 'netlify-cms-ui-default';
|
import { shadows, colors, lengths, zIndex } from 'netlify-cms-ui-default';
|
||||||
|
|
||||||
const ReduxNotificationsGlobalStyles = () => (
|
function ReduxNotificationsGlobalStyles() {
|
||||||
|
return (
|
||||||
<Global
|
<Global
|
||||||
styles={css`
|
styles={css`
|
||||||
${reduxNotificationsStyles};
|
${reduxNotificationsStyles};
|
||||||
@ -17,7 +18,8 @@ const ReduxNotificationsGlobalStyles = () => (
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
toast: css`
|
toast: css`
|
||||||
@ -47,12 +49,14 @@ const styles = {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Toast = ({ kind, message, t }) => (
|
function Toast({ kind, message, t }) {
|
||||||
|
return (
|
||||||
<div css={[styles.toast, styles[kind]]}>
|
<div css={[styles.toast, styles[kind]]}>
|
||||||
<ReduxNotificationsGlobalStyles />
|
<ReduxNotificationsGlobalStyles />
|
||||||
{t(message.key, { details: message.details })}
|
{t(message.key, { details: message.details })}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Toast.propTypes = {
|
Toast.propTypes = {
|
||||||
kind: PropTypes.oneOf(['info', 'success', 'warning', 'danger']).isRequired,
|
kind: PropTypes.oneOf(['info', 'success', 'warning', 'danger']).isRequired,
|
||||||
|
@ -4,9 +4,9 @@ import { render } from '@testing-library/react';
|
|||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { oneLineTrim } from 'common-tags';
|
import { oneLineTrim } from 'common-tags';
|
||||||
|
|
||||||
const WithError = () => {
|
function WithError() {
|
||||||
throw new Error('Some unknown error');
|
throw new Error('Some unknown error');
|
||||||
};
|
}
|
||||||
|
|
||||||
jest.spyOn(console, 'error').mockImplementation(() => ({}));
|
jest.spyOn(console, 'error').mockImplementation(() => ({}));
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ const WorkflowCardContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const lastChangePhraseKey = (date, author) => {
|
function lastChangePhraseKey(date, author) {
|
||||||
if (date && author) {
|
if (date && author) {
|
||||||
return 'lastChange';
|
return 'lastChange';
|
||||||
} else if (date) {
|
} else if (date) {
|
||||||
@ -106,7 +106,7 @@ const lastChangePhraseKey = (date, author) => {
|
|||||||
} else if (author) {
|
} else if (author) {
|
||||||
return 'lastChangeNoDate';
|
return 'lastChangeNoDate';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const CardDate = translate()(({ t, date, author }) => {
|
const CardDate = translate()(({ t, date, author }) => {
|
||||||
const key = lastChangePhraseKey(date, author);
|
const key = lastChangePhraseKey(date, author);
|
||||||
@ -117,7 +117,7 @@ const CardDate = translate()(({ t, date, author }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const WorkflowCard = ({
|
function WorkflowCard({
|
||||||
collectionLabel,
|
collectionLabel,
|
||||||
title,
|
title,
|
||||||
authorLastChange,
|
authorLastChange,
|
||||||
@ -130,7 +130,8 @@ const WorkflowCard = ({
|
|||||||
canPublish,
|
canPublish,
|
||||||
onPublish,
|
onPublish,
|
||||||
t,
|
t,
|
||||||
}) => (
|
}) {
|
||||||
|
return (
|
||||||
<WorkflowCardContainer>
|
<WorkflowCardContainer>
|
||||||
<WorkflowLink to={editLink}>
|
<WorkflowLink to={editLink}>
|
||||||
<CardCollection>{collectionLabel}</CardCollection>
|
<CardCollection>{collectionLabel}</CardCollection>
|
||||||
@ -153,7 +154,8 @@ const WorkflowCard = ({
|
|||||||
)}
|
)}
|
||||||
</CardButtonContainer>
|
</CardButtonContainer>
|
||||||
</WorkflowCardContainer>
|
</WorkflowCardContainer>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
WorkflowCard.propTypes = {
|
WorkflowCard.propTypes = {
|
||||||
collectionLabel: PropTypes.string.isRequired,
|
collectionLabel: PropTypes.string.isRequired,
|
||||||
|
@ -116,7 +116,7 @@ const ColumnCount = styled.p`
|
|||||||
// This is a namespace so that we can only drop these elements on a DropTarget with the same
|
// This is a namespace so that we can only drop these elements on a DropTarget with the same
|
||||||
const DNDNamespace = 'cms-workflow';
|
const DNDNamespace = 'cms-workflow';
|
||||||
|
|
||||||
const getColumnHeaderText = (columnName, t) => {
|
function getColumnHeaderText(columnName, t) {
|
||||||
switch (columnName) {
|
switch (columnName) {
|
||||||
case 'draft':
|
case 'draft':
|
||||||
return t('workflow.workflowList.draftHeader');
|
return t('workflow.workflowList.draftHeader');
|
||||||
@ -125,7 +125,7 @@ const getColumnHeaderText = (columnName, t) => {
|
|||||||
case 'pending_publish':
|
case 'pending_publish':
|
||||||
return t('workflow.workflowList.readyHeader');
|
return t('workflow.workflowList.readyHeader');
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
class WorkflowList extends React.Component {
|
class WorkflowList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -43,7 +43,7 @@ const i18nField = {
|
|||||||
/**
|
/**
|
||||||
* Config for fields in both file and folder collections.
|
* Config for fields in both file and folder collections.
|
||||||
*/
|
*/
|
||||||
const fieldsConfig = () => {
|
function fieldsConfig() {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
return {
|
return {
|
||||||
$id: `fields_${id}`,
|
$id: `fields_${id}`,
|
||||||
@ -77,7 +77,7 @@ const fieldsConfig = () => {
|
|||||||
},
|
},
|
||||||
uniqueItemProperties: ['name'],
|
uniqueItemProperties: ['name'],
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const viewFilters = {
|
const viewFilters = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
@ -121,7 +121,8 @@ const viewGroups = {
|
|||||||
* fix a circular dependency problem for WebPack,
|
* fix a circular dependency problem for WebPack,
|
||||||
* where the imports get resolved asynchronously.
|
* where the imports get resolved asynchronously.
|
||||||
*/
|
*/
|
||||||
const getConfigSchema = () => ({
|
function getConfigSchema() {
|
||||||
|
return {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
backend: {
|
backend: {
|
||||||
@ -315,7 +316,8 @@ const getConfigSchema = () => ({
|
|||||||
},
|
},
|
||||||
required: ['backend', 'collections'],
|
required: ['backend', 'collections'],
|
||||||
anyOf: [{ required: ['media_folder'] }, { required: ['media_library'] }],
|
anyOf: [{ required: ['media_folder'] }, { required: ['media_library'] }],
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getWidgetSchemas() {
|
function getWidgetSchemas() {
|
||||||
const schemas = getWidgets().map(widget => ({ [widget.name]: widget.schema }));
|
const schemas = getWidgets().map(widget => ({ [widget.name]: widget.schema }));
|
||||||
|
@ -28,8 +28,8 @@ export const extensionFormatters = {
|
|||||||
html: FrontmatterInfer,
|
html: FrontmatterInfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatByName = (name, customDelimiter) =>
|
function formatByName(name, customDelimiter) {
|
||||||
({
|
return {
|
||||||
yml: yamlFormatter,
|
yml: yamlFormatter,
|
||||||
yaml: yamlFormatter,
|
yaml: yamlFormatter,
|
||||||
toml: tomlFormatter,
|
toml: tomlFormatter,
|
||||||
@ -38,7 +38,8 @@ const formatByName = (name, customDelimiter) =>
|
|||||||
'json-frontmatter': frontmatterJSON(customDelimiter),
|
'json-frontmatter': frontmatterJSON(customDelimiter),
|
||||||
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
||||||
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
||||||
}[name]);
|
}[name];
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveFormat(collection, entry) {
|
export function resolveFormat(collection, entry) {
|
||||||
// Check for custom delimiter
|
// Check for custom delimiter
|
||||||
|
@ -51,12 +51,13 @@ function inferFrontmatterFormat(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFormatOpts = format =>
|
export function getFormatOpts(format) {
|
||||||
({
|
return {
|
||||||
yaml: { language: 'yaml', delimiters: '---' },
|
yaml: { language: 'yaml', delimiters: '---' },
|
||||||
toml: { language: 'toml', delimiters: '+++' },
|
toml: { language: 'toml', delimiters: '+++' },
|
||||||
json: { language: 'json', delimiters: ['{', '}'] },
|
json: { language: 'json', delimiters: ['{', '}'] },
|
||||||
}[format]);
|
}[format];
|
||||||
|
}
|
||||||
|
|
||||||
class FrontmatterFormatter {
|
class FrontmatterFormatter {
|
||||||
constructor(format, customDelimiter) {
|
constructor(format, customDelimiter) {
|
||||||
@ -99,6 +100,15 @@ class FrontmatterFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FrontmatterInfer = new FrontmatterFormatter();
|
export const FrontmatterInfer = new FrontmatterFormatter();
|
||||||
export const frontmatterYAML = customDelimiter => new FrontmatterFormatter('yaml', customDelimiter);
|
|
||||||
export const frontmatterTOML = customDelimiter => new FrontmatterFormatter('toml', customDelimiter);
|
export function frontmatterYAML(customDelimiter) {
|
||||||
export const frontmatterJSON = customDelimiter => new FrontmatterFormatter('json', customDelimiter);
|
return new FrontmatterFormatter('yaml', customDelimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontmatterTOML(customDelimiter) {
|
||||||
|
return new FrontmatterFormatter('toml', customDelimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontmatterJSON(customDelimiter) {
|
||||||
|
return new FrontmatterFormatter('json', customDelimiter);
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
export const sortKeys = (sortedKeys = [], selector = a => a) => (a, b) => {
|
export function sortKeys(sortedKeys = [], selector = a => a) {
|
||||||
|
return (a, b) => {
|
||||||
const idxA = sortedKeys.indexOf(selector(a));
|
const idxA = sortedKeys.indexOf(selector(a));
|
||||||
const idxB = sortedKeys.indexOf(selector(b));
|
const idxB = sortedKeys.indexOf(selector(b));
|
||||||
if (idxA === -1 || idxB === -1) return 0;
|
if (idxA === -1 || idxB === -1) return 0;
|
||||||
if (idxA > idxB) return 1;
|
if (idxA > idxB) return 1;
|
||||||
if (idxA < idxB) return -1;
|
if (idxA < idxB) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import moment from 'moment';
|
|||||||
import AssetProxy from 'ValueObjects/AssetProxy';
|
import AssetProxy from 'ValueObjects/AssetProxy';
|
||||||
import { sortKeys } from './helpers';
|
import { sortKeys } from './helpers';
|
||||||
|
|
||||||
const outputReplacer = (key, value) => {
|
function outputReplacer(key, value) {
|
||||||
if (moment.isMoment(value)) {
|
if (moment.isMoment(value)) {
|
||||||
return value.format(value._f);
|
return value.format(value._f);
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ const outputReplacer = (key, value) => {
|
|||||||
}
|
}
|
||||||
// Return `false` to use default (`undefined` would delete key).
|
// Return `false` to use default (`undefined` would delete key).
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fromFile(content) {
|
fromFile(content) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
import { sortKeys } from './helpers';
|
import { sortKeys } from './helpers';
|
||||||
|
|
||||||
const addComments = (items, comments, prefix = '') => {
|
function addComments(items, comments, prefix = '') {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
if (item.key !== undefined) {
|
if (item.key !== undefined) {
|
||||||
const itemKey = item.key.toString();
|
const itemKey = item.key.toString();
|
||||||
@ -15,7 +15,7 @@ const addComments = (items, comments, prefix = '') => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const timestampTag = {
|
const timestampTag = {
|
||||||
identify: value => value instanceof Date,
|
identify: value => value instanceof Date,
|
||||||
|
@ -41,12 +41,12 @@ type Options = {
|
|||||||
authorName?: string;
|
authorName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commitMessageFormatter = (
|
export function commitMessageFormatter(
|
||||||
type: string,
|
type: string,
|
||||||
config: Config,
|
config: Config,
|
||||||
{ slug, path, collection, authorLogin, authorName }: Options,
|
{ slug, path, collection, authorLogin, authorName }: Options,
|
||||||
isOpenAuthoring?: boolean,
|
isOpenAuthoring?: boolean,
|
||||||
) => {
|
) {
|
||||||
const templates = commitMessageTemplates.merge(
|
const templates = commitMessageTemplates.merge(
|
||||||
config.getIn(['backend', 'commit_messages'], Map<string, string>()),
|
config.getIn(['backend', 'commit_messages'], Map<string, string>()),
|
||||||
);
|
);
|
||||||
@ -88,9 +88,9 @@ export const commitMessageFormatter = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const prepareSlug = (slug: string) => {
|
export function prepareSlug(slug: string) {
|
||||||
return (
|
return (
|
||||||
slug
|
slug
|
||||||
.trim()
|
.trim()
|
||||||
@ -103,20 +103,20 @@ export const prepareSlug = (slug: string) => {
|
|||||||
// Replace periods with dashes.
|
// Replace periods with dashes.
|
||||||
.replace(/[.]/g, '-')
|
.replace(/[.]/g, '-')
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getProcessSegment = (slugConfig: SlugConfig, ignoreValues: string[] = []) => {
|
export function getProcessSegment(slugConfig: SlugConfig, ignoreValues: string[] = []) {
|
||||||
return (value: string) =>
|
return (value: string) =>
|
||||||
ignoreValues.includes(value)
|
ignoreValues.includes(value)
|
||||||
? value
|
? value
|
||||||
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
|
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const slugFormatter = (
|
export function slugFormatter(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryData: Map<string, unknown>,
|
entryData: Map<string, unknown>,
|
||||||
slugConfig: SlugConfig,
|
slugConfig: SlugConfig,
|
||||||
) => {
|
) {
|
||||||
const slugTemplate = collection.get('slug') || '{{slug}}';
|
const slugTemplate = collection.get('slug') || '{{slug}}';
|
||||||
|
|
||||||
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
|
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
|
||||||
@ -138,15 +138,15 @@ export const slugFormatter = (
|
|||||||
value === slug ? value : processSegment(value),
|
value === slug ? value : processSegment(value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const previewUrlFormatter = (
|
export function previewUrlFormatter(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
slug: string,
|
slug: string,
|
||||||
slugConfig: SlugConfig,
|
slugConfig: SlugConfig,
|
||||||
entry: EntryMap,
|
entry: EntryMap,
|
||||||
) => {
|
) {
|
||||||
/**
|
/**
|
||||||
* Preview URL can't be created without `baseUrl`. This makes preview URLs
|
* Preview URL can't be created without `baseUrl`. This makes preview URLs
|
||||||
* optional for backends that don't support them.
|
* optional for backends that don't support them.
|
||||||
@ -160,12 +160,13 @@ export const previewUrlFormatter = (
|
|||||||
const isFileCollection = collection.get('type') === FILES;
|
const isFileCollection = collection.get('type') === FILES;
|
||||||
const file = isFileCollection ? getFileFromSlug(collection, entry.get('slug')) : undefined;
|
const file = isFileCollection ? getFileFromSlug(collection, entry.get('slug')) : undefined;
|
||||||
|
|
||||||
const getPathTemplate = () => {
|
function getPathTemplate() {
|
||||||
return file?.get('preview_path') ?? collection.get('preview_path');
|
return file?.get('preview_path') ?? collection.get('preview_path');
|
||||||
};
|
}
|
||||||
const getDateField = () => {
|
|
||||||
|
function getDateField() {
|
||||||
return file?.get('preview_path_date_field') ?? collection.get('preview_path_date_field');
|
return file?.get('preview_path_date_field') ?? collection.get('preview_path_date_field');
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a `previewPath` is provided for the collection/file, use it to construct the
|
* If a `previewPath` is provided for the collection/file, use it to construct the
|
||||||
@ -209,13 +210,9 @@ export const previewUrlFormatter = (
|
|||||||
|
|
||||||
const previewPath = trimStart(compiledPath, ' /');
|
const previewPath = trimStart(compiledPath, ' /');
|
||||||
return `${basePath}/${previewPath}`;
|
return `${basePath}/${previewPath}`;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const summaryFormatter = (
|
export function summaryFormatter(summaryTemplate: string, entry: EntryMap, collection: Collection) {
|
||||||
summaryTemplate: string,
|
|
||||||
entry: EntryMap,
|
|
||||||
collection: Collection,
|
|
||||||
) => {
|
|
||||||
let entryData = entry.get('data');
|
let entryData = entry.get('data');
|
||||||
const date =
|
const date =
|
||||||
parseDateFromEntry(
|
parseDateFromEntry(
|
||||||
@ -234,16 +231,16 @@ export const summaryFormatter = (
|
|||||||
}
|
}
|
||||||
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
|
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
|
||||||
return summary;
|
return summary;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const folderFormatter = (
|
export function folderFormatter(
|
||||||
folderTemplate: string,
|
folderTemplate: string,
|
||||||
entry: EntryMap | undefined,
|
entry: EntryMap | undefined,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
defaultFolder: string,
|
defaultFolder: string,
|
||||||
folderKey: string,
|
folderKey: string,
|
||||||
slugConfig: SlugConfig,
|
slugConfig: SlugConfig,
|
||||||
) => {
|
) {
|
||||||
if (!entry || !entry.get('data')) {
|
if (!entry || !entry.get('data')) {
|
||||||
return folderTemplate;
|
return folderTemplate;
|
||||||
}
|
}
|
||||||
@ -268,4 +265,4 @@ export const folderFormatter = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return mediaFolder;
|
return mediaFolder;
|
||||||
};
|
}
|
||||||
|
@ -18,9 +18,9 @@ export enum I18N_FIELD {
|
|||||||
NONE = 'none',
|
NONE = 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasI18n = (collection: Collection) => {
|
export function hasI18n(collection: Collection) {
|
||||||
return collection.has(I18N);
|
return collection.has(I18N);
|
||||||
};
|
}
|
||||||
|
|
||||||
type I18nInfo = {
|
type I18nInfo = {
|
||||||
locales: string[];
|
locales: string[];
|
||||||
@ -28,53 +28,53 @@ type I18nInfo = {
|
|||||||
structure: I18N_STRUCTURE;
|
structure: I18N_STRUCTURE;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getI18nInfo = (collection: Collection) => {
|
export function getI18nInfo(collection: Collection) {
|
||||||
if (!hasI18n(collection)) {
|
if (!hasI18n(collection)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const { structure, locales, default_locale: defaultLocale } = collection.get(I18N).toJS();
|
const { structure, locales, default_locale: defaultLocale } = collection.get(I18N).toJS();
|
||||||
return { structure, locales, defaultLocale } as I18nInfo;
|
return { structure, locales, defaultLocale } as I18nInfo;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getI18nFilesDepth = (collection: Collection, depth: number) => {
|
export function getI18nFilesDepth(collection: Collection, depth: number) {
|
||||||
const { structure } = getI18nInfo(collection) as I18nInfo;
|
const { structure } = getI18nInfo(collection) as I18nInfo;
|
||||||
if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS) {
|
if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS) {
|
||||||
return depth + 1;
|
return depth + 1;
|
||||||
}
|
}
|
||||||
return depth;
|
return depth;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const isFieldTranslatable = (field: EntryField, locale: string, defaultLocale: string) => {
|
export function isFieldTranslatable(field: EntryField, locale: string, defaultLocale: string) {
|
||||||
const isTranslatable = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.TRANSLATE;
|
const isTranslatable = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.TRANSLATE;
|
||||||
return isTranslatable;
|
return isTranslatable;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const isFieldDuplicate = (field: EntryField, locale: string, defaultLocale: string) => {
|
export function isFieldDuplicate(field: EntryField, locale: string, defaultLocale: string) {
|
||||||
const isDuplicate = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.DUPLICATE;
|
const isDuplicate = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.DUPLICATE;
|
||||||
return isDuplicate;
|
return isDuplicate;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const isFieldHidden = (field: EntryField, locale: string, defaultLocale: string) => {
|
export function isFieldHidden(field: EntryField, locale: string, defaultLocale: string) {
|
||||||
const isHidden = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.NONE;
|
const isHidden = locale !== defaultLocale && field.get(I18N) === I18N_FIELD.NONE;
|
||||||
return isHidden;
|
return isHidden;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getLocaleDataPath = (locale: string) => {
|
export function getLocaleDataPath(locale: string) {
|
||||||
return [I18N, locale, 'data'];
|
return [I18N, locale, 'data'];
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getDataPath = (locale: string, defaultLocale: string) => {
|
export function getDataPath(locale: string, defaultLocale: string) {
|
||||||
const dataPath = locale !== defaultLocale ? getLocaleDataPath(locale) : ['data'];
|
const dataPath = locale !== defaultLocale ? getLocaleDataPath(locale) : ['data'];
|
||||||
return dataPath;
|
return dataPath;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getFilePath = (
|
export function getFilePath(
|
||||||
structure: I18N_STRUCTURE,
|
structure: I18N_STRUCTURE,
|
||||||
extension: string,
|
extension: string,
|
||||||
path: string,
|
path: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
locale: string,
|
locale: string,
|
||||||
) => {
|
) {
|
||||||
switch (structure) {
|
switch (structure) {
|
||||||
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
|
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
|
||||||
return path.replace(`/${slug}`, `/${locale}/${slug}`);
|
return path.replace(`/${slug}`, `/${locale}/${slug}`);
|
||||||
@ -84,9 +84,9 @@ export const getFilePath = (
|
|||||||
default:
|
default:
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getLocaleFromPath = (structure: I18N_STRUCTURE, extension: string, path: string) => {
|
export function getLocaleFromPath(structure: I18N_STRUCTURE, extension: string, path: string) {
|
||||||
switch (structure) {
|
switch (structure) {
|
||||||
case I18N_STRUCTURE.MULTIPLE_FOLDERS: {
|
case I18N_STRUCTURE.MULTIPLE_FOLDERS: {
|
||||||
const parts = path.split('/');
|
const parts = path.split('/');
|
||||||
@ -103,23 +103,23 @@ export const getLocaleFromPath = (structure: I18N_STRUCTURE, extension: string,
|
|||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getFilePaths = (
|
export function getFilePaths(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
extension: string,
|
extension: string,
|
||||||
path: string,
|
path: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
) => {
|
) {
|
||||||
const { structure, locales } = getI18nInfo(collection) as I18nInfo;
|
const { structure, locales } = getI18nInfo(collection) as I18nInfo;
|
||||||
const paths = locales.map(locale =>
|
const paths = locales.map(locale =>
|
||||||
getFilePath(structure as I18N_STRUCTURE, extension, path, slug, locale),
|
getFilePath(structure as I18N_STRUCTURE, extension, path, slug, locale),
|
||||||
);
|
);
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const normalizeFilePath = (structure: I18N_STRUCTURE, path: string, locale: string) => {
|
export function normalizeFilePath(structure: I18N_STRUCTURE, path: string, locale: string) {
|
||||||
switch (structure) {
|
switch (structure) {
|
||||||
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
|
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
|
||||||
return path.replace(`${locale}/`, '');
|
return path.replace(`${locale}/`, '');
|
||||||
@ -129,9 +129,9 @@ export const normalizeFilePath = (structure: I18N_STRUCTURE, path: string, local
|
|||||||
default:
|
default:
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getI18nFiles = (
|
export function getI18nFiles(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
extension: string,
|
extension: string,
|
||||||
entryDraft: EntryMap,
|
entryDraft: EntryMap,
|
||||||
@ -139,7 +139,7 @@ export const getI18nFiles = (
|
|||||||
path: string,
|
path: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
newPath?: string,
|
newPath?: string,
|
||||||
) => {
|
) {
|
||||||
const { structure, defaultLocale, locales } = getI18nInfo(collection) as I18nInfo;
|
const { structure, defaultLocale, locales } = getI18nInfo(collection) as I18nInfo;
|
||||||
|
|
||||||
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
||||||
@ -176,13 +176,13 @@ export const getI18nFiles = (
|
|||||||
})
|
})
|
||||||
.filter(dataFile => dataFile.raw);
|
.filter(dataFile => dataFile.raw);
|
||||||
return dataFiles;
|
return dataFiles;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getI18nBackup = (
|
export function getI18nBackup(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entry: EntryMap,
|
entry: EntryMap,
|
||||||
entryToRaw: (entry: EntryMap) => string,
|
entryToRaw: (entry: EntryMap) => string,
|
||||||
) => {
|
) {
|
||||||
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
||||||
|
|
||||||
const i18nBackup = locales
|
const i18nBackup = locales
|
||||||
@ -198,26 +198,26 @@ export const getI18nBackup = (
|
|||||||
}, {} as Record<string, { raw: string }>);
|
}, {} as Record<string, { raw: string }>);
|
||||||
|
|
||||||
return i18nBackup;
|
return i18nBackup;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const formatI18nBackup = (
|
export function formatI18nBackup(
|
||||||
i18nBackup: Record<string, { raw: string }>,
|
i18nBackup: Record<string, { raw: string }>,
|
||||||
formatRawData: (raw: string) => EntryValue,
|
formatRawData: (raw: string) => EntryValue,
|
||||||
) => {
|
) {
|
||||||
const i18n = Object.entries(i18nBackup).reduce((acc, [locale, { raw }]) => {
|
const i18n = Object.entries(i18nBackup).reduce((acc, [locale, { raw }]) => {
|
||||||
const entry = formatRawData(raw);
|
const entry = formatRawData(raw);
|
||||||
return { ...acc, [locale]: { data: entry.data } };
|
return { ...acc, [locale]: { data: entry.data } };
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return i18n;
|
return i18n;
|
||||||
};
|
}
|
||||||
|
|
||||||
const mergeValues = (
|
function mergeValues(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
structure: I18N_STRUCTURE,
|
structure: I18N_STRUCTURE,
|
||||||
defaultLocale: string,
|
defaultLocale: string,
|
||||||
values: { locale: string; value: EntryValue }[],
|
values: { locale: string; value: EntryValue }[],
|
||||||
) => {
|
) {
|
||||||
let defaultEntry = values.find(e => e.locale === defaultLocale);
|
let defaultEntry = values.find(e => e.locale === defaultLocale);
|
||||||
if (!defaultEntry) {
|
if (!defaultEntry) {
|
||||||
defaultEntry = values[0];
|
defaultEntry = values[0];
|
||||||
@ -241,9 +241,9 @@ const mergeValues = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
return entryValue;
|
return entryValue;
|
||||||
};
|
}
|
||||||
|
|
||||||
const mergeSingleFileValue = (entryValue: EntryValue, defaultLocale: string, locales: string[]) => {
|
function mergeSingleFileValue(entryValue: EntryValue, defaultLocale: string, locales: string[]) {
|
||||||
const data = entryValue.data[defaultLocale] || {};
|
const data = entryValue.data[defaultLocale] || {};
|
||||||
const i18n = locales
|
const i18n = locales
|
||||||
.filter(l => l !== defaultLocale)
|
.filter(l => l !== defaultLocale)
|
||||||
@ -259,15 +259,15 @@ const mergeSingleFileValue = (entryValue: EntryValue, defaultLocale: string, loc
|
|||||||
i18n,
|
i18n,
|
||||||
raw: '',
|
raw: '',
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getI18nEntry = async (
|
export async function getI18nEntry(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
extension: string,
|
extension: string,
|
||||||
path: string,
|
path: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
getEntryValue: (path: string) => Promise<EntryValue>,
|
getEntryValue: (path: string) => Promise<EntryValue>,
|
||||||
) => {
|
) {
|
||||||
const { structure, locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
const { structure, locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
||||||
|
|
||||||
let entryValue: EntryValue;
|
let entryValue: EntryValue;
|
||||||
@ -291,9 +291,9 @@ export const getI18nEntry = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entryValue;
|
return entryValue;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const groupEntries = (collection: Collection, extension: string, entries: EntryValue[]) => {
|
export function groupEntries(collection: Collection, extension: string, entries: EntryValue[]) {
|
||||||
const { structure, defaultLocale, locales } = getI18nInfo(collection) as I18nInfo;
|
const { structure, defaultLocale, locales } = getI18nInfo(collection) as I18nInfo;
|
||||||
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
||||||
return entries.map(e => mergeSingleFileValue(e, defaultLocale, locales));
|
return entries.map(e => mergeSingleFileValue(e, defaultLocale, locales));
|
||||||
@ -315,15 +315,15 @@ export const groupEntries = (collection: Collection, extension: string, entries:
|
|||||||
}, [] as EntryValue[]);
|
}, [] as EntryValue[]);
|
||||||
|
|
||||||
return groupedEntries;
|
return groupedEntries;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getI18nDataFiles = (
|
export function getI18nDataFiles(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
extension: string,
|
extension: string,
|
||||||
path: string,
|
path: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
diffFiles: { path: string; id: string; newFile: boolean }[],
|
diffFiles: { path: string; id: string; newFile: boolean }[],
|
||||||
) => {
|
) {
|
||||||
const { structure } = getI18nInfo(collection) as I18nInfo;
|
const { structure } = getI18nInfo(collection) as I18nInfo;
|
||||||
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
if (structure === I18N_STRUCTURE.SINGLE_FILE) {
|
||||||
return diffFiles;
|
return diffFiles;
|
||||||
@ -339,10 +339,10 @@ export const getI18nDataFiles = (
|
|||||||
}, [] as { path: string; id: string; newFile: boolean }[]);
|
}, [] as { path: string; id: string; newFile: boolean }[]);
|
||||||
|
|
||||||
return dataFiles;
|
return dataFiles;
|
||||||
};
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const duplicateDefaultI18nFields = (collection: Collection, dataFields: any) => {
|
export function duplicateDefaultI18nFields(collection: Collection, dataFields: any) {
|
||||||
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
||||||
|
|
||||||
const i18nFields = Object.fromEntries(
|
const i18nFields = Object.fromEntries(
|
||||||
@ -352,15 +352,15 @@ export const duplicateDefaultI18nFields = (collection: Collection, dataFields: a
|
|||||||
);
|
);
|
||||||
|
|
||||||
return i18nFields;
|
return i18nFields;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const duplicateI18nFields = (
|
export function duplicateI18nFields(
|
||||||
entryDraft: EntryDraft,
|
entryDraft: EntryDraft,
|
||||||
field: EntryField,
|
field: EntryField,
|
||||||
locales: string[],
|
locales: string[],
|
||||||
defaultLocale: string,
|
defaultLocale: string,
|
||||||
fieldPath: string[] = [field.get('name')],
|
fieldPath: string[] = [field.get('name')],
|
||||||
) => {
|
) {
|
||||||
const value = entryDraft.getIn(['entry', 'data', ...fieldPath]);
|
const value = entryDraft.getIn(['entry', 'data', ...fieldPath]);
|
||||||
if (field.get(I18N) === I18N_FIELD.DUPLICATE) {
|
if (field.get(I18N) === I18N_FIELD.DUPLICATE) {
|
||||||
locales
|
locales
|
||||||
@ -392,21 +392,21 @@ export const duplicateI18nFields = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entryDraft;
|
return entryDraft;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getPreviewEntry = (entry: EntryMap, locale: string, defaultLocale: string) => {
|
export function getPreviewEntry(entry: EntryMap, locale: string, defaultLocale: string) {
|
||||||
if (locale === defaultLocale) {
|
if (locale === defaultLocale) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
return entry.set('data', entry.getIn([I18N, locale, 'data']));
|
return entry.set('data', entry.getIn([I18N, locale, 'data']));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const serializeI18n = (
|
export function serializeI18n(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entry: Entry,
|
entry: Entry,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
serializeValues: (data: any) => any,
|
serializeValues: (data: any) => any,
|
||||||
) => {
|
) {
|
||||||
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo;
|
||||||
|
|
||||||
locales
|
locales
|
||||||
@ -417,4 +417,4 @@ export const serializeI18n = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
};
|
}
|
||||||
|
@ -20,7 +20,7 @@ import { getWidgetValueSerializer } from './registry';
|
|||||||
* registered deserialization handlers run on entry load, and serialization
|
* registered deserialization handlers run on entry load, and serialization
|
||||||
* handlers run on persist.
|
* handlers run on persist.
|
||||||
*/
|
*/
|
||||||
const runSerializer = (values, fields, method) => {
|
function runSerializer(values, fields, method) {
|
||||||
/**
|
/**
|
||||||
* Reduce the list of fields to a map where keys are field names and values
|
* Reduce the list of fields to a map where keys are field names and values
|
||||||
* are field values, serializing the values of fields whose widgets have
|
* are field values, serializing the values of fields whose widgets have
|
||||||
@ -63,12 +63,12 @@ const runSerializer = (values, fields, method) => {
|
|||||||
serializedData = values.mergeDeep(serializedData);
|
serializedData = values.mergeDeep(serializedData);
|
||||||
|
|
||||||
return serializedData;
|
return serializedData;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const serializeValues = (values, fields) => {
|
export function serializeValues(values, fields) {
|
||||||
return runSerializer(values, fields, 'serialize');
|
return runSerializer(values, fields, 'serialize');
|
||||||
};
|
}
|
||||||
|
|
||||||
export const deserializeValues = (values, fields) => {
|
export function deserializeValues(values, fields) {
|
||||||
return runSerializer(values, fields, 'deserialize');
|
return runSerializer(values, fields, 'deserialize');
|
||||||
};
|
}
|
||||||
|
@ -36,8 +36,14 @@ export function stripProtocol(urlString: string) {
|
|||||||
*/
|
*/
|
||||||
const uriChars = /[\w\-.~]/i;
|
const uriChars = /[\w\-.~]/i;
|
||||||
const ucsChars = /[\xA0-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}]/u;
|
const ucsChars = /[\xA0-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}]/u;
|
||||||
const validURIChar = (char: string) => uriChars.test(char);
|
|
||||||
const validIRIChar = (char: string) => uriChars.test(char) || ucsChars.test(char);
|
function validURIChar(char: string) {
|
||||||
|
return uriChars.test(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validIRIChar(char: string) {
|
||||||
|
return uriChars.test(char) || ucsChars.test(char);
|
||||||
|
}
|
||||||
|
|
||||||
export function getCharReplacer(encoding: string, replacement: string) {
|
export function getCharReplacer(encoding: string, replacement: string) {
|
||||||
let validChar: (char: string) => boolean;
|
let validChar: (char: string) => boolean;
|
||||||
|
@ -18,6 +18,12 @@ interface MediaLibrary {
|
|||||||
}) => MediaLibraryInstance;
|
}) => MediaLibraryInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleInsert(url: string) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||||
|
// @ts-ignore
|
||||||
|
return store.dispatch(insertMedia(url, undefined));
|
||||||
|
}
|
||||||
|
|
||||||
const initializeMediaLibrary = once(async function initializeMediaLibrary(name, options) {
|
const initializeMediaLibrary = once(async function initializeMediaLibrary(name, options) {
|
||||||
const lib = (getMediaLibrary(name) as unknown) as MediaLibrary | undefined;
|
const lib = (getMediaLibrary(name) as unknown) as MediaLibrary | undefined;
|
||||||
if (!lib) {
|
if (!lib) {
|
||||||
@ -26,9 +32,6 @@ const initializeMediaLibrary = once(async function initializeMediaLibrary(name,
|
|||||||
);
|
);
|
||||||
store.dispatch(configFailed(err));
|
store.dispatch(configFailed(err));
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
||||||
// @ts-ignore
|
|
||||||
const handleInsert = (url: string) => store.dispatch(insertMedia(url, undefined));
|
|
||||||
const instance = await lib.init({ options, handleInsert });
|
const instance = await lib.init({ options, handleInsert });
|
||||||
store.dispatch(createMediaLibrary(instance));
|
store.dispatch(createMediaLibrary(instance));
|
||||||
}
|
}
|
||||||
|
@ -460,7 +460,9 @@ describe('collections', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const updater = field => field.set('default', 'default');
|
function updater(field) {
|
||||||
|
return field.set('default', 'default');
|
||||||
|
}
|
||||||
|
|
||||||
expect(updateFieldByKey(collection, 'non-existent', updater)).toBe(collection);
|
expect(updateFieldByKey(collection, 'non-existent', updater)).toBe(collection);
|
||||||
expect(updateFieldByKey(collection, 'title', updater)).toEqual(
|
expect(updateFieldByKey(collection, 'title', updater)).toEqual(
|
||||||
|
@ -22,7 +22,7 @@ import { Backend } from '../backend';
|
|||||||
|
|
||||||
const { keyToPathArray } = stringTemplate;
|
const { keyToPathArray } = stringTemplate;
|
||||||
|
|
||||||
const collections = (state = null, action: CollectionsAction) => {
|
function collections(state = null, action: CollectionsAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS: {
|
case CONFIG_SUCCESS: {
|
||||||
const configCollections = action.payload
|
const configCollections = action.payload
|
||||||
@ -49,7 +49,7 @@ const collections = (state = null, action: CollectionsAction) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
[FOLDER]: {
|
[FOLDER]: {
|
||||||
@ -120,7 +120,7 @@ const selectors = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFieldsWithMediaFolders = (fields: EntryField[]) => {
|
function getFieldsWithMediaFolders(fields: EntryField[]) {
|
||||||
const fieldsWithMediaFolders = fields.reduce((acc, f) => {
|
const fieldsWithMediaFolders = fields.reduce((acc, f) => {
|
||||||
if (f.has('media_folder')) {
|
if (f.has('media_folder')) {
|
||||||
acc = [...acc, f];
|
acc = [...acc, f];
|
||||||
@ -141,16 +141,16 @@ const getFieldsWithMediaFolders = (fields: EntryField[]) => {
|
|||||||
}, [] as EntryField[]);
|
}, [] as EntryField[]);
|
||||||
|
|
||||||
return fieldsWithMediaFolders;
|
return fieldsWithMediaFolders;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getFileFromSlug = (collection: Collection, slug: string) => {
|
export function getFileFromSlug(collection: Collection, slug: string) {
|
||||||
return collection
|
return collection
|
||||||
.get('files')
|
.get('files')
|
||||||
?.toArray()
|
?.toArray()
|
||||||
.find(f => f.get('name') === slug);
|
.find(f => f.get('name') === slug);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectFieldsWithMediaFolders = (collection: Collection, slug: string) => {
|
export function selectFieldsWithMediaFolders(collection: Collection, slug: string) {
|
||||||
if (collection.has('folder')) {
|
if (collection.has('folder')) {
|
||||||
const fields = collection.get('fields').toArray();
|
const fields = collection.get('fields').toArray();
|
||||||
return getFieldsWithMediaFolders(fields);
|
return getFieldsWithMediaFolders(fields);
|
||||||
@ -163,9 +163,9 @@ export const selectFieldsWithMediaFolders = (collection: Collection, slug: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectMediaFolders = (state: State, collection: Collection, entry: EntryMap) => {
|
export function selectMediaFolders(state: State, 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(state.config, collection, entry, f));
|
||||||
if (collection.has('files')) {
|
if (collection.has('files')) {
|
||||||
@ -181,26 +181,41 @@ export const selectMediaFolders = (state: State, collection: Collection, entry:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Set(folders).toArray();
|
return Set(folders).toArray();
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectFields = (collection: Collection, slug: string) =>
|
export function selectFields(collection: Collection, slug: string) {
|
||||||
selectors[collection.get('type')].fields(collection, slug);
|
return selectors[collection.get('type')].fields(collection, slug);
|
||||||
export const selectFolderEntryExtension = (collection: Collection) =>
|
}
|
||||||
selectors[FOLDER].entryExtension(collection);
|
|
||||||
export const selectFileEntryLabel = (collection: Collection, slug: string) =>
|
|
||||||
selectors[FILES].entryLabel(collection, slug);
|
|
||||||
export const selectEntryPath = (collection: Collection, slug: string) =>
|
|
||||||
selectors[collection.get('type')].entryPath(collection, slug);
|
|
||||||
export const selectEntrySlug = (collection: Collection, path: string) =>
|
|
||||||
selectors[collection.get('type')].entrySlug(collection, path);
|
|
||||||
export const selectAllowNewEntries = (collection: Collection) =>
|
|
||||||
selectors[collection.get('type')].allowNewEntries(collection);
|
|
||||||
export const selectAllowDeletion = (collection: Collection) =>
|
|
||||||
selectors[collection.get('type')].allowDeletion(collection);
|
|
||||||
export const selectTemplateName = (collection: Collection, slug: string) =>
|
|
||||||
selectors[collection.get('type')].templateName(collection, slug);
|
|
||||||
|
|
||||||
export const getFieldsNames = (fields: EntryField[], prefix = '') => {
|
export function selectFolderEntryExtension(collection: Collection) {
|
||||||
|
return selectors[FOLDER].entryExtension(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectFileEntryLabel(collection: Collection, slug: string) {
|
||||||
|
return selectors[FILES].entryLabel(collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectEntryPath(collection: Collection, slug: string) {
|
||||||
|
return selectors[collection.get('type')].entryPath(collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectEntrySlug(collection: Collection, path: string) {
|
||||||
|
return selectors[collection.get('type')].entrySlug(collection, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectAllowNewEntries(collection: Collection) {
|
||||||
|
return selectors[collection.get('type')].allowNewEntries(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectAllowDeletion(collection: Collection) {
|
||||||
|
return selectors[collection.get('type')].allowDeletion(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectTemplateName(collection: Collection, slug: string) {
|
||||||
|
return selectors[collection.get('type')].templateName(collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFieldsNames(fields: EntryField[], prefix = '') {
|
||||||
let names = fields.map(f => `${prefix}${f.get('name')}`);
|
let names = fields.map(f => `${prefix}${f.get('name')}`);
|
||||||
|
|
||||||
fields.forEach((f, index) => {
|
fields.forEach((f, index) => {
|
||||||
@ -217,9 +232,9 @@ export const getFieldsNames = (fields: EntryField[], prefix = '') => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectField = (collection: Collection, key: string) => {
|
export function selectField(collection: Collection, key: string) {
|
||||||
const array = keyToPathArray(key);
|
const array = keyToPathArray(key);
|
||||||
let name: string | undefined;
|
let name: string | undefined;
|
||||||
let field;
|
let field;
|
||||||
@ -236,13 +251,13 @@ export const selectField = (collection: Collection, key: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const traverseFields = (
|
export function traverseFields(
|
||||||
fields: List<EntryField>,
|
fields: List<EntryField>,
|
||||||
updater: (field: EntryField) => EntryField,
|
updater: (field: EntryField) => EntryField,
|
||||||
done = () => false,
|
done = () => false,
|
||||||
) => {
|
) {
|
||||||
if (done()) {
|
if (done()) {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
@ -268,20 +283,21 @@ export const traverseFields = (
|
|||||||
.toList() as List<EntryField>;
|
.toList() as List<EntryField>;
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const updateFieldByKey = (
|
export function updateFieldByKey(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
key: string,
|
key: string,
|
||||||
updater: (field: EntryField) => EntryField,
|
updater: (field: EntryField) => EntryField,
|
||||||
) => {
|
) {
|
||||||
const selected = selectField(collection, key);
|
const selected = selectField(collection, key);
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
let updated = false;
|
let updated = false;
|
||||||
const updateAndBreak = (f: EntryField) => {
|
|
||||||
|
function updateAndBreak(f: EntryField) {
|
||||||
const field = f as EntryField;
|
const field = f as EntryField;
|
||||||
if (field === selected) {
|
if (field === selected) {
|
||||||
updated = true;
|
updated = true;
|
||||||
@ -289,7 +305,7 @@ export const updateFieldByKey = (
|
|||||||
} else {
|
} else {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
collection = collection.set(
|
collection = collection.set(
|
||||||
'fields',
|
'fields',
|
||||||
@ -297,18 +313,18 @@ export const updateFieldByKey = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const 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<EntryField>()).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()),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectInferedField = (collection: Collection, fieldName: string) => {
|
export function selectInferedField(collection: Collection, fieldName: string) {
|
||||||
if (fieldName === 'title' && collection.get('identifier_field')) {
|
if (fieldName === 'title' && collection.get('identifier_field')) {
|
||||||
return selectIdentifier(collection);
|
return selectIdentifier(collection);
|
||||||
}
|
}
|
||||||
@ -355,9 +371,9 @@ export const selectInferedField = (collection: Collection, fieldName: string) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntryCollectionTitle = (collection: Collection, entry: EntryMap) => {
|
export function selectEntryCollectionTitle(collection: Collection, entry: EntryMap) {
|
||||||
// prefer formatted summary over everything else
|
// prefer formatted summary over everything else
|
||||||
const summaryTemplate = collection.get('summary');
|
const summaryTemplate = collection.get('summary');
|
||||||
if (summaryTemplate) return summaryFormatter(summaryTemplate, entry, collection);
|
if (summaryTemplate) return summaryFormatter(summaryTemplate, entry, collection);
|
||||||
@ -372,16 +388,16 @@ export const selectEntryCollectionTitle = (collection: Collection, entry: EntryM
|
|||||||
const entryData = entry.get('data');
|
const entryData = entry.get('data');
|
||||||
const titleField = selectInferedField(collection, 'title');
|
const titleField = selectInferedField(collection, 'title');
|
||||||
return titleField && entryData.getIn(keyToPathArray(titleField));
|
return titleField && entryData.getIn(keyToPathArray(titleField));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const COMMIT_AUTHOR = 'commit_author';
|
export const COMMIT_AUTHOR = 'commit_author';
|
||||||
export const COMMIT_DATE = 'commit_date';
|
export const COMMIT_DATE = 'commit_date';
|
||||||
|
|
||||||
export const selectDefaultSortableFields = (
|
export function selectDefaultSortableFields(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
hasIntegration: boolean,
|
hasIntegration: boolean,
|
||||||
) => {
|
) {
|
||||||
let defaultSortable = SORTABLE_FIELDS.map((type: string) => {
|
let defaultSortable = SORTABLE_FIELDS.map((type: string) => {
|
||||||
const field = selectInferedField(collection, type);
|
const field = selectInferedField(collection, type);
|
||||||
if (backend.isGitBackend() && type === 'author' && !field && !hasIntegration) {
|
if (backend.isGitBackend() && type === 'author' && !field && !hasIntegration) {
|
||||||
@ -397,9 +413,9 @@ export const selectDefaultSortableFields = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return defaultSortable as string[];
|
return defaultSortable as string[];
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectSortableFields = (collection: Collection, t: (key: string) => string) => {
|
export function selectSortableFields(collection: Collection, t: (key: string) => string) {
|
||||||
const fields = collection
|
const fields = collection
|
||||||
.get('sortable_fields')
|
.get('sortable_fields')
|
||||||
.toArray()
|
.toArray()
|
||||||
@ -418,9 +434,9 @@ export const selectSortableFields = (collection: Collection, t: (key: string) =>
|
|||||||
.map(item => ({ ...item.field, key: item.key }));
|
.map(item => ({ ...item.field, key: item.key }));
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectSortDataPath = (collection: Collection, key: string) => {
|
export function selectSortDataPath(collection: Collection, key: string) {
|
||||||
if (key === COMMIT_DATE) {
|
if (key === COMMIT_DATE) {
|
||||||
return 'updatedOn';
|
return 'updatedOn';
|
||||||
} else if (key === COMMIT_AUTHOR && !selectField(collection, key)) {
|
} else if (key === COMMIT_AUTHOR && !selectField(collection, key)) {
|
||||||
@ -428,19 +444,19 @@ export const selectSortDataPath = (collection: Collection, key: string) => {
|
|||||||
} else {
|
} else {
|
||||||
return `data.${key}`;
|
return `data.${key}`;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectViewFilters = (collection: Collection) => {
|
export function selectViewFilters(collection: Collection) {
|
||||||
const viewFilters = collection.get('view_filters').toJS() as ViewFilter[];
|
const viewFilters = collection.get('view_filters').toJS() as ViewFilter[];
|
||||||
return viewFilters;
|
return viewFilters;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectViewGroups = (collection: Collection) => {
|
export function selectViewGroups(collection: Collection) {
|
||||||
const viewGroups = collection.get('view_groups').toJS() as ViewGroup[];
|
const viewGroups = collection.get('view_groups').toJS() as ViewGroup[];
|
||||||
return viewGroups;
|
return viewGroups;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectFieldsComments = (collection: Collection, entryMap: EntryMap) => {
|
export function selectFieldsComments(collection: Collection, entryMap: EntryMap) {
|
||||||
let fields: EntryField[] = [];
|
let fields: EntryField[] = [];
|
||||||
if (collection.has('folder')) {
|
if (collection.has('folder')) {
|
||||||
fields = collection.get('fields').toArray();
|
fields = collection.get('fields').toArray();
|
||||||
@ -458,15 +474,15 @@ export const selectFieldsComments = (collection: Collection, entryMap: EntryMap)
|
|||||||
});
|
});
|
||||||
|
|
||||||
return comments;
|
return comments;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectHasMetaPath = (collection: Collection) => {
|
export function selectHasMetaPath(collection: Collection) {
|
||||||
return (
|
return (
|
||||||
collection.has('folder') &&
|
collection.has('folder') &&
|
||||||
collection.get('type') === FOLDER &&
|
collection.get('type') === FOLDER &&
|
||||||
collection.has('meta') &&
|
collection.has('meta') &&
|
||||||
collection.get('meta')?.has('path')
|
collection.get('meta')?.has('path')
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default collections;
|
export default collections;
|
||||||
|
@ -3,12 +3,12 @@ import { connectRouter } from 'connected-react-router';
|
|||||||
import { reducer as notifReducer } from 'redux-notifications';
|
import { reducer as notifReducer } from 'redux-notifications';
|
||||||
import reducers from './index';
|
import reducers from './index';
|
||||||
|
|
||||||
const createRootReducer = history => {
|
function createRootReducer(history) {
|
||||||
return combineReducers({
|
return combineReducers({
|
||||||
...reducers,
|
...reducers,
|
||||||
notifs: notifReducer,
|
notifs: notifReducer,
|
||||||
router: connectRouter(history),
|
router: connectRouter(history),
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export default createRootReducer;
|
export default createRootReducer;
|
||||||
|
@ -5,7 +5,7 @@ import { EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
|||||||
|
|
||||||
const defaultState: Map<string, boolean | string> = Map({ isFetching: true });
|
const defaultState: Map<string, boolean | string> = Map({ isFetching: true });
|
||||||
|
|
||||||
const config = (state = defaultState, action: ConfigAction) => {
|
function config(state = defaultState, action: ConfigAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_REQUEST:
|
case CONFIG_REQUEST:
|
||||||
return state.set('isFetching', true);
|
return state.set('isFetching', true);
|
||||||
@ -24,11 +24,14 @@ const config = (state = defaultState, action: ConfigAction) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectLocale = (state: Config) => state.get('locale', 'en') as string;
|
export function selectLocale(state: Config) {
|
||||||
|
return state.get('locale', 'en') as string;
|
||||||
|
}
|
||||||
|
|
||||||
export const selectUseWorkflow = (state: Config) =>
|
export function selectUseWorkflow(state: Config) {
|
||||||
state.get('publish_mode') === EDITORIAL_WORKFLOW;
|
return state.get('publish_mode') === EDITORIAL_WORKFLOW;
|
||||||
|
}
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -10,10 +10,11 @@ import {
|
|||||||
// Since pagination can be used for a variety of views (collections
|
// Since pagination can be used for a variety of views (collections
|
||||||
// and searches are the most common examples), we namespace cursors by
|
// and searches are the most common examples), we namespace cursors by
|
||||||
// their type before storing them in the state.
|
// their type before storing them in the state.
|
||||||
export const selectCollectionEntriesCursor = (state, collectionName) =>
|
export function selectCollectionEntriesCursor(state, collectionName) {
|
||||||
new Cursor(state.getIn(['cursorsByType', 'collectionEntries', collectionName]));
|
return new Cursor(state.getIn(['cursorsByType', 'collectionEntries', collectionName]));
|
||||||
|
}
|
||||||
|
|
||||||
const cursors = (state = fromJS({ cursorsByType: { collectionEntries: {} } }), action) => {
|
function cursors(state = fromJS({ cursorsByType: { collectionEntries: {} } }), action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ENTRIES_SUCCESS: {
|
case ENTRIES_SUCCESS: {
|
||||||
return state.setIn(
|
return state.setIn(
|
||||||
@ -29,6 +30,6 @@ const cursors = (state = fromJS({ cursorsByType: { collectionEntries: {} } }), a
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default cursors;
|
export default cursors;
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
DEPLOY_PREVIEW_FAILURE,
|
DEPLOY_PREVIEW_FAILURE,
|
||||||
} from 'Actions/deploys';
|
} from 'Actions/deploys';
|
||||||
|
|
||||||
const deploys = (state = Map({ deploys: Map() }), action) => {
|
function deploys(state = Map({ deploys: Map() }), action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DEPLOY_PREVIEW_REQUEST: {
|
case DEPLOY_PREVIEW_REQUEST: {
|
||||||
const { collection, slug } = action.payload;
|
const { collection, slug } = action.payload;
|
||||||
@ -37,9 +37,10 @@ const deploys = (state = Map({ deploys: Map() }), action) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectDeployPreview = (state, collection, slug) =>
|
export function selectDeployPreview(state, collection, slug) {
|
||||||
state.getIn(['deploys', `${collection}.${slug}`]);
|
return state.getIn(['deploys', `${collection}.${slug}`]);
|
||||||
|
}
|
||||||
|
|
||||||
export default deploys;
|
export default deploys;
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
import { CONFIG_SUCCESS } from '../actions/config';
|
import { CONFIG_SUCCESS } from '../actions/config';
|
||||||
import { EditorialWorkflowAction, EditorialWorkflow, Entities } from '../types/redux';
|
import { EditorialWorkflowAction, EditorialWorkflow, Entities } from '../types/redux';
|
||||||
|
|
||||||
const 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.get('publish_mode');
|
||||||
@ -137,27 +137,25 @@ const unpublishedEntries = (state = Map(), action: EditorialWorkflowAction) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectUnpublishedEntry = (
|
export function selectUnpublishedEntry(state: EditorialWorkflow, collection: string, slug: string) {
|
||||||
state: EditorialWorkflow,
|
return state && state.getIn(['entities', `${collection}.${slug}`]);
|
||||||
collection: string,
|
}
|
||||||
slug: string,
|
|
||||||
) => state && state.getIn(['entities', `${collection}.${slug}`]);
|
|
||||||
|
|
||||||
export const selectUnpublishedEntriesByStatus = (state: EditorialWorkflow, status: string) => {
|
export function selectUnpublishedEntriesByStatus(state: EditorialWorkflow, status: string) {
|
||||||
if (!state) return null;
|
if (!state) return null;
|
||||||
const entities = state.get('entities') as Entities;
|
const entities = state.get('entities') as Entities;
|
||||||
return entities.filter(entry => entry.get('status') === status).valueSeq();
|
return entities.filter(entry => entry.get('status') === status).valueSeq();
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectUnpublishedSlugs = (state: EditorialWorkflow, collection: string) => {
|
export function selectUnpublishedSlugs(state: EditorialWorkflow, collection: string) {
|
||||||
if (!state.get('entities')) return null;
|
if (!state.get('entities')) return null;
|
||||||
const entities = state.get('entities') as Entities;
|
const entities = state.get('entities') as Entities;
|
||||||
return entities
|
return entities
|
||||||
.filter((_v, k) => startsWith(k as string, `${collection}.`))
|
.filter((_v, k) => startsWith(k as string, `${collection}.`))
|
||||||
.map(entry => entry.get('slug'))
|
.map(entry => entry.get('slug'))
|
||||||
.valueSeq();
|
.valueSeq();
|
||||||
};
|
}
|
||||||
|
|
||||||
export default unpublishedEntries;
|
export default unpublishedEntries;
|
||||||
|
@ -95,11 +95,11 @@ const loadSort = once(() => {
|
|||||||
return Map() as Sort;
|
return Map() as Sort;
|
||||||
});
|
});
|
||||||
|
|
||||||
const clearSort = () => {
|
function clearSort() {
|
||||||
localStorage.removeItem(storageSortKey);
|
localStorage.removeItem(storageSortKey);
|
||||||
};
|
}
|
||||||
|
|
||||||
const persistSort = (sort: Sort | undefined) => {
|
function persistSort(sort: Sort | undefined) {
|
||||||
if (sort) {
|
if (sort) {
|
||||||
const storageSort: StorageSort = {};
|
const storageSort: StorageSort = {};
|
||||||
sort.keySeq().forEach(key => {
|
sort.keySeq().forEach(key => {
|
||||||
@ -117,7 +117,7 @@ const persistSort = (sort: Sort | undefined) => {
|
|||||||
} else {
|
} else {
|
||||||
clearSort();
|
clearSort();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const loadViewStyle = once(() => {
|
const loadViewStyle = once(() => {
|
||||||
const viewStyle = localStorage.getItem(viewStyleKey);
|
const viewStyle = localStorage.getItem(viewStyleKey);
|
||||||
@ -129,22 +129,22 @@ const loadViewStyle = once(() => {
|
|||||||
return VIEW_STYLE_LIST;
|
return VIEW_STYLE_LIST;
|
||||||
});
|
});
|
||||||
|
|
||||||
const clearViewStyle = () => {
|
function clearViewStyle() {
|
||||||
localStorage.removeItem(viewStyleKey);
|
localStorage.removeItem(viewStyleKey);
|
||||||
};
|
}
|
||||||
|
|
||||||
const persistViewStyle = (viewStyle: string | undefined) => {
|
function persistViewStyle(viewStyle: string | undefined) {
|
||||||
if (viewStyle) {
|
if (viewStyle) {
|
||||||
localStorage.setItem(viewStyleKey, viewStyle);
|
localStorage.setItem(viewStyleKey, viewStyle);
|
||||||
} else {
|
} else {
|
||||||
clearViewStyle();
|
clearViewStyle();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const entries = (
|
function entries(
|
||||||
state = Map({ entities: Map(), pages: Map(), sort: loadSort(), viewStyle: loadViewStyle() }),
|
state = Map({ entities: Map(), pages: Map(), sort: loadSort(), viewStyle: loadViewStyle() }),
|
||||||
action: EntriesAction,
|
action: EntriesAction,
|
||||||
) => {
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ENTRY_REQUEST: {
|
case ENTRY_REQUEST: {
|
||||||
const payload = action.payload as EntryRequestPayload;
|
const payload = action.payload as EntryRequestPayload;
|
||||||
@ -344,30 +344,30 @@ const entries = (
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesSort = (entries: Entries, collection: string) => {
|
export function selectEntriesSort(entries: Entries, collection: string) {
|
||||||
const sort = entries.get('sort') as Sort | undefined;
|
const sort = entries.get('sort') as Sort | undefined;
|
||||||
return sort?.get(collection);
|
return sort?.get(collection);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesFilter = (entries: Entries, collection: string) => {
|
export function selectEntriesFilter(entries: Entries, collection: string) {
|
||||||
const filter = entries.get('filter') as Filter | undefined;
|
const filter = entries.get('filter') as Filter | undefined;
|
||||||
return filter?.get(collection) || Map();
|
return filter?.get(collection) || Map();
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesGroup = (entries: Entries, collection: string) => {
|
export function selectEntriesGroup(entries: Entries, collection: string) {
|
||||||
const group = entries.get('group') as Group | undefined;
|
const group = entries.get('group') as Group | undefined;
|
||||||
return group?.get(collection) || Map();
|
return group?.get(collection) || Map();
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesGroupField = (entries: Entries, collection: string) => {
|
export function selectEntriesGroupField(entries: Entries, collection: string) {
|
||||||
const groups = selectEntriesGroup(entries, collection);
|
const groups = selectEntriesGroup(entries, collection);
|
||||||
const value = groups?.valueSeq().find(v => v?.get('active') === true);
|
const value = groups?.valueSeq().find(v => v?.get('active') === true);
|
||||||
return value;
|
return value;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesSortFields = (entries: Entries, collection: string) => {
|
export function selectEntriesSortFields(entries: Entries, collection: string) {
|
||||||
const sort = selectEntriesSort(entries, collection);
|
const sort = selectEntriesSort(entries, collection);
|
||||||
const values =
|
const values =
|
||||||
sort
|
sort
|
||||||
@ -376,9 +376,9 @@ export const selectEntriesSortFields = (entries: Entries, collection: string) =>
|
|||||||
.toArray() || [];
|
.toArray() || [];
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesFilterFields = (entries: Entries, collection: string) => {
|
export function selectEntriesFilterFields(entries: Entries, collection: string) {
|
||||||
const filter = selectEntriesFilter(entries, collection);
|
const filter = selectEntriesFilter(entries, collection);
|
||||||
const values =
|
const values =
|
||||||
filter
|
filter
|
||||||
@ -386,27 +386,29 @@ export const selectEntriesFilterFields = (entries: Entries, collection: string)
|
|||||||
.filter(v => v?.get('active') === true)
|
.filter(v => v?.get('active') === true)
|
||||||
.toArray() || [];
|
.toArray() || [];
|
||||||
return values;
|
return values;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectViewStyle = (entries: Entries) => {
|
export function selectViewStyle(entries: Entries) {
|
||||||
return entries.get('viewStyle');
|
return entries.get('viewStyle');
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntry = (state: Entries, collection: string, slug: string) =>
|
export function selectEntry(state: Entries, collection: string, slug: string) {
|
||||||
state.getIn(['entities', `${collection}.${slug}`]);
|
return state.getIn(['entities', `${collection}.${slug}`]);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectPublishedSlugs = (state: Entries, collection: string) =>
|
export function selectPublishedSlugs(state: Entries, collection: string) {
|
||||||
state.getIn(['pages', collection, 'ids'], List<string>());
|
return state.getIn(['pages', collection, 'ids'], List<string>());
|
||||||
|
}
|
||||||
|
|
||||||
const getPublishedEntries = (state: Entries, collectionName: string) => {
|
function getPublishedEntries(state: Entries, collectionName: string) {
|
||||||
const slugs = selectPublishedSlugs(state, collectionName);
|
const slugs = selectPublishedSlugs(state, collectionName);
|
||||||
const entries =
|
const entries =
|
||||||
slugs &&
|
slugs &&
|
||||||
(slugs.map(slug => selectEntry(state, collectionName, slug as string)) as List<EntryMap>);
|
(slugs.map(slug => selectEntry(state, collectionName, slug as string)) as List<EntryMap>);
|
||||||
return entries;
|
return entries;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntries = (state: Entries, collection: Collection) => {
|
export function selectEntries(state: Entries, collection: Collection) {
|
||||||
const collectionName = collection.get('name');
|
const collectionName = collection.get('name');
|
||||||
let entries = getPublishedEntries(state, collectionName);
|
let entries = getPublishedEntries(state, collectionName);
|
||||||
|
|
||||||
@ -438,9 +440,9 @@ export const selectEntries = (state: Entries, collection: Collection) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
};
|
}
|
||||||
|
|
||||||
const getGroup = (entry: EntryMap, selectedGroup: GroupMap) => {
|
function getGroup(entry: EntryMap, selectedGroup: GroupMap) {
|
||||||
const label = selectedGroup.get('label');
|
const label = selectedGroup.get('label');
|
||||||
const field = selectedGroup.get('field');
|
const field = selectedGroup.get('field');
|
||||||
|
|
||||||
@ -478,9 +480,9 @@ const getGroup = (entry: EntryMap, selectedGroup: GroupMap) => {
|
|||||||
label,
|
label,
|
||||||
value: typeof fieldData === 'boolean' ? fieldData : dataAsString,
|
value: typeof fieldData === 'boolean' ? fieldData : dataAsString,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectGroups = (state: Entries, collection: Collection) => {
|
export function selectGroups(state: Entries, collection: Collection) {
|
||||||
const collectionName = collection.get('name');
|
const collectionName = collection.get('name');
|
||||||
const entries = getPublishedEntries(state, collectionName);
|
const entries = getPublishedEntries(state, collectionName);
|
||||||
|
|
||||||
@ -507,37 +509,37 @@ export const selectGroups = (state: Entries, collection: Collection) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return groupsArray;
|
return groupsArray;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntryByPath = (state: Entries, collection: string, path: string) => {
|
export function selectEntryByPath(state: Entries, collection: string, path: string) {
|
||||||
const slugs = selectPublishedSlugs(state, collection);
|
const slugs = selectPublishedSlugs(state, collection);
|
||||||
const entries =
|
const entries =
|
||||||
slugs && (slugs.map(slug => selectEntry(state, collection, slug as string)) as List<EntryMap>);
|
slugs && (slugs.map(slug => selectEntry(state, collection, slug as string)) as List<EntryMap>);
|
||||||
|
|
||||||
return entries && entries.find(e => e?.get('path') === path);
|
return entries && entries.find(e => e?.get('path') === path);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEntriesLoaded = (state: Entries, collection: string) => {
|
export function selectEntriesLoaded(state: Entries, collection: string) {
|
||||||
return !!state.getIn(['pages', collection]);
|
return !!state.getIn(['pages', collection]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectIsFetching = (state: Entries, collection: string) => {
|
export function selectIsFetching(state: Entries, collection: string) {
|
||||||
return state.getIn(['pages', collection, 'isFetching'], false);
|
return state.getIn(['pages', collection, 'isFetching'], false);
|
||||||
};
|
}
|
||||||
|
|
||||||
const DRAFT_MEDIA_FILES = 'DRAFT_MEDIA_FILES';
|
const DRAFT_MEDIA_FILES = 'DRAFT_MEDIA_FILES';
|
||||||
|
|
||||||
const getFileField = (collectionFiles: CollectionFiles, slug: string | undefined) => {
|
function getFileField(collectionFiles: CollectionFiles, slug: string | undefined) {
|
||||||
const file = collectionFiles.find(f => f?.get('name') === slug);
|
const file = collectionFiles.find(f => f?.get('name') === slug);
|
||||||
return file;
|
return file;
|
||||||
};
|
}
|
||||||
|
|
||||||
const hasCustomFolder = (
|
function hasCustomFolder(
|
||||||
folderKey: 'media_folder' | 'public_folder',
|
folderKey: 'media_folder' | 'public_folder',
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
slug: string | undefined,
|
slug: string | undefined,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) => {
|
) {
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -558,9 +560,9 @@ const hasCustomFolder = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
const traverseFields = (
|
function traverseFields(
|
||||||
folderKey: 'media_folder' | 'public_folder',
|
folderKey: 'media_folder' | 'public_folder',
|
||||||
config: Config,
|
config: Config,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
@ -568,7 +570,7 @@ const traverseFields = (
|
|||||||
field: EntryField,
|
field: EntryField,
|
||||||
fields: EntryField[],
|
fields: EntryField[],
|
||||||
currentFolder: string,
|
currentFolder: string,
|
||||||
): string | null => {
|
): string | null {
|
||||||
const matchedField = fields.filter(f => f === field)[0];
|
const matchedField = fields.filter(f => f === field)[0];
|
||||||
if (matchedField) {
|
if (matchedField) {
|
||||||
return folderFormatter(
|
return folderFormatter(
|
||||||
@ -632,15 +634,15 @@ const traverseFields = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
const evaluateFolder = (
|
function evaluateFolder(
|
||||||
folderKey: 'media_folder' | 'public_folder',
|
folderKey: 'media_folder' | 'public_folder',
|
||||||
config: Config,
|
config: Config,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) => {
|
) {
|
||||||
let currentFolder = config.get(folderKey);
|
let currentFolder = config.get(folderKey);
|
||||||
|
|
||||||
// add identity template if doesn't exist
|
// add identity template if doesn't exist
|
||||||
@ -723,14 +725,14 @@ const evaluateFolder = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return currentFolder;
|
return currentFolder;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectMediaFolder = (
|
export function selectMediaFolder(
|
||||||
config: Config,
|
config: Config,
|
||||||
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.get(name);
|
||||||
|
|
||||||
@ -750,15 +752,15 @@ export const selectMediaFolder = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return trim(mediaFolder, '/');
|
return trim(mediaFolder, '/');
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectMediaFilePath = (
|
export function selectMediaFilePath(
|
||||||
config: Config,
|
config: Config,
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
mediaPath: string,
|
mediaPath: string,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) => {
|
) {
|
||||||
if (isAbsolutePath(mediaPath)) {
|
if (isAbsolutePath(mediaPath)) {
|
||||||
return mediaPath;
|
return mediaPath;
|
||||||
}
|
}
|
||||||
@ -766,15 +768,15 @@ export const selectMediaFilePath = (
|
|||||||
const mediaFolder = selectMediaFolder(config, collection, entryMap, field);
|
const mediaFolder = selectMediaFolder(config, collection, entryMap, field);
|
||||||
|
|
||||||
return join(mediaFolder, basename(mediaPath));
|
return join(mediaFolder, basename(mediaPath));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectMediaFilePublicPath = (
|
export function selectMediaFilePublicPath(
|
||||||
config: Config,
|
config: Config,
|
||||||
collection: Collection | null,
|
collection: Collection | null,
|
||||||
mediaPath: string,
|
mediaPath: string,
|
||||||
entryMap: EntryMap | undefined,
|
entryMap: EntryMap | undefined,
|
||||||
field: EntryField | undefined,
|
field: EntryField | undefined,
|
||||||
) => {
|
) {
|
||||||
if (isAbsolutePath(mediaPath)) {
|
if (isAbsolutePath(mediaPath)) {
|
||||||
return mediaPath;
|
return mediaPath;
|
||||||
}
|
}
|
||||||
@ -789,12 +791,12 @@ export const selectMediaFilePublicPath = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return join(publicFolder, basename(mediaPath));
|
return join(publicFolder, basename(mediaPath));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectEditingDraft = (state: EntryDraft) => {
|
export function selectEditingDraft(state: EntryDraft) {
|
||||||
const entry = state.get('entry');
|
const entry = state.get('entry');
|
||||||
const workflowDraft = entry && !entry.isEmpty();
|
const workflowDraft = entry && !entry.isEmpty();
|
||||||
return workflowDraft;
|
return workflowDraft;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default entries;
|
export default entries;
|
||||||
|
@ -35,7 +35,7 @@ const initialState = Map({
|
|||||||
key: '',
|
key: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const entryDraftReducer = (state = Map(), action) => {
|
function entryDraftReducer(state = Map(), action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DRAFT_CREATE_FROM_ENTRY:
|
case DRAFT_CREATE_FROM_ENTRY:
|
||||||
// Existing Entry
|
// Existing Entry
|
||||||
@ -180,9 +180,9 @@ const entryDraftReducer = (state = Map(), action) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectCustomPath = (collection, entryDraft) => {
|
export function selectCustomPath(collection, entryDraft) {
|
||||||
if (!selectHasMetaPath(collection)) {
|
if (!selectHasMetaPath(collection)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -192,6 +192,6 @@ export const selectCustomPath = (collection, entryDraft) => {
|
|||||||
const extension = selectFolderEntryExtension(collection);
|
const extension = selectFolderEntryExtension(collection);
|
||||||
const customPath = path && join(collection.get('folder'), path, `${indexFile}.${extension}`);
|
const customPath = path && join(collection.get('folder'), path, `${indexFile}.${extension}`);
|
||||||
return customPath;
|
return customPath;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default entryDraftReducer;
|
export default entryDraftReducer;
|
||||||
|
@ -8,12 +8,14 @@ const LOADING_IGNORE_LIST = [
|
|||||||
'STATUS_FAILURE',
|
'STATUS_FAILURE',
|
||||||
];
|
];
|
||||||
|
|
||||||
const ignoreWhenLoading = action => LOADING_IGNORE_LIST.some(type => action.type.includes(type));
|
function ignoreWhenLoading(action) {
|
||||||
|
return LOADING_IGNORE_LIST.some(type => action.type.includes(type));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Reducer for some global UI state that we want to share between components
|
* Reducer for some global UI state that we want to share between components
|
||||||
* */
|
*/
|
||||||
const globalUI = (state = Map({ isFetching: false, useOpenAuthoring: false }), action) => {
|
function globalUI(state = Map({ isFetching: false, useOpenAuthoring: false }), action) {
|
||||||
// Generic, global loading indicator
|
// Generic, global loading indicator
|
||||||
if (!ignoreWhenLoading(action) && action.type.includes('REQUEST')) {
|
if (!ignoreWhenLoading(action) && action.type.includes('REQUEST')) {
|
||||||
return state.set('isFetching', true);
|
return state.set('isFetching', true);
|
||||||
@ -26,6 +28,6 @@ const globalUI = (state = Map({ isFetching: false, useOpenAuthoring: false }), a
|
|||||||
return state.set('useOpenAuthoring', true);
|
return state.set('useOpenAuthoring', true);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default globalUI;
|
export default globalUI;
|
||||||
|
@ -37,16 +37,19 @@ export default reducers;
|
|||||||
/*
|
/*
|
||||||
* Selectors
|
* Selectors
|
||||||
*/
|
*/
|
||||||
export const selectEntry = (state: State, collection: string, slug: string) =>
|
export function selectEntry(state: State, collection: string, slug: string) {
|
||||||
fromEntries.selectEntry(state.entries, collection, slug);
|
return fromEntries.selectEntry(state.entries, collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectEntries = (state: State, collection: Collection) =>
|
export function selectEntries(state: State, collection: Collection) {
|
||||||
fromEntries.selectEntries(state.entries, collection);
|
return fromEntries.selectEntries(state.entries, collection);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectPublishedSlugs = (state: State, collection: string) =>
|
export function selectPublishedSlugs(state: State, collection: string) {
|
||||||
fromEntries.selectPublishedSlugs(state.entries, collection);
|
return fromEntries.selectPublishedSlugs(state.entries, collection);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectSearchedEntries = (state: State, availableCollections: string[]) => {
|
export function selectSearchedEntries(state: State, availableCollections: string[]) {
|
||||||
const searchItems = state.search.get('entryIds');
|
const searchItems = state.search.get('entryIds');
|
||||||
// only return search results for actually available collections
|
// only return search results for actually available collections
|
||||||
return (
|
return (
|
||||||
@ -55,19 +58,24 @@ export const selectSearchedEntries = (state: State, availableCollections: string
|
|||||||
.filter(({ collection }) => availableCollections.indexOf(collection) !== -1)
|
.filter(({ collection }) => availableCollections.indexOf(collection) !== -1)
|
||||||
.map(({ collection, slug }) => fromEntries.selectEntry(state.entries, collection, slug))
|
.map(({ collection, slug }) => fromEntries.selectEntry(state.entries, collection, slug))
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectDeployPreview = (state: State, collection: string, slug: string) =>
|
export function selectDeployPreview(state: State, collection: string, slug: string) {
|
||||||
fromDeploys.selectDeployPreview(state.deploys, collection, slug);
|
return fromDeploys.selectDeployPreview(state.deploys, collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectUnpublishedEntry = (state: State, collection: string, slug: string) =>
|
export function selectUnpublishedEntry(state: State, collection: string, slug: string) {
|
||||||
fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, collection, slug);
|
return fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, collection, slug);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectUnpublishedEntriesByStatus = (state: State, status: Status) =>
|
export function selectUnpublishedEntriesByStatus(state: State, status: Status) {
|
||||||
fromEditorialWorkflow.selectUnpublishedEntriesByStatus(state.editorialWorkflow, status);
|
return fromEditorialWorkflow.selectUnpublishedEntriesByStatus(state.editorialWorkflow, status);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectUnpublishedSlugs = (state: State, collection: string) =>
|
export function selectUnpublishedSlugs(state: State, collection: string) {
|
||||||
fromEditorialWorkflow.selectUnpublishedSlugs(state.editorialWorkflow, collection);
|
return fromEditorialWorkflow.selectUnpublishedSlugs(state.editorialWorkflow, collection);
|
||||||
|
}
|
||||||
|
|
||||||
export const selectIntegration = (state: State, collection: string | null, hook: string) =>
|
export function selectIntegration(state: State, collection: string | null, hook: string) {
|
||||||
fromIntegrations.selectIntegration(state.integrations, collection, hook);
|
return fromIntegrations.selectIntegration(state.integrations, collection, hook);
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ interface Acc {
|
|||||||
hooks: Record<string, string | Record<string, string>>;
|
hooks: Record<string, string | Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIntegrations = (config: Config) => {
|
export function getIntegrations(config: Config) {
|
||||||
const integrations: Integration[] = config.get('integrations', List()).toJS() || [];
|
const integrations: Integration[] = config.get('integrations', List()).toJS() || [];
|
||||||
const newState = integrations.reduce(
|
const newState = integrations.reduce(
|
||||||
(acc, integration) => {
|
(acc, integration) => {
|
||||||
@ -38,9 +38,9 @@ export const getIntegrations = (config: Config) => {
|
|||||||
{ providers: {}, hooks: {} } as Acc,
|
{ providers: {}, hooks: {} } as Acc,
|
||||||
);
|
);
|
||||||
return fromJS(newState);
|
return fromJS(newState);
|
||||||
};
|
}
|
||||||
|
|
||||||
const integrations = (state = null, action: IntegrationsAction): Integrations | null => {
|
function integrations(state = null, action: IntegrationsAction): Integrations | null {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS: {
|
case CONFIG_SUCCESS: {
|
||||||
return getIntegrations(action.payload);
|
return getIntegrations(action.payload);
|
||||||
@ -48,11 +48,12 @@ const integrations = (state = null, action: IntegrationsAction): Integrations |
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const selectIntegration = (state: Integrations, collection: string | null, hook: string) =>
|
export function selectIntegration(state: Integrations, collection: string | null, hook: string) {
|
||||||
collection
|
return collection
|
||||||
? state.getIn(['hooks', collection, hook], false)
|
? state.getIn(['hooks', collection, hook], false)
|
||||||
: state.getIn(['hooks', hook], false);
|
: state.getIn(['hooks', hook], false);
|
||||||
|
}
|
||||||
|
|
||||||
export default integrations;
|
export default integrations;
|
||||||
|
@ -51,7 +51,7 @@ const defaultState: {
|
|||||||
config: Map(),
|
config: Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) => {
|
function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case MEDIA_LIBRARY_CREATE:
|
case MEDIA_LIBRARY_CREATE:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
@ -213,7 +213,7 @@ const mediaLibrary = (state = Map(defaultState), action: MediaLibraryAction) =>
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export function selectMediaFiles(state: State, field?: EntryField) {
|
export function selectMediaFiles(state: State, field?: EntryField) {
|
||||||
const { mediaLibrary, entryDraft } = state;
|
const { mediaLibrary, entryDraft } = state;
|
||||||
|
@ -57,7 +57,8 @@ const medias = produce((state: Medias, action: MediasAction) => {
|
|||||||
}
|
}
|
||||||
}, defaultState);
|
}, defaultState);
|
||||||
|
|
||||||
export const selectIsLoadingAsset = (state: Medias) =>
|
export function selectIsLoadingAsset(state: Medias) {
|
||||||
Object.values(state).some(state => state.isLoading);
|
return Object.values(state).some(state => state.isLoading);
|
||||||
|
}
|
||||||
|
|
||||||
export default medias;
|
export default medias;
|
||||||
|
@ -23,7 +23,7 @@ const defaultState = Map({
|
|||||||
queryHits: Map({}),
|
queryHits: Map({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const entries = (state = defaultState, action) => {
|
function entries(state = defaultState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SEARCH_CLEAR:
|
case SEARCH_CLEAR:
|
||||||
return defaultState;
|
return defaultState;
|
||||||
@ -84,6 +84,6 @@ const entries = (state = defaultState, action) => {
|
|||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default entries;
|
export default entries;
|
||||||
|
@ -21,6 +21,7 @@ interface WaitAction extends WaitActionArgs {
|
|||||||
type: typeof WAIT_UNTIL_ACTION;
|
type: typeof WAIT_UNTIL_ACTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line func-style
|
||||||
export const waitUntilAction: Middleware<{}, State, Dispatch> = ({
|
export const waitUntilAction: Middleware<{}, State, Dispatch> = ({
|
||||||
dispatch,
|
dispatch,
|
||||||
getState,
|
getState,
|
||||||
@ -50,7 +51,7 @@ export const waitUntilAction: Middleware<{}, State, Dispatch> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (next: Dispatch) => (action: AnyAction): null | AnyAction => {
|
return (next: Dispatch<AnyAction>) => (action: AnyAction): null | AnyAction => {
|
||||||
if (action.type === WAIT_UNTIL_ACTION) {
|
if (action.type === WAIT_UNTIL_ACTION) {
|
||||||
pending.push(action as WaitAction);
|
pending.push(action as WaitAction);
|
||||||
return null;
|
return null;
|
||||||
|
@ -2,11 +2,16 @@ import { createHashHistory } from 'history';
|
|||||||
|
|
||||||
const history = createHashHistory();
|
const history = createHashHistory();
|
||||||
|
|
||||||
export const navigateToCollection = (collectionName: string) =>
|
export function navigateToCollection(collectionName: string) {
|
||||||
history.push(`/collections/${collectionName}`);
|
return history.push(`/collections/${collectionName}`);
|
||||||
export const navigateToNewEntry = (collectionName: string) =>
|
}
|
||||||
history.replace(`/collections/${collectionName}/new`);
|
|
||||||
export const navigateToEntry = (collectionName: string, slug: string) =>
|
export function navigateToNewEntry(collectionName: string) {
|
||||||
history.replace(`/collections/${collectionName}/entries/${slug}`);
|
return history.replace(`/collections/${collectionName}/new`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navigateToEntry(collectionName: string, slug: string) {
|
||||||
|
return history.replace(`/collections/${collectionName}/entries/${slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
export default history;
|
export default history;
|
||||||
|
@ -2,7 +2,10 @@ import { fromJS } from 'immutable';
|
|||||||
import { isFunction } from 'lodash';
|
import { isFunction } from 'lodash';
|
||||||
|
|
||||||
const catchesNothing = /.^/;
|
const catchesNothing = /.^/;
|
||||||
const bind = fn => isFunction(fn) && fn.bind(null);
|
|
||||||
|
function bind(fn) {
|
||||||
|
return isFunction(fn) && fn.bind(null);
|
||||||
|
}
|
||||||
|
|
||||||
export default function createEditorComponent(config) {
|
export default function createEditorComponent(config) {
|
||||||
const {
|
const {
|
||||||
|
@ -8,13 +8,13 @@ const versionPlugin = new webpack.DefinePlugin({
|
|||||||
NETLIFY_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
|
NETLIFY_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const configs = () => {
|
function configs() {
|
||||||
return getConfig().map(config => {
|
return getConfig().map(config => {
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
plugins: [...config.plugins, versionPlugin],
|
plugins: [...config.plugins, versionPlugin],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = configs();
|
module.exports = configs();
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import component from '../index';
|
import component from '../index';
|
||||||
|
|
||||||
const getAsset = path => path;
|
function getAsset(path) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
const image = '/image';
|
const image = '/image';
|
||||||
const alt = 'alt';
|
const alt = 'alt';
|
||||||
const title = 'title';
|
const title = 'title';
|
||||||
|
@ -38,11 +38,11 @@ class RateLimitError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestWithBackoff = async (
|
export async function requestWithBackoff(
|
||||||
api: API,
|
api: API,
|
||||||
req: ApiRequest,
|
req: ApiRequest,
|
||||||
attempt = 1,
|
attempt = 1,
|
||||||
): Promise<Response> => {
|
): Promise<Response> {
|
||||||
if (api.rateLimiter) {
|
if (api.rateLimiter) {
|
||||||
await api.rateLimiter.acquire();
|
await api.rateLimiter.acquire();
|
||||||
}
|
}
|
||||||
@ -92,14 +92,14 @@ export const requestWithBackoff = async (
|
|||||||
return requestWithBackoff(api, req, attempt + 1);
|
return requestWithBackoff(api, req, attempt + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const readFile = async (
|
export async function readFile(
|
||||||
id: string | null | undefined,
|
id: string | null | undefined,
|
||||||
fetchContent: () => Promise<string | Blob>,
|
fetchContent: () => Promise<string | Blob>,
|
||||||
localForage: LocalForage,
|
localForage: LocalForage,
|
||||||
isText: boolean,
|
isText: boolean,
|
||||||
) => {
|
) {
|
||||||
const key = id ? (isText ? `gh.${id}` : `gh.${id}.blob`) : null;
|
const key = id ? (isText ? `gh.${id}` : `gh.${id}.blob`) : null;
|
||||||
const cached = key ? await localForage.getItem<string | Blob>(key) : null;
|
const cached = key ? await localForage.getItem<string | Blob>(key) : null;
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@ -111,20 +111,22 @@ export const readFile = async (
|
|||||||
await localForage.setItem(key, content);
|
await localForage.setItem(key, content);
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type FileMetadata = {
|
export type FileMetadata = {
|
||||||
author: string;
|
author: string;
|
||||||
updatedOn: string;
|
updatedOn: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileMetadataKey = (id: string) => `gh.${id}.meta`;
|
function getFileMetadataKey(id: string) {
|
||||||
|
return `gh.${id}.meta`;
|
||||||
|
}
|
||||||
|
|
||||||
export const readFileMetadata = async (
|
export async function readFileMetadata(
|
||||||
id: string | null | undefined,
|
id: string | null | undefined,
|
||||||
fetchMetadata: () => Promise<FileMetadata>,
|
fetchMetadata: () => Promise<FileMetadata>,
|
||||||
localForage: LocalForage,
|
localForage: LocalForage,
|
||||||
) => {
|
) {
|
||||||
const key = id ? getFileMetadataKey(id) : null;
|
const key = id ? getFileMetadataKey(id) : null;
|
||||||
const cached = key && (await localForage.getItem<FileMetadata>(key));
|
const cached = key && (await localForage.getItem<FileMetadata>(key));
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@ -136,7 +138,7 @@ export const readFileMetadata = async (
|
|||||||
await localForage.setItem<FileMetadata>(key, metadata);
|
await localForage.setItem<FileMetadata>(key, metadata);
|
||||||
}
|
}
|
||||||
return metadata;
|
return metadata;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keywords for inferring a status that will provide a deploy preview URL.
|
* Keywords for inferring a status that will provide a deploy preview URL.
|
||||||
@ -148,12 +150,12 @@ const PREVIEW_CONTEXT_KEYWORDS = ['deploy'];
|
|||||||
* deploy preview. Checks for an exact match against `previewContext` if given,
|
* deploy preview. Checks for an exact match against `previewContext` if given,
|
||||||
* otherwise checks for inclusion of a value from `PREVIEW_CONTEXT_KEYWORDS`.
|
* otherwise checks for inclusion of a value from `PREVIEW_CONTEXT_KEYWORDS`.
|
||||||
*/
|
*/
|
||||||
export const isPreviewContext = (context: string, previewContext: string) => {
|
export function isPreviewContext(context: string, previewContext: string) {
|
||||||
if (previewContext) {
|
if (previewContext) {
|
||||||
return context === previewContext;
|
return context === previewContext;
|
||||||
}
|
}
|
||||||
return PREVIEW_CONTEXT_KEYWORDS.some(keyword => context.includes(keyword));
|
return PREVIEW_CONTEXT_KEYWORDS.some(keyword => context.includes(keyword));
|
||||||
};
|
}
|
||||||
|
|
||||||
export enum PreviewState {
|
export enum PreviewState {
|
||||||
Other = 'other',
|
Other = 'other',
|
||||||
@ -164,20 +166,20 @@ export enum PreviewState {
|
|||||||
* Retrieve a deploy preview URL from an array of statuses. By default, a
|
* Retrieve a deploy preview URL from an array of statuses. By default, a
|
||||||
* matching status is inferred via `isPreviewContext`.
|
* matching status is inferred via `isPreviewContext`.
|
||||||
*/
|
*/
|
||||||
export const getPreviewStatus = (
|
export function getPreviewStatus(
|
||||||
statuses: {
|
statuses: {
|
||||||
context: string;
|
context: string;
|
||||||
target_url: string;
|
target_url: string;
|
||||||
state: PreviewState;
|
state: PreviewState;
|
||||||
}[],
|
}[],
|
||||||
previewContext: string,
|
previewContext: string,
|
||||||
) => {
|
) {
|
||||||
return statuses.find(({ context }) => {
|
return statuses.find(({ context }) => {
|
||||||
return isPreviewContext(context, previewContext);
|
return isPreviewContext(context, previewContext);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const getConflictingBranches = (branchName: string) => {
|
function getConflictingBranches(branchName: string) {
|
||||||
// for cms/posts/post-1, conflicting branches are cms/posts, cms
|
// for cms/posts/post-1, conflicting branches are cms/posts, cms
|
||||||
const parts = branchName.split('/');
|
const parts = branchName.split('/');
|
||||||
parts.pop();
|
parts.pop();
|
||||||
@ -188,13 +190,13 @@ const getConflictingBranches = (branchName: string) => {
|
|||||||
}, [] as string[]);
|
}, [] as string[]);
|
||||||
|
|
||||||
return conflictingBranches;
|
return conflictingBranches;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const throwOnConflictingBranches = async (
|
export async function throwOnConflictingBranches(
|
||||||
branchName: string,
|
branchName: string,
|
||||||
getBranch: (name: string) => Promise<{ name: string }>,
|
getBranch: (name: string) => Promise<{ name: string }>,
|
||||||
apiName: string,
|
apiName: string,
|
||||||
) => {
|
) {
|
||||||
const possibleConflictingBranches = getConflictingBranches(branchName);
|
const possibleConflictingBranches = getConflictingBranches(branchName);
|
||||||
|
|
||||||
const conflictingBranches = await Promise.all(
|
const conflictingBranches = await Promise.all(
|
||||||
@ -213,4 +215,4 @@ export const throwOnConflictingBranches = async (
|
|||||||
apiName,
|
apiName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -3,27 +3,36 @@ export const DEFAULT_PR_BODY = 'Automatically generated by Netlify CMS';
|
|||||||
export const MERGE_COMMIT_MESSAGE = 'Automatically generated. Merged on Netlify CMS.';
|
export const MERGE_COMMIT_MESSAGE = 'Automatically generated. Merged on Netlify CMS.';
|
||||||
|
|
||||||
const DEFAULT_NETLIFY_CMS_LABEL_PREFIX = 'netlify-cms/';
|
const DEFAULT_NETLIFY_CMS_LABEL_PREFIX = 'netlify-cms/';
|
||||||
const getLabelPrefix = (labelPrefix: string) => labelPrefix || DEFAULT_NETLIFY_CMS_LABEL_PREFIX;
|
|
||||||
|
|
||||||
export const isCMSLabel = (label: string, labelPrefix: string) =>
|
function getLabelPrefix(labelPrefix: string) {
|
||||||
label.startsWith(getLabelPrefix(labelPrefix));
|
return labelPrefix || DEFAULT_NETLIFY_CMS_LABEL_PREFIX;
|
||||||
export const labelToStatus = (label: string, labelPrefix: string) =>
|
}
|
||||||
label.substr(getLabelPrefix(labelPrefix).length);
|
|
||||||
export const statusToLabel = (status: string, labelPrefix: string) =>
|
|
||||||
`${getLabelPrefix(labelPrefix)}${status}`;
|
|
||||||
|
|
||||||
export const generateContentKey = (collectionName: string, slug: string) =>
|
export function isCMSLabel(label: string, labelPrefix: string) {
|
||||||
`${collectionName}/${slug}`;
|
return label.startsWith(getLabelPrefix(labelPrefix));
|
||||||
|
}
|
||||||
|
|
||||||
export const parseContentKey = (contentKey: string) => {
|
export function labelToStatus(label: string, labelPrefix: string) {
|
||||||
|
return label.substr(getLabelPrefix(labelPrefix).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statusToLabel(status: string, labelPrefix: string) {
|
||||||
|
return `${getLabelPrefix(labelPrefix)}${status}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateContentKey(collectionName: string, slug: string) {
|
||||||
|
return `${collectionName}/${slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseContentKey(contentKey: string) {
|
||||||
const index = contentKey.indexOf('/');
|
const index = contentKey.indexOf('/');
|
||||||
return { collection: contentKey.substr(0, index), slug: contentKey.substr(index + 1) };
|
return { collection: contentKey.substr(0, index), slug: contentKey.substr(index + 1) };
|
||||||
};
|
}
|
||||||
|
|
||||||
export const contentKeyFromBranch = (branch: string) => {
|
export function contentKeyFromBranch(branch: string) {
|
||||||
return branch.substring(`${CMS_BRANCH_PREFIX}/`.length);
|
return branch.substring(`${CMS_BRANCH_PREFIX}/`.length);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const branchFromContentKey = (contentKey: string) => {
|
export function branchFromContentKey(contentKey: string) {
|
||||||
return `${CMS_BRANCH_PREFIX}/${contentKey}`;
|
return `${CMS_BRANCH_PREFIX}/${contentKey}`;
|
||||||
};
|
}
|
||||||
|
@ -27,7 +27,7 @@ export type CursorStore = {
|
|||||||
|
|
||||||
type ActionHandler = (action: string) => unknown;
|
type ActionHandler = (action: string) => unknown;
|
||||||
|
|
||||||
const jsToMap = (obj: {}) => {
|
function jsToMap(obj: {}) {
|
||||||
if (obj === undefined) {
|
if (obj === undefined) {
|
||||||
return Map();
|
return Map();
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ const jsToMap = (obj: {}) => {
|
|||||||
throw new Error('Object must be equivalent to a Map.');
|
throw new Error('Object must be equivalent to a Map.');
|
||||||
}
|
}
|
||||||
return immutableObj;
|
return immutableObj;
|
||||||
};
|
}
|
||||||
|
|
||||||
const knownMetaKeys = Set([
|
const knownMetaKeys = Set([
|
||||||
'index',
|
'index',
|
||||||
@ -49,8 +49,10 @@ const knownMetaKeys = Set([
|
|||||||
'folder',
|
'folder',
|
||||||
'depth',
|
'depth',
|
||||||
]);
|
]);
|
||||||
const filterUnknownMetaKeys = (meta: Map<string, string>) =>
|
|
||||||
meta.filter((_v, k) => knownMetaKeys.has(k as string));
|
function filterUnknownMetaKeys(meta: Map<string, string>) {
|
||||||
|
return meta.filter((_v, k) => knownMetaKeys.has(k as string));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
createCursorMap takes one of three signatures:
|
createCursorMap takes one of three signatures:
|
||||||
@ -58,7 +60,7 @@ const filterUnknownMetaKeys = (meta: Map<string, string>) =>
|
|||||||
- (cursorMap: <object/Map with optional actions, data, and meta keys>) -> cursor
|
- (cursorMap: <object/Map with optional actions, data, and meta keys>) -> cursor
|
||||||
- (actions: <array/List>, data: <object/Map>, meta: <optional object/Map>) -> cursor
|
- (actions: <array/List>, data: <object/Map>, meta: <optional object/Map>) -> cursor
|
||||||
*/
|
*/
|
||||||
const createCursorStore = (...args: {}[]) => {
|
function createCursorStore(...args: {}[]) {
|
||||||
const { actions, data, meta } =
|
const { actions, data, meta } =
|
||||||
args.length === 1
|
args.length === 1
|
||||||
? jsToMap(args[0]).toObject()
|
? jsToMap(args[0]).toObject()
|
||||||
@ -71,15 +73,18 @@ const createCursorStore = (...args: {}[]) => {
|
|||||||
data: jsToMap(data),
|
data: jsToMap(data),
|
||||||
meta: jsToMap(meta).update(filterUnknownMetaKeys),
|
meta: jsToMap(meta).update(filterUnknownMetaKeys),
|
||||||
}) as CursorStore;
|
}) as CursorStore;
|
||||||
};
|
}
|
||||||
|
|
||||||
const hasAction = (store: CursorStore, action: string) => store.hasIn(['actions', action]);
|
function hasAction(store: CursorStore, action: string) {
|
||||||
|
return store.hasIn(['actions', action]);
|
||||||
|
}
|
||||||
|
|
||||||
const getActionHandlers = (store: CursorStore, handler: ActionHandler) =>
|
function getActionHandlers(store: CursorStore, handler: ActionHandler) {
|
||||||
store
|
return store
|
||||||
.get('actions', Set<string>())
|
.get('actions', Set<string>())
|
||||||
.toMap()
|
.toMap()
|
||||||
.map(action => handler(action as string));
|
.map(action => handler(action as string));
|
||||||
|
}
|
||||||
|
|
||||||
// The cursor logic is entirely functional, so this class simply
|
// The cursor logic is entirely functional, so this class simply
|
||||||
// provides a chainable interface
|
// provides a chainable interface
|
||||||
|
@ -21,17 +21,20 @@ describe('parseLinkHeader', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getAllResponses', () => {
|
describe('getAllResponses', () => {
|
||||||
const generatePulls = length => {
|
function generatePulls(length) {
|
||||||
return Array.from({ length }, (_, id) => {
|
return Array.from({ length }, (_, id) => {
|
||||||
return { id: id + 1, number: `134${id}`, state: 'open' };
|
return { id: id + 1, number: `134${id}`, state: 'open' };
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
function createLinkHeaders({ page, pageCount }) {
|
function createLinkHeaders({ page, pageCount }) {
|
||||||
const pageNum = parseInt(page, 10);
|
const pageNum = parseInt(page, 10);
|
||||||
const pageCountNum = parseInt(pageCount, 10);
|
const pageCountNum = parseInt(pageCount, 10);
|
||||||
const url = 'https://api.github.com/pulls';
|
const url = 'https://api.github.com/pulls';
|
||||||
const link = linkPage => `<${url}?page=${linkPage}>`;
|
|
||||||
|
function link(linkPage) {
|
||||||
|
return `<${url}?page=${linkPage}>`;
|
||||||
|
}
|
||||||
|
|
||||||
const linkHeader = oneLine`
|
const linkHeader = oneLine`
|
||||||
${pageNum === 1 ? '' : `${link(1)}; rel="first",`}
|
${pageNum === 1 ? '' : `${link(1)}; rel="first",`}
|
||||||
|
@ -2,10 +2,10 @@ import semaphore from 'semaphore';
|
|||||||
|
|
||||||
export type AsyncLock = { release: () => void; acquire: () => Promise<boolean> };
|
export type AsyncLock = { release: () => void; acquire: () => Promise<boolean> };
|
||||||
|
|
||||||
export const asyncLock = (): AsyncLock => {
|
export function asyncLock(): AsyncLock {
|
||||||
let lock = semaphore(1);
|
let lock = semaphore(1);
|
||||||
|
|
||||||
const acquire = (timeout = 15000) => {
|
function acquire(timeout = 15000) {
|
||||||
const promise = new Promise<boolean>(resolve => {
|
const promise = new Promise<boolean>(resolve => {
|
||||||
// this makes sure a caller doesn't gets stuck forever awaiting on the lock
|
// this makes sure a caller doesn't gets stuck forever awaiting on the lock
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
@ -21,9 +21,9 @@ export const asyncLock = (): AsyncLock => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
};
|
}
|
||||||
|
|
||||||
const release = () => {
|
function release() {
|
||||||
try {
|
try {
|
||||||
// suppress too many calls to leave error
|
// suppress too many calls to leave error
|
||||||
lock.leave();
|
lock.leave();
|
||||||
@ -37,7 +37,7 @@ export const asyncLock = (): AsyncLock => {
|
|||||||
lock = semaphore(1);
|
lock = semaphore(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return { acquire, release };
|
return { acquire, release };
|
||||||
};
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user