feat: populate new entry from URL params (#3343)
This commit is contained in:
parent
773d83900d
commit
e0b1246810
@ -1,5 +1,6 @@
|
|||||||
import { fromJS, Map } from 'immutable';
|
import { fromJS, Map } from 'immutable';
|
||||||
import {
|
import {
|
||||||
|
createEmptyDraft,
|
||||||
createEmptyDraftData,
|
createEmptyDraftData,
|
||||||
retrieveLocalBackup,
|
retrieveLocalBackup,
|
||||||
persistLocalBackup,
|
persistLocalBackup,
|
||||||
@ -23,6 +24,99 @@ const middlewares = [thunk];
|
|||||||
const mockStore = configureMockStore(middlewares);
|
const mockStore = configureMockStore(middlewares);
|
||||||
|
|
||||||
describe('entries', () => {
|
describe('entries', () => {
|
||||||
|
describe('createEmptyDraft', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
it('should dispatch draft created action', () => {
|
||||||
|
const store = mockStore({ mediaLibrary: fromJS({ files: [] }) });
|
||||||
|
|
||||||
|
const collection = fromJS({
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return store.dispatch(createEmptyDraft(collection, '')).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(actions[0]).toEqual({
|
||||||
|
payload: {
|
||||||
|
collection: undefined,
|
||||||
|
data: {},
|
||||||
|
isModification: null,
|
||||||
|
label: null,
|
||||||
|
mediaFiles: fromJS([]),
|
||||||
|
metaData: null,
|
||||||
|
partial: false,
|
||||||
|
path: '',
|
||||||
|
raw: '',
|
||||||
|
slug: '',
|
||||||
|
},
|
||||||
|
type: 'DRAFT_CREATE_EMPTY',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate draft entry from URL param', () => {
|
||||||
|
const store = mockStore({ mediaLibrary: fromJS({ files: [] }) });
|
||||||
|
|
||||||
|
const collection = fromJS({
|
||||||
|
fields: [{ name: 'title' }, { name: 'boolean' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return store.dispatch(createEmptyDraft(collection, '?title=title&boolean=True')).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(actions[0]).toEqual({
|
||||||
|
payload: {
|
||||||
|
collection: undefined,
|
||||||
|
data: { title: 'title', boolean: true },
|
||||||
|
isModification: null,
|
||||||
|
label: null,
|
||||||
|
mediaFiles: fromJS([]),
|
||||||
|
metaData: null,
|
||||||
|
partial: false,
|
||||||
|
path: '',
|
||||||
|
raw: '',
|
||||||
|
slug: '',
|
||||||
|
},
|
||||||
|
type: 'DRAFT_CREATE_EMPTY',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should html escape URL params', () => {
|
||||||
|
const store = mockStore({ mediaLibrary: fromJS({ files: [] }) });
|
||||||
|
|
||||||
|
const collection = fromJS({
|
||||||
|
fields: [{ name: 'title' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return store
|
||||||
|
.dispatch(createEmptyDraft(collection, "?title=<script>alert('hello')</script>"))
|
||||||
|
.then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(actions[0]).toEqual({
|
||||||
|
payload: {
|
||||||
|
collection: undefined,
|
||||||
|
data: { title: '<script>alert('hello')</script>' },
|
||||||
|
isModification: null,
|
||||||
|
label: null,
|
||||||
|
mediaFiles: fromJS([]),
|
||||||
|
metaData: null,
|
||||||
|
partial: false,
|
||||||
|
path: '',
|
||||||
|
raw: '',
|
||||||
|
slug: '',
|
||||||
|
},
|
||||||
|
type: 'DRAFT_CREATE_EMPTY',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('createEmptyDraftData', () => {
|
describe('createEmptyDraftData', () => {
|
||||||
it('should set default value for list field widget', () => {
|
it('should set default value for list field widget', () => {
|
||||||
const fields = fromJS([
|
const fields = fromJS([
|
||||||
|
@ -5,7 +5,7 @@ import { serializeValues } from '../lib/serializeEntryValues';
|
|||||||
import { currentBackend, Backend } from '../backend';
|
import { currentBackend, Backend } from '../backend';
|
||||||
import { getIntegrationProvider } from '../integrations';
|
import { getIntegrationProvider } from '../integrations';
|
||||||
import { selectIntegration, selectPublishedSlugs } from '../reducers';
|
import { selectIntegration, selectPublishedSlugs } from '../reducers';
|
||||||
import { selectFields } from '../reducers/collections';
|
import { selectFields, updateFieldByKey } from '../reducers/collections';
|
||||||
import { selectCollectionEntriesCursor } from '../reducers/cursors';
|
import { selectCollectionEntriesCursor } from '../reducers/cursors';
|
||||||
import { Cursor, ImplementationMediaFile } from 'netlify-cms-lib-util';
|
import { Cursor, ImplementationMediaFile } from 'netlify-cms-lib-util';
|
||||||
import { createEntry, EntryValue } from '../valueObjects/Entry';
|
import { createEntry, EntryValue } from '../valueObjects/Entry';
|
||||||
@ -488,9 +488,37 @@ export function traverseCollectionCursor(collection: Collection, action: string)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyDraft(collection: Collection) {
|
const escapeHtml = (unsafe: string) => {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
};
|
||||||
|
|
||||||
|
const processValue = (unsafe: string) => {
|
||||||
|
if (['true', 'True', 'TRUE'].includes(unsafe)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (['false', 'False', 'FALSE'].includes(unsafe)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return escapeHtml(unsafe);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createEmptyDraft(collection: Collection, search: string) {
|
||||||
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
|
||||||
const dataFields = createEmptyDraftData(collection.get('fields', List()));
|
const params = new URLSearchParams(search);
|
||||||
|
params.forEach((value, key) => {
|
||||||
|
collection = updateFieldByKey(collection, key, field =>
|
||||||
|
field.set('default', processValue(value)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fields = collection.get('fields', List());
|
||||||
|
const dataFields = createEmptyDraftData(fields);
|
||||||
|
|
||||||
let mediaFiles = [] as MediaFile[];
|
let mediaFiles = [] as MediaFile[];
|
||||||
if (!collection.has('media_folder')) {
|
if (!collection.has('media_folder')) {
|
||||||
@ -504,12 +532,21 @@ export function createEmptyDraft(collection: Collection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DraftEntryData {
|
interface DraftEntryData {
|
||||||
[name: string]: string | null | DraftEntryData | DraftEntryData[] | (string | DraftEntryData)[];
|
[name: string]:
|
||||||
|
| string
|
||||||
|
| null
|
||||||
|
| boolean
|
||||||
|
| DraftEntryData
|
||||||
|
| DraftEntryData[]
|
||||||
|
| (string | DraftEntryData | boolean)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyDraftData(fields: EntryFields, withNameKey = true) {
|
export function createEmptyDraftData(fields: EntryFields, withNameKey = true) {
|
||||||
return fields.reduce(
|
return fields.reduce(
|
||||||
(reduction: DraftEntryData | string | undefined, value: EntryField | undefined) => {
|
(
|
||||||
|
reduction: DraftEntryData | string | undefined | boolean,
|
||||||
|
value: EntryField | undefined | boolean,
|
||||||
|
) => {
|
||||||
const acc = reduction as DraftEntryData;
|
const acc = reduction as DraftEntryData;
|
||||||
const item = value as EntryField;
|
const item = value as EntryField;
|
||||||
const subfields = item.get('field') || item.get('fields');
|
const subfields = item.get('field') || item.get('fields');
|
||||||
|
@ -78,6 +78,7 @@ export class Editor extends React.Component {
|
|||||||
user: ImmutablePropTypes.map.isRequired,
|
user: ImmutablePropTypes.map.isRequired,
|
||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string,
|
pathname: PropTypes.string,
|
||||||
|
search: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
hasChanged: PropTypes.bool,
|
hasChanged: PropTypes.bool,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
@ -104,7 +105,7 @@ export class Editor extends React.Component {
|
|||||||
retrieveLocalBackup(collection, slug);
|
retrieveLocalBackup(collection, slug);
|
||||||
|
|
||||||
if (newEntry) {
|
if (newEntry) {
|
||||||
createEmptyDraft(collection);
|
createEmptyDraft(collection, this.props.location.search);
|
||||||
} else {
|
} else {
|
||||||
loadEntry(collection, slug);
|
loadEntry(collection, slug);
|
||||||
}
|
}
|
||||||
@ -209,7 +210,7 @@ export class Editor extends React.Component {
|
|||||||
const fieldsMetaData = this.props.entryDraft && this.props.entryDraft.get('fieldsMetaData');
|
const fieldsMetaData = this.props.entryDraft && this.props.entryDraft.get('fieldsMetaData');
|
||||||
this.createDraft(deserializedEntry, fieldsMetaData);
|
this.createDraft(deserializedEntry, fieldsMetaData);
|
||||||
} else if (newEntry) {
|
} else if (newEntry) {
|
||||||
prevProps.createEmptyDraft(collection);
|
prevProps.createEmptyDraft(collection, this.props.location.search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ describe('Editor', () => {
|
|||||||
localBackup: fromJS({}),
|
localBackup: fromJS({}),
|
||||||
retrieveLocalBackup: jest.fn(),
|
retrieveLocalBackup: jest.fn(),
|
||||||
persistLocalBackup: jest.fn(),
|
persistLocalBackup: jest.fn(),
|
||||||
|
location: { search: '?title=title' },
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -114,7 +115,7 @@ describe('Editor', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(props.createEmptyDraft).toHaveBeenCalledTimes(1);
|
expect(props.createEmptyDraft).toHaveBeenCalledTimes(1);
|
||||||
expect(props.createEmptyDraft).toHaveBeenCalledWith(props.collection);
|
expect(props.createEmptyDraft).toHaveBeenCalledWith(props.collection, '?title=title');
|
||||||
expect(props.loadEntry).toHaveBeenCalledTimes(0);
|
expect(props.loadEntry).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import collections, {
|
|||||||
selectMediaFolders,
|
selectMediaFolders,
|
||||||
getFieldsNames,
|
getFieldsNames,
|
||||||
selectField,
|
selectField,
|
||||||
|
updateFieldByKey,
|
||||||
} from '../collections';
|
} from '../collections';
|
||||||
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
||||||
|
|
||||||
@ -381,4 +382,109 @@ describe('collections', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('updateFieldByKey', () => {
|
||||||
|
it('should update field by key', () => {
|
||||||
|
const collection = fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const updater = field => field.set('default', 'default');
|
||||||
|
|
||||||
|
expect(updateFieldByKey(collection, 'non-existent', updater)).toBe(collection);
|
||||||
|
expect(updateFieldByKey(collection, 'title', updater)).toEqual(
|
||||||
|
fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title', default: 'default' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(updateFieldByKey(collection, 'object.title', updater)).toEqual(
|
||||||
|
fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [
|
||||||
|
{ name: 'title', default: 'default' },
|
||||||
|
{ name: 'gallery', fields: [{ name: 'image' }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateFieldByKey(collection, 'object.gallery.image', updater)).toEqual(
|
||||||
|
fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'gallery', fields: [{ name: 'image', default: 'default' }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(updateFieldByKey(collection, 'list.image', updater)).toEqual(
|
||||||
|
fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image', default: 'default' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateFieldByKey(collection, 'widgetList.widget', updater)).toEqual(
|
||||||
|
fromJS({
|
||||||
|
fields: [
|
||||||
|
{ name: 'title' },
|
||||||
|
{ name: 'image' },
|
||||||
|
{
|
||||||
|
name: 'object',
|
||||||
|
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
||||||
|
},
|
||||||
|
{ name: 'list', field: { name: 'image' } },
|
||||||
|
{ name: 'body' },
|
||||||
|
{ name: 'widgetList', types: [{ name: 'widget', default: 'default' }] },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -233,6 +233,53 @@ export const selectField = (collection: Collection, key: string) => {
|
|||||||
return field;
|
return field;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateFieldByKey = (
|
||||||
|
collection: Collection,
|
||||||
|
key: string,
|
||||||
|
updater: (field: EntryField) => EntryField,
|
||||||
|
) => {
|
||||||
|
const selected = selectField(collection, key);
|
||||||
|
if (!selected) {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
const traverseFields = (fields: List<EntryField>) => {
|
||||||
|
if (updated) {
|
||||||
|
// we can stop once the field is found
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = fields
|
||||||
|
.map(f => {
|
||||||
|
const field = f as EntryField;
|
||||||
|
if (field === selected) {
|
||||||
|
updated = true;
|
||||||
|
return updater(field);
|
||||||
|
} else if (field.has('fields')) {
|
||||||
|
return field.set('fields', traverseFields(field.get('fields')!));
|
||||||
|
} else if (field.has('field')) {
|
||||||
|
return field.set('field', traverseFields(List([field.get('field')!])).get(0));
|
||||||
|
} else if (field.has('types')) {
|
||||||
|
return field.set('types', traverseFields(field.get('types')!));
|
||||||
|
} else {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toList() as List<EntryField>;
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
collection = collection.set(
|
||||||
|
'fields',
|
||||||
|
traverseFields(collection.get('fields', List<EntryField>())),
|
||||||
|
);
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
};
|
||||||
|
|
||||||
export const selectIdentifier = (collection: Collection) => {
|
export const selectIdentifier = (collection: Collection) => {
|
||||||
const identifier = collection.get('identifier_field');
|
const identifier = collection.get('identifier_field');
|
||||||
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : IDENTIFIER_FIELDS;
|
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : IDENTIFIER_FIELDS;
|
||||||
|
@ -95,7 +95,7 @@ export type EntryField = StaticallyTypedRecord<{
|
|||||||
types?: List<EntryField>;
|
types?: List<EntryField>;
|
||||||
widget: string;
|
widget: string;
|
||||||
name: string;
|
name: string;
|
||||||
default: string | null;
|
default: string | null | boolean;
|
||||||
media_folder?: string;
|
media_folder?: string;
|
||||||
public_folder?: string;
|
public_folder?: string;
|
||||||
}>;
|
}>;
|
||||||
|
@ -142,10 +142,10 @@ And for the image field being populated with a value of `image.png`.
|
|||||||
|
|
||||||
Supports all of the [`slug` templates](/docs/configuration-options#slug) and:
|
Supports all of the [`slug` templates](/docs/configuration-options#slug) and:
|
||||||
|
|
||||||
* `{{filename}}` The file name without the extension part.
|
- `{{filename}}` The file name without the extension part.
|
||||||
* `{{extension}}` The file extension.
|
- `{{extension}}` The file extension.
|
||||||
* `{{media_folder}}` The global `media_folder`.
|
- `{{media_folder}}` The global `media_folder`.
|
||||||
* `{{public_folder}}` The global `public_folder`.
|
- `{{public_folder}}` The global `public_folder`.
|
||||||
|
|
||||||
## List Widget: Variable Types
|
## List Widget: Variable Types
|
||||||
|
|
||||||
@ -156,9 +156,9 @@ pane.**
|
|||||||
|
|
||||||
To use variable types in the list widget, update your field configuration as follows:
|
To use variable types in the list widget, update your field configuration as follows:
|
||||||
|
|
||||||
1. Instead of defining your list fields under `fields` or `field`, define them under `types`. Similar to `fields`, `types` must be an array of field definition objects.
|
1. Instead of defining your list fields under `fields` or `field`, define them under `types`. Similar to `fields`, `types` must be an array of field definition objects.
|
||||||
2. Each field definition under `types` must use the `object` widget (this is the default value for
|
2. Each field definition under `types` must use the `object` widget (this is the default value for
|
||||||
`widget`).
|
`widget`).
|
||||||
|
|
||||||
### Additional list widget options
|
### Additional list widget options
|
||||||
|
|
||||||
@ -171,27 +171,27 @@ The example configuration below imagines a scenario where the editor can add two
|
|||||||
either a "carousel" or a "spotlight". Each type has a unique name and set of fields.
|
either a "carousel" or a "spotlight". Each type has a unique name and set of fields.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- label: "Home Section"
|
- label: 'Home Section'
|
||||||
name: "sections"
|
name: 'sections'
|
||||||
widget: "list"
|
widget: 'list'
|
||||||
types:
|
types:
|
||||||
- label: "Carousel"
|
- label: 'Carousel'
|
||||||
name: "carousel"
|
name: 'carousel'
|
||||||
widget: object
|
widget: object
|
||||||
fields:
|
fields:
|
||||||
- {label: Header, name: header, widget: string, default: "Image Gallery"}
|
- { label: Header, name: header, widget: string, default: 'Image Gallery' }
|
||||||
- {label: Template, name: template, widget: string, default: "carousel.html"}
|
- { label: Template, name: template, widget: string, default: 'carousel.html' }
|
||||||
- label: Images
|
- label: Images
|
||||||
name: images
|
name: images
|
||||||
widget: list
|
widget: list
|
||||||
field: {label: Image, name: image, widget: image}
|
field: { label: Image, name: image, widget: image }
|
||||||
- label: "Spotlight"
|
- label: 'Spotlight'
|
||||||
name: "spotlight"
|
name: 'spotlight'
|
||||||
widget: object
|
widget: object
|
||||||
fields:
|
fields:
|
||||||
- {label: Header, name: header, widget: string, default: "Spotlight"}
|
- { label: Header, name: header, widget: string, default: 'Spotlight' }
|
||||||
- {label: Template, name: template, widget: string, default: "spotlight.html"}
|
- { label: Template, name: template, widget: string, default: 'spotlight.html' }
|
||||||
- {label: Text, name: text, widget: text, default: "Hello World"}
|
- { label: Text, name: text, widget: text, default: 'Hello World' }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Output
|
### Example Output
|
||||||
@ -310,8 +310,8 @@ CMS.registerPreviewTemplate(...);
|
|||||||
* Takes advantage of the `toString` method in the return value of `css-loader`.
|
* Takes advantage of the `toString` method in the return value of `css-loader`.
|
||||||
*/
|
*/
|
||||||
import CMS from 'netlify-cms';
|
import CMS from 'netlify-cms';
|
||||||
import styles from '!css-loader!sass-loader!../main.scss'
|
import styles from '!css-loader!sass-loader!../main.scss';
|
||||||
CMS.registerPreviewStyle(styles.toString(), { raw: true })
|
CMS.registerPreviewStyle(styles.toString(), { raw: true });
|
||||||
```
|
```
|
||||||
|
|
||||||
## Squash merge GitHub pull requests
|
## Squash merge GitHub pull requests
|
||||||
@ -348,14 +348,14 @@ backend:
|
|||||||
|
|
||||||
Netlify CMS generates the following commit types:
|
Netlify CMS generates the following commit types:
|
||||||
|
|
||||||
Commit type | When is it triggered? | Available template tags
|
| Commit type | When is it triggered? | Available template tags |
|
||||||
--------------|------------------------------|-----------------------------
|
| --------------- | ---------------------------------------- | ---------------------------------------- |
|
||||||
`create` | A new entry is created | `slug`, `path`, `collection`
|
| `create` | A new entry is created | `slug`, `path`, `collection` |
|
||||||
`update` | An existing entry is changed | `slug`, `path`, `collection`
|
| `update` | An existing entry is changed | `slug`, `path`, `collection` |
|
||||||
`delete` | An exising entry is deleted | `slug`, `path`, `collection`
|
| `delete` | An exising entry is deleted | `slug`, `path`, `collection` |
|
||||||
`uploadMedia` | A media file is uploaded | `path`
|
| `uploadMedia` | A media file is uploaded | `path` |
|
||||||
`deleteMedia` | A media file is deleted | `path`
|
| `deleteMedia` | A media file is deleted | `path` |
|
||||||
`openAuthoring` | A commit is made via a forked repository | `message`, `author-login`, `author-name`
|
| `openAuthoring` | A commit is made via a forked repository | `message`, `author-login`, `author-name` |
|
||||||
|
|
||||||
Template tags produce the following output:
|
Template tags produce the following output:
|
||||||
|
|
||||||
@ -378,10 +378,10 @@ You can set a limit to as what the maximum file size of a file is that users can
|
|||||||
Example config:
|
Example config:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- label: "Featured Image"
|
- label: 'Featured Image'
|
||||||
name: "thumbnail"
|
name: 'thumbnail'
|
||||||
widget: "image"
|
widget: 'image'
|
||||||
default: "/uploads/chocolate-dogecoin.jpg"
|
default: '/uploads/chocolate-dogecoin.jpg'
|
||||||
media_library:
|
media_library:
|
||||||
config:
|
config:
|
||||||
max_file_size: 512000 # in bytes, only for default media library
|
max_file_size: 512000 # in bytes, only for default media library
|
||||||
@ -415,4 +415,39 @@ CMS.registerEventListener({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
> Supported events are `prePublish`, `postPublish`, `preUnpublish` and `postUnpublish`
|
**Note:** Supported events are `prePublish`, `postPublish`, `preUnpublish` and `postUnpublish`.
|
||||||
|
|
||||||
|
## Dynamic Default Values
|
||||||
|
|
||||||
|
When linking to `/admin/#/collections/posts/new` you can pass URL parameters to pre-populate an entry.
|
||||||
|
|
||||||
|
For example given the configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
collections:
|
||||||
|
- name: posts
|
||||||
|
label: Posts
|
||||||
|
folder: content/posts
|
||||||
|
create: true
|
||||||
|
fields:
|
||||||
|
- label: Title
|
||||||
|
name: title
|
||||||
|
widget: string
|
||||||
|
- label: Object
|
||||||
|
name: object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- label: Title
|
||||||
|
name: title
|
||||||
|
widget: string
|
||||||
|
- label: body
|
||||||
|
name: body
|
||||||
|
widget: markdown
|
||||||
|
```
|
||||||
|
|
||||||
|
clicking the following link: `/#/collections/posts/new?title=first&object.title=second&body=%23%20content`
|
||||||
|
|
||||||
|
will open the editor for a new post with the `title` field populated with `first`, the nested `object.title` field
|
||||||
|
with `second` and the markdown `body` field with `# content`.
|
||||||
|
|
||||||
|
**Note:** URL Encoding might be required for certain values (e.g. in the previous example the value for `body` is URL encoded).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user