Fix scrolling sync, tweak styles
This commit is contained in:
parent
e33f3dec02
commit
e2b11ba3ff
30
packages/netlify-cms-core/src/actions/scroll.ts
Normal file
30
packages/netlify-cms-core/src/actions/scroll.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { State } from '../types/redux';
|
||||
|
||||
export const SCROLL_SYNC_ENABLED = 'cms.scroll-sync-enabled';
|
||||
|
||||
export const TOGGLE_SCROLL = 'TOGGLE_SCROLL';
|
||||
export const SET_SCROLL = 'SET_SCROLL';
|
||||
|
||||
export function togglingScroll() {
|
||||
return {
|
||||
type: TOGGLE_SCROLL,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function loadScroll() {
|
||||
return {
|
||||
type: SET_SCROLL,
|
||||
payload: localStorage.getItem(SCROLL_SYNC_ENABLED) !== 'false',
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function toggleScroll() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return async (dispatch: ThunkDispatch<State, undefined, AnyAction>, _getState: () => State) => {
|
||||
return dispatch(togglingScroll());
|
||||
};
|
||||
}
|
||||
|
||||
export type ScrollAction = ReturnType<typeof togglingScroll | typeof loadScroll>;
|
@ -140,7 +140,7 @@ export function searchEntries(searchTerm: string, searchCollections: string[], p
|
||||
try {
|
||||
const response: SearchResponse = await searchPromise;
|
||||
return dispatch(searchSuccess(response.entries, response.pagination));
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return dispatch(searchFailure(error));
|
||||
}
|
||||
};
|
||||
@ -177,7 +177,7 @@ export function query(
|
||||
try {
|
||||
const response: QueryResponse = await queryPromise;
|
||||
return dispatch(querySuccess(namespace, response.hits));
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return dispatch(queryFailure(error));
|
||||
}
|
||||
};
|
||||
|
@ -87,7 +87,7 @@ export function checkBackendStatus() {
|
||||
}
|
||||
|
||||
dispatch(statusSuccess(status));
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
dispatch(statusFailure(error));
|
||||
}
|
||||
};
|
||||
|
@ -36,12 +36,12 @@ TopBarProgress.config({
|
||||
|
||||
const AppRoot = styled.div`
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
min-height: 100vh;
|
||||
`;
|
||||
|
||||
const AppWrapper = styled.div`
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
min-height: 100vh;
|
||||
`;
|
||||
|
||||
const AppMainContainer = styled.div`
|
||||
@ -96,6 +96,7 @@ class App extends React.Component {
|
||||
useMediaLibrary: PropTypes.bool,
|
||||
openMediaLibrary: PropTypes.func.isRequired,
|
||||
showMediaButton: PropTypes.bool,
|
||||
scrollSyncEnabled: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -164,6 +165,7 @@ class App extends React.Component {
|
||||
openMediaLibrary,
|
||||
t,
|
||||
showMediaButton,
|
||||
scrollSyncEnabled,
|
||||
} = this.props;
|
||||
|
||||
if (config === null) {
|
||||
@ -186,7 +188,7 @@ class App extends React.Component {
|
||||
const hasWorkflow = publishMode === EDITORIAL_WORKFLOW;
|
||||
|
||||
return (
|
||||
<ScrollSync>
|
||||
<ScrollSync enabled={scrollSyncEnabled}>
|
||||
<AppRoot id="cms-root">
|
||||
<AppWrapper className="cms-wrapper">
|
||||
<Notifs CustomComponent={Toast} />
|
||||
@ -269,12 +271,13 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { auth, config, collections, globalUI, mediaLibrary } = state;
|
||||
const { auth, config, collections, globalUI, mediaLibrary, scroll } = state;
|
||||
const user = auth.user;
|
||||
const isFetching = globalUI.isFetching;
|
||||
const publishMode = config.publish_mode;
|
||||
const useMediaLibrary = !mediaLibrary.get('externalLibrary');
|
||||
const showMediaButton = mediaLibrary.get('showMediaButton');
|
||||
const scrollSyncEnabled = scroll.isScrolling;
|
||||
return {
|
||||
auth,
|
||||
config,
|
||||
@ -284,6 +287,7 @@ function mapStateToProps(state) {
|
||||
publishMode,
|
||||
showMediaButton,
|
||||
useMediaLibrary,
|
||||
scrollSyncEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,38 +1,39 @@
|
||||
import { debounce } from 'lodash';
|
||||
import { Loader } from 'netlify-cms-ui-default';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader } from 'netlify-cms-ui-default';
|
||||
import { translate } from 'react-polyglot';
|
||||
import { debounce } from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { history, navigateToCollection, navigateToNewEntry } from '../../routing/history';
|
||||
import { logoutUser } from '../../actions/auth';
|
||||
import { loadDeployPreview } from '../../actions/deploys';
|
||||
import {
|
||||
loadEntry,
|
||||
loadEntries,
|
||||
createDraftDuplicateFromEntry,
|
||||
createEmptyDraft,
|
||||
discardDraft,
|
||||
changeDraftField,
|
||||
changeDraftFieldValidation,
|
||||
persistEntry,
|
||||
deleteEntry,
|
||||
persistLocalBackup,
|
||||
loadLocalBackup,
|
||||
retrieveLocalBackup,
|
||||
deleteLocalBackup,
|
||||
} from '../../actions/entries';
|
||||
import {
|
||||
updateUnpublishedEntryStatus,
|
||||
deleteUnpublishedEntry,
|
||||
publishUnpublishedEntry,
|
||||
unpublishPublishedEntry,
|
||||
deleteUnpublishedEntry,
|
||||
updateUnpublishedEntryStatus,
|
||||
} from '../../actions/editorialWorkflow';
|
||||
import { loadDeployPreview } from '../../actions/deploys';
|
||||
import { selectEntry, selectUnpublishedEntry, selectDeployPreview } from '../../reducers';
|
||||
import {
|
||||
changeDraftField,
|
||||
changeDraftFieldValidation,
|
||||
createDraftDuplicateFromEntry,
|
||||
createEmptyDraft,
|
||||
deleteEntry,
|
||||
deleteLocalBackup,
|
||||
discardDraft,
|
||||
loadEntries,
|
||||
loadEntry,
|
||||
loadLocalBackup,
|
||||
persistEntry,
|
||||
persistLocalBackup,
|
||||
retrieveLocalBackup,
|
||||
} from '../../actions/entries';
|
||||
import { loadScroll, toggleScroll } from '../../actions/scroll';
|
||||
import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes';
|
||||
import { selectDeployPreview, selectEntry, selectUnpublishedEntry } from '../../reducers';
|
||||
import { selectFields } from '../../reducers/collections';
|
||||
import { status, EDITORIAL_WORKFLOW } from '../../constants/publishModes';
|
||||
import { history, navigateToCollection, navigateToNewEntry } from '../../routing/history';
|
||||
import EditorInterface from './EditorInterface';
|
||||
import withWorkflow from './withWorkflow';
|
||||
|
||||
@ -79,6 +80,9 @@ export class Editor extends React.Component {
|
||||
loadLocalBackup: PropTypes.func,
|
||||
persistLocalBackup: PropTypes.func.isRequired,
|
||||
deleteLocalBackup: PropTypes.func,
|
||||
toggleScroll: PropTypes.func.isRequired,
|
||||
scrollSyncEnabled: PropTypes.bool.isRequired,
|
||||
loadScroll: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -356,6 +360,9 @@ export class Editor extends React.Component {
|
||||
slug,
|
||||
t,
|
||||
editorBackLink,
|
||||
toggleScroll,
|
||||
scrollSyncEnabled,
|
||||
loadScroll,
|
||||
} = this.props;
|
||||
|
||||
const isPublished = !newEntry && !unpublishedEntry;
|
||||
@ -405,6 +412,9 @@ export class Editor extends React.Component {
|
||||
deployPreview={deployPreview}
|
||||
loadDeployPreview={opts => loadDeployPreview(collection, slug, entry, isPublished, opts)}
|
||||
editorBackLink={editorBackLink}
|
||||
toggleScroll={toggleScroll}
|
||||
scrollSyncEnabled={scrollSyncEnabled}
|
||||
loadScroll={loadScroll}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
@ -412,7 +422,7 @@ export class Editor extends React.Component {
|
||||
}
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const { collections, entryDraft, auth, config, entries, globalUI } = state;
|
||||
const { collections, entryDraft, auth, config, entries, globalUI, scroll } = state;
|
||||
const slug = ownProps.match.params[0];
|
||||
const collection = collections.get(ownProps.match.params.name);
|
||||
const collectionName = collection.get('name');
|
||||
@ -444,6 +454,8 @@ function mapStateToProps(state, ownProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const scrollSyncEnabled = scroll.isScrolling;
|
||||
|
||||
return {
|
||||
collection,
|
||||
collections,
|
||||
@ -466,6 +478,7 @@ function mapStateToProps(state, ownProps) {
|
||||
publishedEntry,
|
||||
unPublishedEntry,
|
||||
editorBackLink,
|
||||
scrollSyncEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
@ -489,6 +502,8 @@ const mapDispatchToProps = {
|
||||
unpublishPublishedEntry,
|
||||
deleteUnpublishedEntry,
|
||||
logoutUser,
|
||||
toggleScroll,
|
||||
loadScroll,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withWorkflow(translate()(Editor)));
|
||||
|
@ -21,7 +21,6 @@ import { FILES } from '../../constants/collectionTypes';
|
||||
import { getFileFromSlug } from '../../reducers/collections';
|
||||
|
||||
const PREVIEW_VISIBLE = 'cms.preview-visible';
|
||||
const SCROLL_SYNC_ENABLED = 'cms.scroll-sync-enabled';
|
||||
const I18N_VISIBLE = 'cms.i18n-visible';
|
||||
|
||||
const styles = {
|
||||
@ -103,6 +102,7 @@ const Editor = styled.div`
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background-color: ${colorsRaw.white};
|
||||
`;
|
||||
|
||||
const PreviewPaneContainer = styled.div`
|
||||
@ -152,10 +152,15 @@ function isPreviewEnabled(collection, entry) {
|
||||
class EditorInterface extends Component {
|
||||
state = {
|
||||
previewVisible: localStorage.getItem(PREVIEW_VISIBLE) !== 'false',
|
||||
scrollSyncEnabled: localStorage.getItem(SCROLL_SYNC_ENABLED) !== 'false',
|
||||
i18nVisible: localStorage.getItem(I18N_VISIBLE) !== 'false',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.props.loadScroll();
|
||||
}
|
||||
|
||||
handleOnPersist = async (opts = {}) => {
|
||||
const { createNew = false, duplicate = false } = opts;
|
||||
await this.controlPaneRef.switchToDefaultLocale();
|
||||
@ -177,9 +182,8 @@ class EditorInterface extends Component {
|
||||
};
|
||||
|
||||
handleToggleScrollSync = () => {
|
||||
const newScrollSyncEnabled = !this.state.scrollSyncEnabled;
|
||||
this.setState({ scrollSyncEnabled: newScrollSyncEnabled });
|
||||
localStorage.setItem(SCROLL_SYNC_ENABLED, newScrollSyncEnabled);
|
||||
const { toggleScroll } = this.props;
|
||||
toggleScroll();
|
||||
};
|
||||
|
||||
handleToggleI18n = () => {
|
||||
@ -222,11 +226,10 @@ class EditorInterface extends Component {
|
||||
deployPreview,
|
||||
draftKey,
|
||||
editorBackLink,
|
||||
scrollSyncEnabled,
|
||||
t,
|
||||
} = this.props;
|
||||
|
||||
const { scrollSyncEnabled } = this.state;
|
||||
|
||||
const previewEnabled = isPreviewEnabled(collection, entry);
|
||||
|
||||
const collectionI18nEnabled = hasI18n(collection);
|
||||
@ -255,7 +258,7 @@ class EditorInterface extends Component {
|
||||
);
|
||||
|
||||
const editor2 = (
|
||||
<ControlPaneContainer overFlow={!this.state.scrollSyncEnabled}>
|
||||
<ControlPaneContainer overFlow={!this.props.scrollSyncEnabled}>
|
||||
<EditorControlPane {...editorProps} locale={locales?.[1]} t={t} />
|
||||
</ControlPaneContainer>
|
||||
);
|
||||
@ -265,27 +268,25 @@ class EditorInterface extends Component {
|
||||
: entry;
|
||||
|
||||
const editorWithPreview = (
|
||||
<ScrollSync enabled={this.state.scrollSyncEnabled}>
|
||||
<>
|
||||
<ReactSplitPaneGlobalStyles />
|
||||
<StyledSplitPane>
|
||||
<ScrollSyncPane>{editor}</ScrollSyncPane>
|
||||
<PreviewPaneContainer>
|
||||
<EditorPreviewPane
|
||||
collection={collection}
|
||||
entry={previewEntry}
|
||||
fields={fields}
|
||||
fieldsMetaData={fieldsMetaData}
|
||||
locale={leftPanelLocale}
|
||||
/>
|
||||
</PreviewPaneContainer>
|
||||
</StyledSplitPane>
|
||||
</>
|
||||
</ScrollSync>
|
||||
<>
|
||||
<ReactSplitPaneGlobalStyles />
|
||||
<StyledSplitPane>
|
||||
<ScrollSyncPane>{editor}</ScrollSyncPane>
|
||||
<PreviewPaneContainer>
|
||||
<EditorPreviewPane
|
||||
collection={collection}
|
||||
entry={previewEntry}
|
||||
fields={fields}
|
||||
fieldsMetaData={fieldsMetaData}
|
||||
locale={leftPanelLocale}
|
||||
/>
|
||||
</PreviewPaneContainer>
|
||||
</StyledSplitPane>
|
||||
</>
|
||||
);
|
||||
|
||||
const editorWithEditor = (
|
||||
<ScrollSync enabled={this.state.scrollSyncEnabled}>
|
||||
<ScrollSync enabled={this.props.scrollSyncEnabled}>
|
||||
<div>
|
||||
<StyledSplitPane>
|
||||
<ScrollSyncPane>{editor}</ScrollSyncPane>
|
||||
@ -406,6 +407,9 @@ EditorInterface.propTypes = {
|
||||
deployPreview: PropTypes.object,
|
||||
loadDeployPreview: PropTypes.func.isRequired,
|
||||
draftKey: PropTypes.string.isRequired,
|
||||
toggleScroll: PropTypes.func.isRequired,
|
||||
scrollSyncEnabled: PropTypes.bool.isRequired,
|
||||
loadScroll: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -174,7 +174,7 @@ export class ErrorBoundary extends React.Component {
|
||||
return this.props.children;
|
||||
}
|
||||
return (
|
||||
<ErrorBoundaryContainer>
|
||||
<ErrorBoundaryContainer key="error-boundary-container">
|
||||
<h1>{t('ui.errorBoundary.title')}</h1>
|
||||
<p>
|
||||
<span>{t('ui.errorBoundary.details')}</span>
|
||||
@ -190,17 +190,15 @@ export class ErrorBoundary extends React.Component {
|
||||
<p>
|
||||
{t('ui.errorBoundary.privacyWarning')
|
||||
.split('\n')
|
||||
.map((item, index) => (
|
||||
<>
|
||||
<PrivacyWarning key={index}>{item}</PrivacyWarning>
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
.map((item, index) => [
|
||||
<PrivacyWarning key={`private-warning-${index}`}>{item}</PrivacyWarning>,
|
||||
<br key={`break-${index}`} />,
|
||||
])}
|
||||
</p>
|
||||
<hr />
|
||||
<h2>{t('ui.errorBoundary.detailsHeading')}</h2>
|
||||
<p>{errorMessage}</p>
|
||||
{backup && showBackup && <RecoveredEntry entry={backup} t={t} />}
|
||||
{backup && showBackup && <RecoveredEntry key="backup" entry={backup} t={t} />}
|
||||
</ErrorBoundaryContainer>
|
||||
);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import mediaLibrary from './mediaLibrary';
|
||||
import deploys, * as fromDeploys from './deploys';
|
||||
import globalUI from './globalUI';
|
||||
import status from './status';
|
||||
import scroll from './scroll';
|
||||
|
||||
import type { Status } from '../constants/publishModes';
|
||||
import type { State, Collection } from '../types/redux';
|
||||
@ -33,6 +34,7 @@ const reducers = {
|
||||
deploys,
|
||||
globalUI,
|
||||
status,
|
||||
scroll,
|
||||
};
|
||||
|
||||
export default reducers;
|
||||
|
28
packages/netlify-cms-core/src/reducers/scroll.ts
Normal file
28
packages/netlify-cms-core/src/reducers/scroll.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { SCROLL_SYNC_ENABLED, SET_SCROLL, TOGGLE_SCROLL } from '../actions/scroll';
|
||||
|
||||
import type { ScrollAction } from '../actions/scroll';
|
||||
|
||||
export type ScrollState = {
|
||||
isScrolling: boolean;
|
||||
};
|
||||
|
||||
const defaultState: ScrollState = {
|
||||
isScrolling: true,
|
||||
};
|
||||
|
||||
const status = produce((state: ScrollState, action: ScrollAction) => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_SCROLL:
|
||||
state.isScrolling = !state.isScrolling;
|
||||
localStorage.setItem(SCROLL_SYNC_ENABLED, `${state.isScrolling}`);
|
||||
break;
|
||||
case SET_SCROLL:
|
||||
state.isScrolling = action.payload;
|
||||
localStorage.setItem(SCROLL_SYNC_ENABLED, `${state.isScrolling}`);
|
||||
break;
|
||||
}
|
||||
}, defaultState);
|
||||
|
||||
export default status;
|
@ -1,15 +1,16 @@
|
||||
import type { List, Map, OrderedMap, Set } from 'immutable';
|
||||
import type { Action } from 'redux';
|
||||
import type { StaticallyTypedRecord } from './immutable';
|
||||
import type { Map, List, OrderedMap, Set } from 'immutable';
|
||||
import type { FILES, FOLDER } from '../constants/collectionTypes';
|
||||
import type { MediaFile as BackendMediaFile } from '../backend';
|
||||
import type { Auth } from '../reducers/auth';
|
||||
import type { Status } from '../reducers/status';
|
||||
import type { Medias } from '../reducers/medias';
|
||||
import type { Deploys } from '../reducers/deploys';
|
||||
import type { Search } from '../reducers/search';
|
||||
import type { GlobalUI } from '../reducers/globalUI';
|
||||
import type { FILES, FOLDER } from '../constants/collectionTypes';
|
||||
import type { formatExtensions } from '../formats/formats';
|
||||
import type { Auth } from '../reducers/auth';
|
||||
import type { Deploys } from '../reducers/deploys';
|
||||
import type { GlobalUI } from '../reducers/globalUI';
|
||||
import type { Medias } from '../reducers/medias';
|
||||
import type { ScrollState } from '../reducers/scroll';
|
||||
import type { Search } from '../reducers/search';
|
||||
import type { Status } from '../reducers/status';
|
||||
import type { StaticallyTypedRecord } from './immutable';
|
||||
|
||||
export type CmsBackendType =
|
||||
| 'azure'
|
||||
@ -699,6 +700,7 @@ export interface State {
|
||||
search: Search;
|
||||
notifs: { message: { key: string }; kind: string; id: number }[];
|
||||
status: Status;
|
||||
scroll: ScrollState;
|
||||
}
|
||||
|
||||
export interface Integration {
|
||||
|
@ -17,8 +17,8 @@ const StyledDropdownButton = styled(DropdownButton)`
|
||||
${buttons.button};
|
||||
${buttons.default};
|
||||
display: block;
|
||||
padding-left: 20px;
|
||||
padding-right: 40px;
|
||||
padding-left: 20px!important;
|
||||
padding-right: 40px!important;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
|
@ -562,6 +562,11 @@ function GlobalStyles() {
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.ol-viewport {
|
||||
position: absolute!important;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user