refactor: convert config actions to TypeScript (#4950)
This commit is contained in:
parent
41e82c2280
commit
0ac17bfc25
@ -69,7 +69,8 @@ module.exports = {
|
|||||||
'require-atomic-updates': [0],
|
'require-atomic-updates': [0],
|
||||||
'import/no-unresolved': [0],
|
'import/no-unresolved': [0],
|
||||||
'@typescript-eslint/no-non-null-assertion': [0],
|
'@typescript-eslint/no-non-null-assertion': [0],
|
||||||
'@typescript-eslint/explicit-function-return-type': 0,
|
'@typescript-eslint/camelcase': [0],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': [0],
|
||||||
'@typescript-eslint/no-use-before-define': [
|
'@typescript-eslint/no-use-before-define': [
|
||||||
'error',
|
'error',
|
||||||
{ functions: false, classes: true, variables: true },
|
{ functions: false, classes: true, variables: true },
|
||||||
|
52
packages/netlify-cms-core/index.d.ts
vendored
52
packages/netlify-cms-core/index.d.ts
vendored
@ -2,6 +2,7 @@
|
|||||||
declare module 'netlify-cms-core' {
|
declare module 'netlify-cms-core' {
|
||||||
import React, { ComponentType } from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
import { List, Map } from 'immutable';
|
import { List, Map } from 'immutable';
|
||||||
|
import { FILES, FOLDER } from '../constants/collectionTypes';
|
||||||
|
|
||||||
export type CmsBackendType =
|
export type CmsBackendType =
|
||||||
| 'azure'
|
| 'azure'
|
||||||
@ -9,7 +10,8 @@ declare module 'netlify-cms-core' {
|
|||||||
| 'github'
|
| 'github'
|
||||||
| 'gitlab'
|
| 'gitlab'
|
||||||
| 'bitbucket'
|
| 'bitbucket'
|
||||||
| 'test-repo';
|
| 'test-repo'
|
||||||
|
| 'proxy';
|
||||||
|
|
||||||
export type CmsMapWidgetType = 'Point' | 'LineString' | 'Polygon';
|
export type CmsMapWidgetType = 'Point' | 'LineString' | 'Polygon';
|
||||||
|
|
||||||
@ -62,7 +64,10 @@ declare module 'netlify-cms-core' {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
pattern?: [string, string];
|
pattern?: [string, string];
|
||||||
i18n?: boolean | 'translate' | 'duplicate';
|
i18n?: boolean | 'translate' | 'duplicate' | 'none';
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsFieldBoolean {
|
export interface CmsFieldBoolean {
|
||||||
@ -236,6 +241,15 @@ declare module 'netlify-cms-core' {
|
|||||||
default?: string;
|
default?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldMeta {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
widget: string;
|
||||||
|
required: boolean;
|
||||||
|
index_file: string;
|
||||||
|
meta: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type CmsField = CmsFieldBase &
|
export type CmsField = CmsFieldBase &
|
||||||
(
|
(
|
||||||
| CmsFieldBoolean
|
| CmsFieldBoolean
|
||||||
@ -252,6 +266,7 @@ declare module 'netlify-cms-core' {
|
|||||||
| CmsFieldSelect
|
| CmsFieldSelect
|
||||||
| CmsFieldHidden
|
| CmsFieldHidden
|
||||||
| CmsFieldStringOrText
|
| CmsFieldStringOrText
|
||||||
|
| CmsFieldMeta
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface CmsCollectionFile {
|
export interface CmsCollectionFile {
|
||||||
@ -261,6 +276,25 @@ declare module 'netlify-cms-core' {
|
|||||||
fields: CmsField[];
|
fields: CmsField[];
|
||||||
label_singular?: string;
|
label_singular?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
preview_path?: string;
|
||||||
|
preview_path_date_field?: string;
|
||||||
|
i18n?: boolean | CmsI18nConfig;
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewFilter {
|
||||||
|
label: string;
|
||||||
|
field: string;
|
||||||
|
pattern: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewGroup {
|
||||||
|
label: string;
|
||||||
|
field: string;
|
||||||
|
pattern: string;
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsCollection {
|
export interface CmsCollection {
|
||||||
@ -280,6 +314,12 @@ declare module 'netlify-cms-core' {
|
|||||||
editor?: {
|
editor?: {
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
};
|
};
|
||||||
|
publish?: boolean;
|
||||||
|
nested?: {
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
type: typeof FOLDER | typeof FILES;
|
||||||
|
meta?: { path?: { label: string; widget: string; index_file: string } };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It accepts the following values: yml, yaml, toml, json, md, markdown, html
|
* It accepts the following values: yml, yaml, toml, json, md, markdown, html
|
||||||
@ -296,6 +336,8 @@ declare module 'netlify-cms-core' {
|
|||||||
media_folder?: string;
|
media_folder?: string;
|
||||||
public_folder?: string;
|
public_folder?: string;
|
||||||
sortable_fields?: string[];
|
sortable_fields?: string[];
|
||||||
|
view_filters?: ViewFilter[];
|
||||||
|
view_groups?: ViewGroup[];
|
||||||
i18n?: boolean | CmsI18nConfig;
|
i18n?: boolean | CmsI18nConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -316,11 +358,13 @@ declare module 'netlify-cms-core' {
|
|||||||
auth_endpoint?: string;
|
auth_endpoint?: string;
|
||||||
cms_label_prefix?: string;
|
cms_label_prefix?: string;
|
||||||
squash_merges?: boolean;
|
squash_merges?: boolean;
|
||||||
|
proxy_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsSlug {
|
export interface CmsSlug {
|
||||||
encoding?: CmsSlugEncoding;
|
encoding?: CmsSlugEncoding;
|
||||||
clean_accents?: boolean;
|
clean_accents?: boolean;
|
||||||
|
sanitize_replacement?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CmsLocalBackend {
|
export interface CmsLocalBackend {
|
||||||
@ -341,9 +385,13 @@ declare module 'netlify-cms-core' {
|
|||||||
media_folder_relative?: boolean;
|
media_folder_relative?: boolean;
|
||||||
media_library?: CmsMediaLibrary;
|
media_library?: CmsMediaLibrary;
|
||||||
publish_mode?: CmsPublishMode;
|
publish_mode?: CmsPublishMode;
|
||||||
|
load_config_file?: boolean;
|
||||||
slug?: CmsSlug;
|
slug?: CmsSlug;
|
||||||
i18n?: CmsI18nConfig;
|
i18n?: CmsI18nConfig;
|
||||||
local_backend?: boolean | CmsLocalBackend;
|
local_backend?: boolean | CmsLocalBackend;
|
||||||
|
editor?: {
|
||||||
|
preview?: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitOptions {
|
export interface InitOptions {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
import {
|
import {
|
||||||
|
loadConfig,
|
||||||
parseConfig,
|
parseConfig,
|
||||||
normalizeConfig,
|
normalizeConfig,
|
||||||
applyDefaults,
|
applyDefaults,
|
||||||
@ -7,6 +9,8 @@ import {
|
|||||||
handleLocalBackend,
|
handleLocalBackend,
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
jest.mock('../../backend', () => {
|
jest.mock('../../backend', () => {
|
||||||
@ -14,6 +18,7 @@ jest.mock('../../backend', () => {
|
|||||||
resolveBackend: jest.fn(() => ({ isGitBackend: jest.fn(() => true) })),
|
resolveBackend: jest.fn(() => ({ isGitBackend: jest.fn(() => true) })),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
jest.mock('../../constants/configSchema');
|
||||||
|
|
||||||
describe('config', () => {
|
describe('config', () => {
|
||||||
describe('parseConfig', () => {
|
describe('parseConfig', () => {
|
||||||
@ -903,4 +908,88 @@ describe('config', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loadConfig', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
document.querySelector = jest.fn();
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should fetch default 'config.yml'`, async () => {
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
|
global.fetch.mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
text: () => Promise.resolve(yaml.dump({ backend: { repo: 'test-repo' } })),
|
||||||
|
headers: new Headers(),
|
||||||
|
});
|
||||||
|
await loadConfig()(dispatch);
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith('config.yml', { credentials: 'same-origin' });
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'CONFIG_SUCCESS',
|
||||||
|
payload: fromJS({
|
||||||
|
backend: { repo: 'test-repo' },
|
||||||
|
collections: [],
|
||||||
|
publish_mode: 'simple',
|
||||||
|
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
||||||
|
public_folder: '/',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should fetch from custom 'config.yml'`, async () => {
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
|
document.querySelector.mockReturnValue({ type: 'text/yaml', href: 'custom-config.yml' });
|
||||||
|
global.fetch.mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
text: () => Promise.resolve(yaml.dump({ backend: { repo: 'github' } })),
|
||||||
|
headers: new Headers(),
|
||||||
|
});
|
||||||
|
await loadConfig()(dispatch);
|
||||||
|
|
||||||
|
expect(document.querySelector).toHaveBeenCalledTimes(1);
|
||||||
|
expect(document.querySelector).toHaveBeenCalledWith('link[rel="cms-config-url"]');
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith('custom-config.yml', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'CONFIG_SUCCESS',
|
||||||
|
payload: fromJS({
|
||||||
|
backend: { repo: 'github' },
|
||||||
|
collections: [],
|
||||||
|
publish_mode: 'simple',
|
||||||
|
slug: { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' },
|
||||||
|
public_folder: '/',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should throw on failure to fetch 'config.yml'`, async () => {
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
|
global.fetch.mockRejectedValue(new Error('Failed to fetch'));
|
||||||
|
await expect(() => loadConfig()(dispatch)).rejects.toEqual(
|
||||||
|
new Error('Failed to load config.yml (Failed to fetch)'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CONFIG_REQUEST' });
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'CONFIG_FAILURE',
|
||||||
|
error: 'Error loading config',
|
||||||
|
payload: new Error('Failed to load config.yml (Failed to fetch)'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,9 @@ import yaml from 'yaml';
|
|||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { trimStart, trim, get, isPlainObject, isEmpty } from 'lodash';
|
import { trimStart, trim, isEmpty } from 'lodash';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { SIMPLE as SIMPLE_PUBLISH_MODE } from '../constants/publishModes';
|
import { SIMPLE as SIMPLE_PUBLISH_MODE } from '../constants/publishModes';
|
||||||
import { validateConfig } from '../constants/configSchema';
|
import { validateConfig } from '../constants/configSchema';
|
||||||
import { selectDefaultSortableFields } from '../reducers/collections';
|
import { selectDefaultSortableFields } from '../reducers/collections';
|
||||||
@ -10,20 +12,43 @@ import { getIntegrations, selectIntegration } from '../reducers/integrations';
|
|||||||
import { resolveBackend } from '../backend';
|
import { resolveBackend } from '../backend';
|
||||||
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n';
|
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n';
|
||||||
import { FILES, FOLDER } from '../constants/collectionTypes';
|
import { FILES, FOLDER } from '../constants/collectionTypes';
|
||||||
|
import {
|
||||||
|
CmsCollection,
|
||||||
|
CmsConfig,
|
||||||
|
CmsField,
|
||||||
|
CmsFieldBase,
|
||||||
|
CmsFieldObject,
|
||||||
|
CmsFieldList,
|
||||||
|
CmsI18nConfig,
|
||||||
|
CmsPublishMode,
|
||||||
|
CmsLocalBackend,
|
||||||
|
State,
|
||||||
|
} from '../types/redux';
|
||||||
|
|
||||||
export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
||||||
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
||||||
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
||||||
|
|
||||||
function traverseFieldsJS(fields, updater) {
|
function isObjectField(field: CmsField): field is CmsFieldBase & CmsFieldObject {
|
||||||
|
return 'fields' in (field as CmsFieldObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFieldList(field: CmsField): field is CmsFieldBase & CmsFieldList {
|
||||||
|
return 'types' in (field as CmsFieldList) || 'field' in (field as CmsFieldList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverseFieldsJS<Field extends CmsField>(
|
||||||
|
fields: Field[],
|
||||||
|
updater: <T extends CmsField>(field: T) => T,
|
||||||
|
): Field[] {
|
||||||
return fields.map(field => {
|
return fields.map(field => {
|
||||||
let newField = updater(field);
|
const newField = updater(field);
|
||||||
if (newField.fields) {
|
if (isObjectField(newField)) {
|
||||||
newField = { ...newField, fields: traverseFieldsJS(newField.fields, updater) };
|
return { ...newField, fields: traverseFieldsJS(newField.fields, updater) };
|
||||||
} else if (newField.field) {
|
} else if (isFieldList(newField) && newField.field) {
|
||||||
newField = { ...newField, field: traverseFieldsJS([newField.field], updater)[0] };
|
return { ...newField, field: traverseFieldsJS([newField.field], updater)[0] };
|
||||||
} else if (newField.types) {
|
} else if (isFieldList(newField) && newField.types) {
|
||||||
newField = { ...newField, types: traverseFieldsJS(newField.types, updater) };
|
return { ...newField, types: traverseFieldsJS(newField.types, updater) };
|
||||||
}
|
}
|
||||||
|
|
||||||
return newField;
|
return newField;
|
||||||
@ -31,18 +56,19 @@ function traverseFieldsJS(fields, updater) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getConfigUrl() {
|
function getConfigUrl() {
|
||||||
const validTypes = { 'text/yaml': 'yaml', 'application/x-yaml': 'yaml' };
|
const validTypes: { [type: string]: string } = {
|
||||||
const configLinkEl = document.querySelector('link[rel="cms-config-url"]');
|
'text/yaml': 'yaml',
|
||||||
const isValidLink = configLinkEl && validTypes[configLinkEl.type] && get(configLinkEl, 'href');
|
'application/x-yaml': 'yaml',
|
||||||
if (isValidLink) {
|
};
|
||||||
const link = get(configLinkEl, 'href');
|
const configLinkEl = document.querySelector<HTMLLinkElement>('link[rel="cms-config-url"]');
|
||||||
console.log(`Using config file path: "${link}"`);
|
if (configLinkEl && validTypes[configLinkEl.type] && configLinkEl.href) {
|
||||||
return link;
|
console.log(`Using config file path: "${configLinkEl.href}"`);
|
||||||
|
return configLinkEl.href;
|
||||||
}
|
}
|
||||||
return 'config.yml';
|
return 'config.yml';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultPublicFolderForField(field) {
|
function setDefaultPublicFolderForField<T extends CmsField>(field: T) {
|
||||||
if ('media_folder' in field && !field.public_folder) {
|
if ('media_folder' in field && !field.public_folder) {
|
||||||
return { ...field, public_folder: field.media_folder };
|
return { ...field, public_folder: field.media_folder };
|
||||||
}
|
}
|
||||||
@ -60,22 +86,25 @@ const WIDGET_KEY_MAP = {
|
|||||||
searchFields: 'search_fields',
|
searchFields: 'search_fields',
|
||||||
displayFields: 'display_fields',
|
displayFields: 'display_fields',
|
||||||
optionsLength: 'options_length',
|
optionsLength: 'options_length',
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
|
function setSnakeCaseConfig<T extends CmsField>(field: T) {
|
||||||
|
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(
|
||||||
|
camel => camel in field,
|
||||||
|
) as ReadonlyArray<keyof typeof WIDGET_KEY_MAP>;
|
||||||
|
|
||||||
function setSnakeCaseConfig(field) {
|
|
||||||
const deprecatedKeys = Object.keys(WIDGET_KEY_MAP).filter(camel => camel in field);
|
|
||||||
const snakeValues = deprecatedKeys.map(camel => {
|
const snakeValues = deprecatedKeys.map(camel => {
|
||||||
const snake = WIDGET_KEY_MAP[camel];
|
const snake = WIDGET_KEY_MAP[camel];
|
||||||
console.warn(
|
console.warn(
|
||||||
`Field ${field.name} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
|
`Field ${field.name} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
|
||||||
);
|
);
|
||||||
return { [snake]: field[camel] };
|
return { [snake]: (field as Record<string, unknown>)[camel] };
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.assign({}, field, ...snakeValues);
|
return Object.assign({}, field, ...snakeValues) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setI18nField(field) {
|
function setI18nField<T extends CmsField>(field: T) {
|
||||||
if (field[I18N] === true) {
|
if (field[I18N] === true) {
|
||||||
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
|
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
|
||||||
} else if (field[I18N] === false || !field[I18N]) {
|
} else if (field[I18N] === false || !field[I18N]) {
|
||||||
@ -84,13 +113,16 @@ function setI18nField(field) {
|
|||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getI18nDefaults(collectionOrFileI18n, defaultI18n) {
|
function getI18nDefaults(
|
||||||
|
collectionOrFileI18n: boolean | CmsI18nConfig,
|
||||||
|
defaultI18n: CmsI18nConfig,
|
||||||
|
) {
|
||||||
if (typeof collectionOrFileI18n === 'boolean') {
|
if (typeof collectionOrFileI18n === 'boolean') {
|
||||||
return defaultI18n;
|
return defaultI18n;
|
||||||
} else {
|
} else {
|
||||||
const locales = collectionOrFileI18n.locales || defaultI18n.locales;
|
const locales = collectionOrFileI18n.locales || defaultI18n.locales;
|
||||||
const defaultLocale = collectionOrFileI18n.default_locale || locales[0];
|
const defaultLocale = collectionOrFileI18n.default_locale || locales[0];
|
||||||
const mergedI18n = deepmerge(defaultI18n, collectionOrFileI18n);
|
const mergedI18n: CmsI18nConfig = deepmerge(defaultI18n, collectionOrFileI18n);
|
||||||
mergedI18n.locales = locales;
|
mergedI18n.locales = locales;
|
||||||
mergedI18n.default_locale = defaultLocale;
|
mergedI18n.default_locale = defaultLocale;
|
||||||
throwOnMissingDefaultLocale(mergedI18n);
|
throwOnMissingDefaultLocale(mergedI18n);
|
||||||
@ -98,7 +130,7 @@ function getI18nDefaults(collectionOrFileI18n, defaultI18n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setI18nDefaultsForFields(collectionOrFileFields, hasI18n) {
|
function setI18nDefaultsForFields(collectionOrFileFields: CmsField[], hasI18n: boolean) {
|
||||||
if (hasI18n) {
|
if (hasI18n) {
|
||||||
return traverseFieldsJS(collectionOrFileFields, setI18nField);
|
return traverseFieldsJS(collectionOrFileFields, setI18nField);
|
||||||
} else {
|
} else {
|
||||||
@ -110,7 +142,7 @@ function setI18nDefaultsForFields(collectionOrFileFields, hasI18n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function throwOnInvalidFileCollectionStructure(i18n) {
|
function throwOnInvalidFileCollectionStructure(i18n?: CmsI18nConfig) {
|
||||||
if (i18n && i18n.structure !== I18N_STRUCTURE.SINGLE_FILE) {
|
if (i18n && i18n.structure !== I18N_STRUCTURE.SINGLE_FILE) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`,
|
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`,
|
||||||
@ -118,7 +150,7 @@ function throwOnInvalidFileCollectionStructure(i18n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function throwOnMissingDefaultLocale(i18n) {
|
function throwOnMissingDefaultLocale(i18n?: CmsI18nConfig) {
|
||||||
if (i18n && i18n.default_locale && !i18n.locales.includes(i18n.default_locale)) {
|
if (i18n && i18n.default_locale && !i18n.locales.includes(i18n.default_locale)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`i18n locales '${i18n.locales.join(', ')}' are missing the default locale ${
|
`i18n locales '${i18n.locales.join(', ')}' are missing the default locale ${
|
||||||
@ -128,14 +160,14 @@ function throwOnMissingDefaultLocale(i18n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasIntegration(config, collection) {
|
function hasIntegration(config: CmsConfig, collection: CmsCollection) {
|
||||||
// TODO remove fromJS when Immutable is removed from the integrations state slice
|
// TODO remove fromJS when Immutable is removed from the integrations state slice
|
||||||
const integrations = getIntegrations(fromJS(config));
|
const integrations = getIntegrations(fromJS(config));
|
||||||
const integration = selectIntegration(integrations, collection.name, 'listEntries');
|
const integration = selectIntegration(integrations, collection.name, 'listEntries');
|
||||||
return !!integration;
|
return !!integration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeConfig(config) {
|
export function normalizeConfig(config: CmsConfig) {
|
||||||
const { collections = [] } = config;
|
const { collections = [] } = config;
|
||||||
|
|
||||||
const normalizedCollections = collections.map(collection => {
|
const normalizedCollections = collections.map(collection => {
|
||||||
@ -170,7 +202,7 @@ export function normalizeConfig(config) {
|
|||||||
return { ...config, collections: normalizedCollections };
|
return { ...config, collections: normalizedCollections };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyDefaults(originalConfig) {
|
export function applyDefaults(originalConfig: CmsConfig) {
|
||||||
return produce(originalConfig, config => {
|
return produce(originalConfig, config => {
|
||||||
config.publish_mode = config.publish_mode || SIMPLE_PUBLISH_MODE;
|
config.publish_mode = config.publish_mode || SIMPLE_PUBLISH_MODE;
|
||||||
config.slug = config.slug || {};
|
config.slug = config.slug || {};
|
||||||
@ -201,13 +233,14 @@ export function applyDefaults(originalConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const i18n = config[I18N];
|
const i18n = config[I18N];
|
||||||
const hasI18n = Boolean(i18n);
|
|
||||||
if (hasI18n) {
|
if (i18n) {
|
||||||
i18n.default_locale = i18n.default_locale || i18n.locales[0];
|
i18n.default_locale = i18n.default_locale || i18n.locales[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
throwOnMissingDefaultLocale(i18n);
|
throwOnMissingDefaultLocale(i18n);
|
||||||
|
|
||||||
|
// TODO remove fromJS when Immutable is removed from backend
|
||||||
const backend = resolveBackend(fromJS(config));
|
const backend = resolveBackend(fromJS(config));
|
||||||
|
|
||||||
for (const collection of config.collections) {
|
for (const collection of config.collections) {
|
||||||
@ -215,15 +248,18 @@ export function applyDefaults(originalConfig) {
|
|||||||
collection.publish = true;
|
collection.publish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionHasI18n = Boolean(collection[I18N]);
|
let collectionI18n = collection[I18N];
|
||||||
if (hasI18n && collectionHasI18n) {
|
|
||||||
collection[I18N] = getI18nDefaults(collection[I18N], i18n);
|
if (i18n && collectionI18n) {
|
||||||
|
collectionI18n = getI18nDefaults(collectionI18n, i18n);
|
||||||
|
collection[I18N] = collectionI18n;
|
||||||
} else {
|
} else {
|
||||||
|
collectionI18n = undefined;
|
||||||
delete collection[I18N];
|
delete collection[I18N];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collection.fields) {
|
if (collection.fields) {
|
||||||
collection.fields = setI18nDefaultsForFields(collection.fields, collectionHasI18n);
|
collection.fields = setI18nDefaultsForFields(collection.fields, Boolean(collectionI18n));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { folder, files, view_filters, view_groups, meta } = collection;
|
const { folder, files, view_filters, view_groups, meta } = collection;
|
||||||
@ -260,9 +296,6 @@ export function applyDefaults(originalConfig) {
|
|||||||
if (files) {
|
if (files) {
|
||||||
collection.type = FILES;
|
collection.type = FILES;
|
||||||
|
|
||||||
// after we invoked setI18nDefaults,
|
|
||||||
// i18n property can't be boolean anymore
|
|
||||||
const collectionI18n = collection[I18N];
|
|
||||||
throwOnInvalidFileCollectionStructure(collectionI18n);
|
throwOnInvalidFileCollectionStructure(collectionI18n);
|
||||||
|
|
||||||
delete collection.nested;
|
delete collection.nested;
|
||||||
@ -279,24 +312,21 @@ export function applyDefaults(originalConfig) {
|
|||||||
file.fields = traverseFieldsJS(file.fields, setDefaultPublicFolderForField);
|
file.fields = traverseFieldsJS(file.fields, setDefaultPublicFolderForField);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileHasI18n = Boolean(file[I18N]);
|
let fileI18n = file[I18N];
|
||||||
|
|
||||||
if (fileHasI18n) {
|
if (fileI18n && collectionI18n) {
|
||||||
if (collectionI18n) {
|
fileI18n = getI18nDefaults(fileI18n, collectionI18n);
|
||||||
file[I18N] = getI18nDefaults(file[I18N], collectionI18n);
|
file[I18N] = fileI18n;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
fileI18n = undefined;
|
||||||
delete file[I18N];
|
delete file[I18N];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.fields) {
|
|
||||||
file.fields = setI18nDefaultsForFields(file.fields, fileHasI18n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// after we invoked setI18nDefaults,
|
|
||||||
// i18n property can't be boolean anymore
|
|
||||||
const fileI18n = file[I18N];
|
|
||||||
throwOnInvalidFileCollectionStructure(fileI18n);
|
throwOnInvalidFileCollectionStructure(fileI18n);
|
||||||
|
|
||||||
|
if (file.fields) {
|
||||||
|
file.fields = setI18nDefaultsForFields(file.fields, Boolean(fileI18n));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,32 +360,42 @@ export function applyDefaults(originalConfig) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseConfig(data) {
|
export function parseConfig(data: string) {
|
||||||
const config = yaml.parse(data, { maxAliasCount: -1, prettyErrors: true, merge: true });
|
const config = yaml.parse(data, { maxAliasCount: -1, prettyErrors: true, merge: true });
|
||||||
if (typeof CMS_ENV === 'string' && config[CMS_ENV]) {
|
if (
|
||||||
Object.keys(config[CMS_ENV]).forEach(key => {
|
typeof window !== 'undefined' &&
|
||||||
config[key] = config[CMS_ENV][key];
|
typeof window.CMS_ENV === 'string' &&
|
||||||
});
|
config[window.CMS_ENV]
|
||||||
|
) {
|
||||||
|
const configKeys = Object.keys(config[window.CMS_ENV]) as ReadonlyArray<keyof CmsConfig>;
|
||||||
|
for (const key of configKeys) {
|
||||||
|
config[key] = config[window.CMS_ENV][key] as CmsConfig[keyof CmsConfig];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return config;
|
return config as Partial<CmsConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getConfigYaml(file, hasManualConfig) {
|
async function getConfigYaml(file: string, hasManualConfig: boolean) {
|
||||||
const response = await fetch(file, { credentials: 'same-origin' }).catch(err => err);
|
const response = await fetch(file, { credentials: 'same-origin' }).catch(error => error as Error);
|
||||||
if (response instanceof Error || response.status !== 200) {
|
if (response instanceof Error || response.status !== 200) {
|
||||||
if (hasManualConfig) return parseConfig('');
|
if (hasManualConfig) {
|
||||||
throw new Error(`Failed to load config.yml (${response.status || response})`);
|
return {};
|
||||||
|
}
|
||||||
|
const message = response instanceof Error ? response.message : response.status;
|
||||||
|
throw new Error(`Failed to load config.yml (${message})`);
|
||||||
}
|
}
|
||||||
const contentType = response.headers.get('Content-Type') || 'Not-Found';
|
const contentType = response.headers.get('Content-Type') || 'Not-Found';
|
||||||
const isYaml = contentType.indexOf('yaml') !== -1;
|
const isYaml = contentType.indexOf('yaml') !== -1;
|
||||||
if (!isYaml) {
|
if (!isYaml) {
|
||||||
console.log(`Response for ${file} was not yaml. (Content-Type: ${contentType})`);
|
console.log(`Response for ${file} was not yaml. (Content-Type: ${contentType})`);
|
||||||
if (hasManualConfig) return parseConfig('');
|
if (hasManualConfig) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return parseConfig(await response.text());
|
return parseConfig(await response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configLoaded(config) {
|
export function configLoaded(config: CmsConfig) {
|
||||||
return {
|
return {
|
||||||
type: CONFIG_SUCCESS,
|
type: CONFIG_SUCCESS,
|
||||||
payload: config,
|
payload: config,
|
||||||
@ -368,7 +408,7 @@ export function configLoading() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function configFailed(err) {
|
export function configFailed(err: Error) {
|
||||||
return {
|
return {
|
||||||
type: CONFIG_FAILURE,
|
type: CONFIG_FAILURE,
|
||||||
error: 'Error loading config',
|
error: 'Error loading config',
|
||||||
@ -376,35 +416,49 @@ export function configFailed(err) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function detectProxyServer(localBackend) {
|
export async function detectProxyServer(localBackend?: boolean | CmsLocalBackend) {
|
||||||
const allowedHosts = ['localhost', '127.0.0.1', ...(localBackend?.allowed_hosts || [])];
|
const allowedHosts = [
|
||||||
if (allowedHosts.includes(location.hostname)) {
|
'localhost',
|
||||||
let proxyUrl;
|
'127.0.0.1',
|
||||||
const defaultUrl = 'http://localhost:8081/api/v1';
|
...(typeof localBackend === 'boolean' ? [] : localBackend?.allowed_hosts || []),
|
||||||
if (localBackend === true) {
|
];
|
||||||
proxyUrl = defaultUrl;
|
|
||||||
} else if (isPlainObject(localBackend)) {
|
if (!allowedHosts.includes(location.hostname) || !localBackend) {
|
||||||
proxyUrl = localBackend.url || defaultUrl.replace('localhost', location.hostname);
|
return {};
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
console.log(`Looking for Netlify CMS Proxy Server at '${proxyUrl}'`);
|
const defaultUrl = 'http://localhost:8081/api/v1';
|
||||||
const { repo, publish_modes, type } = await fetch(`${proxyUrl}`, {
|
const proxyUrl =
|
||||||
method: 'POST',
|
localBackend === true
|
||||||
headers: { 'Content-Type': 'application/json' },
|
? defaultUrl
|
||||||
body: JSON.stringify({ action: 'info' }),
|
: localBackend.url || defaultUrl.replace('localhost', location.hostname);
|
||||||
}).then(res => res.json());
|
|
||||||
if (typeof repo === 'string' && Array.isArray(publish_modes) && typeof type === 'string') {
|
try {
|
||||||
console.log(`Detected Netlify CMS Proxy Server at '${proxyUrl}' with repo: '${repo}'`);
|
console.log(`Looking for Netlify CMS Proxy Server at '${proxyUrl}'`);
|
||||||
return { proxyUrl, publish_modes, type };
|
const res = await fetch(`${proxyUrl}`, {
|
||||||
}
|
method: 'POST',
|
||||||
} catch {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
console.log(`Netlify CMS Proxy Server not detected at '${proxyUrl}'`);
|
body: JSON.stringify({ action: 'info' }),
|
||||||
}
|
});
|
||||||
|
const { repo, publish_modes, type } = (await res.json()) as {
|
||||||
|
repo?: string;
|
||||||
|
publish_modes?: CmsPublishMode[];
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
if (typeof repo === 'string' && Array.isArray(publish_modes) && typeof type === 'string') {
|
||||||
|
console.log(`Detected Netlify CMS Proxy Server at '${proxyUrl}' with repo: '${repo}'`);
|
||||||
|
return { proxyUrl, publish_modes, type };
|
||||||
|
} else {
|
||||||
|
console.log(`Netlify CMS Proxy Server not detected at '${proxyUrl}'`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.log(`Netlify CMS Proxy Server not detected at '${proxyUrl}'`);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublishMode(config, publishModes, backendType) {
|
function getPublishMode(config: CmsConfig, publishModes?: CmsPublishMode[], backendType?: string) {
|
||||||
if (config.publish_mode && publishModes && !publishModes.includes(config.publish_mode)) {
|
if (config.publish_mode && publishModes && !publishModes.includes(config.publish_mode)) {
|
||||||
const newPublishMode = publishModes[0];
|
const newPublishMode = publishModes[0];
|
||||||
console.log(
|
console.log(
|
||||||
@ -416,32 +470,34 @@ function getPublishMode(config, publishModes, backendType) {
|
|||||||
return config.publish_mode;
|
return config.publish_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleLocalBackend(config) {
|
export async function handleLocalBackend(originalConfig: CmsConfig) {
|
||||||
if (!config.local_backend) {
|
if (!originalConfig.local_backend) {
|
||||||
return config;
|
return originalConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { proxyUrl, publish_modes: publishModes, type: backendType } = await detectProxyServer(
|
const { proxyUrl, publish_modes: publishModes, type: backendType } = await detectProxyServer(
|
||||||
config.local_backend,
|
originalConfig.local_backend,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!proxyUrl) {
|
if (!proxyUrl) {
|
||||||
return config;
|
return originalConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishMode = getPublishMode(config, publishModes, backendType);
|
return produce(originalConfig, config => {
|
||||||
return {
|
config.backend.name = 'proxy';
|
||||||
...config,
|
config.backend.proxy_url = proxyUrl;
|
||||||
...(publishMode && { publish_mode: publishMode }),
|
|
||||||
backend: { ...config.backend, name: 'proxy', proxy_url: proxyUrl },
|
if (config.publish_mode) {
|
||||||
};
|
config.publish_mode = getPublishMode(config, publishModes, backendType);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadConfig(manualConfig = {}, onLoad) {
|
export function loadConfig(manualConfig: Partial<CmsConfig> = {}, onLoad: () => unknown) {
|
||||||
if (window.CMS_CONFIG) {
|
if (window.CMS_CONFIG) {
|
||||||
return configLoaded(fromJS(window.CMS_CONFIG));
|
return configLoaded(fromJS(window.CMS_CONFIG));
|
||||||
}
|
}
|
||||||
return async dispatch => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>) => {
|
||||||
dispatch(configLoading());
|
dispatch(configLoading());
|
||||||
|
|
||||||
try {
|
try {
|
8
packages/netlify-cms-core/src/types/global.d.ts
vendored
Normal file
8
packages/netlify-cms-core/src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { CmsConfig } from './redux';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
CMS_CONFIG?: CmsConfig;
|
||||||
|
CMS_ENV?: string;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,409 @@
|
|||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
import { StaticallyTypedRecord } from './immutable';
|
import { StaticallyTypedRecord } from './immutable';
|
||||||
import { Map, List, OrderedMap, Set } from 'immutable';
|
import { Map, List, OrderedMap, Set } from 'immutable';
|
||||||
|
import { FILES, FOLDER } from '../constants/collectionTypes';
|
||||||
import { MediaFile as BackendMediaFile } from '../backend';
|
import { MediaFile as BackendMediaFile } from '../backend';
|
||||||
import { Auth } from '../reducers/auth';
|
import { Auth } from '../reducers/auth';
|
||||||
import { Status } from '../reducers/status';
|
import { Status } from '../reducers/status';
|
||||||
import { Medias } from '../reducers/medias';
|
import { Medias } from '../reducers/medias';
|
||||||
|
|
||||||
|
export type CmsBackendType =
|
||||||
|
| 'azure'
|
||||||
|
| 'git-gateway'
|
||||||
|
| 'github'
|
||||||
|
| 'gitlab'
|
||||||
|
| 'bitbucket'
|
||||||
|
| 'test-repo'
|
||||||
|
| 'proxy';
|
||||||
|
|
||||||
|
export type CmsMapWidgetType = 'Point' | 'LineString' | 'Polygon';
|
||||||
|
|
||||||
|
export type CmsMarkdownWidgetButton =
|
||||||
|
| 'bold'
|
||||||
|
| 'italic'
|
||||||
|
| 'code'
|
||||||
|
| 'link'
|
||||||
|
| 'heading-one'
|
||||||
|
| 'heading-two'
|
||||||
|
| 'heading-three'
|
||||||
|
| 'heading-four'
|
||||||
|
| 'heading-five'
|
||||||
|
| 'heading-six'
|
||||||
|
| 'quote'
|
||||||
|
| 'code-block'
|
||||||
|
| 'bulleted-list'
|
||||||
|
| 'numbered-list';
|
||||||
|
|
||||||
|
export interface CmsSelectWidgetOptionObject {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CmsCollectionFormatType =
|
||||||
|
| 'yml'
|
||||||
|
| 'yaml'
|
||||||
|
| 'toml'
|
||||||
|
| 'json'
|
||||||
|
| 'frontmatter'
|
||||||
|
| 'yaml-frontmatter'
|
||||||
|
| 'toml-frontmatter'
|
||||||
|
| 'json-frontmatter';
|
||||||
|
|
||||||
|
export type CmsAuthScope = 'repo' | 'public_repo';
|
||||||
|
|
||||||
|
export type CmsPublishMode = 'simple' | 'editorial_workflow';
|
||||||
|
|
||||||
|
export type CmsSlugEncoding = 'unicode' | 'ascii';
|
||||||
|
|
||||||
|
export interface CmsI18nConfig {
|
||||||
|
structure: 'multiple_folders' | 'multiple_files' | 'single_file';
|
||||||
|
locales: string[];
|
||||||
|
default_locale?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldBase {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
required?: boolean;
|
||||||
|
hint?: string;
|
||||||
|
pattern?: [string, string];
|
||||||
|
i18n?: boolean | 'translate' | 'duplicate' | 'none';
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldBoolean {
|
||||||
|
widget: 'boolean';
|
||||||
|
default?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldCode {
|
||||||
|
widget: 'code';
|
||||||
|
default?: unknown;
|
||||||
|
|
||||||
|
default_language?: string;
|
||||||
|
allow_language_selection?: boolean;
|
||||||
|
keys?: { code: string; lang: string };
|
||||||
|
output_code_only?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldColor {
|
||||||
|
widget: 'color';
|
||||||
|
default?: string;
|
||||||
|
|
||||||
|
allowInput?: boolean;
|
||||||
|
enableAlpha?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldDateTime {
|
||||||
|
widget: 'datetime';
|
||||||
|
default?: string;
|
||||||
|
|
||||||
|
format?: string;
|
||||||
|
date_format?: boolean | string;
|
||||||
|
time_format?: boolean | string;
|
||||||
|
picker_utc?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use date_format instead
|
||||||
|
*/
|
||||||
|
dateFormat?: boolean | string;
|
||||||
|
/**
|
||||||
|
* @deprecated Use time_format instead
|
||||||
|
*/
|
||||||
|
timeFormat?: boolean | string;
|
||||||
|
/**
|
||||||
|
* @deprecated Use picker_utc instead
|
||||||
|
*/
|
||||||
|
pickerUtc?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldFileOrImage {
|
||||||
|
widget: 'file' | 'image';
|
||||||
|
default?: string;
|
||||||
|
|
||||||
|
media_library?: CmsMediaLibrary;
|
||||||
|
allow_multiple?: boolean;
|
||||||
|
config?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldObject {
|
||||||
|
widget: 'object';
|
||||||
|
default?: unknown;
|
||||||
|
|
||||||
|
collapsed?: boolean;
|
||||||
|
summary?: string;
|
||||||
|
fields: CmsField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldList {
|
||||||
|
widget: 'list';
|
||||||
|
default?: unknown;
|
||||||
|
|
||||||
|
allow_add?: boolean;
|
||||||
|
collapsed?: boolean;
|
||||||
|
summary?: string;
|
||||||
|
minimize_collapsed?: boolean;
|
||||||
|
label_singular?: string;
|
||||||
|
field?: CmsField;
|
||||||
|
fields?: CmsField[];
|
||||||
|
max?: number;
|
||||||
|
min?: number;
|
||||||
|
add_to_top?: boolean;
|
||||||
|
types?: (CmsFieldBase & CmsFieldObject)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldMap {
|
||||||
|
widget: 'map';
|
||||||
|
default?: string;
|
||||||
|
|
||||||
|
decimals?: number;
|
||||||
|
type?: CmsMapWidgetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldMarkdown {
|
||||||
|
widget: 'markdown';
|
||||||
|
default?: string;
|
||||||
|
|
||||||
|
minimal?: boolean;
|
||||||
|
buttons?: CmsMarkdownWidgetButton[];
|
||||||
|
editor_components?: string[];
|
||||||
|
modes?: ('raw' | 'rich_text')[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use editor_components instead
|
||||||
|
*/
|
||||||
|
editorComponents?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldNumber {
|
||||||
|
widget: 'number';
|
||||||
|
default?: string | number;
|
||||||
|
|
||||||
|
value_type?: 'int' | 'float' | string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
|
||||||
|
step?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use valueType instead
|
||||||
|
*/
|
||||||
|
valueType?: 'int' | 'float' | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldSelect {
|
||||||
|
widget: 'select';
|
||||||
|
default?: string | string[];
|
||||||
|
|
||||||
|
options: string[] | CmsSelectWidgetOptionObject[];
|
||||||
|
multiple?: boolean;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldRelation {
|
||||||
|
widget: 'relation';
|
||||||
|
default?: string | string[];
|
||||||
|
|
||||||
|
collection: string;
|
||||||
|
value_field: string;
|
||||||
|
search_fields: string[];
|
||||||
|
file?: string;
|
||||||
|
display_fields?: string[];
|
||||||
|
multiple?: boolean;
|
||||||
|
options_length?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use value_field instead
|
||||||
|
*/
|
||||||
|
valueField?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated Use search_fields instead
|
||||||
|
*/
|
||||||
|
searchFields?: string[];
|
||||||
|
/**
|
||||||
|
* @deprecated Use display_fields instead
|
||||||
|
*/
|
||||||
|
displayFields?: string[];
|
||||||
|
/**
|
||||||
|
* @deprecated Use options_length instead
|
||||||
|
*/
|
||||||
|
optionsLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldHidden {
|
||||||
|
widget: 'hidden';
|
||||||
|
default?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldStringOrText {
|
||||||
|
// This is the default widget, so declaring its type is optional.
|
||||||
|
widget?: 'string' | 'text';
|
||||||
|
default?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFieldMeta {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
widget: string;
|
||||||
|
required: boolean;
|
||||||
|
index_file: string;
|
||||||
|
meta: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CmsField = CmsFieldBase &
|
||||||
|
(
|
||||||
|
| CmsFieldBoolean
|
||||||
|
| CmsFieldCode
|
||||||
|
| CmsFieldColor
|
||||||
|
| CmsFieldDateTime
|
||||||
|
| CmsFieldFileOrImage
|
||||||
|
| CmsFieldList
|
||||||
|
| CmsFieldMap
|
||||||
|
| CmsFieldMarkdown
|
||||||
|
| CmsFieldNumber
|
||||||
|
| CmsFieldObject
|
||||||
|
| CmsFieldRelation
|
||||||
|
| CmsFieldSelect
|
||||||
|
| CmsFieldHidden
|
||||||
|
| CmsFieldStringOrText
|
||||||
|
| CmsFieldMeta
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface CmsCollectionFile {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
file: string;
|
||||||
|
fields: CmsField[];
|
||||||
|
label_singular?: string;
|
||||||
|
description?: string;
|
||||||
|
preview_path?: string;
|
||||||
|
preview_path_date_field?: string;
|
||||||
|
i18n?: boolean | CmsI18nConfig;
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewFilter {
|
||||||
|
label: string;
|
||||||
|
field: string;
|
||||||
|
pattern: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewGroup {
|
||||||
|
label: string;
|
||||||
|
field: string;
|
||||||
|
pattern: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsCollection {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
label_singular?: string;
|
||||||
|
description?: string;
|
||||||
|
folder?: string;
|
||||||
|
files?: CmsCollectionFile[];
|
||||||
|
identifier_field?: string;
|
||||||
|
summary?: string;
|
||||||
|
slug?: string;
|
||||||
|
preview_path?: string;
|
||||||
|
preview_path_date_field?: string;
|
||||||
|
create?: boolean;
|
||||||
|
delete?: boolean;
|
||||||
|
editor?: {
|
||||||
|
preview?: boolean;
|
||||||
|
};
|
||||||
|
publish?: boolean;
|
||||||
|
nested?: {
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
type: typeof FOLDER | typeof FILES;
|
||||||
|
meta?: { path?: { label: string; widget: string; index_file: string } };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It accepts the following values: yml, yaml, toml, json, md, markdown, html
|
||||||
|
*
|
||||||
|
* You may also specify a custom extension not included in the list above, by specifying the format value.
|
||||||
|
*/
|
||||||
|
extension?: string;
|
||||||
|
format?: CmsCollectionFormatType;
|
||||||
|
|
||||||
|
frontmatter_delimiter?: string[] | string;
|
||||||
|
fields?: CmsField[];
|
||||||
|
filter?: { field: string; value: unknown };
|
||||||
|
path?: string;
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
sortable_fields?: string[];
|
||||||
|
view_filters?: ViewFilter[];
|
||||||
|
view_groups?: ViewGroup[];
|
||||||
|
i18n?: boolean | CmsI18nConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use sortable_fields instead
|
||||||
|
*/
|
||||||
|
sortableFields?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsBackend {
|
||||||
|
name: CmsBackendType;
|
||||||
|
auth_scope?: CmsAuthScope;
|
||||||
|
open_authoring?: boolean;
|
||||||
|
repo?: string;
|
||||||
|
branch?: string;
|
||||||
|
api_root?: string;
|
||||||
|
site_domain?: string;
|
||||||
|
base_url?: string;
|
||||||
|
auth_endpoint?: string;
|
||||||
|
cms_label_prefix?: string;
|
||||||
|
squash_merges?: boolean;
|
||||||
|
proxy_url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsSlug {
|
||||||
|
encoding?: CmsSlugEncoding;
|
||||||
|
clean_accents?: boolean;
|
||||||
|
sanitize_replacement?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsLocalBackend {
|
||||||
|
url?: string;
|
||||||
|
allowed_hosts?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsConfig {
|
||||||
|
backend: CmsBackend;
|
||||||
|
collections: CmsCollection[];
|
||||||
|
locale?: string;
|
||||||
|
site_url?: string;
|
||||||
|
display_url?: string;
|
||||||
|
logo_url?: string;
|
||||||
|
show_preview_links?: boolean;
|
||||||
|
media_folder?: string;
|
||||||
|
public_folder?: string;
|
||||||
|
media_folder_relative?: boolean;
|
||||||
|
media_library?: CmsMediaLibrary;
|
||||||
|
publish_mode?: CmsPublishMode;
|
||||||
|
load_config_file?: boolean;
|
||||||
|
slug?: CmsSlug;
|
||||||
|
i18n?: CmsI18nConfig;
|
||||||
|
local_backend?: boolean | CmsLocalBackend;
|
||||||
|
editor?: {
|
||||||
|
preview?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CmsMediaLibraryOptions = unknown; // TODO: type properly
|
||||||
|
|
||||||
|
export interface CmsMediaLibrary {
|
||||||
|
name: string;
|
||||||
|
config?: CmsMediaLibraryOptions;
|
||||||
|
}
|
||||||
|
|
||||||
export type SlugConfig = StaticallyTypedRecord<{
|
export type SlugConfig = StaticallyTypedRecord<{
|
||||||
encoding: string;
|
encoding: string;
|
||||||
clean_accents: boolean;
|
clean_accents: boolean;
|
||||||
@ -162,20 +560,6 @@ export type CollectionFile = StaticallyTypedRecord<{
|
|||||||
|
|
||||||
export type CollectionFiles = List<CollectionFile>;
|
export type CollectionFiles = List<CollectionFile>;
|
||||||
|
|
||||||
export type ViewFilter = {
|
|
||||||
label: string;
|
|
||||||
field: string;
|
|
||||||
pattern: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ViewGroup = {
|
|
||||||
label: string;
|
|
||||||
field: string;
|
|
||||||
pattern: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NestedObject = { depth: number };
|
type NestedObject = { depth: number };
|
||||||
|
|
||||||
type Nested = StaticallyTypedRecord<NestedObject>;
|
type Nested = StaticallyTypedRecord<NestedObject>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user