@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/index.js
CHANGED
|
@@ -2646,6 +2646,10 @@ var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
2646
2646
|
"maintenance",
|
|
2647
2647
|
"launch period"
|
|
2648
2648
|
]);
|
|
2649
|
+
var FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
|
|
2650
|
+
function toFrequency(raw) {
|
|
2651
|
+
return typeof raw === "string" && FREQUENCIES.includes(raw) ? raw : "None";
|
|
2652
|
+
}
|
|
2649
2653
|
function mapRow(rec) {
|
|
2650
2654
|
const f = rec.fields;
|
|
2651
2655
|
const attachments = f["Header image"] ?? [];
|
|
@@ -2656,8 +2660,8 @@ function mapRow(rec) {
|
|
|
2656
2660
|
url: String(f["url"] ?? ""),
|
|
2657
2661
|
status: f["Status"] ?? null,
|
|
2658
2662
|
pointOfContact: f["point of contact"] ?? null,
|
|
2659
|
-
maintenanceFreq: f["maintenence freq"]
|
|
2660
|
-
testingFreq: f["testing freq"]
|
|
2663
|
+
maintenanceFreq: toFrequency(f["maintenence freq"]),
|
|
2664
|
+
testingFreq: toFrequency(f["testing freq"]),
|
|
2661
2665
|
maintenanceDay: f["maintenance day"] ?? null,
|
|
2662
2666
|
testingDay: f["testing day"] ?? null,
|
|
2663
2667
|
ga4PropertyId: f["GA4 property ID"] ?? null,
|
|
@@ -2785,15 +2789,10 @@ var DEFAULT_COPY = {
|
|
|
2785
2789
|
],
|
|
2786
2790
|
announceHeading: "YOUR ONGOING SITE CARE",
|
|
2787
2791
|
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:",
|
|
2788
|
-
announceCadenceHeading: "WHAT TO EXPECT",
|
|
2789
|
-
announceTestingLabel: "Full site testing",
|
|
2790
|
-
announceMaintenanceLabel: "Routine maintenance",
|
|
2791
|
-
announcePreviewLabel: "From your latest full site test:",
|
|
2792
|
-
announceScoreNote: "These are independent Google Lighthouse scores, each out of 100 \u2014 higher is better.",
|
|
2793
2792
|
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
2794
2793
|
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
2795
2794
|
announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
|
|
2796
|
-
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just
|
|
2795
|
+
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just let us know."
|
|
2797
2796
|
};
|
|
2798
2797
|
function override(v) {
|
|
2799
2798
|
if (typeof v !== "string") return null;
|
|
@@ -2883,70 +2882,118 @@ function safeUrl(raw) {
|
|
|
2883
2882
|
return "#";
|
|
2884
2883
|
}
|
|
2885
2884
|
|
|
2886
|
-
// src/reports/
|
|
2885
|
+
// src/reports/email-sections.ts
|
|
2887
2886
|
var escapeXml = escapeHtml;
|
|
2887
|
+
var RED = "#C00";
|
|
2888
|
+
var GREY = "#757575";
|
|
2889
|
+
var BORDER = "#CCCCCC";
|
|
2890
|
+
var TREND_UP = "#2E7D32";
|
|
2891
|
+
var TREND_NEUTRAL = GREY;
|
|
2888
2892
|
var CHECK_PNG = `cid:${CHECK_CID}`;
|
|
2889
|
-
var BLURRED_TESTS = `cid:${BLURRED_CID}`;
|
|
2890
|
-
function fmtDate(d) {
|
|
2891
|
-
if (!d || Number.isNaN(d.getTime())) return "";
|
|
2892
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2893
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2894
|
-
const yyyy = d.getUTCFullYear();
|
|
2895
|
-
return `${mm}.${dd}.${yyyy}`;
|
|
2896
|
-
}
|
|
2897
2893
|
function fmtUsers(n) {
|
|
2898
2894
|
return n.toLocaleString("en-US");
|
|
2899
2895
|
}
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2896
|
+
function checklistRowsSection(rows, opts) {
|
|
2897
|
+
return rows.map((label, i) => {
|
|
2898
|
+
const isLast = i === rows.length - 1;
|
|
2899
|
+
const border = isLast ? "" : ` border-bottom="solid ${BORDER} 1px"`;
|
|
2900
|
+
const lastPad = isLast ? ` padding-bottom="${opts.lastPaddingBottom}"` : "";
|
|
2901
|
+
return `
|
|
2902
|
+
<mj-section background-color="${opts.background}" padding="0px"${lastPad}>
|
|
2903
|
+
<mj-group>
|
|
2904
|
+
<mj-column padding-left="0px" width="90%"${border}>
|
|
2905
|
+
<mj-text height="25px" padding-left="0px" color="${GREY}" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
2906
|
+
</mj-column>
|
|
2907
|
+
<mj-column width="10%"${border} padding-top="15px">
|
|
2908
|
+
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
2909
|
+
</mj-column>
|
|
2910
|
+
</mj-group>
|
|
2911
|
+
</mj-section>`;
|
|
2912
|
+
}).join("");
|
|
2913
|
+
}
|
|
2914
|
+
var LIGHTHOUSE_ROWS = [
|
|
2915
|
+
{ label: "Performance", key: "performance", range: "Acceptable 50\u201389 // Ideal 90\u2013100" },
|
|
2916
|
+
{ label: "Readability (A11y)", key: "accessibility", range: "Acceptable 80\u201399 // Ideal 100" },
|
|
2917
|
+
{ label: "Best Practices", key: "bestPractices", range: "Acceptable 60\u201379 // Ideal 80\u2013100" },
|
|
2918
|
+
{ label: "Site Structure", key: "seo", range: "Acceptable 50\u201389 // Ideal 90\u2013100" }
|
|
2919
|
+
];
|
|
2920
|
+
function lighthouseScoresSection(lighthouse2, opts = {}) {
|
|
2921
|
+
const background = opts.background ?? "#F4F4F4";
|
|
2922
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
2923
|
+
const labelTop = opts.pad ?? "55px";
|
|
2924
|
+
const footnoteBottom = opts.pad ? "0px" : "36px";
|
|
2925
|
+
const rows = LIGHTHOUSE_ROWS.map(
|
|
2926
|
+
({ label, key, range }, i) => `
|
|
2927
|
+
<mj-text color="${RED}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
2928
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400" padding-top="0px">${lighthouse2[key]}</mj-text>
|
|
2929
|
+
<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 ? `
|
|
2930
|
+
<mj-divider border-width="1px" border-style="solid" border-color="${BORDER}" padding="0" />` : ""}`
|
|
2931
|
+
).join("");
|
|
2932
|
+
return `
|
|
2933
|
+
<mj-section background-color="${background}"${sectionPad}>
|
|
2934
|
+
<mj-column>
|
|
2935
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">LIGHTHOUSE SCORES*</mj-text>${rows}
|
|
2936
|
+
<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>
|
|
2937
|
+
</mj-column>
|
|
2938
|
+
</mj-section>`;
|
|
2904
2939
|
}
|
|
2905
2940
|
function analyticsTrendLine(cur, prev) {
|
|
2906
2941
|
if (cur === void 0 || prev === void 0) {
|
|
2907
|
-
return
|
|
2942
|
+
return trendLine(TREND_NEUTRAL, `Last Period: ${prev !== void 0 ? fmtUsers(prev) : "\u2014"}`);
|
|
2908
2943
|
}
|
|
2909
2944
|
if (prev === 0) {
|
|
2910
|
-
return cur > 0 ?
|
|
2945
|
+
return cur > 0 ? trendLine(TREND_UP, "\u25B2 New this period (0 last period)") : trendLine(TREND_NEUTRAL, "Last Period: 0");
|
|
2911
2946
|
}
|
|
2912
2947
|
const pct = Math.round((cur - prev) / prev * 100);
|
|
2913
2948
|
const range = `(${fmtUsers(prev)} \u2192 ${fmtUsers(cur)})`;
|
|
2914
|
-
if (pct > 0) return
|
|
2915
|
-
if (pct < 0) return
|
|
2916
|
-
return
|
|
2949
|
+
if (pct > 0) return trendLine(TREND_UP, `\u25B2 ${pct}% vs last period ${range}`);
|
|
2950
|
+
if (pct < 0) return trendLine(TREND_NEUTRAL, `\u25BC ${Math.abs(pct)}% vs last period ${range}`);
|
|
2951
|
+
return trendLine(TREND_NEUTRAL, `No change vs last period (${fmtUsers(prev)})`);
|
|
2952
|
+
}
|
|
2953
|
+
function trendLine(color, text) {
|
|
2954
|
+
return `<mj-text color="${color}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${text}</mj-text>`;
|
|
2955
|
+
}
|
|
2956
|
+
function footnoteLine(text) {
|
|
2957
|
+
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>`;
|
|
2958
|
+
}
|
|
2959
|
+
function analyticsSection(opts) {
|
|
2960
|
+
const users = opts.current !== void 0 ? fmtUsers(opts.current) : "\u2014";
|
|
2961
|
+
const body = (opts.bodyLines ?? []).map((l) => trendLine(TREND_NEUTRAL, l)).join("\n ");
|
|
2962
|
+
const footnotes = (opts.footnoteLines ?? []).map(footnoteLine).join("\n ");
|
|
2963
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
2964
|
+
const labelTop = opts.pad ?? "75px";
|
|
2965
|
+
return `
|
|
2966
|
+
<mj-section background-color="${opts.background}"${sectionPad}>
|
|
2967
|
+
<mj-column>
|
|
2968
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">ANALYTICS</mj-text>
|
|
2969
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400">${users} Users</mj-text>
|
|
2970
|
+
${analyticsTrendLine(opts.current, opts.previous)}
|
|
2971
|
+
${body}
|
|
2972
|
+
${footnotes}
|
|
2973
|
+
</mj-column>
|
|
2974
|
+
</mj-section>`;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/reports/maintenance-email/template.ts
|
|
2978
|
+
var escapeXml2 = escapeHtml;
|
|
2979
|
+
var BLURRED_TESTS = `cid:${BLURRED_CID}`;
|
|
2980
|
+
function fmtDate(d) {
|
|
2981
|
+
if (!d || Number.isNaN(d.getTime())) return "";
|
|
2982
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2983
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2984
|
+
const yyyy = d.getUTCFullYear();
|
|
2985
|
+
return `${mm}.${dd}.${yyyy}`;
|
|
2917
2986
|
}
|
|
2918
2987
|
function maintenanceChecksSection(copy, searchPosition) {
|
|
2919
2988
|
const googleLabel = searchPosition !== void 0 ? `Page 1 Google Result (#${searchPosition})` : copy.maintenanceChecks[3] ?? "";
|
|
2920
2989
|
const rows = copy.maintenanceChecks.map((label, i) => i === 3 ? googleLabel : label);
|
|
2921
|
-
return rows
|
|
2922
|
-
(label, i) => `
|
|
2923
|
-
<mj-section background-color="white" padding="0px"${i === rows.length - 1 ? ' padding-bottom="36px"' : ""}>
|
|
2924
|
-
<mj-group>
|
|
2925
|
-
<mj-column padding-left="0px" width="90%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
2926
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
2927
|
-
</mj-column>
|
|
2928
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
2929
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
2930
|
-
</mj-column>
|
|
2931
|
-
</mj-group>
|
|
2932
|
-
</mj-section>`
|
|
2933
|
-
).join("");
|
|
2990
|
+
return checklistRowsSection(rows, { background: "white", lastPaddingBottom: "36px" });
|
|
2934
2991
|
}
|
|
2935
2992
|
function testingChecklistSection(copy) {
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
<mj-group>
|
|
2941
|
-
<mj-column width="90%" padding-left="0px"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
2942
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
2943
|
-
</mj-column>
|
|
2944
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
2945
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
2946
|
-
</mj-column>
|
|
2947
|
-
</mj-group>
|
|
2948
|
-
</mj-section>`
|
|
2949
|
-
).join("");
|
|
2993
|
+
return checklistRowsSection(copy.testingChecklist, {
|
|
2994
|
+
background: "#F4F4F4",
|
|
2995
|
+
lastPaddingBottom: "60px"
|
|
2996
|
+
});
|
|
2950
2997
|
}
|
|
2951
2998
|
function maintenanceTestingPlaceholder(lastTested) {
|
|
2952
2999
|
return `
|
|
@@ -2966,7 +3013,7 @@ function testingIntroSection(copy) {
|
|
|
2966
3013
|
<mj-section background-color="#F4F4F4">
|
|
2967
3014
|
<mj-column>
|
|
2968
3015
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">TESTING</mj-text>
|
|
2969
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
3016
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.testingIntro)}</mj-text>
|
|
2970
3017
|
</mj-column>
|
|
2971
3018
|
</mj-section>`;
|
|
2972
3019
|
}
|
|
@@ -2974,8 +3021,8 @@ function commentarySection(text, copy) {
|
|
|
2974
3021
|
return `
|
|
2975
3022
|
<mj-section background-color="white">
|
|
2976
3023
|
<mj-column>
|
|
2977
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${
|
|
2978
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
3024
|
+
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${escapeXml2(copy.notesHeader)}</mj-text>
|
|
3025
|
+
<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>
|
|
2979
3026
|
</mj-column>
|
|
2980
3027
|
</mj-section>`;
|
|
2981
3028
|
}
|
|
@@ -2984,8 +3031,8 @@ function hasHeaderDims(data) {
|
|
|
2984
3031
|
}
|
|
2985
3032
|
function headerImageTag(data) {
|
|
2986
3033
|
const src = `cid:${data.headerImageCid}`;
|
|
2987
|
-
const alt = `${
|
|
2988
|
-
const href = isHttpUrl(data.siteUrl) ?
|
|
3034
|
+
const alt = `${escapeXml2(data.siteName)} maintenance report`;
|
|
3035
|
+
const href = isHttpUrl(data.siteUrl) ? escapeXml2(data.siteUrl) : "#";
|
|
2989
3036
|
if (hasHeaderDims(data)) {
|
|
2990
3037
|
return `<mj-image href="${href}" src="${src}" alt="${alt}" width="${data.headerWidth}px" css-class="rd-header" container-background-color="${data.headerBgColor}" />`;
|
|
2991
3038
|
}
|
|
@@ -2998,7 +3045,7 @@ function headerStyleBlock(data) {
|
|
|
2998
3045
|
function buildMjml(data) {
|
|
2999
3046
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
3000
3047
|
const isTesting = data.reportType === "Testing";
|
|
3001
|
-
const previewText = `Checked up on ${
|
|
3048
|
+
const previewText = `Checked up on ${escapeXml2(data.siteName)}`;
|
|
3002
3049
|
return `<mjml>
|
|
3003
3050
|
<mj-head>
|
|
3004
3051
|
<mj-attributes>
|
|
@@ -3020,52 +3067,30 @@ function buildMjml(data) {
|
|
|
3020
3067
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">COMPLETED ON</mj-text>
|
|
3021
3068
|
<mj-text color="#C00" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
3022
3069
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">MAINTENANCE CHECKS</mj-text>
|
|
3023
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
3070
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.maintenanceIntro)}</mj-text>
|
|
3024
3071
|
</mj-column>
|
|
3025
3072
|
</mj-section>
|
|
3026
3073
|
${maintenanceChecksSection(copy, data.searchPosition)}
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability (A11y)</mj-text>
|
|
3035
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.accessibility}</mj-text>
|
|
3036
|
-
<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>
|
|
3037
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3038
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Best Practices</mj-text>
|
|
3039
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.bestPractices}</mj-text>
|
|
3040
|
-
<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>
|
|
3041
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3042
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Site Structure</mj-text>
|
|
3043
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.seo}</mj-text>
|
|
3044
|
-
<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>
|
|
3045
|
-
<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>
|
|
3046
|
-
</mj-column>
|
|
3047
|
-
</mj-section>
|
|
3048
|
-
<mj-section background-color="white">
|
|
3049
|
-
<mj-column>
|
|
3050
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">ANALYTICS</mj-text>
|
|
3051
|
-
<mj-text color="#C00" font-size="44px" font-weight="400">${data.gaUsersCurrent !== void 0 ? fmtUsers(data.gaUsersCurrent) : "\u2014"} Users</mj-text>
|
|
3052
|
-
${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}
|
|
3053
|
-
<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>
|
|
3054
|
-
</mj-column>
|
|
3055
|
-
</mj-section>
|
|
3074
|
+
${lighthouseScoresSection(data.lighthouse)}
|
|
3075
|
+
${analyticsSection({
|
|
3076
|
+
current: data.gaUsersCurrent,
|
|
3077
|
+
previous: data.gaUsersPrevious,
|
|
3078
|
+
background: "white",
|
|
3079
|
+
footnoteLines: [escapeXml2(copy.seoCta)]
|
|
3080
|
+
})}
|
|
3056
3081
|
${isTesting ? testingIntroSection(copy) + testingChecklistSection(copy) : maintenanceTestingPlaceholder(data.lastTestedDate)}
|
|
3057
3082
|
${data.commentary ? commentarySection(data.commentary, copy) : ""}
|
|
3058
3083
|
<mj-section background-color="white">
|
|
3059
3084
|
<mj-column padding-top="36px">
|
|
3060
3085
|
<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>
|
|
3061
3086
|
${copy.contact.map(
|
|
3062
|
-
(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">${
|
|
3087
|
+
(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>`
|
|
3063
3088
|
).join("\n ")}
|
|
3064
3089
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3065
|
-
<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()} ${
|
|
3090
|
+
<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>
|
|
3066
3091
|
<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>
|
|
3067
3092
|
${[copy.footerOrg, ...copy.footerAddress].map(
|
|
3068
|
-
(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">${
|
|
3093
|
+
(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>`
|
|
3069
3094
|
).join("\n ")}
|
|
3070
3095
|
</mj-column>
|
|
3071
3096
|
</mj-section>
|
|
@@ -3074,22 +3099,22 @@ function buildMjml(data) {
|
|
|
3074
3099
|
}
|
|
3075
3100
|
|
|
3076
3101
|
// src/reports/launch-email/template.ts
|
|
3077
|
-
var
|
|
3078
|
-
var
|
|
3102
|
+
var RED2 = "#C00";
|
|
3103
|
+
var GREY2 = "#757575";
|
|
3079
3104
|
function buildLaunchMjml(data) {
|
|
3080
3105
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
3081
|
-
const previewText = `${
|
|
3106
|
+
const previewText = `${escapeXml2(data.siteName)} is live`;
|
|
3082
3107
|
const setupRows = copy.launchSetupItems.map(
|
|
3083
3108
|
(item) => `
|
|
3084
|
-
<mj-text color="${
|
|
3109
|
+
<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>`
|
|
3085
3110
|
).join("");
|
|
3086
3111
|
const contactRows = copy.contact.map(
|
|
3087
3112
|
(line) => `
|
|
3088
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
3113
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
3089
3114
|
).join("");
|
|
3090
3115
|
const footerAddressRows = copy.footerAddress.map(
|
|
3091
3116
|
(line) => `
|
|
3092
|
-
<mj-text color="${
|
|
3117
|
+
<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>`
|
|
3093
3118
|
).join("");
|
|
3094
3119
|
return `<mjml>
|
|
3095
3120
|
<mj-head>
|
|
@@ -3107,20 +3132,20 @@ function buildLaunchMjml(data) {
|
|
|
3107
3132
|
</mj-section>
|
|
3108
3133
|
<mj-section background-color="white">
|
|
3109
3134
|
<mj-column>
|
|
3110
|
-
<mj-text color="${
|
|
3111
|
-
<mj-text color="${
|
|
3112
|
-
<mj-text color="${
|
|
3135
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="75px">${escapeXml2(copy.launchHeading)}</mj-text>
|
|
3136
|
+
<mj-text color="${RED2}" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
3137
|
+
<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>
|
|
3113
3138
|
${setupRows}
|
|
3114
3139
|
</mj-column>
|
|
3115
3140
|
</mj-section>
|
|
3116
3141
|
<mj-section background-color="white">
|
|
3117
3142
|
<mj-column padding-top="36px">
|
|
3118
|
-
<mj-text color="${
|
|
3143
|
+
<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>
|
|
3119
3144
|
${contactRows}
|
|
3120
3145
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3121
|
-
<mj-text color="${
|
|
3122
|
-
<mj-text color="${
|
|
3123
|
-
<mj-text color="${
|
|
3146
|
+
<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>
|
|
3147
|
+
<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>
|
|
3148
|
+
<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>
|
|
3124
3149
|
${footerAddressRows}
|
|
3125
3150
|
</mj-column>
|
|
3126
3151
|
</mj-section>
|
|
@@ -3134,18 +3159,14 @@ var FREQ_PHRASE = {
|
|
|
3134
3159
|
Quarterly: "every quarter",
|
|
3135
3160
|
Yearly: "every year"
|
|
3136
3161
|
};
|
|
3137
|
-
var
|
|
3138
|
-
var
|
|
3139
|
-
var
|
|
3140
|
-
function
|
|
3141
|
-
return
|
|
3162
|
+
var RED3 = "#C00";
|
|
3163
|
+
var GREY3 = "#757575";
|
|
3164
|
+
var SECTION_PAD = "40px";
|
|
3165
|
+
function sectionLabel(text) {
|
|
3166
|
+
return `<mj-text color="${RED3}" font-size="20px" font-weight="700" padding-top="0px">${escapeXml2(text)}</mj-text>`;
|
|
3142
3167
|
}
|
|
3143
|
-
function
|
|
3144
|
-
|
|
3145
|
-
const pct = Math.round((cur - prev) / prev * 100);
|
|
3146
|
-
if (pct > 0) return `\u25B2 ${pct}% vs the previous month`;
|
|
3147
|
-
if (pct < 0) return `\u25BC ${Math.abs(pct)}% vs the previous month`;
|
|
3148
|
-
return "No change vs the previous month";
|
|
3168
|
+
function bodyLine(text, paddingTop = "8px") {
|
|
3169
|
+
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>`;
|
|
3149
3170
|
}
|
|
3150
3171
|
function announcementSiteExtras(site) {
|
|
3151
3172
|
return {
|
|
@@ -3153,83 +3174,68 @@ function announcementSiteExtras(site) {
|
|
|
3153
3174
|
improvements: { resendForms: true, svelte5: true }
|
|
3154
3175
|
};
|
|
3155
3176
|
}
|
|
3156
|
-
var SCORE_PREVIEW = [
|
|
3157
|
-
{ label: "Performance", key: "performance" },
|
|
3158
|
-
{ label: "Readability (A11y)", key: "accessibility" },
|
|
3159
|
-
{ label: "Best Practices", key: "bestPractices" },
|
|
3160
|
-
{ label: "Site Structure", key: "seo" }
|
|
3161
|
-
];
|
|
3162
3177
|
function buildAnnouncementMjml(data) {
|
|
3163
3178
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
3164
3179
|
const previewText = "Your monthly report from Reddoor";
|
|
3180
|
+
const cad = data.cadence;
|
|
3181
|
+
const hasMaint = Boolean(cad && cad.maintenance !== "None");
|
|
3182
|
+
const hasTesting = Boolean(cad && cad.testing !== "None");
|
|
3165
3183
|
const improvementItems = [];
|
|
3166
3184
|
if (data.improvements?.resendForms) improvementItems.push(copy.announceImprovementResend);
|
|
3167
3185
|
if (data.improvements?.svelte5) improvementItems.push(copy.announceImprovementSvelte5);
|
|
3168
|
-
const
|
|
3169
|
-
|
|
3186
|
+
const hasImpr = improvementItems.length > 0;
|
|
3187
|
+
const BANDS = ["white", "#F4F4F4"];
|
|
3188
|
+
let bandN = 0;
|
|
3189
|
+
const nextBg = () => BANDS[bandN++ % 2];
|
|
3190
|
+
const introBg = nextBg();
|
|
3191
|
+
const maintBg = hasMaint ? nextBg() : "";
|
|
3192
|
+
const testBg = hasTesting ? nextBg() : "";
|
|
3193
|
+
const lighthouseBg = nextBg();
|
|
3194
|
+
const analyticsBg = nextBg();
|
|
3195
|
+
const improvementsBg = hasImpr ? nextBg() : "";
|
|
3196
|
+
const contactBg = nextBg();
|
|
3197
|
+
const maintenanceSection = cad && cad.maintenance !== "None" ? `
|
|
3198
|
+
<mj-section background-color="${maintBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
3170
3199
|
<mj-column>
|
|
3171
|
-
|
|
3172
|
-
${
|
|
3173
|
-
(item) => `
|
|
3174
|
-
<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>`
|
|
3175
|
-
).join("")}
|
|
3200
|
+
${sectionLabel("MAINTENANCE CHECKS")}
|
|
3201
|
+
${bodyLine(`${copy.maintenanceIntro} We do this ${FREQ_PHRASE[cad.maintenance]}.${cad.testing === "None" ? ` ${copy.announceCadence}` : ""}`)}
|
|
3176
3202
|
</mj-column>
|
|
3177
|
-
</mj-section
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
checks: copy.testingChecklist
|
|
3184
|
-
});
|
|
3185
|
-
if (cad && cad.maintenance !== "None")
|
|
3186
|
-
cadenceBlocks.push({
|
|
3187
|
-
line: `${copy.announceMaintenanceLabel} \u2014 ${FREQ_PHRASE[cad.maintenance]}`,
|
|
3188
|
-
checks: copy.maintenanceChecks
|
|
3189
|
-
});
|
|
3190
|
-
const cadenceSection = cadenceBlocks.length > 0 ? `
|
|
3191
|
-
<mj-section background-color="white">
|
|
3203
|
+
</mj-section>${checklistRowsSection(copy.maintenanceChecks, {
|
|
3204
|
+
background: maintBg,
|
|
3205
|
+
lastPaddingBottom: SECTION_PAD
|
|
3206
|
+
})}` : "";
|
|
3207
|
+
const testingSection = cad && cad.testing !== "None" ? `
|
|
3208
|
+
<mj-section background-color="${testBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
3192
3209
|
<mj-column>
|
|
3193
|
-
|
|
3194
|
-
${
|
|
3195
|
-
(b) => `
|
|
3196
|
-
<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(
|
|
3197
|
-
(c) => `
|
|
3198
|
-
<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>`
|
|
3199
|
-
).join("")}`
|
|
3200
|
-
).join("")}
|
|
3201
|
-
<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>
|
|
3210
|
+
${sectionLabel("TESTING")}
|
|
3211
|
+
${bodyLine(`${copy.testingIntro} We run a full test ${FREQ_PHRASE[cad.testing]}. ${copy.announceCadence}`)}
|
|
3202
3212
|
</mj-column>
|
|
3203
|
-
</mj-section
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
if (data.searchPosition !== void 0)
|
|
3217
|
-
trafficRows.push(`
|
|
3218
|
-
<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>`);
|
|
3219
|
-
const trafficSection = trafficRows.length > 0 ? `
|
|
3220
|
-
<mj-section background-color="white">
|
|
3213
|
+
</mj-section>${checklistRowsSection(copy.testingChecklist, {
|
|
3214
|
+
background: testBg,
|
|
3215
|
+
lastPaddingBottom: SECTION_PAD
|
|
3216
|
+
})}` : "";
|
|
3217
|
+
const analytics = analyticsSection({
|
|
3218
|
+
current: data.gaUsersCurrent,
|
|
3219
|
+
previous: data.gaUsersPrevious,
|
|
3220
|
+
background: analyticsBg,
|
|
3221
|
+
pad: SECTION_PAD,
|
|
3222
|
+
bodyLines: data.searchPosition !== void 0 ? [`Page 1 Google result (#${data.searchPosition}) for your brand search`] : []
|
|
3223
|
+
});
|
|
3224
|
+
const improvementsSection = hasImpr ? `
|
|
3225
|
+
<mj-section background-color="${improvementsBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
3221
3226
|
<mj-column>
|
|
3222
|
-
|
|
3223
|
-
${
|
|
3227
|
+
${sectionLabel("RECENT IMPROVEMENTS")}
|
|
3228
|
+
${improvementItems.map((item) => bodyLine(item)).join("\n ")}
|
|
3229
|
+
${bodyLine(copy.announceOpenDoor, "16px")}
|
|
3224
3230
|
</mj-column>
|
|
3225
3231
|
</mj-section>` : "";
|
|
3226
3232
|
const contactRows = copy.contact.map(
|
|
3227
3233
|
(line) => `
|
|
3228
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
3234
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
3229
3235
|
).join("");
|
|
3230
3236
|
const footerAddressRows = copy.footerAddress.map(
|
|
3231
3237
|
(line) => `
|
|
3232
|
-
<mj-text color="${
|
|
3238
|
+
<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>`
|
|
3233
3239
|
).join("");
|
|
3234
3240
|
return `<mjml>
|
|
3235
3241
|
<mj-head>
|
|
@@ -3238,42 +3244,33 @@ function buildAnnouncementMjml(data) {
|
|
|
3238
3244
|
<mj-section padding-left="11%" padding-right="11%"/>
|
|
3239
3245
|
<mj-image padding="0px" />
|
|
3240
3246
|
</mj-attributes>
|
|
3241
|
-
<mj-preview>${
|
|
3247
|
+
<mj-preview>${escapeXml2(previewText)}</mj-preview>
|
|
3242
3248
|
${headerStyleBlock(data)}
|
|
3243
3249
|
</mj-head>
|
|
3244
3250
|
<mj-body background-color="white">
|
|
3245
3251
|
<mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
|
|
3246
3252
|
<mj-column>${headerImageTag(data)}</mj-column>
|
|
3247
3253
|
</mj-section>
|
|
3248
|
-
<mj-section background-color="
|
|
3254
|
+
<mj-section background-color="${introBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
3249
3255
|
<mj-column>
|
|
3250
|
-
|
|
3251
|
-
<mj-text color="${
|
|
3252
|
-
|
|
3256
|
+
${sectionLabel(copy.announceHeading)}
|
|
3257
|
+
<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>
|
|
3258
|
+
${bodyLine(copy.announceBody)}
|
|
3253
3259
|
</mj-column>
|
|
3254
3260
|
</mj-section>
|
|
3255
|
-
${
|
|
3261
|
+
${maintenanceSection}
|
|
3262
|
+
${testingSection}
|
|
3263
|
+
${lighthouseScoresSection(data.lighthouse, { background: lighthouseBg, pad: SECTION_PAD })}
|
|
3264
|
+
${analytics}
|
|
3256
3265
|
${improvementsSection}
|
|
3257
|
-
<mj-section background-color="
|
|
3266
|
+
<mj-section background-color="${contactBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
3258
3267
|
<mj-column>
|
|
3259
|
-
<mj-text color="${
|
|
3260
|
-
${scoreRows}${scoreNote}
|
|
3261
|
-
</mj-column>
|
|
3262
|
-
</mj-section>
|
|
3263
|
-
${trafficSection}
|
|
3264
|
-
<mj-section background-color="white">
|
|
3265
|
-
<mj-column>
|
|
3266
|
-
<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>
|
|
3267
|
-
</mj-column>
|
|
3268
|
-
</mj-section>
|
|
3269
|
-
<mj-section background-color="white">
|
|
3270
|
-
<mj-column padding-top="36px">
|
|
3271
|
-
<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>
|
|
3268
|
+
<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>
|
|
3272
3269
|
${contactRows}
|
|
3273
3270
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3274
|
-
<mj-text color="${
|
|
3275
|
-
<mj-text color="${
|
|
3276
|
-
<mj-text color="${
|
|
3271
|
+
<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>
|
|
3272
|
+
<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>
|
|
3273
|
+
<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>
|
|
3277
3274
|
${footerAddressRows}
|
|
3278
3275
|
</mj-column>
|
|
3279
3276
|
</mj-section>
|
|
@@ -3443,6 +3440,33 @@ async function stampSent(base, recordId, sentAt, messageId) {
|
|
|
3443
3440
|
]);
|
|
3444
3441
|
}
|
|
3445
3442
|
|
|
3443
|
+
// src/reports/queue.ts
|
|
3444
|
+
var REPORT_TIER = {
|
|
3445
|
+
Maintenance: 1,
|
|
3446
|
+
Testing: 2,
|
|
3447
|
+
Announcement: 3,
|
|
3448
|
+
Launch: 3
|
|
3449
|
+
};
|
|
3450
|
+
function reportTier(type) {
|
|
3451
|
+
return REPORT_TIER[type];
|
|
3452
|
+
}
|
|
3453
|
+
async function queueDraft(base, report) {
|
|
3454
|
+
const newTier = reportTier(report.reportType);
|
|
3455
|
+
const others = (await listReportsForSite(base, report.siteId)).filter(isPendingApproval).filter((r) => r.id !== report.id);
|
|
3456
|
+
const blocker = others.find((r) => reportTier(r.reportType) >= newTier);
|
|
3457
|
+
if (blocker) {
|
|
3458
|
+
await setDraftReady(base, report.id, false);
|
|
3459
|
+
return { queued: false, blockedBy: blocker.reportType, supersededIds: [] };
|
|
3460
|
+
}
|
|
3461
|
+
const supersededIds = [];
|
|
3462
|
+
for (const r of others) {
|
|
3463
|
+
await setDraftReady(base, r.id, false);
|
|
3464
|
+
supersededIds.push(r.id);
|
|
3465
|
+
}
|
|
3466
|
+
await setDraftReady(base, report.id, true);
|
|
3467
|
+
return { queued: true, supersededIds };
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3446
3470
|
// src/reports/airtable/attachments.ts
|
|
3447
3471
|
function looksLikeHtml(bytes) {
|
|
3448
3472
|
const start = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0;
|
|
@@ -3681,12 +3705,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
3681
3705
|
const path = options.previewPath ?? `reports/${slug}/draft.html`;
|
|
3682
3706
|
await mkdir3(dirname6(path), { recursive: true });
|
|
3683
3707
|
await writeFile10(path, html, "utf-8");
|
|
3684
|
-
return { reportRow: null, htmlPath: path, html, softFailures };
|
|
3708
|
+
return { reportRow: null, htmlPath: path, html, softFailures, queued: null, supersededIds: [] };
|
|
3685
3709
|
}
|
|
3686
3710
|
if (base === null) throw new Error("base required when previewOnly=false");
|
|
3687
3711
|
if (options.completeRowId) {
|
|
3688
|
-
await
|
|
3689
|
-
|
|
3712
|
+
await uploadDraftHtml(options.completeRowId, slug, periodEnd, html);
|
|
3713
|
+
const outcome2 = await queueDraft(base, {
|
|
3714
|
+
id: options.completeRowId,
|
|
3715
|
+
siteId: siteRow.id,
|
|
3716
|
+
reportType
|
|
3717
|
+
});
|
|
3718
|
+
return {
|
|
3719
|
+
reportRow: options.existingRow ?? null,
|
|
3720
|
+
htmlPath: null,
|
|
3721
|
+
html,
|
|
3722
|
+
softFailures,
|
|
3723
|
+
queued: outcome2.queued,
|
|
3724
|
+
supersededIds: outcome2.supersededIds
|
|
3725
|
+
};
|
|
3690
3726
|
}
|
|
3691
3727
|
const reportId = `${siteRow.name} \u2014 ${reportType} \u2014 ${periodEnd.toISOString().slice(0, 10)}`;
|
|
3692
3728
|
const created = await createDraft(base, {
|
|
@@ -3703,13 +3739,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
3703
3739
|
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
3704
3740
|
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
3705
3741
|
});
|
|
3706
|
-
await
|
|
3707
|
-
|
|
3742
|
+
await uploadDraftHtml(created.id, slug, periodEnd, html);
|
|
3743
|
+
const outcome = await queueDraft(base, {
|
|
3744
|
+
id: created.id,
|
|
3745
|
+
siteId: siteRow.id,
|
|
3746
|
+
reportType
|
|
3747
|
+
});
|
|
3748
|
+
return {
|
|
3749
|
+
reportRow: created,
|
|
3750
|
+
htmlPath: null,
|
|
3751
|
+
html,
|
|
3752
|
+
softFailures,
|
|
3753
|
+
queued: outcome.queued,
|
|
3754
|
+
supersededIds: outcome.supersededIds
|
|
3755
|
+
};
|
|
3708
3756
|
}
|
|
3709
|
-
async function
|
|
3757
|
+
async function uploadDraftHtml(rowId, slug, periodEnd, html) {
|
|
3710
3758
|
const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;
|
|
3711
3759
|
await uploadAttachment(rowId, "Rendered HTML", html, htmlFilename, "text/html");
|
|
3712
|
-
await setDraftReady(base, rowId, true);
|
|
3713
3760
|
}
|
|
3714
3761
|
var NO_ENRICHMENT = { value: null, softFailed: false };
|
|
3715
3762
|
async function fetchGaUsers(siteRow, periodStart, periodEnd) {
|
|
@@ -3973,9 +4020,8 @@ async function sendOne(client, base, site, report) {
|
|
|
3973
4020
|
headerHeight: header.displayHeight,
|
|
3974
4021
|
headerBgColor: header.placeholderColor,
|
|
3975
4022
|
// Announcement-only: re-derive cadence + improvements from the site row so the SENT email
|
|
3976
|
-
// keeps its
|
|
3977
|
-
//
|
|
3978
|
-
// other report templates.
|
|
4023
|
+
// keeps its cadence copy + improvement callouts. Without this the send-time re-render drops
|
|
4024
|
+
// them entirely (they're not stored on the Reports row). Ignored by the other templates.
|
|
3979
4025
|
...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
|
|
3980
4026
|
});
|
|
3981
4027
|
const reportDate = report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date();
|