sfc-utils 1.3.64 → 1.3.66

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.
package/brands2.js CHANGED
@@ -39,7 +39,9 @@ let getBrands2 = function(market){
39
39
  "twitter": "sfchronicle",
40
40
  "gaAccount": "UA-1616916-26",
41
41
  "subscribeLink": "https://www.sfchronicle.com/subproject",
42
- "sailCustomer": "fca2a0390286f0e53120a668534d9529"
42
+ "sailCustomer": "fca2a0390286f0e53120a668534d9529",
43
+ "sailSiteName": "san-francisco-chronicle",
44
+ "siteId": 35,
43
45
  }
44
46
  },
45
47
 
@@ -61,7 +63,9 @@ let getBrands2 = function(market){
61
63
  "invert": true,
62
64
  "gaAccount": "UA-1616916-24",
63
65
  "subscribeLink": "https://www.houstonchronicle.com/subproject",
64
- "sailCustomer": "48e30b5083cf6bf47c519651453c9e8a"
66
+ "sailCustomer": "48e30b5083cf6bf47c519651453c9e8a",
67
+ "sailSiteName": "houston-chronicle",
68
+ "siteId": 33
65
69
  }
66
70
  },
67
71
 
@@ -83,7 +87,9 @@ let getBrands2 = function(market){
83
87
  "invert": true,
84
88
  "gaAccount": "UA-1616916-7",
85
89
  "subscribeLink": "https://www.timesunion.com/subproject",
86
- "sailCustomer": "5bb9eee089bdc2e27cbd265535ad1f90"
90
+ "sailCustomer": "5bb9eee089bdc2e27cbd265535ad1f90",
91
+ "sailSiteName": "times-union",
92
+ "siteId": 3
87
93
  }
88
94
  },
89
95
 
@@ -100,7 +106,9 @@ let getBrands2 = function(market){
100
106
  "invert": true,
101
107
  "gaAccount": "UA-1616916-27",
102
108
  "subscribeLink": "https://www.expressnews.com/subproject",
103
- "sailCustomer": "aec52c4681ed63b5beab139a2584f0d4"
109
+ "sailCustomer": "aec52c4681ed63b5beab139a2584f0d4",
110
+ "sailSiteName": "san-antonio-express-news",
111
+ "siteId": 36
104
112
  }
105
113
  },
106
114
 
@@ -117,7 +125,9 @@ let getBrands2 = function(market){
117
125
  "invert": true,
118
126
  "gaAccount": "UA-1616916-99",
119
127
  "subscribeLink": "https://www.ctinsider.com/subproject",
120
- "sailCustomer": "9b21160afa226c9f84d27b47c3d52364"
128
+ "sailCustomer": "9b21160afa226c9f84d27b47c3d52364",
129
+ "sailSiteName": "ct-insider",
130
+ "siteId": 61
121
131
  }
122
132
  },
123
133
 
@@ -134,7 +144,9 @@ let getBrands2 = function(market){
134
144
  "invert": true,
135
145
  "gaAccount": "UA-1616916-99",
136
146
  "subscribeLink": "/subproject",
137
- "sailCustomer": "4a181de0b63a131cf27f8ea9485e5e1c"
147
+ "sailCustomer": "4a181de0b63a131cf27f8ea9485e5e1c",
148
+ "sailSiteName": "midland-reporter-telegram",
149
+ "siteId": 57
138
150
  }
139
151
  },
140
152
  Midcom: {
@@ -149,7 +161,9 @@ let getBrands2 = function(market){
149
161
  "invert": true,
150
162
  "gaAccount": "UA-1616916-99",
151
163
  "subscribeLink": "/subproject",
152
- "sailCustomer": "f3dc0abb53995f957783e76eff3ef01e"
164
+ "sailCustomer": "4a181de0b63a131cf27f8ea9485e5e1c",
165
+ "sailSiteName": "midland-daily-news",
166
+ "siteId": 61
153
167
  }
154
168
  },
155
169
  Conroe: {
@@ -164,7 +178,9 @@ let getBrands2 = function(market){
164
178
  "invert": true,
165
179
  "gaAccount": "UA-1616916-99",
166
180
  "subscribeLink": "https://www.yourconroenews.com/subproject",
167
- "sailCustomer": "4a181de0b63a131cf27f8ea9485e5e1c"
181
+ "sailCustomer": "4a181de0b63a131cf27f8ea9485e5e1c",
182
+ "sailSiteName": "the-courier-of-montgomery-county",
183
+ "siteId": 68
168
184
  }
169
185
  },
170
186
 
@@ -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,53 @@
1
+ import React, { useEffect, useState, useRef } from "react"
2
+
3
+ const CaptionCreditSlideshow = ({ captionList, creditList, extraStyles }) => {
4
+ let captionCss = ["topper-image", "caption"];
5
+ if (extraStyles) captionCss = captionCss.concat(extraStyles);
6
+
7
+ const [index, setIndex] = useState(0);
8
+ const timeoutRef = useRef(null);
9
+
10
+ function resetTimeout() {
11
+ if (timeoutRef.current) {
12
+ clearTimeout(timeoutRef.current);
13
+ }
14
+ }
15
+
16
+ useEffect(() => {
17
+ resetTimeout();
18
+ timeoutRef.current = setTimeout(() => {
19
+ // Set current image index
20
+ setIndex((prevIndex) => (prevIndex === captionList.length - 1) ? 0 : prevIndex + 1);
21
+ },
22
+ 4000 // Delay for switching images
23
+ );
24
+
25
+ return () => {
26
+ resetTimeout();
27
+ };
28
+ }, [index]);
29
+
30
+ const hasCaption = (captionList.length > 0)
31
+ const hasCredit = (creditList.length > 0)
32
+ return (
33
+ <>
34
+ {hasCaption && hasCredit && (
35
+ <figcaption className={captionCss.join(' ')}>
36
+ {captionList[index]} <span className="credit">{creditList[index]}</span>
37
+ </figcaption>
38
+ )}
39
+ {!hasCaption && hasCredit && (
40
+ <figcaption className={captionCss.join(' ')}>
41
+ <span className="credit">{creditList[index]}</span>
42
+ </figcaption>
43
+ )}
44
+ {hasCaption && !hasCredit && (
45
+ <figcaption className={captionCss.join(' ')}>
46
+ {captionList[index]}
47
+ </figcaption>
48
+ )}
49
+ </>
50
+ )
51
+ }
52
+
53
+ export default CaptionCreditSlideshow
@@ -0,0 +1,95 @@
1
+ import React, { useEffect, useState, useRef } from "react"
2
+ import TopperImage from "../topperimage.mjs"
3
+ import * as styles from "../../styles/modules/imageslideshow.module.less"
4
+ import * as imageStyles from "../../styles/modules/topperimage.module.less"
5
+ import { TransitionGroup, CSSTransition } from "react-transition-group"
6
+
7
+ const ImageSlideshow = ({ wcmData, imageList, altList, topperStyle }) => {
8
+ const [index, setIndex] = useState(0);
9
+ const timeoutRef = useRef(null);
10
+
11
+ function resetTimeout() {
12
+ if (timeoutRef.current) {
13
+ clearTimeout(timeoutRef.current);
14
+ }
15
+ }
16
+
17
+ useEffect(() => {
18
+ resetTimeout();
19
+ timeoutRef.current = setTimeout(() => {
20
+ // Set current image index
21
+ setIndex((prevIndex) => (prevIndex === imageList.length - 1) ? 0 : prevIndex + 1);
22
+ },
23
+ 4000 // Delay for switching images
24
+ );
25
+
26
+ return () => {
27
+ resetTimeout();
28
+ };
29
+ }, [index]);
30
+
31
+ const getContainerClass = () => {
32
+ switch (topperStyle) {
33
+ case "stacked":
34
+ return styles.containerStacked;
35
+ case "full-screen":
36
+ return styles.containerStacked;
37
+ default:
38
+ return "";
39
+ }
40
+ }
41
+
42
+ const getWrapperClass = () => {
43
+ switch (topperStyle) {
44
+ case "stacked":
45
+ return styles.imageWrapperStacked;
46
+ case "full-screen":
47
+ return styles.imageWrapperFullscreen;
48
+ default:
49
+ return "";
50
+ }
51
+ }
52
+
53
+ const getImageClassList = () => {
54
+ switch (topperStyle) {
55
+ case "stacked":
56
+ return [imageStyles.cForceAspectRatio];
57
+ case "full-screen":
58
+ return [imageStyles.cImgSlideshowFullscreen];
59
+ default:
60
+ return [""];
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Overall CSS architecture:
66
+ * <Container>
67
+ * <Wrapper>
68
+ * <Image/>
69
+ * </Wrapper>
70
+ * </Container>
71
+ */
72
+ return (
73
+ <div className={getContainerClass()}>
74
+ <TransitionGroup>
75
+ <CSSTransition
76
+ key={index}
77
+ timeout={4000}
78
+ classNames={{
79
+ enter: styles.fadeEnter,
80
+ enterActive: styles.fadeEnterActive,
81
+ exit: styles.fadeExit,
82
+ exitActive: styles.fadeExitActive,
83
+ exitDone: styles.fadeExitDone
84
+ }}
85
+ >
86
+ <div className={getWrapperClass()}>
87
+ <TopperImage wcm={imageList[index]} alt={altList[index]} wcmData={wcmData} overrideCssList={getImageClassList()} />
88
+ </div>
89
+ </CSSTransition>
90
+ </TransitionGroup>
91
+ </div>
92
+ )
93
+ }
94
+
95
+ export default ImageSlideshow