diff --git a/netlify-cms@2/packages/netlify-cms-core/src/actions/entries.ts b/netlify-cms@2/packages/netlify-cms-core/src/actions/entries.ts index 6eaa8d1..d5b411a 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/actions/entries.ts +++ b/netlify-cms@2/packages/netlify-cms-core/src/actions/entries.ts @@ -69,6 +69,7 @@ export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY'; export const DRAFT_CREATE_EMPTY = 'DRAFT_CREATE_EMPTY'; export const DRAFT_DISCARD = 'DRAFT_DISCARD'; export const DRAFT_CHANGE_FIELD = 'DRAFT_CHANGE_FIELD'; +export const DRAFT_FOCUS_FIELD = 'DRAFT_FOCUS_FIELD'; export const DRAFT_VALIDATION_ERRORS = 'DRAFT_VALIDATION_ERRORS'; export const DRAFT_CLEAR_ERRORS = 'DRAFT_CLEAR_ERRORS'; export const DRAFT_LOCAL_BACKUP_RETRIEVED = 'DRAFT_LOCAL_BACKUP_RETRIEVED'; @@ -433,6 +434,29 @@ export function changeDraftField({ }; } +export function focusDraftField({ + field, + value, + metadata, + entries, + i18n, +}: { + field: EntryField; + value: string; + metadata: Record; + entries: EntryMap[]; + i18n?: { + currentLocale: string; + defaultLocale: string; + locales: string[]; + }; +}) { + return { + type: DRAFT_FOCUS_FIELD, + payload: { field, value, metadata, entries, i18n }, + }; +} + export function changeDraftFieldValidation( uniquefieldId: string, errors: { type: string; parentIds: string[]; message: string }[], @@ -610,7 +634,7 @@ export function loadEntries(collection: Collection, page = 0) { entries: EntryValue[]; } = await (loadAllEntries ? // nested collections require all entries to construct the tree - provider.listAllEntries(collection).then((entries: EntryValue[]) => ({ entries })) + provider.listAllEntries(collection).then((entries: EntryValue[]) => ({ entries })) : provider.listEntries(collection, page)); response = { ...response, @@ -622,10 +646,10 @@ export function loadEntries(collection: Collection, page = 0) { // cursor, which behaves identically to no cursor at all. cursor: integration ? Cursor.create({ - actions: ['next'], - meta: { usingOldPaginationAPI: true }, - data: { nextPage: page + 1 }, - }) + actions: ['next'], + meta: { usingOldPaginationAPI: true }, + data: { nextPage: page + 1 }, + }) : Cursor.create(response.cursor), }; @@ -776,13 +800,13 @@ export function createEmptyDraft(collection: Collection, search: string) { interface DraftEntryData { [name: string]: - | string - | null - | boolean - | List - | DraftEntryData - | DraftEntryData[] - | (string | DraftEntryData | boolean | List)[]; + | string + | null + | boolean + | List + | DraftEntryData + | DraftEntryData[] + | (string | DraftEntryData | boolean | List)[]; } export function createEmptyDraftData( diff --git a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/Editor.js b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/Editor.js index 47f3ade..24f23d0 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/Editor.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/Editor.js @@ -15,6 +15,7 @@ import { createEmptyDraft, discardDraft, changeDraftField, + focusDraftField, changeDraftFieldValidation, persistEntry, deleteEntry, @@ -39,6 +40,7 @@ import withWorkflow from './withWorkflow'; export class Editor extends React.Component { static propTypes = { changeDraftField: PropTypes.func.isRequired, + focusDraftField: PropTypes.func.isRequired, changeDraftFieldValidation: PropTypes.func.isRequired, collection: ImmutablePropTypes.map.isRequired, createDraftDuplicateFromEntry: PropTypes.func.isRequired, @@ -201,6 +203,11 @@ export class Editor extends React.Component { this.props.changeDraftField({ field, value, metadata, entries, i18n }); }; + handleFocusDraftField = (field, value, metadata, i18n) => { + const entries = [this.props.unPublishedEntry, this.props.publishedEntry].filter(Boolean); + this.props.focusDraftField({ field, value, metadata, entries, i18n }); + }; + handleChangeStatus = newStatusName => { const { entryDraft, updateUnpublishedEntryStatus, collection, slug, currentStatus, t } = this.props; @@ -383,6 +390,7 @@ export class Editor extends React.Component { fieldsMetaData={entryDraft.get('fieldsMetaData')} fieldsErrors={entryDraft.get('fieldsErrors')} onChange={this.handleChangeDraftField} + onFocus={this.handleFocusDraftField} onValidate={changeDraftFieldValidation} onPersist={this.handlePersistEntry} onDelete={this.handleDeleteEntry} @@ -471,6 +479,7 @@ function mapStateToProps(state, ownProps) { const mapDispatchToProps = { changeDraftField, + focusDraftField, changeDraftFieldValidation, loadEntry, loadEntries, diff --git a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js index e3ad8ee..4b6e412 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js @@ -119,6 +119,7 @@ class EditorControl extends React.Component { mediaPaths: ImmutablePropTypes.map.isRequired, boundGetAsset: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func, openMediaLibrary: PropTypes.func.isRequired, addAsset: PropTypes.func.isRequired, removeInsertedMedia: PropTypes.func.isRequired, @@ -177,6 +178,7 @@ class EditorControl extends React.Component { mediaPaths, boundGetAsset, onChange, + onFocus, openMediaLibrary, clearMediaControl, removeMediaControl, @@ -290,6 +292,7 @@ class EditorControl extends React.Component { mediaPaths={mediaPaths} metadata={metadata} onChange={(newValue, newMetadata) => onChange(field, newValue, newMetadata)} + onFocus={(newValue, newMetadata) => onFocus(field, newValue, newMetadata)} onValidate={onValidate && partial(onValidate, this.uniqueFieldId)} onOpenMediaLibrary={openMediaLibrary} onClearMediaControl={clearMediaControl} diff --git a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControlPane.js b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControlPane.js index 8711c87..308125e 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControlPane.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControlPane.js @@ -166,7 +166,7 @@ export default class ControlPane extends React.Component { }; render() { - const { collection, entry, fields, fieldsMetaData, fieldsErrors, onChange, onValidate, t } = + const { collection, entry, fields, fieldsMetaData, fieldsErrors, onChange, onFocus, onValidate, t } = this.props; if (!collection || !fields) { @@ -227,6 +227,10 @@ export default class ControlPane extends React.Component { console.log('newMeta', newMetadata); onChange(field, newValue, newMetadata, i18n); }} + onFocus={(field, newValue, newMetadata) => { + console.log('newMeta', newMetadata); + onFocus(field, newValue, newMetadata, i18n); + }} onValidate={onValidate} processControlRef={this.controlRef.bind(this)} controlRef={this.controlRef} @@ -251,5 +255,6 @@ ControlPane.propTypes = { fieldsMetaData: ImmutablePropTypes.map.isRequired, fieldsErrors: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, onValidate: PropTypes.func.isRequired, }; diff --git a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js index f6093a4..048ac7c 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js @@ -43,6 +43,8 @@ export default class Widget extends Component { metadata: ImmutablePropTypes.map, fieldsErrors: ImmutablePropTypes.map, onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + onValidate: PropTypes.func, onOpenMediaLibrary: PropTypes.func.isRequired, onClearMediaControl: PropTypes.func.isRequired, @@ -257,6 +259,7 @@ export default class Widget extends Component { mediaPaths, metadata, onChange, + onFocus, onValidateObject, onOpenMediaLibrary, onRemoveMediaControl, @@ -303,6 +306,7 @@ export default class Widget extends Component { mediaPaths, metadata, onChange, + onFocus, onChangeObject: this.onChangeObject, onValidateObject, onOpenMediaLibrary, diff --git a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorInterface.js b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorInterface.js index 0baefea..d67ea10 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorInterface.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/components/Editor/EditorInterface.js @@ -209,6 +209,7 @@ class EditorInterface extends Component { fieldsMetaData, fieldsErrors, onChange, + onFocus, showDelete, onDelete, onDeleteUnpublishedChanges, @@ -247,6 +248,7 @@ class EditorInterface extends Component { fieldsMetaData, fieldsErrors, onChange, + onFocus, onValidate, }; @@ -405,6 +407,7 @@ EditorInterface.propTypes = { fieldsMetaData: ImmutablePropTypes.map.isRequired, fieldsErrors: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, onValidate: PropTypes.func.isRequired, onPersist: PropTypes.func.isRequired, showDelete: PropTypes.bool.isRequired, diff --git a/netlify-cms@2/packages/netlify-cms-core/src/reducers/entryDraft.js b/netlify-cms@2/packages/netlify-cms-core/src/reducers/entryDraft.js index c82b1ab..2d287df 100644 --- a/netlify-cms@2/packages/netlify-cms-core/src/reducers/entryDraft.js +++ b/netlify-cms@2/packages/netlify-cms-core/src/reducers/entryDraft.js @@ -8,6 +8,7 @@ import { DRAFT_CREATE_EMPTY, DRAFT_DISCARD, DRAFT_CHANGE_FIELD, + DRAFT_FOCUS_FIELD, DRAFT_VALIDATION_ERRORS, DRAFT_CLEAR_ERRORS, DRAFT_LOCAL_BACKUP_RETRIEVED, @@ -117,7 +118,29 @@ function entryDraftReducer(state = Map(), action) { state.set( 'hasChanged', !entries.some(e => newData.equals(e.get(...dataPath))) || - !entries.some(e => newMeta.equals(e.get('meta'))), + !entries.some(e => newMeta.equals(e.get('meta'))), + ); + }); + } + case DRAFT_FOCUS_FIELD: { + return state.withMutations(state => { + + const { field, value, metadata, entries, i18n } = action.payload; + const name = field.get('name'); + window.storefrontCmsFocusField = { + name: field.get('name'), + label: field.get('label'), + } + const dataPath = (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data']; + state.setIn(['entry', 'meta', name], value); + + state.mergeDeepIn(['fieldsMetaData'], fromJS(metadata)); + const newData = state.getIn(['entry', ...dataPath]); + const newMeta = state.getIn(['entry', 'meta']); + state.set( + 'hasFocused', + !entries.some(e => newData.equals(e.get(...dataPath))) || + !entries.some(e => newMeta.equals(e.get('meta'))), ); }); } diff --git a/netlify-cms@2/packages/netlify-cms-widget-code/src/CodeControl.js b/netlify-cms@2/packages/netlify-cms-widget-code/src/CodeControl.js index ad5b0d2..630cde2 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-code/src/CodeControl.js +++ b/netlify-cms@2/packages/netlify-cms-widget-code/src/CodeControl.js @@ -64,6 +64,7 @@ export default class CodeControl extends React.Component { static propTypes = { field: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, value: PropTypes.node, forID: PropTypes.string.isRequired, classNameWrapper: PropTypes.string.isRequired, diff --git a/netlify-cms@2/packages/netlify-cms-widget-code/src/CodePreview.js b/netlify-cms@2/packages/netlify-cms-widget-code/src/CodePreview.js index 46da098..23e19fc 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-code/src/CodePreview.js +++ b/netlify-cms@2/packages/netlify-cms-widget-code/src/CodePreview.js @@ -16,7 +16,12 @@ function toValue(value, field) { function CodePreview(props) { return ( - +
         {toValue(props.value, props.field)}
       
diff --git a/netlify-cms@2/packages/netlify-cms-widget-code/src/SettingsPane.js b/netlify-cms@2/packages/netlify-cms-widget-code/src/SettingsPane.js index c683afd..95f5da6 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-code/src/SettingsPane.js +++ b/netlify-cms@2/packages/netlify-cms-widget-code/src/SettingsPane.js @@ -39,7 +39,7 @@ const SettingsSectionTitle = styled.h3` } `; -function SettingsSelect({ value, options, onChange, forID, type, autoFocus }) { +function SettingsSelect({ value, options, onChange, forID, type, autoFocus, onFocus }) { return ( ); diff --git a/netlify-cms@2/packages/netlify-cms-widget-string/src/StringPreview.js b/netlify-cms@2/packages/netlify-cms-widget-string/src/StringPreview.js index 4e591b0..bb64617 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-string/src/StringPreview.js +++ b/netlify-cms@2/packages/netlify-cms-widget-string/src/StringPreview.js @@ -2,8 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { WidgetPreviewContainer } from 'netlify-cms-ui-default'; -function StringPreview({ value }) { - return {value}; +function StringPreview({ value, field }) { + return {value}; } StringPreview.propTypes = { diff --git a/netlify-cms@2/packages/netlify-cms-widget-text/src/TextControl.js b/netlify-cms@2/packages/netlify-cms-widget-text/src/TextControl.js index 26cbe44..6f0beba 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-text/src/TextControl.js +++ b/netlify-cms@2/packages/netlify-cms-widget-text/src/TextControl.js @@ -5,6 +5,7 @@ import Textarea from 'react-textarea-autosize'; export default class TextControl extends React.Component { static propTypes = { onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, forID: PropTypes.string, value: PropTypes.node, classNameWrapper: PropTypes.string.isRequired, @@ -28,7 +29,7 @@ export default class TextControl extends React.Component { } render() { - const { forID, value, onChange, classNameWrapper, setActiveStyle, setInactiveStyle } = + const { forID, value, onChange, classNameWrapper, setActiveStyle, setInactiveStyle, onFocus } = this.props; return ( @@ -36,7 +37,7 @@ export default class TextControl extends React.Component { id={forID} value={value || ''} className={classNameWrapper} - onFocus={setActiveStyle} + onFocus={e => { onFocus(e.target.value); this.props.setActiveStyle() }} onBlur={setInactiveStyle} minRows={5} css={{ fontFamily: 'inherit' }} diff --git a/netlify-cms@2/packages/netlify-cms-widget-text/src/TextPreview.js b/netlify-cms@2/packages/netlify-cms-widget-text/src/TextPreview.js index ec5fb8f..deb06e4 100644 --- a/netlify-cms@2/packages/netlify-cms-widget-text/src/TextPreview.js +++ b/netlify-cms@2/packages/netlify-cms-widget-text/src/TextPreview.js @@ -2,8 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { WidgetPreviewContainer } from 'netlify-cms-ui-default'; -function TextPreview({ value }) { - return {value}; +function TextPreview({ value, field }) { + return {value}; } TextPreview.propTypes = {