Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Horizon] Update editing functions for Horizon compatibility #712

Merged
merged 10 commits into from
Jul 13, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ The Next.js Fast Refresh feature requires special handling when used within the

> See the [Next.js documentation](https://nextjs.org/docs/basic-features/fast-refresh) to learn more about Fast Refresh.

The JSS Next.js SDK provides a `handleExperienceEditorFastRefresh` utility function to solve this problem. You should call the function within a React `useEffect` hook on any Next.js page that will be editable.
The JSS Next.js SDK provides a `handleEditorFastRefresh` utility function to solve this problem. You should call the function within a React `useEffect` hook on any Next.js page that will be editable.

```typescript
import { handleExperienceEditorFastRefresh } from '@sitecore-jss/sitecore-jss-nextjs';
import { handleEditorFastRefresh } from '@sitecore-jss/sitecore-jss-nextjs';

const MyPage = ({ notFound, layoutData, componentProps }: SitecorePageProps): JSX.Element => {
useEffect(() => {
// Since Experience Editor does not support Fast Refresh need to refresh EE chromes after Fast Refresh finished
handleExperienceEditorFastRefresh();
// Since Sitecore editors do not support Fast Refresh, need to refresh EE chromes after Fast Refresh finished
handleEditorFastRefresh();
}, []);
...
};
Expand Down
2 changes: 2 additions & 0 deletions packages/sitecore-jss-angular/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export { JssModule } from './lib.module';
export {
dataApi,
mediaApi,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
LayoutServiceData,
Expand Down
5 changes: 4 additions & 1 deletion packages/sitecore-jss-nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export {
GraphQLLayoutServiceConfig,
RestLayoutService,
RestLayoutServiceConfig,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
PlaceholdersData,
Expand Down Expand Up @@ -69,7 +71,7 @@ export {
useComponentProps,
} from './components/ComponentPropsContext';

export { handleExperienceEditorFastRefresh } from './utils';
export { handleEditorFastRefresh, handleExperienceEditorFastRefresh } from './utils';

export { EditingData, EditingPreviewData, isEditingData } from './sharedTypes/editing-data';
export {
Expand Down Expand Up @@ -98,6 +100,7 @@ export {
SitecoreContextReactContext,
withSitecoreContext,
useSitecoreContext,
withEditorChromes,
withExperienceEditorChromes,
withPlaceholder,
} from '@sitecore-jss/sitecore-jss-react';
32 changes: 20 additions & 12 deletions packages/sitecore-jss-nextjs/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import chalk from 'chalk';
import { isExperienceEditorActive, resetExperienceEditorChromes } from '@sitecore-jss/sitecore-jss';
import { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss';

export const getPublicUrl = (): string => {
let url = process.env.PUBLIC_URL;
Expand All @@ -22,45 +22,53 @@ export const getPublicUrl = (): string => {
};

/**
* Since Experience Editor does not support Fast Refresh:
* Since Sitecore editors do not support Fast Refresh:
* 1. Subscribe on events provided by webpack.
* 2. Reset experience editor chromes when build is finished
* 2. Reset editor chromes when build is finished
* @param {boolean} [forceReload] force page reload instead of reset chromes
* @default forceReload false
*/
export const handleExperienceEditorFastRefresh = (forceReload = false): void => {
if (process.env.NODE_ENV !== 'development' || !isExperienceEditorActive()) {
// Only run if development mode and Experience Editor is active
export const handleEditorFastRefresh = (forceReload = false): void => {
if (process.env.NODE_ENV !== 'development' || !isEditorActive()) {
// Only run if development mode and editor is active
return;
}
const eventSource = new window.EventSource(`${getPublicUrl()}/_next/webpack-hmr`);

window.addEventListener('beforeunload', () => eventSource.close());

eventSource.onopen = () => console.log('[Experience Editor Fast Refresh Listener] Online');
eventSource.onopen = () => console.log('[Sitecore Editor Fast Refresh Listener] Online');

eventSource.onmessage = (event) => {
if (event.data.indexOf('{') === -1) return; // heartbeat

const payload = JSON.parse(event.data);

console.debug(
`[Experience Editor Fast Refresh Listener] Saw event: ${JSON.stringify(payload)}`
);
console.debug(`[Sitecore Editor Fast Refresh Listener] Saw event: ${JSON.stringify(payload)}`);

if (payload.action !== 'built') return;

if (forceReload) return window.location.reload();

setTimeout(() => {
console.log(
'[Experience Editor HMR Listener] Experience Editor does not support Fast Refresh, reloading chromes...'
'[Sitecore Editor HMR Listener] Sitecore editor does not support Fast Refresh, reloading chromes...'
);
resetExperienceEditorChromes();
resetEditorChromes();
}, 500);
};
};

/**
* Since Sitecore editors do not support Fast Refresh:
* 1. Subscribe on events provided by webpack.
* 2. Reset editor chromes when build is finished
* @deprecated Will be removed in a future release. Please use handleEditorFastRefresh instead.
* @param {boolean} [forceReload] force page reload instead of reset chromes
* @default forceReload false
*/
export const handleExperienceEditorFastRefresh = handleEditorFastRefresh;

export const getJssEditingSecret = (): string => {
const secret = process.env.JSS_EDITING_SECRET;
if (!secret || secret.length === 0) {
Expand Down
2 changes: 2 additions & 0 deletions packages/sitecore-jss-react-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export {
dataApi,
mediaApi,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
LayoutServiceData,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { ComponentType } from 'react';
import { resetExperienceEditorChromes } from '../';
import { resetEditorChromes } from '..';

export const withExperienceEditorChromes = (
export const withEditorChromes = (
WrappedComponent: React.ComponentClass<unknown> | React.SFC<unknown>
) => {
class Enhancer extends React.Component<unknown> {
displayName: string =
(WrappedComponent as ComponentType).displayName || WrappedComponent.name || 'Component';

componentDidUpdate() {
resetExperienceEditorChromes();
resetEditorChromes();
}

render() {
Expand All @@ -19,3 +19,8 @@ export const withExperienceEditorChromes = (

return Enhancer as React.ComponentClass;
};

/**
* @deprecated Will be removed in a future release. Please use withEditorChromes instead.
*/
export const withExperienceEditorChromes = withEditorChromes;
4 changes: 3 additions & 1 deletion packages/sitecore-jss-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export {
dataApi,
mediaApi,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
DictionaryPhrases,
Expand Down Expand Up @@ -34,5 +36,5 @@ export {
SitecoreContextReactContext,
} from './components/SitecoreContext';
export { withSitecoreContext, useSitecoreContext } from './enhancers/withSitecoreContext';
export { withExperienceEditorChromes } from './enhancers/withExperienceEditorChromes';
export { withEditorChromes, withExperienceEditorChromes } from './enhancers/withEditorChromes';
export { withPlaceholder } from './enhancers/withPlaceholder';
2 changes: 2 additions & 0 deletions packages/sitecore-jss-vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export {
dataApi,
mediaApi,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
LayoutServiceData,
Expand Down
68 changes: 68 additions & 0 deletions packages/sitecore-jss/src/utils/editing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import isServer from './is-server';

/**
* Static utility class for Sitecore Experience Editor
*/
export class ExperienceEditor {
static isActive(): boolean {
// eslint-disable-next-line
const sc = window && (window as any).Sitecore;
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
return Boolean(sc && sc.PageModes && sc.PageModes.ChromeManager);
}
static resetChromes(): void {
// eslint-disable-next-line
window && (window as any).Sitecore.PageModes.ChromeManager.resetChromes();
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Static utility class for Sitecore Horizon Editor
*/
export class HorizonEditor {
static isActive(): boolean {
// Horizon will add "sc_horizon=editor" query string parameter for the editor and "sc_horizon=simulator" for the preview
return window && window.location.search.indexOf('sc_horizon=editor') > -1;
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
}
static resetChromes(): void {
// No way to reset chromes in Horizon, simply reload the canvas (iframe) instead
window && window.location.reload();
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Determines whether the current execution context is within a Sitecore editor
* @returns true if executing within a Sitecore editor
*/
export const isEditorActive = (): boolean => {
if (isServer()) {
return false;
}
return ExperienceEditor.isActive() || HorizonEditor.isActive();
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Resets Sitecore editor "chromes"
*/
export const resetEditorChromes = (): void => {
if (isServer()) {
return;
}
if (ExperienceEditor.isActive()) {
ExperienceEditor.resetChromes();
} else if (HorizonEditor.isActive()) {
HorizonEditor.resetChromes();
}
};

/**
* Determines whether the current execution context is within the Sitecore Experience Editor
* @deprecated Will be removed in a future release. Please use isEditorActive instead.
* @returns true if executing within the Sitecore Experience Editor
*/
export const isExperienceEditorActive = isEditorActive;
ambrauer marked this conversation as resolved.
Show resolved Hide resolved

/**
* Resets Sitecore Experience Editor "chromes"
* @deprecated Will be removed in a future release. Please use resetEditorChromes instead.
*/
export const resetExperienceEditorChromes = resetEditorChromes;
ambrauer marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 0 additions & 17 deletions packages/sitecore-jss/src/utils/experience-editor.ts

This file was deleted.

9 changes: 8 additions & 1 deletion packages/sitecore-jss/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import isServer from './is-server';
import resolveUrl from './resolve-url';
export { isServer, resolveUrl };
export { isExperienceEditorActive, resetExperienceEditorChromes } from './experience-editor';
export {
ExperienceEditor,
HorizonEditor,
isEditorActive,
resetEditorChromes,
isExperienceEditorActive,
resetExperienceEditorChromes,
} from './editing';
73 changes: 66 additions & 7 deletions packages/sitecore-jss/src/utils/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-expressions */
import { expect } from 'chai';
import { isExperienceEditorActive, isServer, resolveUrl } from '.';
import { expect, spy } from 'chai';
import { isEditorActive, resetEditorChromes, isServer, resolveUrl } from '.';

// must make TypeScript happy with `global` variable modification
interface CustomWindow {
Expand Down Expand Up @@ -31,18 +31,77 @@ describe('utils', () => {
});
});

describe('isExperienceEditorActive', () => {
describe('isEditorActive', () => {
it('should return false when invoked on server', () => {
expect(isEditorActive()).to.be.false;
});

it('should return true when EE is active', () => {
global.window = {
document: {},
location: { search: '' },
Sitecore: { PageModes: { ChromeManager: {} } },
};
expect(isExperienceEditorActive()).to.be.true;
expect(isEditorActive()).to.be.true;
});

it('should return true when Horizon is active', () => {
global.window = {
document: {},
location: { search: '?sc_horizon=editor' },
Sitecore: null,
};
expect(isEditorActive()).to.be.true;
});

it('should return false when EE and Horizon are not active', () => {
global.window = {
document: {},
location: { search: '' },
Sitecore: null,
};
expect(isEditorActive()).to.be.false;
});

after(() => {
global.window = undefined;
});
});

describe('resetEditorChromes', () => {
it('should not throw when invoked on server', () => {
expect(resetEditorChromes()).to.not.throw;
});

it('should resetChromes on ChromeManager when EE is active', () => {
const resetChromes = spy();
global.window = {
document: {},
location: { search: '' },
Sitecore: { PageModes: { ChromeManager: { resetChromes } } },
};
resetEditorChromes();
expect(resetChromes).to.have.been.called.once;
});

it('should return false when EE is not active', () => {
global.window = { document: {}, Sitecore: null };
expect(isExperienceEditorActive()).to.be.false;
it('should perform reload when Horizon is active', () => {
const reload = spy();
global.window = {
document: {},
location: { search: '?sc_horizon=editor', reload },
Sitecore: null,
};
resetEditorChromes();
expect(reload).to.have.been.called.once;
});

it('should not throw when EE and Horizon are not active', () => {
global.window = {
document: {},
location: { search: '' },
Sitecore: null,
};
expect(resetEditorChromes()).to.not.throw;
});

after(() => {
Expand Down
Loading