sfc-utils 1.3.65 → 1.3.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,29 @@
1
1
  import React from 'react'
2
- // import * as capcredStyles from "../styles/modules/captioncredit.module.less"
3
2
 
4
- const CaptionCredit = (props) => {
5
- let {caption, credit, extraStyles} = props
6
-
7
- let captionCss = ["topper-image", "caption"]; //capcredStyles.cFigCap,
8
- if (extraStyles) captionCss = captionCss.concat(extraStyles);
3
+ const CaptionCredit = ({ caption, credit, extraStyles, creditStyles = [] }) => {
4
+ let captionCss = ["topper-image", "caption"];
5
+ if (extraStyles) {
6
+ captionCss = captionCss.concat(extraStyles);
7
+ }
9
8
 
9
+ let creditCss = `credit ${creditStyles.join(" ")}`
10
10
  return (
11
11
  <>
12
- {caption && credit && (
13
- <figcaption className={captionCss.join(' ')}>
14
- {caption} <span className="credit">{credit}</span>
15
- </figcaption>
16
- )}
17
- {!caption && credit && (
18
- <figcaption className={captionCss.join(' ')}>
19
- <span className="credit">{credit}</span>
20
- </figcaption>
21
- )}
22
- {caption && !credit && (
23
- <figcaption className={captionCss.join(' ')}>
24
- {caption}
25
- </figcaption>
26
- )}
12
+ {caption && credit && (
13
+ <figcaption className={captionCss.join(' ')}>
14
+ {caption} <span className={creditCss}>{credit}</span>
15
+ </figcaption>
16
+ )}
17
+ {!caption && credit && (
18
+ <figcaption className={captionCss.join(' ')}>
19
+ <span className={creditCss}>{credit}</span>
20
+ </figcaption>
21
+ )}
22
+ {caption && !credit && (
23
+ <figcaption className={captionCss.join(' ')}>
24
+ {caption}
25
+ </figcaption>
26
+ )}
27
27
  </>
28
28
  )
29
29
  }
@@ -1,12 +1,75 @@
1
+ import { getBlueconic } from "../../blueconic"
2
+ import { appCheck, blendHDN } from "../../index"
3
+
4
+ /** Used for resizing the WCM Image */
1
5
  function debounce(fn, ms) {
2
- let timer
3
- return _ => {
4
- clearTimeout(timer)
5
- timer = setTimeout(_ => {
6
- timer = null
7
- fn.apply(this, arguments)
8
- }, ms)
6
+ let timer
7
+ return _ => {
8
+ clearTimeout(timer)
9
+ timer = setTimeout(_ => {
10
+ timer = null
11
+ fn.apply(this, arguments)
12
+ }, ms)
13
+ }
14
+ }
15
+
16
+ function appendLayoutScripts(isEmbedded, isAdRemoved) {
17
+ const isApp = appCheck();
18
+
19
+ // React Helmet is actually terrible and runs these scripts twice, so we are including them async ourselves
20
+ // Run analytics and resizing scripts right away so we take care of that
21
+ if (!isEmbedded) {
22
+ let script = document.createElement('script');
23
+ script.type = 'text/javascript';
24
+ script.src = 'https://nexus.ensighten.com/hearst/news/Bootstrap.js';
25
+ document.body.appendChild(script);
26
+ } else {
27
+ let script = document.createElement('script');
28
+ script.type = 'text/javascript';
29
+ script.src = 'https://projects.sfchronicle.com/shared/js/responsive-child.js';
30
+ document.body.appendChild(script);
31
+ }
32
+
33
+ if (!isEmbedded && !isAdRemoved) {
34
+ let script = document.createElement('script');
35
+ script.type = 'text/javascript';
36
+ script.id = 'adPositionManagerScriptTag';
37
+ script.src = 'https://aps.hearstnp.com/Scripts/loadAds.js';
38
+ document.body.appendChild(script);
39
+ }
40
+
41
+ // Wait a beat, then add to body so it doesn't mess with the head (which Helmet seems to want to manage)
42
+ setTimeout(() => {
43
+ if (!isEmbedded && !isApp) {
44
+ let blueconicURL = getBlueconic(window.location.origin)
45
+ let script = document.createElement('script');
46
+ script.type = 'text/javascript';
47
+ script.defer = true;
48
+ script.src = blueconicURL;
49
+ document.body.appendChild(script);
50
+ }
51
+ }, 5000)
52
+ }
53
+
54
+ function formatHDN(isEmbedded, url_add, meta) {
55
+ const isApp = appCheck();
56
+
57
+ // Combine our settings with what Hearst puts on page
58
+ let stringHDN = ''
59
+ if (!isEmbedded) {
60
+ // Put url_add into a new meta object to pass in
61
+ const metaHDN = Object.assign({}, meta)
62
+ metaHDN.URL_ADD = url_add
63
+ // Make sure this is free on app
64
+ if (isApp) {
65
+ metaHDN.PAYWALL_SETTING = "free"
9
66
  }
67
+ // Allow gift button to appear next to sharebuttons
68
+ metaHDN.GIFT_ENABLED = true
69
+ let blended = blendHDN(metaHDN)
70
+ stringHDN = blended.stringHDN
10
71
  }
72
+ return stringHDN;
73
+ }
11
74
 
12
- export { debounce }
75
+ export { debounce, appendLayoutScripts, formatHDN }
@@ -0,0 +1,125 @@
1
+ import React from 'react'
2
+
3
+ import { Helmet } from 'react-helmet'
4
+ import { appCheck, getBrands2 } from "../../index"
5
+
6
+ const LayoutHelmet = ({ meta, url_add }) => {
7
+ let {
8
+ EMBEDDED,
9
+ MAIN_DOMAIN,
10
+ PROJECT: {
11
+ AUTHORS,
12
+ DESCRIPTION,
13
+ IMAGE,
14
+ ISO_MODDATE,
15
+ ISO_PUBDATE,
16
+ OPT_SLASH,
17
+ SLUG,
18
+ SOCIAL_TITLE,
19
+ SUBFOLDER,
20
+ TITLE,
21
+ MARKET_KEY,
22
+ CANONICAL_URL
23
+ },
24
+ } = meta
25
+
26
+ const isApp = appCheck()
27
+ const thisBrand = getBrands2(MARKET_KEY);
28
+
29
+ // Get stylesheet id from market key
30
+ let styleSheetID
31
+ if ((MARKET_KEY === "SFC") || (MARKET_KEY === "Houston") || (MARKET_KEY === "Albany")) {
32
+ styleSheetID = MARKET_KEY
33
+ }
34
+ else {
35
+ styleSheetID = "default"
36
+ }
37
+
38
+ // Handle author data
39
+ let authorObj = []
40
+ let newAuthor = {}
41
+ try {
42
+ AUTHORS.forEach(author => {
43
+ newAuthor = {
44
+ '@type': 'Person',
45
+ name: author.AUTHOR_NAME,
46
+ url: author.AUTHOR_PAGE,
47
+ }
48
+ authorObj.push(newAuthor)
49
+ })
50
+ } catch (err) {
51
+ // If it errored, just set to neutral default
52
+ authorObj = {
53
+ '@type': 'Person',
54
+ name: thisBrand.attributes.siteName,
55
+ url: MAIN_DOMAIN,
56
+ }
57
+ }
58
+
59
+ return (
60
+ <Helmet>
61
+ <title>{TITLE}</title>
62
+ <meta name="description" content={DESCRIPTION} />
63
+ <link
64
+ rel="shortcut icon"
65
+ href="/favicon.ico"
66
+ type="image/x-icon"
67
+ />
68
+ <link rel="canonical" href={`${CANONICAL_URL}/${url_add}`} />
69
+ <link rel="stylesheet" href={`https://files.sfchronicle.com/brand-styles/${styleSheetID}.css`} />
70
+
71
+ {(isApp || EMBEDDED) ? (
72
+ <meta name="robots" content="noindex, nofollow" />
73
+ ) : (
74
+ <meta name="robots" content="max-image-preview:large" />
75
+ )}
76
+
77
+ <meta name="twitter:card" content="summary_large_image" />
78
+ <meta name="twitter:title" content={SOCIAL_TITLE} />
79
+ <meta name="twitter:site" content={"@" + thisBrand.attributes.twitter} />
80
+ <meta
81
+ name="twitter:url"
82
+ content={`${MAIN_DOMAIN}/${SUBFOLDER}${OPT_SLASH}${SLUG}/${url_add}`}
83
+ />
84
+ <meta name="twitter:image" content={IMAGE} />
85
+ <meta name="twitter:description" content={DESCRIPTION} />
86
+
87
+ <meta property="og:type" content="article" />
88
+ <meta property="og:title" content={SOCIAL_TITLE} />
89
+ <meta property="og:site_name" content={thisBrand.attributes.siteName} />
90
+ <meta property="og:url" content={`${MAIN_DOMAIN}/${SUBFOLDER}${OPT_SLASH}${SLUG}/${url_add}`} />
91
+ <meta property="og:image" content={IMAGE} />
92
+ <meta property="og:description" content={DESCRIPTION} />
93
+
94
+ <script data-schema="NewsArticle" type="application/ld+json">{`{
95
+ "@context": "http://schema.org",
96
+ "@type": "NewsArticle",
97
+ "mainEntityOfPage": {
98
+ "@type": "WebPage",
99
+ "@id": "${MAIN_DOMAIN}/${SUBFOLDER}${OPT_SLASH}${SLUG}/${url_add}"
100
+ },
101
+ "headline": "${TITLE}",
102
+ "image": {
103
+ "@type": "ImageObject",
104
+ "url": "${IMAGE}"
105
+ },
106
+ "datePublished": "${ISO_PUBDATE}",
107
+ "dateModified": "${ISO_MODDATE}",
108
+ "author": ${JSON.stringify(authorObj)},
109
+ "publisher": {
110
+ "@type": "Organization",
111
+ "name": "${thisBrand.attributes.siteName}",
112
+ "logo": {
113
+ "@type": "ImageObject",
114
+ "url": "/apple-touch-icon.png",
115
+ "width": "180",
116
+ "height": "180"
117
+ }
118
+ },
119
+ "description": "${DESCRIPTION}"
120
+ }`}</script>
121
+ </Helmet>
122
+ )
123
+ }
124
+
125
+ export default LayoutHelmet
@@ -0,0 +1,12 @@
1
+ import React from 'react'
2
+
3
+ const LayoutScript = () => {
4
+ return (
5
+ <>
6
+ <script src="https://projects.sfchronicle.com/shared/js/jquery.min.js"></script>
7
+ <script src="https://treg.hearstnp.com/treg.js"></script>
8
+ </>
9
+ )
10
+ }
11
+
12
+ export default LayoutScript
@@ -0,0 +1,18 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ pubdateString,
5
+ moddateString
6
+ } from './helpers/datehelpers.mjs'
7
+
8
+ const OgPubDate = ({ settings }) => {
9
+ return (
10
+ <>
11
+ {moddateString &&
12
+ <p><i>Originally published on {pubdateString}</i></p>
13
+ }
14
+ </>
15
+ )
16
+ }
17
+
18
+ export default OgPubDate
@@ -1,8 +1,10 @@
1
1
  import React, { useEffect, useState, useRef } from "react"
2
2
 
3
- const CaptionCreditSlideshow = ({ captionList, creditList, extraStyles }) => {
3
+ const CaptionCreditSlideshow = ({ captionList, creditList, extraStyles, creditStyles = [] }) => {
4
4
  let captionCss = ["topper-image", "caption"];
5
- if (extraStyles) captionCss = captionCss.concat(extraStyles);
5
+ if (extraStyles) {
6
+ captionCss = extraStyles.concat(captionCss);
7
+ }
6
8
 
7
9
  const [index, setIndex] = useState(0);
8
10
  const timeoutRef = useRef(null);
@@ -29,16 +31,17 @@ const CaptionCreditSlideshow = ({ captionList, creditList, extraStyles }) => {
29
31
 
30
32
  const hasCaption = (captionList.length > 0)
31
33
  const hasCredit = (creditList.length > 0)
34
+ let creditCss = `credit ${creditStyles.join(" ")}`
32
35
  return (
33
36
  <>
34
37
  {hasCaption && hasCredit && (
35
38
  <figcaption className={captionCss.join(' ')}>
36
- {captionList[index]} <span className="credit">{creditList[index]}</span>
39
+ {captionList[index]} <span className={creditCss}>{creditList[index]}</span>
37
40
  </figcaption>
38
41
  )}
39
42
  {!hasCaption && hasCredit && (
40
43
  <figcaption className={captionCss.join(' ')}>
41
- <span className="credit">{creditList[index]}</span>
44
+ <span className={creditCss}>{creditList[index]}</span>
42
45
  </figcaption>
43
46
  )}
44
47
  {hasCaption && !hasCredit && (
@@ -4,7 +4,7 @@ import * as styles from "../../styles/modules/imageslideshow.module.less"
4
4
  import * as imageStyles from "../../styles/modules/topperimage.module.less"
5
5
  import { TransitionGroup, CSSTransition } from "react-transition-group"
6
6
 
7
- const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle }) => {
7
+ const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle, isLayoutInverted = false }) => {
8
8
  const [index, setIndex] = useState(0);
9
9
  const timeoutRef = useRef(null);
10
10
 
@@ -31,7 +31,6 @@ const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle }) => {
31
31
  const getContainerClass = () => {
32
32
  switch (topperStyle) {
33
33
  case "stacked":
34
- return styles.containerStacked;
35
34
  case "full-screen":
36
35
  return styles.containerStacked;
37
36
  default:
@@ -45,6 +44,10 @@ const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle }) => {
45
44
  return styles.imageWrapperStacked;
46
45
  case "full-screen":
47
46
  return styles.imageWrapperFullscreen;
47
+ case "side-by-side":
48
+ return styles.imageWrapperSideBySide;
49
+ case "side-by-side-portrait":
50
+ return styles.imageWrapperSideBySidePortrait;
48
51
  default:
49
52
  return "";
50
53
  }
@@ -56,6 +59,18 @@ const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle }) => {
56
59
  return [imageStyles.cForceAspectRatio];
57
60
  case "full-screen":
58
61
  return [imageStyles.cImgSlideshowFullscreen];
62
+ case "side-by-side":
63
+ return [imageStyles.cImgSlideshowSideBySide].concat(
64
+ // Add styling for left padding on topper image
65
+ (isLayoutInverted) ? [imageStyles.cLargePaddingLeft] : [imageStyles.cLargePaddingRight]
66
+ );
67
+ case "side-by-side-portrait":
68
+ return [imageStyles.cImgSlideshowSideBySidePortrait].concat(
69
+ [
70
+ // Add styling for left padding on image caption
71
+ (isLayoutInverted) ? [] : [styles.sideBySidePortraitMarginLeft]
72
+ ]
73
+ );
59
74
  default:
60
75
  return [""];
61
76
  }
@@ -10,7 +10,9 @@ import * as imageStyles from "../styles/modules/topperimage.module.less"
10
10
  const Topper2 = ({ settings, wcmData }) => {
11
11
  const {
12
12
  Topper_Style, Title, Title_Style, Deck, Image, Image_Alt, Image_Caption, Image_Credits,
13
- HeaderDek_Vertical_Position, HeaderDek_Vertical_Offset, HeaderDek_Horizontal_Offset, HeaderDek_Horizontal_Position, Inverted_Colors
13
+ HeaderDek_Vertical_Position, HeaderDek_Vertical_Offset, HeaderDek_Horizontal_Offset,
14
+ HeaderDek_Horizontal_Position, Inverted_Colors, Inverted_Layout, Inverted_Text_Color,
15
+ Topper_Background_Color
14
16
  } = settings
15
17
 
16
18
  const headerDekStyleList = () => {
@@ -35,6 +37,19 @@ const Topper2 = ({ settings, wcmData }) => {
35
37
  ... (Inverted_Colors === "black-text-white-bg") ? [topperStyles.blackTextWhiteBg] : [topperStyles.whiteTextBlackBg]
36
38
  ];
37
39
  case "side-by-side":
40
+ return [
41
+ topperStyles.headerDekSideBySide,
42
+ // Add styling for left padding on header-deck
43
+ ... (Inverted_Layout === "headerdek-right-image-left") ? [topperStyles.largePaddingRight] : [topperStyles.largePaddingLeft]
44
+ ];
45
+ case "side-by-side-portrait":
46
+ return [
47
+ topperStyles.headerDekSideBySide,
48
+ // Add styling for left padding on header-deck
49
+ ... (Inverted_Layout === "headerdek-right-image-left") ? [topperStyles.largePaddingRight] : [topperStyles.largePaddingLeft]
50
+ ];
51
+ default:
52
+ console.error(`${Topper_Style} is not a valid topper style!`)
38
53
  return [];
39
54
  }
40
55
  }
@@ -49,7 +64,7 @@ const Topper2 = ({ settings, wcmData }) => {
49
64
  }
50
65
 
51
66
  const headerStyleList = () => {
52
- let defaultStyles;
67
+ let defaultStyles = [];
53
68
  switch (Topper_Style) {
54
69
  case "stacked":
55
70
  defaultStyles = [];
@@ -61,7 +76,8 @@ const Topper2 = ({ settings, wcmData }) => {
61
76
  defaultStyles = [topperStyles.hedFullScreen, fullScreenTextAlignCss()];
62
77
  break;
63
78
  case "side-by-side":
64
- defaultStyles = [];
79
+ case "side-by-side-portrait":
80
+ defaultStyles = ["left"];
65
81
  break;
66
82
  }
67
83
 
@@ -83,11 +99,14 @@ const Topper2 = ({ settings, wcmData }) => {
83
99
  case "full-screen":
84
100
  return ["deck", topperStyles.deckFullScreen, fullScreenTextAlignCss()];
85
101
  case "side-by-side":
86
- return ["deck"];
102
+ case "side-by-side-portrait":
103
+ return ["deck left"];
104
+ default:
105
+ return [""]
87
106
  }
88
107
  }
89
108
 
90
- /** get text alignment based on header-deck position **/
109
+ /** Get text alignment based on header-deck position **/
91
110
  const fullScreenTextAlignCss = () => {
92
111
  switch (HeaderDek_Horizontal_Position) {
93
112
  case "left": return topperStyles.textAlignLeft;
@@ -96,6 +115,27 @@ const Topper2 = ({ settings, wcmData }) => {
96
115
  }
97
116
  }
98
117
 
118
+ /** Add styling for text color on topper slideshow captions. Note that the credits
119
+ * are grey when the caption is black and white when the captions are white. */
120
+ const sideBySideCapCredColorCss = () => {
121
+ return (Inverted_Text_Color === "black") ? topperStyles.captionTextColor : topperStyles.captionTextColorImportant;
122
+ }
123
+
124
+ /** Add styling for left padding on topper slideshow captions */
125
+ const sideBySideCapCredPaddingCss = () => {
126
+ return (Inverted_Layout === "headerdek-right-image-left") ? topperStyles.captionLargePaddingLeft : topperStyles.captionLargePaddingRight
127
+ }
128
+
129
+ /** Add styling for left padding on topper slideshow captions */
130
+ const sideBySidePortraitCapCredPaddingCss = () => {
131
+ return (Inverted_Layout === "headerdek-right-image-left") ? topperStyles.captionSmallPaddingLeft : topperStyles.captionSideBySidePortraitPadding;
132
+ }
133
+
134
+ /** Add styling for float position for side-by-side-portrait image */
135
+ const sideBySidePortraitFloatCss = () => {
136
+ return (Inverted_Layout === "headerdek-right-image-left") ? topperStyles.floatLeftWhenDesktop : topperStyles.floatRightWhenDesktop;
137
+ }
138
+
99
139
  /** Converts wcm string from spreadsheet into a list of WCM ids */
100
140
  const getWcmIdList = (listStr) => {
101
141
  return listStr.split(";").map((d) => parseInt(d));
@@ -119,15 +159,52 @@ const Topper2 = ({ settings, wcmData }) => {
119
159
  return list;
120
160
  }
121
161
 
122
- const ImageHTML = () => <TopperImage wcm={Image} alt={Image_Alt} wcmData={wcmData} />
123
- const FullScreenImageHTML = () => <TopperImage wcm={Image} alt={Image_Alt} wcmData={wcmData} overrideCssList={[imageStyles.cImgFullscreen]} />
124
- const ImageSlideshowHTML = () =>
125
- <ImageSlideshow
126
- wcmData={wcmData}
127
- imageList={wcmIdList}
128
- altList={convertStringToList(Image_Alt, wcmIdList.length)}
129
- topperStyle={Topper_Style}
130
- />
162
+ /* Returns the corresponding image HTML for each topper style and slideshow status */
163
+ const getImageHTML = (isSlideshow) => {
164
+ if (isSlideshow) return (
165
+ <ImageSlideshow
166
+ wcmData={wcmData}
167
+ imageList={wcmIdList}
168
+ altList={convertStringToList(Image_Alt, wcmIdList.length)}
169
+ topperStyle={Topper_Style}
170
+ isLayoutInverted={(Inverted_Layout === "headerdek-right-image-left")}
171
+ />
172
+ )
173
+
174
+ switch (Topper_Style) {
175
+ case "stacked":
176
+ return (<TopperImage wcm={Image} alt={Image_Alt} wcmData={wcmData} />)
177
+ case "full-screen":
178
+ return (<TopperImage
179
+ wcm={Image}
180
+ alt={Image_Alt}
181
+ wcmData={wcmData}
182
+ overrideCssList={[imageStyles.cImgFullscreen]}
183
+ />)
184
+ case "side-by-side":
185
+ return (
186
+ <TopperImage
187
+ wcm={Image}
188
+ alt={Image_Alt}
189
+ wcmData={wcmData}
190
+ containerCssList={[imageStyles.cContainerSideBySide]}
191
+ overrideCssList={[imageStyles.cImgSideBySide]}
192
+ />)
193
+ case "side-by-side-portrait":
194
+ return (
195
+ <TopperImage
196
+ wcm={Image}
197
+ alt={Image_Alt}
198
+ wcmData={wcmData}
199
+ containerCssList={[imageStyles.cContainerSideBySidePortrait, sideBySidePortraitFloatCss()]}
200
+ overrideCssList={[imageStyles.cImgSideBySidePortrait]}
201
+ />
202
+ )
203
+ case "no-visual":
204
+ default:
205
+ return null
206
+ }
207
+ }
131
208
 
132
209
  const wcmIdList = getWcmIdList(Image);
133
210
  const TopperHtml = () => {
@@ -138,15 +215,14 @@ const Topper2 = ({ settings, wcmData }) => {
138
215
  <>
139
216
  <div className={containerCss}>
140
217
  <figure className={`topper-image ${topperStyles.imageFullScreen}`} aria-labelledby="topperCaptionText">
141
- {isSlideshow(wcmIdList) && <ImageSlideshowHTML />}
142
- {!isSlideshow(wcmIdList) && <FullScreenImageHTML />}
218
+ {getImageHTML(isSlideshow(wcmIdList))}
143
219
 
144
220
  {/* This caption-credit only shows when the screen size is tablet or mobile */}
145
221
  {isSlideshow(wcmIdList) &&
146
222
  <CaptionCreditSlideshow
147
223
  captionList={convertStringToList(Image_Caption, wcmIdList.length)}
148
224
  creditList={convertStringToList(Image_Credits, wcmIdList.length)}
149
- extraStyles={topperStyles.hideWhenDesktop}
225
+ extraStyles={[topperStyles.hideWhenDesktop]}
150
226
  />
151
227
  }
152
228
  {!isSlideshow(wcmIdList) && <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={topperStyles.hideWhenDesktop} />}
@@ -193,8 +269,8 @@ const Topper2 = ({ settings, wcmData }) => {
193
269
  />
194
270
  </div>
195
271
  <figure className={`mw-xl ml-auto mr-auto ${topperStyles.imageStacked}`}>
196
- {isSlideshow(wcmIdList) && <ImageSlideshowHTML />}
197
- {!isSlideshow(wcmIdList) && <ImageHTML />}
272
+ {getImageHTML(isSlideshow(wcmIdList))}
273
+
198
274
  {isSlideshow(wcmIdList) &&
199
275
  <CaptionCreditSlideshow
200
276
  captionList={convertStringToList(Image_Caption, wcmIdList.length)}
@@ -202,7 +278,7 @@ const Topper2 = ({ settings, wcmData }) => {
202
278
  extraStyles={[topperStyles.smallPaddingLeftWhenTablet]}
203
279
  />
204
280
  }
205
- {!isSlideshow(wcmIdList) && <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={[topperStyles.smallPaddingLeftWhenTablet]}/>}
281
+ {!isSlideshow(wcmIdList) && <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={[topperStyles.smallPaddingLeftWhenTablet]} />}
206
282
  </figure>
207
283
  </div>
208
284
  </>
@@ -227,18 +303,90 @@ const Topper2 = ({ settings, wcmData }) => {
227
303
  </div>
228
304
  </>
229
305
  );
306
+
307
+ case "side-by-side":
308
+ let figureCss = isSlideshow(wcmIdList) ? `${topperStyles.imageSideBySideSlideshow}` : `${topperStyles.imageSideBySide}`;
309
+ let sideBySideContainerCss = (Inverted_Layout === "headerdek-right-image-left") ? `${topperStyles.topperContainerSideBySide} ${topperStyles.reverseFlexbox}` : `${topperStyles.topperContainerSideBySide}`;
310
+ setBackgroundAndTextColor();
311
+ return (
312
+ <div className={sideBySideContainerCss}>
313
+ <div className={headerDekStyleList().join(' ')}>
314
+ <Heading
315
+ level={1}
316
+ text={Title}
317
+ className={headerStyleList().join(' ')}
318
+ />
319
+ <Heading
320
+ level={2}
321
+ text={Deck}
322
+ className={deckStyleList().join(' ')}
323
+ />
324
+ </div>
325
+ <figure className={figureCss}>
326
+ {getImageHTML(isSlideshow(wcmIdList))}
327
+
328
+ {isSlideshow(wcmIdList) &&
329
+ <CaptionCreditSlideshow
330
+ captionList={convertStringToList(Image_Caption, wcmIdList.length)}
331
+ creditList={convertStringToList(Image_Credits, wcmIdList.length)}
332
+ extraStyles={[topperStyles.slideshowCaptionSideBySide, sideBySideCapCredColorCss(), sideBySideCapCredPaddingCss()]}
333
+ creditStyles={[sideBySideCapCredColorCss()]}
334
+ />
335
+ }
336
+ {!isSlideshow(wcmIdList) && <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={[topperStyles.captionSideBySide, sideBySideCapCredColorCss()]} creditStyles={[sideBySideCapCredColorCss()]} />}
337
+ </figure>
338
+ </div>
339
+ );
340
+
341
+ case "side-by-side-portrait":
342
+ let portraitFigureCss = isSlideshow(wcmIdList) ? `${topperStyles.imageSideBySidePortraitSlideshow}` : `${topperStyles.imageSideBySidePortrait}`;
343
+ let sideBySidePortraitContainerCss = (Inverted_Layout === "headerdek-right-image-left") ? `${topperStyles.topperContainerSideBySidePortrait} ${topperStyles.reverseFlexbox}` : `${topperStyles.topperContainerSideBySidePortrait}`;
344
+ setBackgroundAndTextColor();
345
+ return (
346
+ <div className={topperStyles.fullWidthContainer}>
347
+ <div className={sideBySidePortraitContainerCss}>
348
+ <div className={headerDekStyleList().join(' ')}>
349
+ <Heading
350
+ level={1}
351
+ text={Title}
352
+ className={headerStyleList().join(' ')}
353
+ />
354
+ <Heading
355
+ level={2}
356
+ text={Deck}
357
+ className={deckStyleList().join(' ')}
358
+ />
359
+ </div>
360
+ <figure className={portraitFigureCss}>
361
+ {getImageHTML(isSlideshow(wcmIdList))}
362
+
363
+ {isSlideshow(wcmIdList) &&
364
+ <CaptionCreditSlideshow
365
+ captionList={convertStringToList(Image_Caption, wcmIdList.length)}
366
+ creditList={convertStringToList(Image_Credits, wcmIdList.length)}
367
+ extraStyles={[topperStyles.slideshowCaptionSideBySidePortrait, sideBySideCapCredColorCss(), sideBySidePortraitCapCredPaddingCss()]}
368
+ creditStyles={[sideBySideCapCredColorCss()]}
369
+ />
370
+ }
371
+ {!isSlideshow(wcmIdList) && <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={[topperStyles.captionSideBySidePortrait, sideBySideCapCredColorCss(), sideBySidePortraitFloatCss()]} creditStyles={[sideBySideCapCredColorCss()]} />}
372
+ </figure>
373
+ </div>
374
+ </div>
375
+ );
230
376
  }
231
377
  }
232
378
 
233
379
  /** Calculate offsets for header-deck container based on the spreadsheet, for full-screen toppers only **/
234
380
  const calculatefullScreenOffsets = () => {
235
- var r = document.querySelector(':root');
381
+ let r = (typeof window != "undefined") ? document.querySelector(':root') : null;
236
382
 
237
383
  let verticalOffset = convertStringToNumber(HeaderDek_Vertical_Offset, (HeaderDek_Vertical_Position === "bottom"));
238
384
  let horizontalOffset = convertStringToNumber(HeaderDek_Horizontal_Offset, (HeaderDek_Horizontal_Position === "right"));
239
385
 
240
- r.style.setProperty('--headerDek-vertical-offset', verticalOffset + "px");
241
- r.style.setProperty('--headerDek-horizontal-offset', horizontalOffset + "px");
386
+ if (r) {
387
+ r.style.setProperty('--headerDek-vertical-offset', verticalOffset + "px");
388
+ r.style.setProperty('--headerDek-horizontal-offset', horizontalOffset + "px");
389
+ }
242
390
  }
243
391
 
244
392
  /** Convert offset values from the spreadsheet into Number type **/
@@ -257,6 +405,19 @@ const Topper2 = ({ settings, wcmData }) => {
257
405
  return num;
258
406
  }
259
407
 
408
+ /** Sets the background and text color in topper for side-by-side and side-by-side-portrait topper styles **/
409
+ const setBackgroundAndTextColor = () => {
410
+ let r = (typeof window != "undefined") ? document.querySelector(':root') : null;
411
+
412
+ if (r && Topper_Background_Color) {
413
+ r.style.setProperty('--container-background-color', Topper_Background_Color)
414
+ }
415
+
416
+ if (r && Inverted_Text_Color) {
417
+ r.style.setProperty('--side-by-side-text-color', Inverted_Text_Color)
418
+ }
419
+ }
420
+
260
421
  return (
261
422
  <TopperHtml />
262
423
  )