@reddoorla/maintenance 0.47.0 → 0.49.0
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/dist/cli/bin.js +334 -235
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +7 -3
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +254 -208
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -161,6 +161,9 @@ function parseNotifyRouting(raw) {
|
|
|
161
161
|
function isDashboardVisible(site) {
|
|
162
162
|
return site.status !== null && ACTIVE_STATUSES.has(site.status);
|
|
163
163
|
}
|
|
164
|
+
function toFrequency(raw) {
|
|
165
|
+
return typeof raw === "string" && FREQUENCIES.includes(raw) ? raw : "None";
|
|
166
|
+
}
|
|
164
167
|
function mapRow(rec) {
|
|
165
168
|
const f = rec.fields;
|
|
166
169
|
const attachments = f["Header image"] ?? [];
|
|
@@ -171,8 +174,8 @@ function mapRow(rec) {
|
|
|
171
174
|
url: String(f["url"] ?? ""),
|
|
172
175
|
status: f["Status"] ?? null,
|
|
173
176
|
pointOfContact: f["point of contact"] ?? null,
|
|
174
|
-
maintenanceFreq: f["maintenence freq"]
|
|
175
|
-
testingFreq: f["testing freq"]
|
|
177
|
+
maintenanceFreq: toFrequency(f["maintenence freq"]),
|
|
178
|
+
testingFreq: toFrequency(f["testing freq"]),
|
|
176
179
|
maintenanceDay: f["maintenance day"] ?? null,
|
|
177
180
|
testingDay: f["testing day"] ?? null,
|
|
178
181
|
ga4PropertyId: f["GA4 property ID"] ?? null,
|
|
@@ -295,7 +298,7 @@ async function updateLaunched(base, recordId, at) {
|
|
|
295
298
|
const fields = { Status: "maintenance", "Launched at": at };
|
|
296
299
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
297
300
|
}
|
|
298
|
-
var WEBSITES_TABLE, ACTIVE_STATUSES;
|
|
301
|
+
var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES;
|
|
299
302
|
var init_websites = __esm({
|
|
300
303
|
"src/reports/airtable/websites.ts"() {
|
|
301
304
|
"use strict";
|
|
@@ -304,6 +307,7 @@ var init_websites = __esm({
|
|
|
304
307
|
"maintenance",
|
|
305
308
|
"launch period"
|
|
306
309
|
]);
|
|
310
|
+
FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
|
|
307
311
|
}
|
|
308
312
|
});
|
|
309
313
|
|
|
@@ -835,15 +839,10 @@ var init_copy = __esm({
|
|
|
835
839
|
],
|
|
836
840
|
announceHeading: "YOUR ONGOING SITE CARE",
|
|
837
841
|
announceBody: "We've completed a full test of your site and set it up for ongoing care to keep it fast, secure, and healthy. Here's what you can expect from us going forward:",
|
|
838
|
-
announceCadenceHeading: "WHAT TO EXPECT",
|
|
839
|
-
announceTestingLabel: "Full site testing",
|
|
840
|
-
announceMaintenanceLabel: "Routine maintenance",
|
|
841
|
-
announcePreviewLabel: "From your latest full site test:",
|
|
842
|
-
announceScoreNote: "These are independent Google Lighthouse scores, each out of 100 \u2014 higher is better.",
|
|
843
842
|
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
844
843
|
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
845
844
|
announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
|
|
846
|
-
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just
|
|
845
|
+
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just let us know."
|
|
847
846
|
};
|
|
848
847
|
}
|
|
849
848
|
});
|
|
@@ -917,65 +916,124 @@ var init_html = __esm({
|
|
|
917
916
|
}
|
|
918
917
|
});
|
|
919
918
|
|
|
920
|
-
// src/reports/
|
|
921
|
-
function fmtDate(d) {
|
|
922
|
-
if (!d || Number.isNaN(d.getTime())) return "";
|
|
923
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
924
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
925
|
-
const yyyy = d.getUTCFullYear();
|
|
926
|
-
return `${mm}.${dd}.${yyyy}`;
|
|
927
|
-
}
|
|
919
|
+
// src/reports/email-sections.ts
|
|
928
920
|
function fmtUsers(n) {
|
|
929
921
|
return n.toLocaleString("en-US");
|
|
930
922
|
}
|
|
931
|
-
function
|
|
932
|
-
return
|
|
923
|
+
function checklistRowsSection(rows, opts) {
|
|
924
|
+
return rows.map((label, i) => {
|
|
925
|
+
const isLast = i === rows.length - 1;
|
|
926
|
+
const border = isLast ? "" : ` border-bottom="solid ${BORDER} 1px"`;
|
|
927
|
+
const lastPad = isLast ? ` padding-bottom="${opts.lastPaddingBottom}"` : "";
|
|
928
|
+
return `
|
|
929
|
+
<mj-section background-color="${opts.background}" padding="0px"${lastPad}>
|
|
930
|
+
<mj-group>
|
|
931
|
+
<mj-column padding-left="0px" width="90%"${border}>
|
|
932
|
+
<mj-text height="25px" padding-left="0px" color="${GREY}" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
933
|
+
</mj-column>
|
|
934
|
+
<mj-column width="10%"${border} padding-top="15px">
|
|
935
|
+
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
936
|
+
</mj-column>
|
|
937
|
+
</mj-group>
|
|
938
|
+
</mj-section>`;
|
|
939
|
+
}).join("");
|
|
940
|
+
}
|
|
941
|
+
function lighthouseScoresSection(lighthouse2, opts = {}) {
|
|
942
|
+
const background = opts.background ?? "#F4F4F4";
|
|
943
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
944
|
+
const labelTop = opts.pad ?? "55px";
|
|
945
|
+
const footnoteBottom = opts.pad ? "0px" : "36px";
|
|
946
|
+
const rows = LIGHTHOUSE_ROWS.map(
|
|
947
|
+
({ label, key, range }, i) => `
|
|
948
|
+
<mj-text color="${RED}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
949
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400" padding-top="0px">${lighthouse2[key]}</mj-text>
|
|
950
|
+
<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">${range}</mj-text>${i < LIGHTHOUSE_ROWS.length - 1 ? `
|
|
951
|
+
<mj-divider border-width="1px" border-style="solid" border-color="${BORDER}" padding="0" />` : ""}`
|
|
952
|
+
).join("");
|
|
953
|
+
return `
|
|
954
|
+
<mj-section background-color="${background}"${sectionPad}>
|
|
955
|
+
<mj-column>
|
|
956
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">LIGHTHOUSE SCORES*</mj-text>${rows}
|
|
957
|
+
<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="${footnoteBottom}" line-height="20px">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>
|
|
958
|
+
</mj-column>
|
|
959
|
+
</mj-section>`;
|
|
933
960
|
}
|
|
934
961
|
function analyticsTrendLine(cur, prev) {
|
|
935
962
|
if (cur === void 0 || prev === void 0) {
|
|
936
|
-
return
|
|
963
|
+
return trendLine(TREND_NEUTRAL, `Last Period: ${prev !== void 0 ? fmtUsers(prev) : "\u2014"}`);
|
|
937
964
|
}
|
|
938
965
|
if (prev === 0) {
|
|
939
|
-
return cur > 0 ?
|
|
966
|
+
return cur > 0 ? trendLine(TREND_UP, "\u25B2 New this period (0 last period)") : trendLine(TREND_NEUTRAL, "Last Period: 0");
|
|
940
967
|
}
|
|
941
968
|
const pct = Math.round((cur - prev) / prev * 100);
|
|
942
969
|
const range = `(${fmtUsers(prev)} \u2192 ${fmtUsers(cur)})`;
|
|
943
|
-
if (pct > 0) return
|
|
944
|
-
if (pct < 0) return
|
|
945
|
-
return
|
|
970
|
+
if (pct > 0) return trendLine(TREND_UP, `\u25B2 ${pct}% vs last period ${range}`);
|
|
971
|
+
if (pct < 0) return trendLine(TREND_NEUTRAL, `\u25BC ${Math.abs(pct)}% vs last period ${range}`);
|
|
972
|
+
return trendLine(TREND_NEUTRAL, `No change vs last period (${fmtUsers(prev)})`);
|
|
973
|
+
}
|
|
974
|
+
function trendLine(color, text) {
|
|
975
|
+
return `<mj-text color="${color}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${text}</mj-text>`;
|
|
976
|
+
}
|
|
977
|
+
function footnoteLine(text) {
|
|
978
|
+
return `<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">${text}</mj-text>`;
|
|
979
|
+
}
|
|
980
|
+
function analyticsSection(opts) {
|
|
981
|
+
const users = opts.current !== void 0 ? fmtUsers(opts.current) : "\u2014";
|
|
982
|
+
const body = (opts.bodyLines ?? []).map((l) => trendLine(TREND_NEUTRAL, l)).join("\n ");
|
|
983
|
+
const footnotes = (opts.footnoteLines ?? []).map(footnoteLine).join("\n ");
|
|
984
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
985
|
+
const labelTop = opts.pad ?? "75px";
|
|
986
|
+
return `
|
|
987
|
+
<mj-section background-color="${opts.background}"${sectionPad}>
|
|
988
|
+
<mj-column>
|
|
989
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">ANALYTICS</mj-text>
|
|
990
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400">${users} Users</mj-text>
|
|
991
|
+
${analyticsTrendLine(opts.current, opts.previous)}
|
|
992
|
+
${body}
|
|
993
|
+
${footnotes}
|
|
994
|
+
</mj-column>
|
|
995
|
+
</mj-section>`;
|
|
996
|
+
}
|
|
997
|
+
var escapeXml, RED, GREY, BORDER, TREND_UP, TREND_NEUTRAL, CHECK_PNG, LIGHTHOUSE_ROWS;
|
|
998
|
+
var init_email_sections = __esm({
|
|
999
|
+
"src/reports/email-sections.ts"() {
|
|
1000
|
+
"use strict";
|
|
1001
|
+
init_html();
|
|
1002
|
+
init_assets();
|
|
1003
|
+
escapeXml = escapeHtml;
|
|
1004
|
+
RED = "#C00";
|
|
1005
|
+
GREY = "#757575";
|
|
1006
|
+
BORDER = "#CCCCCC";
|
|
1007
|
+
TREND_UP = "#2E7D32";
|
|
1008
|
+
TREND_NEUTRAL = GREY;
|
|
1009
|
+
CHECK_PNG = `cid:${CHECK_CID}`;
|
|
1010
|
+
LIGHTHOUSE_ROWS = [
|
|
1011
|
+
{ label: "Performance", key: "performance", range: "Acceptable 50\u201389 // Ideal 90\u2013100" },
|
|
1012
|
+
{ label: "Readability (A11y)", key: "accessibility", range: "Acceptable 80\u201399 // Ideal 100" },
|
|
1013
|
+
{ label: "Best Practices", key: "bestPractices", range: "Acceptable 60\u201379 // Ideal 80\u2013100" },
|
|
1014
|
+
{ label: "Site Structure", key: "seo", range: "Acceptable 50\u201389 // Ideal 90\u2013100" }
|
|
1015
|
+
];
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
// src/reports/maintenance-email/template.ts
|
|
1020
|
+
function fmtDate(d) {
|
|
1021
|
+
if (!d || Number.isNaN(d.getTime())) return "";
|
|
1022
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1023
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
1024
|
+
const yyyy = d.getUTCFullYear();
|
|
1025
|
+
return `${mm}.${dd}.${yyyy}`;
|
|
946
1026
|
}
|
|
947
1027
|
function maintenanceChecksSection(copy, searchPosition) {
|
|
948
1028
|
const googleLabel = searchPosition !== void 0 ? `Page 1 Google Result (#${searchPosition})` : copy.maintenanceChecks[3] ?? "";
|
|
949
1029
|
const rows = copy.maintenanceChecks.map((label, i) => i === 3 ? googleLabel : label);
|
|
950
|
-
return rows
|
|
951
|
-
(label, i) => `
|
|
952
|
-
<mj-section background-color="white" padding="0px"${i === rows.length - 1 ? ' padding-bottom="36px"' : ""}>
|
|
953
|
-
<mj-group>
|
|
954
|
-
<mj-column padding-left="0px" width="90%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
955
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
956
|
-
</mj-column>
|
|
957
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
958
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
959
|
-
</mj-column>
|
|
960
|
-
</mj-group>
|
|
961
|
-
</mj-section>`
|
|
962
|
-
).join("");
|
|
1030
|
+
return checklistRowsSection(rows, { background: "white", lastPaddingBottom: "36px" });
|
|
963
1031
|
}
|
|
964
1032
|
function testingChecklistSection(copy) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
<mj-group>
|
|
970
|
-
<mj-column width="90%" padding-left="0px"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
971
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
972
|
-
</mj-column>
|
|
973
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
974
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
975
|
-
</mj-column>
|
|
976
|
-
</mj-group>
|
|
977
|
-
</mj-section>`
|
|
978
|
-
).join("");
|
|
1033
|
+
return checklistRowsSection(copy.testingChecklist, {
|
|
1034
|
+
background: "#F4F4F4",
|
|
1035
|
+
lastPaddingBottom: "60px"
|
|
1036
|
+
});
|
|
979
1037
|
}
|
|
980
1038
|
function maintenanceTestingPlaceholder(lastTested) {
|
|
981
1039
|
return `
|
|
@@ -995,7 +1053,7 @@ function testingIntroSection(copy) {
|
|
|
995
1053
|
<mj-section background-color="#F4F4F4">
|
|
996
1054
|
<mj-column>
|
|
997
1055
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">TESTING</mj-text>
|
|
998
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1056
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.testingIntro)}</mj-text>
|
|
999
1057
|
</mj-column>
|
|
1000
1058
|
</mj-section>`;
|
|
1001
1059
|
}
|
|
@@ -1003,8 +1061,8 @@ function commentarySection(text, copy) {
|
|
|
1003
1061
|
return `
|
|
1004
1062
|
<mj-section background-color="white">
|
|
1005
1063
|
<mj-column>
|
|
1006
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${
|
|
1007
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1064
|
+
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${escapeXml2(copy.notesHeader)}</mj-text>
|
|
1065
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(text).replace(/\r\n?|\n/g, "<br/>")}</mj-text>
|
|
1008
1066
|
</mj-column>
|
|
1009
1067
|
</mj-section>`;
|
|
1010
1068
|
}
|
|
@@ -1013,8 +1071,8 @@ function hasHeaderDims(data) {
|
|
|
1013
1071
|
}
|
|
1014
1072
|
function headerImageTag(data) {
|
|
1015
1073
|
const src = `cid:${data.headerImageCid}`;
|
|
1016
|
-
const alt = `${
|
|
1017
|
-
const href = isHttpUrl(data.siteUrl) ?
|
|
1074
|
+
const alt = `${escapeXml2(data.siteName)} maintenance report`;
|
|
1075
|
+
const href = isHttpUrl(data.siteUrl) ? escapeXml2(data.siteUrl) : "#";
|
|
1018
1076
|
if (hasHeaderDims(data)) {
|
|
1019
1077
|
return `<mj-image href="${href}" src="${src}" alt="${alt}" width="${data.headerWidth}px" css-class="rd-header" container-background-color="${data.headerBgColor}" />`;
|
|
1020
1078
|
}
|
|
@@ -1027,7 +1085,7 @@ function headerStyleBlock(data) {
|
|
|
1027
1085
|
function buildMjml(data) {
|
|
1028
1086
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1029
1087
|
const isTesting = data.reportType === "Testing";
|
|
1030
|
-
const previewText = `Checked up on ${
|
|
1088
|
+
const previewText = `Checked up on ${escapeXml2(data.siteName)}`;
|
|
1031
1089
|
return `<mjml>
|
|
1032
1090
|
<mj-head>
|
|
1033
1091
|
<mj-attributes>
|
|
@@ -1049,89 +1107,65 @@ function buildMjml(data) {
|
|
|
1049
1107
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">COMPLETED ON</mj-text>
|
|
1050
1108
|
<mj-text color="#C00" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
1051
1109
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">MAINTENANCE CHECKS</mj-text>
|
|
1052
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1110
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.maintenanceIntro)}</mj-text>
|
|
1053
1111
|
</mj-column>
|
|
1054
1112
|
</mj-section>
|
|
1055
1113
|
${maintenanceChecksSection(copy, data.searchPosition)}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability (A11y)</mj-text>
|
|
1064
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.accessibility}</mj-text>
|
|
1065
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 80\u201399 // Ideal 100</mj-text>
|
|
1066
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1067
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Best Practices</mj-text>
|
|
1068
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.bestPractices}</mj-text>
|
|
1069
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 60\u201379 // Ideal 80\u201392</mj-text>
|
|
1070
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1071
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Site Structure</mj-text>
|
|
1072
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.seo}</mj-text>
|
|
1073
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 50\u201389 // Ideal 90\u2013100</mj-text>
|
|
1074
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>
|
|
1075
|
-
</mj-column>
|
|
1076
|
-
</mj-section>
|
|
1077
|
-
<mj-section background-color="white">
|
|
1078
|
-
<mj-column>
|
|
1079
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">ANALYTICS</mj-text>
|
|
1080
|
-
<mj-text color="#C00" font-size="44px" font-weight="400">${data.gaUsersCurrent !== void 0 ? fmtUsers(data.gaUsersCurrent) : "\u2014"} Users</mj-text>
|
|
1081
|
-
${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}
|
|
1082
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">${escapeXml(copy.seoCta)}</mj-text>
|
|
1083
|
-
</mj-column>
|
|
1084
|
-
</mj-section>
|
|
1114
|
+
${lighthouseScoresSection(data.lighthouse)}
|
|
1115
|
+
${analyticsSection({
|
|
1116
|
+
current: data.gaUsersCurrent,
|
|
1117
|
+
previous: data.gaUsersPrevious,
|
|
1118
|
+
background: "white",
|
|
1119
|
+
footnoteLines: [escapeXml2(copy.seoCta)]
|
|
1120
|
+
})}
|
|
1085
1121
|
${isTesting ? testingIntroSection(copy) + testingChecklistSection(copy) : maintenanceTestingPlaceholder(data.lastTestedDate)}
|
|
1086
1122
|
${data.commentary ? commentarySection(data.commentary, copy) : ""}
|
|
1087
1123
|
<mj-section background-color="white">
|
|
1088
1124
|
<mj-column padding-top="36px">
|
|
1089
1125
|
<mj-text color="#C00" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="36px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1090
1126
|
${copy.contact.map(
|
|
1091
|
-
(line, i) => i === copy.contact.length - 1 ? `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" padding-top="0px" line-height="30px" padding-bottom="36px">${
|
|
1127
|
+
(line, i) => i === copy.contact.length - 1 ? `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" padding-top="0px" line-height="30px" padding-bottom="36px">${escapeXml2(line)}</mj-text>` : `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1092
1128
|
).join("\n ")}
|
|
1093
1129
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1094
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" line-height="20px" font-style="italic">Copyright ${(/* @__PURE__ */ new Date()).getUTCFullYear()} ${
|
|
1130
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" line-height="20px" font-style="italic">Copyright ${(/* @__PURE__ */ new Date()).getUTCFullYear()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1095
1131
|
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="700" line-height="16px" padding-top="0" padding-bottom="0px">Our mailing address is:</mj-text>
|
|
1096
1132
|
${[copy.footerOrg, ...copy.footerAddress].map(
|
|
1097
|
-
(line) => `<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${
|
|
1133
|
+
(line) => `<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1098
1134
|
).join("\n ")}
|
|
1099
1135
|
</mj-column>
|
|
1100
1136
|
</mj-section>
|
|
1101
1137
|
</mj-body>
|
|
1102
1138
|
</mjml>`;
|
|
1103
1139
|
}
|
|
1104
|
-
var
|
|
1140
|
+
var escapeXml2, BLURRED_TESTS;
|
|
1105
1141
|
var init_template = __esm({
|
|
1106
1142
|
"src/reports/maintenance-email/template.ts"() {
|
|
1107
1143
|
"use strict";
|
|
1108
1144
|
init_copy();
|
|
1109
1145
|
init_assets();
|
|
1146
|
+
init_email_sections();
|
|
1110
1147
|
init_html();
|
|
1111
1148
|
init_url();
|
|
1112
|
-
|
|
1113
|
-
CHECK_PNG = `cid:${CHECK_CID}`;
|
|
1149
|
+
escapeXml2 = escapeHtml;
|
|
1114
1150
|
BLURRED_TESTS = `cid:${BLURRED_CID}`;
|
|
1115
|
-
TREND_UP = "#2E7D32";
|
|
1116
|
-
TREND_NEUTRAL = "#757575";
|
|
1117
1151
|
}
|
|
1118
1152
|
});
|
|
1119
1153
|
|
|
1120
1154
|
// src/reports/launch-email/template.ts
|
|
1121
1155
|
function buildLaunchMjml(data) {
|
|
1122
1156
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1123
|
-
const previewText = `${
|
|
1157
|
+
const previewText = `${escapeXml2(data.siteName)} is live`;
|
|
1124
1158
|
const setupRows = copy.launchSetupItems.map(
|
|
1125
1159
|
(item) => `
|
|
1126
|
-
<mj-text color="${
|
|
1160
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px" padding-bottom="4px">\u2022 ${escapeXml2(item)}</mj-text>`
|
|
1127
1161
|
).join("");
|
|
1128
1162
|
const contactRows = copy.contact.map(
|
|
1129
1163
|
(line) => `
|
|
1130
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
1164
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1131
1165
|
).join("");
|
|
1132
1166
|
const footerAddressRows = copy.footerAddress.map(
|
|
1133
1167
|
(line) => `
|
|
1134
|
-
<mj-text color="${
|
|
1168
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1135
1169
|
).join("");
|
|
1136
1170
|
return `<mjml>
|
|
1137
1171
|
<mj-head>
|
|
@@ -1149,47 +1183,43 @@ function buildLaunchMjml(data) {
|
|
|
1149
1183
|
</mj-section>
|
|
1150
1184
|
<mj-section background-color="white">
|
|
1151
1185
|
<mj-column>
|
|
1152
|
-
<mj-text color="${
|
|
1153
|
-
<mj-text color="${
|
|
1154
|
-
<mj-text color="${
|
|
1186
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="75px">${escapeXml2(copy.launchHeading)}</mj-text>
|
|
1187
|
+
<mj-text color="${RED2}" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
1188
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="20px">${escapeXml2(copy.launchBody)}</mj-text>
|
|
1155
1189
|
${setupRows}
|
|
1156
1190
|
</mj-column>
|
|
1157
1191
|
</mj-section>
|
|
1158
1192
|
<mj-section background-color="white">
|
|
1159
1193
|
<mj-column padding-top="36px">
|
|
1160
|
-
<mj-text color="${
|
|
1194
|
+
<mj-text color="${RED2}" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="36px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1161
1195
|
${contactRows}
|
|
1162
1196
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1163
|
-
<mj-text color="${
|
|
1164
|
-
<mj-text color="${
|
|
1165
|
-
<mj-text color="${
|
|
1197
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" line-height="20px" font-style="italic">Copyright ${(/* @__PURE__ */ new Date()).getUTCFullYear()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1198
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="700" line-height="16px" padding-top="0" padding-bottom="0px">Our mailing address is:</mj-text>
|
|
1199
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(copy.footerOrg)}</mj-text>
|
|
1166
1200
|
${footerAddressRows}
|
|
1167
1201
|
</mj-column>
|
|
1168
1202
|
</mj-section>
|
|
1169
1203
|
</mj-body>
|
|
1170
1204
|
</mjml>`;
|
|
1171
1205
|
}
|
|
1172
|
-
var
|
|
1206
|
+
var RED2, GREY2;
|
|
1173
1207
|
var init_template2 = __esm({
|
|
1174
1208
|
"src/reports/launch-email/template.ts"() {
|
|
1175
1209
|
"use strict";
|
|
1176
1210
|
init_copy();
|
|
1177
1211
|
init_template();
|
|
1178
|
-
|
|
1179
|
-
|
|
1212
|
+
RED2 = "#C00";
|
|
1213
|
+
GREY2 = "#757575";
|
|
1180
1214
|
}
|
|
1181
1215
|
});
|
|
1182
1216
|
|
|
1183
1217
|
// src/reports/announcement-email/template.ts
|
|
1184
|
-
function
|
|
1185
|
-
return
|
|
1218
|
+
function sectionLabel(text) {
|
|
1219
|
+
return `<mj-text color="${RED3}" font-size="20px" font-weight="700" padding-top="0px">${escapeXml2(text)}</mj-text>`;
|
|
1186
1220
|
}
|
|
1187
|
-
function
|
|
1188
|
-
|
|
1189
|
-
const pct = Math.round((cur - prev) / prev * 100);
|
|
1190
|
-
if (pct > 0) return `\u25B2 ${pct}% vs the previous month`;
|
|
1191
|
-
if (pct < 0) return `\u25BC ${Math.abs(pct)}% vs the previous month`;
|
|
1192
|
-
return "No change vs the previous month";
|
|
1221
|
+
function bodyLine(text, paddingTop = "8px") {
|
|
1222
|
+
return `<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="${paddingTop}">${escapeXml2(text)}</mj-text>`;
|
|
1193
1223
|
}
|
|
1194
1224
|
function announcementSiteExtras(site) {
|
|
1195
1225
|
return {
|
|
@@ -1200,74 +1230,65 @@ function announcementSiteExtras(site) {
|
|
|
1200
1230
|
function buildAnnouncementMjml(data) {
|
|
1201
1231
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1202
1232
|
const previewText = "Your monthly report from Reddoor";
|
|
1233
|
+
const cad = data.cadence;
|
|
1234
|
+
const hasMaint = Boolean(cad && cad.maintenance !== "None");
|
|
1235
|
+
const hasTesting = Boolean(cad && cad.testing !== "None");
|
|
1203
1236
|
const improvementItems = [];
|
|
1204
1237
|
if (data.improvements?.resendForms) improvementItems.push(copy.announceImprovementResend);
|
|
1205
1238
|
if (data.improvements?.svelte5) improvementItems.push(copy.announceImprovementSvelte5);
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1239
|
+
const hasImpr = improvementItems.length > 0;
|
|
1240
|
+
const BANDS = ["white", "#F4F4F4"];
|
|
1241
|
+
let bandN = 0;
|
|
1242
|
+
const nextBg = () => BANDS[bandN++ % 2];
|
|
1243
|
+
const introBg = nextBg();
|
|
1244
|
+
const maintBg = hasMaint ? nextBg() : "";
|
|
1245
|
+
const testBg = hasTesting ? nextBg() : "";
|
|
1246
|
+
const lighthouseBg = nextBg();
|
|
1247
|
+
const analyticsBg = nextBg();
|
|
1248
|
+
const improvementsBg = hasImpr ? nextBg() : "";
|
|
1249
|
+
const contactBg = nextBg();
|
|
1250
|
+
const maintenanceSection = cad && cad.maintenance !== "None" ? `
|
|
1251
|
+
<mj-section background-color="${maintBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
1208
1252
|
<mj-column>
|
|
1209
|
-
|
|
1210
|
-
${
|
|
1211
|
-
(item) => `
|
|
1212
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px" padding-bottom="4px">\u2022 ${escapeXml(item)}</mj-text>`
|
|
1213
|
-
).join("")}
|
|
1253
|
+
${sectionLabel("MAINTENANCE CHECKS")}
|
|
1254
|
+
${bodyLine(`${copy.maintenanceIntro} We do this ${FREQ_PHRASE[cad.maintenance]}.${cad.testing === "None" ? ` ${copy.announceCadence}` : ""}`)}
|
|
1214
1255
|
</mj-column>
|
|
1215
|
-
</mj-section
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
checks: copy.testingChecklist
|
|
1222
|
-
});
|
|
1223
|
-
if (cad && cad.maintenance !== "None")
|
|
1224
|
-
cadenceBlocks.push({
|
|
1225
|
-
line: `${copy.announceMaintenanceLabel} \u2014 ${FREQ_PHRASE[cad.maintenance]}`,
|
|
1226
|
-
checks: copy.maintenanceChecks
|
|
1227
|
-
});
|
|
1228
|
-
const cadenceSection = cadenceBlocks.length > 0 ? `
|
|
1229
|
-
<mj-section background-color="white">
|
|
1256
|
+
</mj-section>${checklistRowsSection(copy.maintenanceChecks, {
|
|
1257
|
+
background: maintBg,
|
|
1258
|
+
lastPaddingBottom: SECTION_PAD
|
|
1259
|
+
})}` : "";
|
|
1260
|
+
const testingSection = cad && cad.testing !== "None" ? `
|
|
1261
|
+
<mj-section background-color="${testBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
1230
1262
|
<mj-column>
|
|
1231
|
-
|
|
1232
|
-
${
|
|
1233
|
-
(b) => `
|
|
1234
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="400" line-height="24px" padding-top="12px" padding-bottom="2px">\u2022 ${escapeXml(b.line)}</mj-text>${b.checks.map(
|
|
1235
|
-
(c) => `
|
|
1236
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="14px" font-weight="300" line-height="22px" padding-top="1px" padding-bottom="0px" padding-left="16px">${escapeXml(c)} <img src="${CHECK_PNG2}" alt="\u2713" width="14" height="14" style="vertical-align:middle;display:inline-block;margin-left:2px;" /></mj-text>`
|
|
1237
|
-
).join("")}`
|
|
1238
|
-
).join("")}
|
|
1239
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="14px">${escapeXml(copy.announceCadence)}</mj-text>
|
|
1263
|
+
${sectionLabel("TESTING")}
|
|
1264
|
+
${bodyLine(`${copy.testingIntro} We run a full test ${FREQ_PHRASE[cad.testing]}. ${copy.announceCadence}`)}
|
|
1240
1265
|
</mj-column>
|
|
1241
|
-
</mj-section
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
if (data.searchPosition !== void 0)
|
|
1255
|
-
trafficRows.push(`
|
|
1256
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px">Page 1 Google result (#${data.searchPosition}) for your brand search</mj-text>`);
|
|
1257
|
-
const trafficSection = trafficRows.length > 0 ? `
|
|
1258
|
-
<mj-section background-color="white">
|
|
1266
|
+
</mj-section>${checklistRowsSection(copy.testingChecklist, {
|
|
1267
|
+
background: testBg,
|
|
1268
|
+
lastPaddingBottom: SECTION_PAD
|
|
1269
|
+
})}` : "";
|
|
1270
|
+
const analytics = analyticsSection({
|
|
1271
|
+
current: data.gaUsersCurrent,
|
|
1272
|
+
previous: data.gaUsersPrevious,
|
|
1273
|
+
background: analyticsBg,
|
|
1274
|
+
pad: SECTION_PAD,
|
|
1275
|
+
bodyLines: data.searchPosition !== void 0 ? [`Page 1 Google result (#${data.searchPosition}) for your brand search`] : []
|
|
1276
|
+
});
|
|
1277
|
+
const improvementsSection = hasImpr ? `
|
|
1278
|
+
<mj-section background-color="${improvementsBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1259
1279
|
<mj-column>
|
|
1260
|
-
|
|
1261
|
-
${
|
|
1280
|
+
${sectionLabel("RECENT IMPROVEMENTS")}
|
|
1281
|
+
${improvementItems.map((item) => bodyLine(item)).join("\n ")}
|
|
1282
|
+
${bodyLine(copy.announceOpenDoor, "16px")}
|
|
1262
1283
|
</mj-column>
|
|
1263
1284
|
</mj-section>` : "";
|
|
1264
1285
|
const contactRows = copy.contact.map(
|
|
1265
1286
|
(line) => `
|
|
1266
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
1287
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1267
1288
|
).join("");
|
|
1268
1289
|
const footerAddressRows = copy.footerAddress.map(
|
|
1269
1290
|
(line) => `
|
|
1270
|
-
<mj-text color="${
|
|
1291
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1271
1292
|
).join("");
|
|
1272
1293
|
return `<mjml>
|
|
1273
1294
|
<mj-head>
|
|
@@ -1276,69 +1297,54 @@ function buildAnnouncementMjml(data) {
|
|
|
1276
1297
|
<mj-section padding-left="11%" padding-right="11%"/>
|
|
1277
1298
|
<mj-image padding="0px" />
|
|
1278
1299
|
</mj-attributes>
|
|
1279
|
-
<mj-preview>${
|
|
1300
|
+
<mj-preview>${escapeXml2(previewText)}</mj-preview>
|
|
1280
1301
|
${headerStyleBlock(data)}
|
|
1281
1302
|
</mj-head>
|
|
1282
1303
|
<mj-body background-color="white">
|
|
1283
1304
|
<mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
|
|
1284
1305
|
<mj-column>${headerImageTag(data)}</mj-column>
|
|
1285
1306
|
</mj-section>
|
|
1286
|
-
<mj-section background-color="
|
|
1307
|
+
<mj-section background-color="${introBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1287
1308
|
<mj-column>
|
|
1288
|
-
|
|
1289
|
-
<mj-text color="${
|
|
1290
|
-
|
|
1309
|
+
${sectionLabel(copy.announceHeading)}
|
|
1310
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="20px">Prepared for ${escapeXml2(data.siteName)}</mj-text>
|
|
1311
|
+
${bodyLine(copy.announceBody)}
|
|
1291
1312
|
</mj-column>
|
|
1292
1313
|
</mj-section>
|
|
1293
|
-
${
|
|
1314
|
+
${maintenanceSection}
|
|
1315
|
+
${testingSection}
|
|
1316
|
+
${lighthouseScoresSection(data.lighthouse, { background: lighthouseBg, pad: SECTION_PAD })}
|
|
1317
|
+
${analytics}
|
|
1294
1318
|
${improvementsSection}
|
|
1295
|
-
<mj-section background-color="
|
|
1319
|
+
<mj-section background-color="${contactBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1296
1320
|
<mj-column>
|
|
1297
|
-
<mj-text color="${
|
|
1298
|
-
${scoreRows}${scoreNote}
|
|
1299
|
-
</mj-column>
|
|
1300
|
-
</mj-section>
|
|
1301
|
-
${trafficSection}
|
|
1302
|
-
<mj-section background-color="white">
|
|
1303
|
-
<mj-column>
|
|
1304
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="36px">${escapeXml(copy.announceOpenDoor)}</mj-text>
|
|
1305
|
-
</mj-column>
|
|
1306
|
-
</mj-section>
|
|
1307
|
-
<mj-section background-color="white">
|
|
1308
|
-
<mj-column padding-top="36px">
|
|
1309
|
-
<mj-text color="${RED2}" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="36px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1321
|
+
<mj-text color="${RED3}" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="0px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1310
1322
|
${contactRows}
|
|
1311
1323
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1312
|
-
<mj-text color="${
|
|
1313
|
-
<mj-text color="${
|
|
1314
|
-
<mj-text color="${
|
|
1324
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" line-height="20px" font-style="italic">Copyright ${(/* @__PURE__ */ new Date()).getUTCFullYear()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1325
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="700" line-height="16px" padding-top="0" padding-bottom="0px">Our mailing address is:</mj-text>
|
|
1326
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(copy.footerOrg)}</mj-text>
|
|
1315
1327
|
${footerAddressRows}
|
|
1316
1328
|
</mj-column>
|
|
1317
1329
|
</mj-section>
|
|
1318
1330
|
</mj-body>
|
|
1319
1331
|
</mjml>`;
|
|
1320
1332
|
}
|
|
1321
|
-
var FREQ_PHRASE,
|
|
1333
|
+
var FREQ_PHRASE, RED3, GREY3, SECTION_PAD;
|
|
1322
1334
|
var init_template3 = __esm({
|
|
1323
1335
|
"src/reports/announcement-email/template.ts"() {
|
|
1324
1336
|
"use strict";
|
|
1325
1337
|
init_copy();
|
|
1326
1338
|
init_template();
|
|
1327
|
-
|
|
1339
|
+
init_email_sections();
|
|
1328
1340
|
FREQ_PHRASE = {
|
|
1329
1341
|
Monthly: "every month",
|
|
1330
1342
|
Quarterly: "every quarter",
|
|
1331
1343
|
Yearly: "every year"
|
|
1332
1344
|
};
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
SCORE_PREVIEW = [
|
|
1337
|
-
{ label: "Performance", key: "performance" },
|
|
1338
|
-
{ label: "Readability (A11y)", key: "accessibility" },
|
|
1339
|
-
{ label: "Best Practices", key: "bestPractices" },
|
|
1340
|
-
{ label: "Site Structure", key: "seo" }
|
|
1341
|
-
];
|
|
1345
|
+
RED3 = "#C00";
|
|
1346
|
+
GREY3 = "#757575";
|
|
1347
|
+
SECTION_PAD = "40px";
|
|
1342
1348
|
}
|
|
1343
1349
|
});
|
|
1344
1350
|
|
|
@@ -1648,16 +1654,16 @@ __export(digest_exports, {
|
|
|
1648
1654
|
runDigest: () => runDigest
|
|
1649
1655
|
});
|
|
1650
1656
|
function readySection(items) {
|
|
1651
|
-
const heading = `<h2 style="color:${
|
|
1657
|
+
const heading = `<h2 style="color:${RED4};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Ready for your yes</h2>`;
|
|
1652
1658
|
if (items.length === 0) {
|
|
1653
|
-
return `${heading}<p style="color:${
|
|
1659
|
+
return `${heading}<p style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;margin:0">Nothing waiting on you.</p>`;
|
|
1654
1660
|
}
|
|
1655
1661
|
const rows = items.map((it) => {
|
|
1656
1662
|
const safeUrl = it.dashboardUrl.startsWith("https://") ? it.dashboardUrl : void 0;
|
|
1657
1663
|
const link = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">review & approve</a>` : `review & approve`;
|
|
1658
1664
|
return `
|
|
1659
1665
|
<tr>
|
|
1660
|
-
<td style="color:${
|
|
1666
|
+
<td style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">
|
|
1661
1667
|
<strong style="color:#222">${escapeHtml(it.siteName)}</strong> \u2014 ${escapeHtml(it.reportType)} (${escapeHtml(it.period)})
|
|
1662
1668
|
\u2014 ${link}
|
|
1663
1669
|
</td>
|
|
@@ -1667,15 +1673,15 @@ function readySection(items) {
|
|
|
1667
1673
|
}
|
|
1668
1674
|
function attentionBadge(status) {
|
|
1669
1675
|
if (status === "new")
|
|
1670
|
-
return `<strong style="color:${
|
|
1676
|
+
return `<strong style="color:${RED4};font-family:helvetica,sans-serif">NEW</strong> `;
|
|
1671
1677
|
if (status === "worse")
|
|
1672
|
-
return `<strong style="color:${
|
|
1678
|
+
return `<strong style="color:${RED4};font-family:helvetica,sans-serif">WORSE</strong> `;
|
|
1673
1679
|
return "";
|
|
1674
1680
|
}
|
|
1675
1681
|
function attentionSection(items) {
|
|
1676
|
-
const heading = `<h2 style="color:${
|
|
1682
|
+
const heading = `<h2 style="color:${RED4};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Needs attention</h2>`;
|
|
1677
1683
|
if (items.length === 0) {
|
|
1678
|
-
return `${heading}<p style="color:${
|
|
1684
|
+
return `${heading}<p style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;margin:0">All clear \u2014 nothing needs attention.</p>`;
|
|
1679
1685
|
}
|
|
1680
1686
|
const bySite = /* @__PURE__ */ new Map();
|
|
1681
1687
|
for (const it of items) {
|
|
@@ -1692,7 +1698,7 @@ function attentionSection(items) {
|
|
|
1692
1698
|
const titleHtml = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">${escapeHtml(it.title)}</a>` : escapeHtml(it.title);
|
|
1693
1699
|
return `
|
|
1694
1700
|
<tr>
|
|
1695
|
-
<td style="color:${
|
|
1701
|
+
<td style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">${attentionBadge(it.status)}${titleHtml}</td>
|
|
1696
1702
|
</tr>`;
|
|
1697
1703
|
}).join("");
|
|
1698
1704
|
return `
|
|
@@ -1817,7 +1823,7 @@ function renderDigestHtml(sections) {
|
|
|
1817
1823
|
<table width="600" style="border-collapse:collapse">
|
|
1818
1824
|
<tr>
|
|
1819
1825
|
<td>
|
|
1820
|
-
<h1 style="color:${
|
|
1826
|
+
<h1 style="color:${RED4};font-family:helvetica,sans-serif;font-size:24px;font-weight:700;margin:0 0 8px">Your fleet today</h1>
|
|
1821
1827
|
${readySection(sections.readyForYourYes)}
|
|
1822
1828
|
${attentionSection(sections.needsAttention)}
|
|
1823
1829
|
</td>
|
|
@@ -1829,7 +1835,7 @@ function renderDigestHtml(sections) {
|
|
|
1829
1835
|
</body>
|
|
1830
1836
|
</html>`;
|
|
1831
1837
|
}
|
|
1832
|
-
var
|
|
1838
|
+
var GREY4, RED4, ANCHOR_STYLE, SEVERITY_ORDER, FROM_ADDRESS, DIGEST_OPERATOR_FALLBACK;
|
|
1833
1839
|
var init_digest = __esm({
|
|
1834
1840
|
"src/reports/digest.ts"() {
|
|
1835
1841
|
"use strict";
|
|
@@ -1841,9 +1847,9 @@ var init_digest = __esm({
|
|
|
1841
1847
|
init_digest_collectors();
|
|
1842
1848
|
init_digest_state();
|
|
1843
1849
|
init_html();
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
ANCHOR_STYLE = `color:${
|
|
1850
|
+
GREY4 = "#757575";
|
|
1851
|
+
RED4 = "#C00";
|
|
1852
|
+
ANCHOR_STYLE = `color:${RED4};font-family:helvetica,sans-serif`;
|
|
1847
1853
|
SEVERITY_ORDER = { critical: 0, warning: 1 };
|
|
1848
1854
|
FROM_ADDRESS = "Reddoor Reports <reports@reddoorla.com>";
|
|
1849
1855
|
DIGEST_OPERATOR_FALLBACK = "info@reddoorla.com";
|
|
@@ -2003,9 +2009,8 @@ async function sendOne(client, base, site, report) {
|
|
|
2003
2009
|
headerHeight: header.displayHeight,
|
|
2004
2010
|
headerBgColor: header.placeholderColor,
|
|
2005
2011
|
// Announcement-only: re-derive cadence + improvements from the site row so the SENT email
|
|
2006
|
-
// keeps its
|
|
2007
|
-
//
|
|
2008
|
-
// other report templates.
|
|
2012
|
+
// keeps its cadence copy + improvement callouts. Without this the send-time re-render drops
|
|
2013
|
+
// them entirely (they're not stored on the Reports row). Ignored by the other templates.
|
|
2009
2014
|
...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
|
|
2010
2015
|
});
|
|
2011
2016
|
const reportDate = report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date();
|
|
@@ -5775,10 +5780,40 @@ init_render();
|
|
|
5775
5780
|
init_websites();
|
|
5776
5781
|
init_copy();
|
|
5777
5782
|
init_reports();
|
|
5778
|
-
init_attachments();
|
|
5779
5783
|
import { mkdir as mkdir4, writeFile as writeFile10 } from "fs/promises";
|
|
5780
5784
|
import { dirname as dirname6 } from "path";
|
|
5781
5785
|
|
|
5786
|
+
// src/reports/queue.ts
|
|
5787
|
+
init_reports();
|
|
5788
|
+
var REPORT_TIER = {
|
|
5789
|
+
Maintenance: 1,
|
|
5790
|
+
Testing: 2,
|
|
5791
|
+
Announcement: 3,
|
|
5792
|
+
Launch: 3
|
|
5793
|
+
};
|
|
5794
|
+
function reportTier(type) {
|
|
5795
|
+
return REPORT_TIER[type];
|
|
5796
|
+
}
|
|
5797
|
+
async function queueDraft(base, report) {
|
|
5798
|
+
const newTier = reportTier(report.reportType);
|
|
5799
|
+
const others = (await listReportsForSite(base, report.siteId)).filter(isPendingApproval).filter((r) => r.id !== report.id);
|
|
5800
|
+
const blocker = others.find((r) => reportTier(r.reportType) >= newTier);
|
|
5801
|
+
if (blocker) {
|
|
5802
|
+
await setDraftReady(base, report.id, false);
|
|
5803
|
+
return { queued: false, blockedBy: blocker.reportType, supersededIds: [] };
|
|
5804
|
+
}
|
|
5805
|
+
const supersededIds = [];
|
|
5806
|
+
for (const r of others) {
|
|
5807
|
+
await setDraftReady(base, r.id, false);
|
|
5808
|
+
supersededIds.push(r.id);
|
|
5809
|
+
}
|
|
5810
|
+
await setDraftReady(base, report.id, true);
|
|
5811
|
+
return { queued: true, supersededIds };
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5814
|
+
// src/reports/draft.ts
|
|
5815
|
+
init_attachments();
|
|
5816
|
+
|
|
5782
5817
|
// src/reports/ga/config.ts
|
|
5783
5818
|
init_credentials();
|
|
5784
5819
|
import { dirname as dirname5, join as join26 } from "path";
|
|
@@ -5961,12 +5996,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
5961
5996
|
const path = options.previewPath ?? `reports/${slug}/draft.html`;
|
|
5962
5997
|
await mkdir4(dirname6(path), { recursive: true });
|
|
5963
5998
|
await writeFile10(path, html, "utf-8");
|
|
5964
|
-
return { reportRow: null, htmlPath: path, html, softFailures };
|
|
5999
|
+
return { reportRow: null, htmlPath: path, html, softFailures, queued: null, supersededIds: [] };
|
|
5965
6000
|
}
|
|
5966
6001
|
if (base === null) throw new Error("base required when previewOnly=false");
|
|
5967
6002
|
if (options.completeRowId) {
|
|
5968
|
-
await
|
|
5969
|
-
|
|
6003
|
+
await uploadDraftHtml(options.completeRowId, slug, periodEnd, html);
|
|
6004
|
+
const outcome2 = await queueDraft(base, {
|
|
6005
|
+
id: options.completeRowId,
|
|
6006
|
+
siteId: siteRow.id,
|
|
6007
|
+
reportType
|
|
6008
|
+
});
|
|
6009
|
+
return {
|
|
6010
|
+
reportRow: options.existingRow ?? null,
|
|
6011
|
+
htmlPath: null,
|
|
6012
|
+
html,
|
|
6013
|
+
softFailures,
|
|
6014
|
+
queued: outcome2.queued,
|
|
6015
|
+
supersededIds: outcome2.supersededIds
|
|
6016
|
+
};
|
|
5970
6017
|
}
|
|
5971
6018
|
const reportId = `${siteRow.name} \u2014 ${reportType} \u2014 ${periodEnd.toISOString().slice(0, 10)}`;
|
|
5972
6019
|
const created = await createDraft(base, {
|
|
@@ -5983,13 +6030,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
5983
6030
|
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
5984
6031
|
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
5985
6032
|
});
|
|
5986
|
-
await
|
|
5987
|
-
|
|
6033
|
+
await uploadDraftHtml(created.id, slug, periodEnd, html);
|
|
6034
|
+
const outcome = await queueDraft(base, {
|
|
6035
|
+
id: created.id,
|
|
6036
|
+
siteId: siteRow.id,
|
|
6037
|
+
reportType
|
|
6038
|
+
});
|
|
6039
|
+
return {
|
|
6040
|
+
reportRow: created,
|
|
6041
|
+
htmlPath: null,
|
|
6042
|
+
html,
|
|
6043
|
+
softFailures,
|
|
6044
|
+
queued: outcome.queued,
|
|
6045
|
+
supersededIds: outcome.supersededIds
|
|
6046
|
+
};
|
|
5988
6047
|
}
|
|
5989
|
-
async function
|
|
6048
|
+
async function uploadDraftHtml(rowId, slug, periodEnd, html) {
|
|
5990
6049
|
const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;
|
|
5991
6050
|
await uploadAttachment(rowId, "Rendered HTML", html, htmlFilename, "text/html");
|
|
5992
|
-
await setDraftReady(base, rowId, true);
|
|
5993
6051
|
}
|
|
5994
6052
|
var NO_ENRICHMENT = { value: null, softFailed: false };
|
|
5995
6053
|
async function fetchGaUsers(siteRow, periodStart, periodEnd) {
|
|
@@ -6039,6 +6097,14 @@ async function derivePeriodStart(base, siteRow, reportType, today) {
|
|
|
6039
6097
|
}
|
|
6040
6098
|
|
|
6041
6099
|
// src/cli/commands/report.ts
|
|
6100
|
+
function draftLine(reportId, queued, supersededIds, verb = "drafted") {
|
|
6101
|
+
const id = reportId ?? "(unknown)";
|
|
6102
|
+
if (queued === false) {
|
|
6103
|
+
return `\u2022 ${verb} but NOT queued: ${id} \u2014 a higher-or-equal-tier report is already pending approval`;
|
|
6104
|
+
}
|
|
6105
|
+
const sup = supersededIds.length > 0 ? ` (superseded ${supersededIds.length} lower-tier draft${supersededIds.length > 1 ? "s" : ""})` : "";
|
|
6106
|
+
return `\u2713 ${verb}: ${id}${sup}`;
|
|
6107
|
+
}
|
|
6042
6108
|
function parseSingleSiteReportType(raw) {
|
|
6043
6109
|
if (raw === void 0 || raw.trim() === "") return "Maintenance";
|
|
6044
6110
|
const norm = raw.trim().toLowerCase();
|
|
@@ -6101,15 +6167,30 @@ async function draftDueReports(base, today) {
|
|
|
6101
6167
|
lines.push(`\u2022 skipped (already drafted ${period}): ${item.site.name} ${item.reportType}`);
|
|
6102
6168
|
continue;
|
|
6103
6169
|
}
|
|
6170
|
+
const blockedByPending = reports.some(
|
|
6171
|
+
(r) => r.siteId === item.site.id && r.id !== existing.id && r.sentAt === null && r.draftReady && reportTier(r.reportType) >= reportTier(item.reportType)
|
|
6172
|
+
);
|
|
6173
|
+
if (blockedByPending) {
|
|
6174
|
+
skipped++;
|
|
6175
|
+
lines.push(
|
|
6176
|
+
`\u2022 skipped (superseded \u2014 a higher-or-equal-tier report is pending): ${item.site.name} ${item.reportType}`
|
|
6177
|
+
);
|
|
6178
|
+
continue;
|
|
6179
|
+
}
|
|
6104
6180
|
try {
|
|
6105
6181
|
const result = await draftReportForSite(base, item.site, item.reportType, {
|
|
6106
6182
|
period,
|
|
6107
6183
|
completeRowId: existing.id,
|
|
6108
6184
|
existingRow: existing
|
|
6109
6185
|
});
|
|
6110
|
-
existing.draftReady = true;
|
|
6186
|
+
existing.draftReady = result.queued === true;
|
|
6111
6187
|
lines.push(
|
|
6112
|
-
|
|
6188
|
+
draftLine(
|
|
6189
|
+
result.reportRow?.reportId ?? existing.reportId,
|
|
6190
|
+
result.queued,
|
|
6191
|
+
result.supersededIds,
|
|
6192
|
+
"completed half-made draft"
|
|
6193
|
+
)
|
|
6113
6194
|
);
|
|
6114
6195
|
if (result.softFailures.length > 0) softFailedSites++;
|
|
6115
6196
|
} catch (e) {
|
|
@@ -6129,7 +6210,7 @@ async function draftDueReports(base, today) {
|
|
|
6129
6210
|
}
|
|
6130
6211
|
try {
|
|
6131
6212
|
const result = await draftReportForSite(base, item.site, item.reportType, { period });
|
|
6132
|
-
lines.push(
|
|
6213
|
+
lines.push(draftLine(result.reportRow?.reportId, result.queued, result.supersededIds));
|
|
6133
6214
|
if (result.reportRow) reports.push(result.reportRow);
|
|
6134
6215
|
if (result.softFailures.length > 0) softFailedSites++;
|
|
6135
6216
|
} catch (e) {
|
|
@@ -6159,7 +6240,15 @@ async function runSingleSiteDraft(slug, opts) {
|
|
|
6159
6240
|
if (opts.previewOnly) {
|
|
6160
6241
|
return { output: `Preview written to ${result.htmlPath}`, code: 0 };
|
|
6161
6242
|
}
|
|
6162
|
-
return {
|
|
6243
|
+
return {
|
|
6244
|
+
output: draftLine(
|
|
6245
|
+
result.reportRow?.reportId,
|
|
6246
|
+
result.queued,
|
|
6247
|
+
result.supersededIds,
|
|
6248
|
+
"Draft created"
|
|
6249
|
+
),
|
|
6250
|
+
code: 0
|
|
6251
|
+
};
|
|
6163
6252
|
}
|
|
6164
6253
|
|
|
6165
6254
|
// src/cli/commands/init.ts
|
|
@@ -6428,7 +6517,7 @@ async function launch(site, deps = {}) {
|
|
|
6428
6517
|
`\u26A0 Launch preview upload skipped for ${target.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6429
6518
|
);
|
|
6430
6519
|
}
|
|
6431
|
-
await
|
|
6520
|
+
await queueDraft(base, { id: report.id, siteId: target.id, reportType: "Launch" });
|
|
6432
6521
|
} catch (err) {
|
|
6433
6522
|
steps.push({ name: "draft", result: errorOf(err) });
|
|
6434
6523
|
return stop();
|
|
@@ -6566,9 +6655,19 @@ async function announce(deps) {
|
|
|
6566
6655
|
`\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6567
6656
|
);
|
|
6568
6657
|
}
|
|
6569
|
-
await
|
|
6658
|
+
const queue = await queueDraft(base, {
|
|
6659
|
+
id: report.id,
|
|
6660
|
+
siteId: w.id,
|
|
6661
|
+
reportType: "Announcement"
|
|
6662
|
+
});
|
|
6570
6663
|
const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
|
|
6571
|
-
results.push({
|
|
6664
|
+
results.push({
|
|
6665
|
+
site: w.name,
|
|
6666
|
+
status: statusKind,
|
|
6667
|
+
reportId: report.id,
|
|
6668
|
+
recipientMissing,
|
|
6669
|
+
queued: queue.queued
|
|
6670
|
+
});
|
|
6572
6671
|
} catch (err) {
|
|
6573
6672
|
results.push({
|
|
6574
6673
|
site: w.name,
|