sfc-utils 1.2.5
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/README.md +47 -0
- package/blueconic.js +58 -0
- package/brands.js +180 -0
- package/copy/c2p_sheet.js +186 -0
- package/copy/docs.js +177 -0
- package/copy/googleauth.js +153 -0
- package/copy/sheets.js +151 -0
- package/fonts/albany.less +45 -0
- package/fonts/default.less +50 -0
- package/fonts/houston.less +51 -0
- package/fonts/sfc.less +57 -0
- package/footer.js +615 -0
- package/index.js +254 -0
- package/nav.js +247 -0
- package/package.json +14 -0
- package/settings.js +86 -0
- package/specialnav.js +128 -0
- package/topper.js +825 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# utils
|
|
2
|
+
Importable util functions for our templates
|
|
3
|
+
|
|
4
|
+
This package includes some helpful functions that you can use across SFC projects
|
|
5
|
+
|
|
6
|
+
The `copy` directory includes the standard ways we copy data from sheets and docs into project data directories. If the sheets command is giving you trouble with types in GraphQL, please note that you can force the command to return only strings by setting the third param to `true`:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
googleAuth(project, null, true)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`brands.js` has all the LESS settings that are specific to each brand. They are applied to the project in `gatsby-config.js` using the modifyVars config option. Since it's done at build time, we can only make brand distinctions by deployable market. We cannot use this same override strategy to make a style change between CTPost and New Haven Register, for example (since they are both CT market).
|
|
13
|
+
|
|
14
|
+
`nav.js` and `footer.js` both export the HTML needed for each markets' nav and footer settings. The styles controlling the actual look of these features exists at the project level.
|
|
15
|
+
|
|
16
|
+
`topper.js` implements a cool standardized set of topper options. Great for templates! If story_settings is configured with the right columns, this should be standard across projects.
|
|
17
|
+
|
|
18
|
+
`blueconic.js` handles the mapping of domains to the arbitrary string that the blueconic script lives on. Why is there an arbitrary string instead of a standard one? Maybe blueconic.houstonchronicle.com, for example? No one knows.
|
|
19
|
+
|
|
20
|
+
`settings.js` reconciles the project-config.json file and a story_settings sheet (if present) into a single standard object.
|
|
21
|
+
|
|
22
|
+
`specialnav.js` is a cool variant of `nav.js` that gives it a floating style for special projects, but works the same way.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
```
|
|
26
|
+
npm i git+https://github.com/sfchronicle/utils.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Import the functions
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
import { blendHDN, getSettings, appCheck } from 'sfc-utils'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Use them
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
let isApp = appCheck()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Troubleshooting
|
|
42
|
+
|
|
43
|
+
If you get a message about a new function not existing in your project, you might need to force a reinstall of this package. The best way to do that is to completely uninstall it and then reinstall it:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
npm uninstall sfc-utils && npm i git+https://github.com/sfchronicle/utils.git
|
|
47
|
+
```
|
package/blueconic.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
// Handle returning the right blueconic URL given the domain
|
|
3
|
+
let getBlueconic = function(domain){
|
|
4
|
+
let blueconicURL = "https://cdn.blueconic.net/hearst.js" // Default, but it's not what we want
|
|
5
|
+
let subdomain = ""
|
|
6
|
+
|
|
7
|
+
// Set subdomain based on URL
|
|
8
|
+
let apex = domain.replace('https://', '')
|
|
9
|
+
apex = apex.replace('www.', '')
|
|
10
|
+
switch(apex){
|
|
11
|
+
case "sfchronicle.com": subdomain = "q777"; break;
|
|
12
|
+
case "beaumontenterprise.com": subdomain = "z680"; break;
|
|
13
|
+
case "chron.com": subdomain = "u566"; break;
|
|
14
|
+
case "ctinsider.com": subdomain = "z492"; break;
|
|
15
|
+
case "ctpost.com": subdomain = "t810"; break;
|
|
16
|
+
case "darientimes.com": subdomain = "y820"; break;
|
|
17
|
+
case "expressnews.com": subdomain = "l936"; break;
|
|
18
|
+
case "fairfieldcitizenonline.com": subdomain = "z590"; break;
|
|
19
|
+
case "fuelfix.com": subdomain = "z929"; break;
|
|
20
|
+
case "greenwichtime.com": subdomain = "y900"; break;
|
|
21
|
+
case "hearstmediact.com": subdomain = "j158"; break;
|
|
22
|
+
case "hearstmediatx.com": subdomain = "f857"; break;
|
|
23
|
+
case "houstonchronicle.com": subdomain = "r541"; break;
|
|
24
|
+
case "lmtonline.com": subdomain = "l997"; break;
|
|
25
|
+
case "middletownpress.com": subdomain = "w982"; break;
|
|
26
|
+
case "milfordmirror.com": subdomain = "y752"; break;
|
|
27
|
+
case "mrt.com": subdomain = "x822"; break;
|
|
28
|
+
case "myplainview.com": subdomain = "u652"; break;
|
|
29
|
+
case "mysanantonio.com": subdomain = "d810"; break;
|
|
30
|
+
case "ncadvertiser.com": subdomain = "h353"; break;
|
|
31
|
+
case "newmilfordspectrum.com": subdomain = "w020"; break;
|
|
32
|
+
case "newstimes.com": subdomain = "w740"; break;
|
|
33
|
+
case "ourmidland.com": subdomain = "d276"; break;
|
|
34
|
+
case "registercitizen.com": subdomain = "j198"; break;
|
|
35
|
+
case "seattlepi.com": subdomain = "p593"; break;
|
|
36
|
+
case "sfgate.com": subdomain = "u927"; break;
|
|
37
|
+
case "sheltonherald.com": subdomain = "f164"; break;
|
|
38
|
+
case "stamfordadvocate.com": subdomain = "h559"; break;
|
|
39
|
+
case "thehour.com": subdomain = "f775"; break;
|
|
40
|
+
case "theintelligencer.com": subdomain = "s232"; break;
|
|
41
|
+
case "theridgefieldpress.com": subdomain = "y653"; break;
|
|
42
|
+
case "timesunion.com": subdomain = "n730"; break;
|
|
43
|
+
case "trumbulltimes.com": subdomain = "o398"; break;
|
|
44
|
+
case "westport-news.com": subdomain = "c993"; break;
|
|
45
|
+
case "wiltonbulletin.com": subdomain = "t570"; break;
|
|
46
|
+
case "yourconroenews.com": subdomain = "z211"; break;
|
|
47
|
+
case "nhregister.com": subdomain = "y738"; break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If we found a subdomain, swap in the new script
|
|
51
|
+
if (subdomain){
|
|
52
|
+
blueconicURL = `https://${subdomain}.${apex}/script.js`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return blueconicURL
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { getBlueconic }
|
package/brands.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
|
|
2
|
+
// Set brand object to override CSS based on this market's styles
|
|
3
|
+
let getBrands = function(market){
|
|
4
|
+
// We can add any Hearst global overrides here:
|
|
5
|
+
let defaultObj = {
|
|
6
|
+
styles: {
|
|
7
|
+
"@alert-red": "#d20000",
|
|
8
|
+
// Defaults
|
|
9
|
+
"@hed": '"Lora Bold", Georgia, serif',
|
|
10
|
+
"@hed-alt": '"Lora Regular", Georgia, serif',
|
|
11
|
+
"@serif-book": '"Lora Light", Georgia, serif',
|
|
12
|
+
"@serif-bold": '"Lora Light", Georgia, serif',
|
|
13
|
+
|
|
14
|
+
"@sans-light": '"Source Sans Pro Light", Helvetica, sans-serif',
|
|
15
|
+
"@sans-med": '"Source Sans Pro Regular", Helvetica, sans-serif',
|
|
16
|
+
"@sans-book": '"Source Sans Pro Regular", Helvetica, sans-serif',
|
|
17
|
+
"@sans-bold": '"Source Sans Pro Bold", Helvetica, sans-serif',
|
|
18
|
+
},
|
|
19
|
+
attributes: {
|
|
20
|
+
"subscribeLink": "https://www.hearst.com/newspapers?projects=true"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle market-specific styles here:
|
|
25
|
+
let marketObj = {
|
|
26
|
+
/* San Franicsco Chronicle */
|
|
27
|
+
SFC: {
|
|
28
|
+
styles: {
|
|
29
|
+
"@brand": "#26A0A5",
|
|
30
|
+
"@hed": '"Tiempos Headline Black", Georgia, serif',
|
|
31
|
+
"@hed-alt": '"Tiempos Headline Light", Georgia, serif',
|
|
32
|
+
"@serif-book": '"Tiempos Regular", Georgia, serif',
|
|
33
|
+
"@serif-bold": '"Tiempos Bold", Georgia, serif',
|
|
34
|
+
|
|
35
|
+
"@sans-light": '"National Light", Helvetica, sans-serif',
|
|
36
|
+
"@sans-med": '"National Medium", Helvetica, sans-serif',
|
|
37
|
+
"@sans-book": '"National Book", Helvetica, sans-serif',
|
|
38
|
+
"@sans-bold": '"National Bold", Helvetica, sans-serif',
|
|
39
|
+
},
|
|
40
|
+
attributes: {
|
|
41
|
+
"marketPrefix": "sf",
|
|
42
|
+
"siteName": "The San Francisco Chronicle",
|
|
43
|
+
"twitter": "sfchronicle",
|
|
44
|
+
"gaAccount": "UA-1616916-26",
|
|
45
|
+
"subscribeLink": "https://www.sfchronicle.com/subproject"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/* Houston Chronicle */
|
|
50
|
+
Houston: {
|
|
51
|
+
styles: {
|
|
52
|
+
"@brand": "#ff7500",
|
|
53
|
+
"@hed": '"Marr Sans Condensed Semibold", Georgia, serif',
|
|
54
|
+
"@hed-alt": '"Publico Headline Medium", Georgia, serif',
|
|
55
|
+
"@serif-book": '"Publico Text Roman", Georgia, serif',
|
|
56
|
+
"@serif-bold": '"Publico Text Bold", Georgia, serif',
|
|
57
|
+
|
|
58
|
+
"@sans-light": '"Marr Sans Regular", Helvetica, sans-serif',
|
|
59
|
+
"@sans-med": '"Marr Sans Regular", Helvetica, sans-serif',
|
|
60
|
+
"@sans-book": '"Marr Sans Regular", Helvetica, sans-serif',
|
|
61
|
+
"@sans-bold": '"Marr Sans Semibold", Helvetica, sans-serif',
|
|
62
|
+
},
|
|
63
|
+
attributes: {
|
|
64
|
+
"marketPrefix": "hc",
|
|
65
|
+
"siteName": "The Houston Chronicle",
|
|
66
|
+
"twitter": "HoustonChron",
|
|
67
|
+
"invert": true,
|
|
68
|
+
"gaAccount": "UA-1616916-24",
|
|
69
|
+
"subscribeLink": "https://www.houstonchronicle.com/subproject"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/* Albany Times Union */
|
|
74
|
+
Albany: {
|
|
75
|
+
styles: {
|
|
76
|
+
"@brand": "#006FBA",
|
|
77
|
+
"@hed": '"ChronicleDispCond-Black", Georgia, serif',
|
|
78
|
+
"@hed-alt": '"ChronicleDispCond-Roman", Georgia, serif',
|
|
79
|
+
"@serif-book": '"ChronicleTextG2-Roman", Georgia, serif',
|
|
80
|
+
"@serif-bold": '"ChronicleTextG2-Bold", Georgia, serif',
|
|
81
|
+
"@base-font-size": "20px",
|
|
82
|
+
|
|
83
|
+
"@sans-light": '"HelveticaNeue-Roman", Helvetica, sans-serif',
|
|
84
|
+
"@sans-med": '"HelveticaNeue-Roman", Helvetica, sans-serif',
|
|
85
|
+
"@sans-book": '"HelveticaNeue-Roman", Helvetica, sans-serif',
|
|
86
|
+
"@sans-bold": '"HelveticaNeue-HeavyCond", Helvetica, sans-serif',
|
|
87
|
+
},
|
|
88
|
+
attributes: {
|
|
89
|
+
"marketPrefix": "tu",
|
|
90
|
+
"siteName": "Times Union",
|
|
91
|
+
"twitter": "timesunion",
|
|
92
|
+
"invert": true,
|
|
93
|
+
"gaAccount": "UA-1616916-7",
|
|
94
|
+
"subscribeLink": "https://www.timesunion.com/subproject"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/* San Antonio Express News */
|
|
99
|
+
SanAntonio: {
|
|
100
|
+
styles: {
|
|
101
|
+
"@brand": "#ba141a",
|
|
102
|
+
},
|
|
103
|
+
attributes: {
|
|
104
|
+
"marketPrefix": "sa",
|
|
105
|
+
"siteName": "Express News",
|
|
106
|
+
"twitter": "ExpressNews",
|
|
107
|
+
"invert": true,
|
|
108
|
+
"gaAccount": "UA-1616916-27",
|
|
109
|
+
"subscribeLink": "https://www.expressnews.com/subproject"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/* Connecticut */
|
|
114
|
+
CT: {
|
|
115
|
+
styles: {
|
|
116
|
+
"@brand": "#900900",
|
|
117
|
+
},
|
|
118
|
+
attributes: {
|
|
119
|
+
"marketPrefix": "in",
|
|
120
|
+
"siteName": "CTInsider",
|
|
121
|
+
"twitter": "insider_ct",
|
|
122
|
+
"invert": true,
|
|
123
|
+
"gaAccount": "UA-1616916-99",
|
|
124
|
+
"subscribeLink": "https://www.ctinsider.com/subproject"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/* Connecticut */
|
|
129
|
+
Texcom: {
|
|
130
|
+
styles: {
|
|
131
|
+
"@brand": "#900900",
|
|
132
|
+
},
|
|
133
|
+
attributes: {
|
|
134
|
+
"marketPrefix": "texcom/mrt",
|
|
135
|
+
"siteName": "Midland Reporter-Telegram",
|
|
136
|
+
"twitter": "mwtnews",
|
|
137
|
+
"invert": true,
|
|
138
|
+
"gaAccount": "UA-1616916-99",
|
|
139
|
+
"subscribeLink": "/subproject"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/* Connecticut */
|
|
144
|
+
Midcom: {
|
|
145
|
+
styles: {
|
|
146
|
+
"@brand": "#900900",
|
|
147
|
+
},
|
|
148
|
+
attributes: {
|
|
149
|
+
"marketPrefix": "midcom/mid",
|
|
150
|
+
"siteName": "Midland Daily News",
|
|
151
|
+
"twitter": "MDN",
|
|
152
|
+
"invert": true,
|
|
153
|
+
"gaAccount": "UA-1616916-99",
|
|
154
|
+
"subscribeLink": "/subproject"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/* Misc */
|
|
159
|
+
TK: {
|
|
160
|
+
styles: {
|
|
161
|
+
"@brand": "#900900",
|
|
162
|
+
},
|
|
163
|
+
attributes: {
|
|
164
|
+
"marketPrefix": "tk",
|
|
165
|
+
"siteName": "Hearst Digital News",
|
|
166
|
+
"twitter": "Hearst",
|
|
167
|
+
"invert": true,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Combine global and market styles to return the final object
|
|
173
|
+
let combinedStyles = {
|
|
174
|
+
styles: Object.assign(defaultObj.styles, marketObj[market].styles),
|
|
175
|
+
attributes: Object.assign(defaultObj.attributes, marketObj[market].attributes)
|
|
176
|
+
}
|
|
177
|
+
return combinedStyles
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = { getBrands }
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
If confirmed through the startup prompt, this task will create a C2P sheet that handles the metadata for this project
|
|
4
|
+
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
var { google } = require("googleapis");
|
|
8
|
+
var api = google.sheets("v4");
|
|
9
|
+
var writeFile = require("write");
|
|
10
|
+
var authObj = require("./googleauth");
|
|
11
|
+
var fs = require("fs");
|
|
12
|
+
var os = require("os");
|
|
13
|
+
var path = require("path");
|
|
14
|
+
|
|
15
|
+
// Get config so we're ready to write it back out
|
|
16
|
+
// let config = fs.readFileSync('../project-config.json')
|
|
17
|
+
// let configData = JSON.parse(config)
|
|
18
|
+
|
|
19
|
+
let googleAuth = (configData) => {
|
|
20
|
+
var auth = null;
|
|
21
|
+
authObj
|
|
22
|
+
.authenticate({ fallback: false })
|
|
23
|
+
.then((resp) => {
|
|
24
|
+
auth = resp;
|
|
25
|
+
createSheet(auth, false, configData).catch(() => {
|
|
26
|
+
// If the first attempt failed, then make another req using the fallback
|
|
27
|
+
authObj.authenticate({ fallback: true }).then((resp) => {
|
|
28
|
+
auth = resp;
|
|
29
|
+
createSheet(auth, true, configData);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
// Failure if we fall back but there's no token
|
|
35
|
+
auth = authObj.task();
|
|
36
|
+
createSheet(auth, true, configData);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let createSheet = (auth, fallback, configData) => {
|
|
41
|
+
console.log(configData, "config data from create sheet");
|
|
42
|
+
return new Promise((resolveAll, rejectAll) => {
|
|
43
|
+
let gmail;
|
|
44
|
+
if (!fallback) {
|
|
45
|
+
// Only need gmail to share if it's not a fallback
|
|
46
|
+
try {
|
|
47
|
+
let credsLocation = path.join(os.homedir(), ".credentials.json");
|
|
48
|
+
let credsData = JSON.parse(fs.readFileSync(credsLocation, "utf-8"));
|
|
49
|
+
gmail = credsData.gmail;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
// If we had any trouble getting the gmail address, fail out
|
|
52
|
+
console.log(err);
|
|
53
|
+
rejectAll();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!gmail) {
|
|
57
|
+
// If gmail is blank, fail out
|
|
58
|
+
console.log("'gmail' is blank in credentials file!");
|
|
59
|
+
rejectAll();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const drive = google.drive({ version: "v3", auth });
|
|
65
|
+
const body = { title: "New C2P sheet" };
|
|
66
|
+
const templateId = "1juVrba9UNHVZV13e23o0gcwOw8YzX_1b7uxTI_jv7_4";
|
|
67
|
+
drive.files.copy(
|
|
68
|
+
{
|
|
69
|
+
fileId: templateId, // Base template
|
|
70
|
+
resource: body,
|
|
71
|
+
//'copyCollaborators': true // This doesn't work unfortunately
|
|
72
|
+
},
|
|
73
|
+
(err, resp) => {
|
|
74
|
+
console.log(resp);
|
|
75
|
+
// Make edits to the sheet to match the repo details
|
|
76
|
+
let resources = {
|
|
77
|
+
auth: auth,
|
|
78
|
+
spreadsheetId: resp.data.id,
|
|
79
|
+
resource: {
|
|
80
|
+
valueInputOption: "RAW",
|
|
81
|
+
data: [
|
|
82
|
+
{
|
|
83
|
+
range: "story_settings!A2",
|
|
84
|
+
values: [[configData.PROJECT.MARKET_KEY]],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
range: "story_settings!B2",
|
|
88
|
+
values: [[configData.PROJECT.SLUG]],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
range: "story_settings!C2",
|
|
92
|
+
values: [[configData.PROJECT.SUBFOLDER]],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
range: "system_settings!A2",
|
|
96
|
+
values: [[configData.PROJECT.SLUG]], // Repo is usually slug by default
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
api.spreadsheets.values.batchUpdate(
|
|
102
|
+
resources,
|
|
103
|
+
(updateErr, updateResp) => {
|
|
104
|
+
if (updateErr) {
|
|
105
|
+
console.log("Data Error :", updateErr);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Write out config with new sheet added
|
|
111
|
+
configData.GOOGLE_SHEETS = [resp.data.id];
|
|
112
|
+
writeFile("project-config.json", JSON.stringify(configData, null, 2));
|
|
113
|
+
|
|
114
|
+
// Okay, now give the team permissions
|
|
115
|
+
if (err) {
|
|
116
|
+
console.log("An error prevented the creation of this sheet!");
|
|
117
|
+
rejectAll();
|
|
118
|
+
} else {
|
|
119
|
+
// If this is a fallback, it was created with the user's account, so we shouldn't need to share it -- finish up
|
|
120
|
+
if (fallback) {
|
|
121
|
+
console.log(
|
|
122
|
+
"The above errors were a failure to create the new sheet with a service account! Your default token was used instead."
|
|
123
|
+
);
|
|
124
|
+
console.log(
|
|
125
|
+
"Your new C2P sheet should be live at this URL:",
|
|
126
|
+
`https://docs.google.com/spreadsheets/d/${resp.data.id}/edit`
|
|
127
|
+
);
|
|
128
|
+
resolveAll();
|
|
129
|
+
} else {
|
|
130
|
+
const permission = {
|
|
131
|
+
type: "user",
|
|
132
|
+
role: "writer",
|
|
133
|
+
emailAddress: gmail,
|
|
134
|
+
};
|
|
135
|
+
drive.permissions.create(
|
|
136
|
+
{
|
|
137
|
+
resource: permission,
|
|
138
|
+
fileId: resp.data.id, // Modify the created file
|
|
139
|
+
},
|
|
140
|
+
(permErr, permResp) => {
|
|
141
|
+
//console.log("permResp", permResp, permErr)
|
|
142
|
+
if (permErr) {
|
|
143
|
+
console.log("An error prevented the sharing of this sheet!");
|
|
144
|
+
rejectAll();
|
|
145
|
+
} else {
|
|
146
|
+
console.log(
|
|
147
|
+
"Your new C2P sheet should be live at this URL:",
|
|
148
|
+
`https://docs.google.com/spreadsheets/d/${resp.data.id}/edit`
|
|
149
|
+
);
|
|
150
|
+
console.log(
|
|
151
|
+
`NOTE: It may take a few seconds before your gmail, ${gmail}, has access`
|
|
152
|
+
);
|
|
153
|
+
// Now share with the service account
|
|
154
|
+
const permission2 = {
|
|
155
|
+
type: "user",
|
|
156
|
+
role: "writer",
|
|
157
|
+
emailAddress: "sfchronicle-gatsby@zinc-proton-250521.iam.gserviceaccount.com",
|
|
158
|
+
};
|
|
159
|
+
drive.permissions.create(
|
|
160
|
+
{
|
|
161
|
+
resource: permission2,
|
|
162
|
+
fileId: resp.data.id, // Modify the created file
|
|
163
|
+
},
|
|
164
|
+
(permErr, permResp) => {
|
|
165
|
+
if (permErr) {
|
|
166
|
+
console.log("An error prevented the sharing of this sheet with the service account!");
|
|
167
|
+
rejectAll();
|
|
168
|
+
} else {
|
|
169
|
+
console.log(
|
|
170
|
+
`Sheet also shared with service account for quick deploying!`
|
|
171
|
+
);
|
|
172
|
+
resolveAll();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
module.exports = { googleAuth };
|
package/copy/docs.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
* Uses the Google Drive API to parse Google Doc with ArchieML generate JSON file
|
|
5
|
+
* Docs must be shared with the service account email before they can be accessed with this task
|
|
6
|
+
* @param {object} config standard object from project-config.json or project.json
|
|
7
|
+
* @param {string} directory optional alternate path to directory in which to save the output
|
|
8
|
+
* @param {array} filenames optional array of objects with name and id key/values used to specify a filename for a specific doc
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var { google } = require("googleapis");
|
|
14
|
+
var async = require("async");
|
|
15
|
+
var url = require("url");
|
|
16
|
+
var writeFile = require("write");
|
|
17
|
+
var authObj = require("./googleauth");
|
|
18
|
+
var archieml = require("archieml");
|
|
19
|
+
var htmlparser = require("htmlparser2");
|
|
20
|
+
var Entities = require("html-entities").AllHtmlEntities;
|
|
21
|
+
|
|
22
|
+
let googleAuth = (config, directory = null, filenames = null) => {
|
|
23
|
+
var auth = null;
|
|
24
|
+
authObj
|
|
25
|
+
.authenticate({ fallback: false })
|
|
26
|
+
.then((resp) => {
|
|
27
|
+
auth = resp;
|
|
28
|
+
grabDocs(auth, config, directory, filenames).catch(() => {
|
|
29
|
+
// If the first attempt failed, then make another req using the fallback
|
|
30
|
+
authObj.authenticate({ fallback: true }).then((resp) => {
|
|
31
|
+
auth = resp;
|
|
32
|
+
grabDocs(auth, config, directory, filenames);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
})
|
|
36
|
+
.catch(() => {
|
|
37
|
+
// Failure if we fall back but there's no token
|
|
38
|
+
auth = authObj.task();
|
|
39
|
+
grabDocs(auth, config, directory, filenames);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
var camelCase = function (str) {
|
|
44
|
+
return str.replace(/[^\w]+(\w)/g, function (all, match) {
|
|
45
|
+
return match.toUpperCase();
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* Large document sets may hit rate limits; you can find details on your quota at:
|
|
51
|
+
* https://console.developers.google.com/apis/api/drive.googleapis.com/quotas?project=<project>
|
|
52
|
+
* where <project> is the project you authenticated with using `grunt google-auth`
|
|
53
|
+
*/
|
|
54
|
+
let grabDocs = (auth, config, directory = null, filenames = null) => {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
var drive = google.drive({
|
|
57
|
+
auth,
|
|
58
|
+
version: "v3",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
async.eachLimit(
|
|
62
|
+
config.GOOGLE_DOCS,
|
|
63
|
+
3, // adjust this up or down based on rate limiting
|
|
64
|
+
async function (fileId) {
|
|
65
|
+
var meta = await drive.files
|
|
66
|
+
.get({
|
|
67
|
+
fileId,
|
|
68
|
+
})
|
|
69
|
+
.catch(() => {
|
|
70
|
+
// Maybe service account we doesn't have permissions -- try with normal token
|
|
71
|
+
reject();
|
|
72
|
+
});
|
|
73
|
+
if (!meta) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var filename = meta.data.name.replace(/\s+/g, "_");
|
|
78
|
+
|
|
79
|
+
//find the appropriate filename if filenames arr is included in params
|
|
80
|
+
if (filenames) {
|
|
81
|
+
let match = filenames.find((f) => f.id === fileId);
|
|
82
|
+
filename = match.name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//specify directory if included in params
|
|
86
|
+
directory = directory || "src/data/";
|
|
87
|
+
|
|
88
|
+
//build file path
|
|
89
|
+
var file_path = `${directory}${filename}.json`;
|
|
90
|
+
|
|
91
|
+
drive.files.export(
|
|
92
|
+
{ fileId: fileId, mimeType: "text/html" },
|
|
93
|
+
function (err, docHtml) {
|
|
94
|
+
var handler = new htmlparser.DomHandler(function (error, dom) {
|
|
95
|
+
var tagHandlers = {
|
|
96
|
+
_base: function (tag) {
|
|
97
|
+
var str = "";
|
|
98
|
+
tag.children.forEach(function (child) {
|
|
99
|
+
if ((func = tagHandlers[child.name || child.type]))
|
|
100
|
+
str += func(child);
|
|
101
|
+
});
|
|
102
|
+
return str;
|
|
103
|
+
},
|
|
104
|
+
text: function (textTag) {
|
|
105
|
+
return textTag.data;
|
|
106
|
+
},
|
|
107
|
+
span: function (spanTag) {
|
|
108
|
+
return tagHandlers._base(spanTag);
|
|
109
|
+
},
|
|
110
|
+
p: function (pTag) {
|
|
111
|
+
return tagHandlers._base(pTag) + "\n";
|
|
112
|
+
},
|
|
113
|
+
a: function (aTag) {
|
|
114
|
+
var href = aTag.attribs.href;
|
|
115
|
+
if (href === undefined) return "";
|
|
116
|
+
|
|
117
|
+
// extract real URLs from Google's tracking
|
|
118
|
+
// from: http://www.google.com/url?q=http%3A%2F%2Fwww.sfchronicle.com...
|
|
119
|
+
// to: http://www.sfchronicle.com...
|
|
120
|
+
if (
|
|
121
|
+
aTag.attribs.href &&
|
|
122
|
+
url.parse(aTag.attribs.href, true).query &&
|
|
123
|
+
url.parse(aTag.attribs.href, true).query.q
|
|
124
|
+
) {
|
|
125
|
+
href = url.parse(aTag.attribs.href, true).query.q;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var str = '<a href="' + href + '">';
|
|
129
|
+
str += tagHandlers._base(aTag);
|
|
130
|
+
str += "</a>";
|
|
131
|
+
return str;
|
|
132
|
+
},
|
|
133
|
+
li: function (tag) {
|
|
134
|
+
return "* " + tagHandlers._base(tag) + "\n";
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
["ul", "ol"].forEach(function (tag) {
|
|
139
|
+
tagHandlers[tag] = tagHandlers.span;
|
|
140
|
+
});
|
|
141
|
+
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(function (tag) {
|
|
142
|
+
tagHandlers[tag] = tagHandlers.p;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
var body = dom[0].children[1];
|
|
146
|
+
var parsedText = tagHandlers._base(body);
|
|
147
|
+
|
|
148
|
+
// Convert html entities into the characters as they exist in the google doc
|
|
149
|
+
var entities = new Entities();
|
|
150
|
+
parsedText = entities.decode(parsedText);
|
|
151
|
+
|
|
152
|
+
// Remove smart quotes from inside tags
|
|
153
|
+
parsedText = parsedText.replace(/<[^<>]*>/g, function (match) {
|
|
154
|
+
return match.replace(/”|“/g, '"').replace(/‘|’/g, "'");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Parse with Archie
|
|
158
|
+
var parsed = archieml.load(parsedText);
|
|
159
|
+
|
|
160
|
+
// Create the file
|
|
161
|
+
writeFile(file_path, JSON.stringify(parsed, null, 2));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
var parser = new htmlparser.Parser(handler);
|
|
165
|
+
parser.write(docHtml.data);
|
|
166
|
+
parser.done();
|
|
167
|
+
console.log("\x1b[32m", file_path + " created successfully");
|
|
168
|
+
// Exit the promise
|
|
169
|
+
resolve(true);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
module.exports = { googleAuth };
|