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

[Editing integration] Merge editing integration feature branch #1802

Merged
merged 8 commits into from
May 23, 2024
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ Our versioning strategy is as follows:

* `[sitecore-jss-nextjs]` Enforce CORS policy that matches Sitecore Pages domains for editing middleware API endpoints ([#1798](https://github.com/Sitecore/jss/pull/1798)[#1801](https://github.com/Sitecore/jss/pull/1801))

### 🛠 Breaking Change

* Editing Integration Support: ([#1776](https://github.com/Sitecore/jss/pull/1776))([#1792](https://github.com/Sitecore/jss/pull/1792))([#1773](https://github.com/Sitecore/jss/pull/1773))([#1797](https://github.com/Sitecore/jss/pull/1797))([#1800](https://github.com/Sitecore/jss/pull/1800))
* `[sitecore-jss-react]` Introduces `PlaceholderMetadata` component which supports the hydration of chromes on Pages by rendering the components and placeholders with required metadata.
* `[sitecore-jss]` Chromes are hydrated based on the basis of new `editMode` property derived from LayoutData, which is defined as an enum consisting of `metadata` and `chromes`.
* `ComponentConsumerProps` is removed. You might need to reuse _WithSitecoreContextProps_ type.
* `[sitecore-jss-react]` `[sitecore-jss-nextjs]` Introduce FieldMetadata component and functionality to render it when metadata field property is provided in the field's layout data. In such case the field component is wrapped with metadata markup to enable chromes hydration when editing in pages. Ability to render metadata has been added to the field rendering components for react and nextjs.
* `[sitecore-jss-react]` Introduced `EditingScripts` component to render clientScripts / clientData in editing.

## 22.0.0

### 🛠 Breaking Changes
Expand Down
18 changes: 18 additions & 0 deletions docs/upgrades/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# react

* With the simplification of Editing Support work we have added the following breaking changes to the `sitecore-jss-react` package. Please make the necessary updates.
- `ComponentConsumerProps` is removed. You might need to reuse _WithSitecoreContextProps_ type.

### headless-ssr-experience-edge
* Replace `scripts/generate-config.js` if you have not modified it. Otherwise:
* Add a `trim()` call to `config[prop]` and replace comma before a newline (`,`) with semicolon (`;`) in configText prop assignment so it would look like this:

Expand Down Expand Up @@ -40,3 +44,17 @@
```
configText += `config.${prop} = process.env.${constantCase(prop)} || '${config[prop]?.trim()}';\n`;
```

# nextjs-xmcloud

* Render a new `EditingScripts` component in your `Scripts.ts` file to support a new Editing Integration feature.
```
import { EditingScripts } from '@sitecore-jss/sitecore-jss-nextjs';
...
const Scripts = (): JSX.Element | null => {
<>
<EditingScripts />
...
</>
);
```
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const tabsComponentWithPlaceholderInjected = withPlaceholder({
// We need to know if experience editor is active, to disable the dynamic tab behavior for editing.
// Using the same technique as injecting the placeholder, we wrap the component again to inject the
// `sitecoreContext` prop.
const tabsWithPlaceholderAndSitecoreContext = withSitecoreContext()<StyleguideLayoutTabsProps>(
const tabsWithPlaceholderAndSitecoreContext = withSitecoreContext()(
tabsComponentWithPlaceholderInjected
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
LinkField,
Text,
useSitecoreContext,
EditMode,
} from '@sitecore-jss/sitecore-jss-nextjs';

interface Fields {
Image: ImageField;
Image: ImageField & { metadata?: { [key: string]: unknown } };
ImageCaption: Field<string>;
TargetUrl: LinkField;
}
Expand All @@ -29,22 +30,25 @@ const ImageDefault = (props: ImageProps): JSX.Element => (
);

export const Banner = (props: ImageProps): JSX.Element => {
const id = props.params.RenderingIdentifier;
const { sitecoreContext } = useSitecoreContext();
const isPageEditing = sitecoreContext.pageEditing;
const isMetadataMode = sitecoreContext?.editMode === EditMode.Metadata;
const classHeroBannerEmpty =
isPageEditing && props.fields?.Image?.value?.class === 'scEmptyImage'
? 'hero-banner-empty'
: '';
const backgroundStyle = (props?.fields?.Image?.value?.src && {
backgroundImage: `url('${props.fields.Image.value.src}')`,
}) as CSSProperties;
const modifyImageProps = {
...props.fields.Image,
editable: props?.fields?.Image?.editable
?.replace(`width="${props?.fields?.Image?.value?.width}"`, 'width="100%"')
.replace(`height="${props?.fields?.Image?.value?.height}"`, 'height="100%"'),
};
const id = props.params.RenderingIdentifier;
const modifyImageProps = !isMetadataMode
? {
...props.fields.Image,
editable: props?.fields?.Image?.editable
?.replace(`width="${props?.fields?.Image?.value?.width}"`, 'width="100%"')
.replace(`height="${props?.fields?.Image?.value?.height}"`, 'height="100%"'),
}
: { ...props.fields.Image };

return (
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import {
Link,
Text,
useSitecoreContext,
LinkField,
Text,
TextField,
useSitecoreContext,
} from '@sitecore-jss/sitecore-jss-nextjs';
import React from 'react';

interface Fields {
data: {
Expand All @@ -17,7 +17,8 @@ interface Fields {
field: {
jsonValue: {
value: string;
editable: string;
editable?: string;
metadata?: { [key: string]: unknown };
};
};
};
Expand All @@ -29,7 +30,8 @@ interface Fields {
field: {
jsonValue: {
value: string;
editable: string;
editable?: string;
metadata?: { [key: string]: unknown };
};
};
};
Expand Down Expand Up @@ -61,16 +63,11 @@ const ComponentContent = (props: ComponentContentProps) => {
export const Default = (props: TitleProps): JSX.Element => {
const datasource = props.fields?.data?.datasource || props.fields?.data?.contextItem;
const { sitecoreContext } = useSitecoreContext();

const text: TextField = {
value: datasource?.field?.jsonValue?.value,
editable: datasource?.field?.jsonValue?.editable,
};
const text: TextField = datasource?.field?.jsonValue;
const link: LinkField = {
value: {
href: datasource?.url?.path,
title: datasource?.field?.jsonValue?.value,
editable: true,
},
};
if (sitecoreContext.pageState !== 'normal') {
Expand All @@ -84,7 +81,7 @@ export const Default = (props: TitleProps): JSX.Element => {
return (
<ComponentContent styles={props.params.styles} id={props.params.RenderingIdentifier}>
<>
{sitecoreContext.pageState === 'edit' ? (
{sitecoreContext.pageEditing ? (
<Text field={text} />
) : (
<Link field={link}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EditingScripts } from '@sitecore-jss/sitecore-jss-nextjs';
// The BYOC bundle imports external (BYOC) components into the app and makes sure they are ready to be used
import BYOC from 'src/byoc';
import CdpPageView from 'components/CdpPageView';
Expand All @@ -9,6 +10,7 @@ const Scripts = (): JSX.Element => {
<BYOC />
<CdpPageView />
<FEAASScripts />
<EditingScripts />
</>
);
};
Expand Down
41 changes: 40 additions & 1 deletion packages/sitecore-jss-nextjs/src/components/Link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,48 @@ describe('<Link />', () => {
expect(rendered).to.have.length(0);
});

it('should render nothing with missing editable and value', () => {
it('should render nothing with missing field', () => {
const field = {};
const rendered = mount(<Link field={field} />).children();
expect(rendered).to.have.length(0);
});

it('should render field metadata component when metadata property is present', () => {
const testMetadata = {
contextItem: {
id: '{09A07660-6834-476C-B93B-584248D3003B}',
language: 'en',
revision: 'a0b36ce0a7db49418edf90eb9621e145',
version: 1,
},
fieldId: '{414061F4-FBB1-4591-BC37-BFFA67F745EB}',
fieldType: 'single-line',
rawValue: 'Test1',
};

const field = {
value: {
href: '/lorem',
text: 'ipsum',
class: 'my-link',
},
metadata: testMetadata,
};

const rendered = mount(
<Page>
<Link field={field} />
</Page>
);

expect(rendered.html()).to.equal(
[
`<code type="text/sitecore" chrometype="field" class="scpm" kind="open">${JSON.stringify(
testMetadata
)}</code>`,
'<a href="/lorem" class="my-link">ipsum</a>',
'<code type="text/sitecore" chrometype="field" class="scpm" kind="close"></code>',
].join('')
);
});
});
4 changes: 3 additions & 1 deletion packages/sitecore-jss-nextjs/src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
? field
: (field as LinkField).value) as LinkFieldValue;
const { href, querystring, anchor } = value;
const isEditing = editable && (field as LinkFieldValue).editable;

const isEditing =
editable && ((field as LinkFieldValue).editable || (field as LinkFieldValue).metadata);

if (href && !isEditing) {
const text = showLinkTextWithChildrenPresent || !children ? value.text || value.href : null;
Expand Down
36 changes: 35 additions & 1 deletion packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,10 @@ describe('<NextImage />', () => {
describe('error cases', () => {
const src = '/assets/img/test0.png';
it('should throw an error if src is present', () => {
expect(() => mount(<NextImage src={src} />)).to.throw(
const field = {
src: '/assets/img/test0.png',
};
expect(() => mount(<NextImage src={src} field={field} />)).to.throw(
'Detected src prop. If you wish to use src, use next/image directly.'
);
});
Expand Down Expand Up @@ -283,4 +286,35 @@ describe('<NextImage />', () => {
);
});
});

it('should render field metadata component when metadata property is present', () => {
const testMetadata = {
contextItem: {
id: '{09A07660-6834-476C-B93B-584248D3003B}',
language: 'en',
revision: 'a0b36ce0a7db49418edf90eb9621e145',
version: 1,
},
fieldId: '{414061F4-FBB1-4591-BC37-BFFA67F745EB}',
fieldType: 'image',
rawValue: 'Test1',
};

const field = {
value: { src: '/assets/img/test0.png', alt: 'my image' },
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} fill={true} />);

expect(rendered.html()).to.equal(
[
`<code type="text/sitecore" chrometype="field" class="scpm" kind="open">${JSON.stringify(
testMetadata
)}</code>`,
'<img alt="my image" loading="lazy" decoding="async" data-nimg="fill" style="position: absolute; height: 100%; width: 100%; left: 0px; top: 0px; right: 0px; bottom: 0px; color: transparent;" sizes="100vw" srcset="/_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=3840&amp;q=75">',
'<code type="text/sitecore" chrometype="field" class="scpm" kind="close"></code>',
].join('')
);
});
});
Loading
Loading