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