Skip to content

Commit e7fc692

Browse files
[Layout][Next.js] Load the content styles for the RichText component (#1670)
* initial implementation * traverse fields * Updated UT * Updated CHANGELOG * Updated editing case * Remove log
1 parent ade8398 commit e7fc692

File tree

7 files changed

+461
-0
lines changed

7 files changed

+461
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Our versioning strategy is as follows:
1111

1212
## Unreleased
1313

14+
### 🎉 New Features & Improvements
15+
16+
* `[sitecore-jss]` `[templates/nextjs-xmcloud]` Load the content styles for the RichText component [#1670](https://github.com/Sitecore/jss/pull/1670)
17+
1418
### 🐛 Bug Fixes
1519

1620
* `[templates/node-headless-ssr-proxy]` [node-headless-ssr-proxy] Add sc_site qs parameter to Layout Service requests by default ([#1660](https://github.com/Sitecore/jss/pull/1660))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { SitecorePageProps } from 'lib/page-props';
2+
import { getContentStylesheetLink } from '@sitecore-jss/sitecore-jss-nextjs';
3+
import { Plugin } from '..';
4+
import config from 'temp/config';
5+
6+
class ContentStylesPlugin implements Plugin {
7+
order = 2;
8+
9+
async exec(props: SitecorePageProps) {
10+
// Get content stylessheet link, empty if styles are not used on the page
11+
const contentStyles = getContentStylesheetLink(props.layoutData, config.sitecoreEdgeUrl);
12+
13+
contentStyles && props.headLinks.push(contentStyles);
14+
15+
return props;
16+
}
17+
}
18+
19+
export const contentStylesPlugin = new ContentStylesPlugin();

packages/sitecore-jss-nextjs/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export {
7676
RenderingType,
7777
EDITING_COMPONENT_PLACEHOLDER,
7878
EDITING_COMPONENT_ID,
79+
getContentStylesheetLink,
7980
} from '@sitecore-jss/sitecore-jss/layout';
8081
export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
8182
export {

packages/sitecore-jss-react/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
EditButtonTypes,
1111
} from '@sitecore-jss/sitecore-jss/utils';
1212
export {
13+
getContentStylesheetLink,
1314
LayoutService,
1415
LayoutServiceData,
1516
LayoutServicePageState,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/* eslint-disable no-unused-expressions */
2+
import { expect } from 'chai';
3+
import {
4+
traverseField,
5+
traverseComponent,
6+
getContentStylesheetUrl,
7+
traversePlaceholder,
8+
getContentStylesheetLink,
9+
} from './content-styles';
10+
import { ComponentRendering, Field, HtmlElementRendering, Item, LayoutServiceData } from './models';
11+
import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
12+
13+
describe('content-styles', () => {
14+
const truthyValue = { value: '<div class="test bar"><p class="foo ck-content">bar</p></div>' };
15+
const falsyValue = { value: '<div class="test bar"><p class="foo">ck-content</p></div>' };
16+
17+
describe('getContentStylesheetLink', () => {
18+
it('should return null when route data is empty', () => {
19+
expect(getContentStylesheetLink({ sitecore: { context: {}, route: null } })).to.be.null;
20+
});
21+
22+
it('should set "loadStyles: false" when layout does not have a ck-content class', () => {
23+
const layoutData: LayoutServiceData = {
24+
sitecore: {
25+
context: {},
26+
route: {
27+
name: 'route',
28+
placeholders: {
29+
car: [{ componentName: 'foo', fields: { car: falsyValue } }],
30+
},
31+
},
32+
},
33+
};
34+
35+
expect(getContentStylesheetLink(layoutData)).to.be.null;
36+
});
37+
38+
it('should set "loadStyles: true" when layout has a ck-content class', () => {
39+
const layoutData: LayoutServiceData = {
40+
sitecore: {
41+
context: {},
42+
route: {
43+
name: 'route',
44+
placeholders: {
45+
car: [
46+
{
47+
componentName: 'foo',
48+
fields: { car: falsyValue },
49+
placeholders: {
50+
bar: [{ componentName: 'cow', fields: { dog: truthyValue } }],
51+
},
52+
},
53+
],
54+
},
55+
},
56+
},
57+
};
58+
59+
expect(getContentStylesheetLink(layoutData)).to.deep.equal({
60+
href: `${SITECORE_EDGE_URL_DEFAULT}/pages/styles/content-styles.min.css`,
61+
rel: 'stylesheet',
62+
});
63+
});
64+
});
65+
66+
describe('traverseField', () => {
67+
describe('Field', () => {
68+
it('should set "loadStyles: false" when field does not have a ck-content class', () => {
69+
const config = { loadStyles: false };
70+
const field: Field = {
71+
value: '<div class="test bar"><p class="foo">ck-content</p></div>',
72+
};
73+
74+
traverseField(field, config);
75+
76+
expect(config.loadStyles).to.be.false;
77+
});
78+
79+
it('should set "loadStyles: true" when field has a ck-content class', () => {
80+
const config = { loadStyles: false };
81+
const field: Field = {
82+
value: '<div class="test bar"><p class="foo ck-content">bar</p></div>',
83+
};
84+
85+
traverseField(field, config);
86+
87+
expect(config.loadStyles).to.be.true;
88+
});
89+
});
90+
91+
describe('Item', () => {
92+
it('should set "loadStyles: false" when field does not have a ck-content class', () => {
93+
const config = { loadStyles: false };
94+
const field: Item = {
95+
name: 'test',
96+
fields: {
97+
richText: {
98+
value: '<div class="test bar"><p class="foo">ck-content</p></div>',
99+
},
100+
},
101+
};
102+
103+
traverseField(field, config);
104+
105+
expect(config.loadStyles).to.be.false;
106+
});
107+
108+
it('should set "loadStyles: true" when field has a ck-content class', () => {
109+
const config = { loadStyles: false };
110+
const field: Item = {
111+
name: 'test',
112+
fields: {
113+
richText: {
114+
value: '<div class="test bar"><p class="foo ck-content">bar</p></div>',
115+
},
116+
},
117+
};
118+
119+
traverseField(field, config);
120+
121+
expect(config.loadStyles).to.be.true;
122+
});
123+
});
124+
125+
describe('Item[]', () => {
126+
it('should set "loadStyles: false" when field does not have a ck-content class', () => {
127+
const config = { loadStyles: false };
128+
const field: Item[] = [
129+
{
130+
name: 'test',
131+
fields: {
132+
richText: {
133+
value: '<div class="test bar"><p class="foo">ck-content</p></div>',
134+
},
135+
},
136+
},
137+
];
138+
139+
traverseField(field, config);
140+
141+
expect(config.loadStyles).to.be.false;
142+
});
143+
144+
it('should set "loadStyles: true" when field has a ck-content class', () => {
145+
const config = { loadStyles: false };
146+
const field: Item[] = [
147+
{
148+
name: 'test',
149+
fields: {
150+
richText: {
151+
value: '<div class="test bar"><p class="foo ck-content">bar</p></div>',
152+
},
153+
},
154+
},
155+
];
156+
157+
traverseField(field, config);
158+
159+
expect(config.loadStyles).to.be.true;
160+
});
161+
});
162+
163+
describe('editing', () => {
164+
it('should set "loadStyles: false" when field does not have a ck-content class', () => {
165+
const config = { loadStyles: false };
166+
const field: Field = {
167+
value: '',
168+
editable: falsyValue.value,
169+
};
170+
171+
traverseField(field, config);
172+
173+
expect(config.loadStyles).to.be.false;
174+
});
175+
176+
it('should set "loadStyles: true" when field has a ck-content class', () => {
177+
const config = { loadStyles: false };
178+
const field: Field = {
179+
value: '',
180+
editable: truthyValue.value,
181+
};
182+
183+
traverseField(field, config);
184+
185+
expect(config.loadStyles).to.be.true;
186+
});
187+
});
188+
189+
it('should skip when field is undefined', () => {
190+
const config = { loadStyles: false };
191+
const field = undefined;
192+
193+
traverseField(field, config);
194+
195+
expect(config.loadStyles).to.be.false;
196+
});
197+
198+
it('should skip when loadStyles is true', () => {
199+
const config = { loadStyles: true };
200+
const field = falsyValue;
201+
202+
traverseField(field, config);
203+
204+
expect(config.loadStyles).to.be.true;
205+
});
206+
});
207+
208+
describe('traverseComponent', () => {
209+
it('should skip when loadStyles is true', () => {
210+
const config = { loadStyles: true };
211+
const component = {
212+
componentName: 'ContentBlock',
213+
fields: {
214+
richText: falsyValue,
215+
},
216+
};
217+
218+
traverseComponent(component, config);
219+
220+
expect(config.loadStyles).to.be.true;
221+
});
222+
223+
it('should set "loadStyles: false" when component does not have a ck-content class', () => {
224+
const config = { loadStyles: false };
225+
const component = {
226+
componentName: 'ContentBlock',
227+
fields: {
228+
richText: falsyValue,
229+
},
230+
placeholders: {
231+
foo: [{ componentName: 'fooComponent', fields: { car: falsyValue } }],
232+
},
233+
};
234+
235+
traverseComponent(component, config);
236+
237+
expect(config.loadStyles).to.be.false;
238+
});
239+
240+
it('should set "loadStyles: true" when component has a ck-content class', () => {
241+
const config = { loadStyles: false };
242+
const component = {
243+
componentName: 'ContentBlock',
244+
fields: {
245+
richText: falsyValue,
246+
},
247+
placeholders: {
248+
foo: [{ componentName: 'fooComponent', fields: { car: truthyValue } }],
249+
},
250+
};
251+
252+
traverseComponent(component, config);
253+
254+
expect(config.loadStyles).to.be.true;
255+
});
256+
});
257+
258+
describe('traversePlaceholder', () => {
259+
it('should skip when loadStyles is true', () => {
260+
const config = { loadStyles: true };
261+
const components = [
262+
{
263+
componentName: 'ContentBlock',
264+
fields: {
265+
richText: falsyValue,
266+
},
267+
},
268+
];
269+
270+
traversePlaceholder(components, config);
271+
272+
expect(config.loadStyles).to.be.true;
273+
});
274+
275+
it('should set "loadStyles: false" when placeholder does not have a ck-content class', () => {
276+
const config = { loadStyles: false };
277+
const components: (ComponentRendering | HtmlElementRendering)[] = [
278+
{
279+
componentName: 'ContentBlock',
280+
fields: {
281+
richText: falsyValue,
282+
},
283+
placeholders: {
284+
foo: [{ componentName: 'foo', fields: { car: falsyValue } }],
285+
},
286+
},
287+
{
288+
componentName: 'Foo',
289+
fields: {
290+
car: falsyValue,
291+
},
292+
placeholders: {
293+
body: [{ componentName: 'foo', fields: { car: falsyValue } }],
294+
},
295+
},
296+
];
297+
298+
traversePlaceholder(components, config);
299+
300+
expect(config.loadStyles).to.be.false;
301+
});
302+
303+
it('should set "loadStyles: true" when component has a ck-content class', () => {
304+
const config = { loadStyles: false };
305+
const components: (ComponentRendering | HtmlElementRendering)[] = [
306+
{
307+
componentName: 'ContentBlock',
308+
fields: {
309+
richText: falsyValue,
310+
},
311+
placeholders: {
312+
foo: [{ componentName: 'foo', fields: { car: falsyValue } }],
313+
},
314+
},
315+
{
316+
componentName: 'Foo',
317+
fields: {
318+
car: falsyValue,
319+
},
320+
placeholders: {
321+
body: [{ componentName: 'foo', fields: { car: truthyValue } }],
322+
},
323+
},
324+
];
325+
326+
traversePlaceholder(components, config);
327+
328+
expect(config.loadStyles).to.be.true;
329+
});
330+
});
331+
332+
describe('getContentStylesheetUrl', () => {
333+
it('should return the default url', () => {
334+
expect(getContentStylesheetUrl()).to.equal(
335+
`${SITECORE_EDGE_URL_DEFAULT}/pages/styles/content-styles.min.css`
336+
);
337+
});
338+
339+
it('should return the custom url', () => {
340+
expect(getContentStylesheetUrl('https://foo.bar')).to.equal(
341+
'https://foo.bar/pages/styles/content-styles.min.css'
342+
);
343+
});
344+
});
345+
});

0 commit comments

Comments
 (0)