@reddoorla/maintenance 0.46.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 +407 -222
- 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 -6
- package/dist/index.js +295 -183
- 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,
|
|
@@ -2769,7 +2773,7 @@ var DEFAULT_COPY = {
|
|
|
2769
2773
|
"Links & Navigation",
|
|
2770
2774
|
"Form Functionality",
|
|
2771
2775
|
"Interactions & Animations",
|
|
2772
|
-
"
|
|
2776
|
+
"Tested After Updates"
|
|
2773
2777
|
],
|
|
2774
2778
|
notesHeader: "NOTES",
|
|
2775
2779
|
seoCta: "Contact us if you are interested in more in-depth data or have questions about SEO.",
|
|
@@ -2785,14 +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
2792
|
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
2793
2793
|
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
2794
2794
|
announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
|
|
2795
|
-
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."
|
|
2796
2796
|
};
|
|
2797
2797
|
function override(v) {
|
|
2798
2798
|
if (typeof v !== "string") return null;
|
|
@@ -2882,70 +2882,118 @@ function safeUrl(raw) {
|
|
|
2882
2882
|
return "#";
|
|
2883
2883
|
}
|
|
2884
2884
|
|
|
2885
|
-
// src/reports/
|
|
2885
|
+
// src/reports/email-sections.ts
|
|
2886
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;
|
|
2887
2892
|
var CHECK_PNG = `cid:${CHECK_CID}`;
|
|
2888
|
-
var BLURRED_TESTS = `cid:${BLURRED_CID}`;
|
|
2889
|
-
function fmtDate(d) {
|
|
2890
|
-
if (!d || Number.isNaN(d.getTime())) return "";
|
|
2891
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2892
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2893
|
-
const yyyy = d.getUTCFullYear();
|
|
2894
|
-
return `${mm}.${dd}.${yyyy}`;
|
|
2895
|
-
}
|
|
2896
2893
|
function fmtUsers(n) {
|
|
2897
2894
|
return n.toLocaleString("en-US");
|
|
2898
2895
|
}
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
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>`;
|
|
2903
2939
|
}
|
|
2904
2940
|
function analyticsTrendLine(cur, prev) {
|
|
2905
2941
|
if (cur === void 0 || prev === void 0) {
|
|
2906
|
-
return
|
|
2942
|
+
return trendLine(TREND_NEUTRAL, `Last Period: ${prev !== void 0 ? fmtUsers(prev) : "\u2014"}`);
|
|
2907
2943
|
}
|
|
2908
2944
|
if (prev === 0) {
|
|
2909
|
-
return cur > 0 ?
|
|
2945
|
+
return cur > 0 ? trendLine(TREND_UP, "\u25B2 New this period (0 last period)") : trendLine(TREND_NEUTRAL, "Last Period: 0");
|
|
2910
2946
|
}
|
|
2911
2947
|
const pct = Math.round((cur - prev) / prev * 100);
|
|
2912
2948
|
const range = `(${fmtUsers(prev)} \u2192 ${fmtUsers(cur)})`;
|
|
2913
|
-
if (pct > 0) return
|
|
2914
|
-
if (pct < 0) return
|
|
2915
|
-
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}`;
|
|
2916
2986
|
}
|
|
2917
2987
|
function maintenanceChecksSection(copy, searchPosition) {
|
|
2918
2988
|
const googleLabel = searchPosition !== void 0 ? `Page 1 Google Result (#${searchPosition})` : copy.maintenanceChecks[3] ?? "";
|
|
2919
2989
|
const rows = copy.maintenanceChecks.map((label, i) => i === 3 ? googleLabel : label);
|
|
2920
|
-
return rows
|
|
2921
|
-
(label, i) => `
|
|
2922
|
-
<mj-section background-color="white" padding="0px"${i === rows.length - 1 ? ' padding-bottom="36px"' : ""}>
|
|
2923
|
-
<mj-group>
|
|
2924
|
-
<mj-column padding-left="0px" width="90%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
2925
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
2926
|
-
</mj-column>
|
|
2927
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
2928
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
2929
|
-
</mj-column>
|
|
2930
|
-
</mj-group>
|
|
2931
|
-
</mj-section>`
|
|
2932
|
-
).join("");
|
|
2990
|
+
return checklistRowsSection(rows, { background: "white", lastPaddingBottom: "36px" });
|
|
2933
2991
|
}
|
|
2934
2992
|
function testingChecklistSection(copy) {
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
<mj-group>
|
|
2940
|
-
<mj-column width="90%" padding-left="0px"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
2941
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
2942
|
-
</mj-column>
|
|
2943
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
2944
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
2945
|
-
</mj-column>
|
|
2946
|
-
</mj-group>
|
|
2947
|
-
</mj-section>`
|
|
2948
|
-
).join("");
|
|
2993
|
+
return checklistRowsSection(copy.testingChecklist, {
|
|
2994
|
+
background: "#F4F4F4",
|
|
2995
|
+
lastPaddingBottom: "60px"
|
|
2996
|
+
});
|
|
2949
2997
|
}
|
|
2950
2998
|
function maintenanceTestingPlaceholder(lastTested) {
|
|
2951
2999
|
return `
|
|
@@ -2965,7 +3013,7 @@ function testingIntroSection(copy) {
|
|
|
2965
3013
|
<mj-section background-color="#F4F4F4">
|
|
2966
3014
|
<mj-column>
|
|
2967
3015
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">TESTING</mj-text>
|
|
2968
|
-
<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>
|
|
2969
3017
|
</mj-column>
|
|
2970
3018
|
</mj-section>`;
|
|
2971
3019
|
}
|
|
@@ -2973,8 +3021,8 @@ function commentarySection(text, copy) {
|
|
|
2973
3021
|
return `
|
|
2974
3022
|
<mj-section background-color="white">
|
|
2975
3023
|
<mj-column>
|
|
2976
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${
|
|
2977
|
-
<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>
|
|
2978
3026
|
</mj-column>
|
|
2979
3027
|
</mj-section>`;
|
|
2980
3028
|
}
|
|
@@ -2983,8 +3031,8 @@ function hasHeaderDims(data) {
|
|
|
2983
3031
|
}
|
|
2984
3032
|
function headerImageTag(data) {
|
|
2985
3033
|
const src = `cid:${data.headerImageCid}`;
|
|
2986
|
-
const alt = `${
|
|
2987
|
-
const href = isHttpUrl(data.siteUrl) ?
|
|
3034
|
+
const alt = `${escapeXml2(data.siteName)} maintenance report`;
|
|
3035
|
+
const href = isHttpUrl(data.siteUrl) ? escapeXml2(data.siteUrl) : "#";
|
|
2988
3036
|
if (hasHeaderDims(data)) {
|
|
2989
3037
|
return `<mj-image href="${href}" src="${src}" alt="${alt}" width="${data.headerWidth}px" css-class="rd-header" container-background-color="${data.headerBgColor}" />`;
|
|
2990
3038
|
}
|
|
@@ -2997,7 +3045,7 @@ function headerStyleBlock(data) {
|
|
|
2997
3045
|
function buildMjml(data) {
|
|
2998
3046
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
2999
3047
|
const isTesting = data.reportType === "Testing";
|
|
3000
|
-
const previewText = `Checked up on ${
|
|
3048
|
+
const previewText = `Checked up on ${escapeXml2(data.siteName)}`;
|
|
3001
3049
|
return `<mjml>
|
|
3002
3050
|
<mj-head>
|
|
3003
3051
|
<mj-attributes>
|
|
@@ -3019,52 +3067,30 @@ function buildMjml(data) {
|
|
|
3019
3067
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">COMPLETED ON</mj-text>
|
|
3020
3068
|
<mj-text color="#C00" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
3021
3069
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">MAINTENANCE CHECKS</mj-text>
|
|
3022
|
-
<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>
|
|
3023
3071
|
</mj-column>
|
|
3024
3072
|
</mj-section>
|
|
3025
3073
|
${maintenanceChecksSection(copy, data.searchPosition)}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability</mj-text>
|
|
3034
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.accessibility}</mj-text>
|
|
3035
|
-
<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>
|
|
3036
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3037
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Best Practices</mj-text>
|
|
3038
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.bestPractices}</mj-text>
|
|
3039
|
-
<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>
|
|
3040
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3041
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Site Structure</mj-text>
|
|
3042
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.seo}</mj-text>
|
|
3043
|
-
<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>
|
|
3044
|
-
<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>
|
|
3045
|
-
</mj-column>
|
|
3046
|
-
</mj-section>
|
|
3047
|
-
<mj-section background-color="white">
|
|
3048
|
-
<mj-column>
|
|
3049
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">ANALYTICS</mj-text>
|
|
3050
|
-
<mj-text color="#C00" font-size="44px" font-weight="400">${data.gaUsersCurrent !== void 0 ? fmtUsers(data.gaUsersCurrent) : "\u2014"} Users</mj-text>
|
|
3051
|
-
${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}
|
|
3052
|
-
<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>
|
|
3053
|
-
</mj-column>
|
|
3054
|
-
</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
|
+
})}
|
|
3055
3081
|
${isTesting ? testingIntroSection(copy) + testingChecklistSection(copy) : maintenanceTestingPlaceholder(data.lastTestedDate)}
|
|
3056
3082
|
${data.commentary ? commentarySection(data.commentary, copy) : ""}
|
|
3057
3083
|
<mj-section background-color="white">
|
|
3058
3084
|
<mj-column padding-top="36px">
|
|
3059
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>
|
|
3060
3086
|
${copy.contact.map(
|
|
3061
|
-
(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>`
|
|
3062
3088
|
).join("\n ")}
|
|
3063
3089
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3064
|
-
<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>
|
|
3065
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>
|
|
3066
3092
|
${[copy.footerOrg, ...copy.footerAddress].map(
|
|
3067
|
-
(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>`
|
|
3068
3094
|
).join("\n ")}
|
|
3069
3095
|
</mj-column>
|
|
3070
3096
|
</mj-section>
|
|
@@ -3073,22 +3099,22 @@ function buildMjml(data) {
|
|
|
3073
3099
|
}
|
|
3074
3100
|
|
|
3075
3101
|
// src/reports/launch-email/template.ts
|
|
3076
|
-
var
|
|
3077
|
-
var
|
|
3102
|
+
var RED2 = "#C00";
|
|
3103
|
+
var GREY2 = "#757575";
|
|
3078
3104
|
function buildLaunchMjml(data) {
|
|
3079
3105
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
3080
|
-
const previewText = `${
|
|
3106
|
+
const previewText = `${escapeXml2(data.siteName)} is live`;
|
|
3081
3107
|
const setupRows = copy.launchSetupItems.map(
|
|
3082
3108
|
(item) => `
|
|
3083
|
-
<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>`
|
|
3084
3110
|
).join("");
|
|
3085
3111
|
const contactRows = copy.contact.map(
|
|
3086
3112
|
(line) => `
|
|
3087
|
-
<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>`
|
|
3088
3114
|
).join("");
|
|
3089
3115
|
const footerAddressRows = copy.footerAddress.map(
|
|
3090
3116
|
(line) => `
|
|
3091
|
-
<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>`
|
|
3092
3118
|
).join("");
|
|
3093
3119
|
return `<mjml>
|
|
3094
3120
|
<mj-head>
|
|
@@ -3106,20 +3132,20 @@ function buildLaunchMjml(data) {
|
|
|
3106
3132
|
</mj-section>
|
|
3107
3133
|
<mj-section background-color="white">
|
|
3108
3134
|
<mj-column>
|
|
3109
|
-
<mj-text color="${
|
|
3110
|
-
<mj-text color="${
|
|
3111
|
-
<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>
|
|
3112
3138
|
${setupRows}
|
|
3113
3139
|
</mj-column>
|
|
3114
3140
|
</mj-section>
|
|
3115
3141
|
<mj-section background-color="white">
|
|
3116
3142
|
<mj-column padding-top="36px">
|
|
3117
|
-
<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>
|
|
3118
3144
|
${contactRows}
|
|
3119
3145
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3120
|
-
<mj-text color="${
|
|
3121
|
-
<mj-text color="${
|
|
3122
|
-
<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>
|
|
3123
3149
|
${footerAddressRows}
|
|
3124
3150
|
</mj-column>
|
|
3125
3151
|
</mj-section>
|
|
@@ -3133,66 +3159,83 @@ var FREQ_PHRASE = {
|
|
|
3133
3159
|
Quarterly: "every quarter",
|
|
3134
3160
|
Yearly: "every year"
|
|
3135
3161
|
};
|
|
3136
|
-
var
|
|
3137
|
-
var
|
|
3138
|
-
var
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
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>`;
|
|
3167
|
+
}
|
|
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>`;
|
|
3170
|
+
}
|
|
3171
|
+
function announcementSiteExtras(site) {
|
|
3172
|
+
return {
|
|
3173
|
+
cadence: { maintenance: site.maintenanceFreq, testing: site.testingFreq },
|
|
3174
|
+
improvements: { resendForms: true, svelte5: true }
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3144
3177
|
function buildAnnouncementMjml(data) {
|
|
3145
3178
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
3146
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");
|
|
3147
3183
|
const improvementItems = [];
|
|
3148
3184
|
if (data.improvements?.resendForms) improvementItems.push(copy.announceImprovementResend);
|
|
3149
3185
|
if (data.improvements?.svelte5) improvementItems.push(copy.announceImprovementSvelte5);
|
|
3150
|
-
const
|
|
3151
|
-
|
|
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">
|
|
3152
3199
|
<mj-column>
|
|
3153
|
-
|
|
3154
|
-
${
|
|
3155
|
-
(item) => `
|
|
3156
|
-
<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>`
|
|
3157
|
-
).join("")}
|
|
3200
|
+
${sectionLabel("MAINTENANCE CHECKS")}
|
|
3201
|
+
${bodyLine(`${copy.maintenanceIntro} We do this ${FREQ_PHRASE[cad.maintenance]}.${cad.testing === "None" ? ` ${copy.announceCadence}` : ""}`)}
|
|
3158
3202
|
</mj-column>
|
|
3159
|
-
</mj-section
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
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">
|
|
3209
|
+
<mj-column>
|
|
3210
|
+
${sectionLabel("TESTING")}
|
|
3211
|
+
${bodyLine(`${copy.testingIntro} We run a full test ${FREQ_PHRASE[cad.testing]}. ${copy.announceCadence}`)}
|
|
3212
|
+
</mj-column>
|
|
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}">
|
|
3174
3226
|
<mj-column>
|
|
3175
|
-
|
|
3176
|
-
${
|
|
3177
|
-
|
|
3178
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="400" line-height="24px" padding-top="12px" padding-bottom="0px">\u2022 ${escapeXml(b.line)}</mj-text>
|
|
3179
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="13px" font-weight="300" line-height="20px" padding-top="2px" padding-bottom="0px" padding-left="16px">${escapeXml(b.checks.join(" \xB7 "))}</mj-text>`
|
|
3180
|
-
).join("")}
|
|
3181
|
-
<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>
|
|
3227
|
+
${sectionLabel("RECENT IMPROVEMENTS")}
|
|
3228
|
+
${improvementItems.map((item) => bodyLine(item)).join("\n ")}
|
|
3229
|
+
${bodyLine(copy.announceOpenDoor, "16px")}
|
|
3182
3230
|
</mj-column>
|
|
3183
3231
|
</mj-section>` : "";
|
|
3184
|
-
const scoreRows = SCORE_PREVIEW.map(
|
|
3185
|
-
({ label, key }) => `
|
|
3186
|
-
<mj-text color="${RED2}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
3187
|
-
<mj-text color="${RED2}" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse[key]}</mj-text>`
|
|
3188
|
-
).join("");
|
|
3189
3232
|
const contactRows = copy.contact.map(
|
|
3190
3233
|
(line) => `
|
|
3191
|
-
<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>`
|
|
3192
3235
|
).join("");
|
|
3193
3236
|
const footerAddressRows = copy.footerAddress.map(
|
|
3194
3237
|
(line) => `
|
|
3195
|
-
<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>`
|
|
3196
3239
|
).join("");
|
|
3197
3240
|
return `<mjml>
|
|
3198
3241
|
<mj-head>
|
|
@@ -3201,41 +3244,33 @@ function buildAnnouncementMjml(data) {
|
|
|
3201
3244
|
<mj-section padding-left="11%" padding-right="11%"/>
|
|
3202
3245
|
<mj-image padding="0px" />
|
|
3203
3246
|
</mj-attributes>
|
|
3204
|
-
<mj-preview>${
|
|
3247
|
+
<mj-preview>${escapeXml2(previewText)}</mj-preview>
|
|
3205
3248
|
${headerStyleBlock(data)}
|
|
3206
3249
|
</mj-head>
|
|
3207
3250
|
<mj-body background-color="white">
|
|
3208
3251
|
<mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
|
|
3209
3252
|
<mj-column>${headerImageTag(data)}</mj-column>
|
|
3210
3253
|
</mj-section>
|
|
3211
|
-
<mj-section background-color="
|
|
3254
|
+
<mj-section background-color="${introBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
3212
3255
|
<mj-column>
|
|
3213
|
-
|
|
3214
|
-
<mj-text color="${
|
|
3215
|
-
|
|
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)}
|
|
3216
3259
|
</mj-column>
|
|
3217
3260
|
</mj-section>
|
|
3218
|
-
${
|
|
3261
|
+
${maintenanceSection}
|
|
3262
|
+
${testingSection}
|
|
3263
|
+
${lighthouseScoresSection(data.lighthouse, { background: lighthouseBg, pad: SECTION_PAD })}
|
|
3264
|
+
${analytics}
|
|
3219
3265
|
${improvementsSection}
|
|
3220
|
-
<mj-section background-color="
|
|
3221
|
-
<mj-column>
|
|
3222
|
-
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="55px">${escapeXml(copy.announcePreviewLabel)}</mj-text>
|
|
3223
|
-
${scoreRows}
|
|
3224
|
-
</mj-column>
|
|
3225
|
-
</mj-section>
|
|
3226
|
-
<mj-section background-color="white">
|
|
3266
|
+
<mj-section background-color="${contactBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
3227
3267
|
<mj-column>
|
|
3228
|
-
<mj-text color="${
|
|
3229
|
-
</mj-column>
|
|
3230
|
-
</mj-section>
|
|
3231
|
-
<mj-section background-color="white">
|
|
3232
|
-
<mj-column padding-top="36px">
|
|
3233
|
-
<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>
|
|
3234
3269
|
${contactRows}
|
|
3235
3270
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
3236
|
-
<mj-text color="${
|
|
3237
|
-
<mj-text color="${
|
|
3238
|
-
<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>
|
|
3239
3274
|
${footerAddressRows}
|
|
3240
3275
|
</mj-column>
|
|
3241
3276
|
</mj-section>
|
|
@@ -3270,7 +3305,10 @@ var TESTING_CHECKLIST = [
|
|
|
3270
3305
|
label: "Interactions & Animations",
|
|
3271
3306
|
field: "Test: Interactions & Animations"
|
|
3272
3307
|
},
|
|
3273
|
-
|
|
3308
|
+
// `field` keeps its original Airtable column name ("…Verified After Updates") even though
|
|
3309
|
+
// the client-facing label is now "Tested After Updates" — the column holds operator data,
|
|
3310
|
+
// so renaming the label is display-only and avoids a live-base column migration.
|
|
3311
|
+
{ key: "updates", label: "Tested After Updates", field: "Test: Verified After Updates" }
|
|
3274
3312
|
];
|
|
3275
3313
|
var ALL_CHECKLIST_FIELDS = [...MAINTENANCE_CHECKLIST, ...TESTING_CHECKLIST].map(
|
|
3276
3314
|
(i) => i.field
|
|
@@ -3402,6 +3440,33 @@ async function stampSent(base, recordId, sentAt, messageId) {
|
|
|
3402
3440
|
]);
|
|
3403
3441
|
}
|
|
3404
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
|
+
|
|
3405
3470
|
// src/reports/airtable/attachments.ts
|
|
3406
3471
|
function looksLikeHtml(bytes) {
|
|
3407
3472
|
const start = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0;
|
|
@@ -3511,6 +3576,7 @@ import { JWT as JWT2 } from "google-auth-library";
|
|
|
3511
3576
|
var WEBMASTERS_READONLY = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
3512
3577
|
var SC_BASE = "https://searchconsole.googleapis.com/webmasters/v3";
|
|
3513
3578
|
var PAGE_1_MAX_POSITION = 10;
|
|
3579
|
+
var BRAND_QUERY_ROW_LIMIT = 100;
|
|
3514
3580
|
function bareHost(s) {
|
|
3515
3581
|
return s.trim().replace(/^sc-domain:/i, "").replace(/^https?:\/\//i, "").split("/")[0].replace(/^www\./i, "").toLowerCase();
|
|
3516
3582
|
}
|
|
@@ -3524,6 +3590,25 @@ function resolvePropertyCandidates(entries, host) {
|
|
|
3524
3590
|
function ymd3(d) {
|
|
3525
3591
|
return d.toISOString().slice(0, 10);
|
|
3526
3592
|
}
|
|
3593
|
+
function pickBrandQuery(rows) {
|
|
3594
|
+
let best;
|
|
3595
|
+
for (const r of rows) {
|
|
3596
|
+
if (typeof r.position !== "number") continue;
|
|
3597
|
+
const impressions = r.impressions ?? 0;
|
|
3598
|
+
if (best === void 0 || impressions > best.impressions || impressions === best.impressions && r.position < best.position) {
|
|
3599
|
+
best = { position: r.position, impressions };
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
return best?.position;
|
|
3603
|
+
}
|
|
3604
|
+
function selectBrandPosition(rows, exactQuery) {
|
|
3605
|
+
const want = exactQuery.toLowerCase();
|
|
3606
|
+
const exact = rows.find(
|
|
3607
|
+
(r) => typeof r.position === "number" && (r.keys?.[0] ?? "").toLowerCase() === want
|
|
3608
|
+
);
|
|
3609
|
+
if (exact) return exact.position;
|
|
3610
|
+
return pickBrandQuery(rows);
|
|
3611
|
+
}
|
|
3527
3612
|
async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
3528
3613
|
const key = JSON.parse(readFileSync4(q.keyPath, "utf8"));
|
|
3529
3614
|
const jwt = new JWT2({
|
|
@@ -3555,14 +3640,14 @@ async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
|
3555
3640
|
dimensionFilterGroups: [
|
|
3556
3641
|
{
|
|
3557
3642
|
filters: [
|
|
3558
|
-
{ dimension: "query", operator: "
|
|
3643
|
+
{ dimension: "query", operator: "contains", expression: q.query.toLowerCase() }
|
|
3559
3644
|
]
|
|
3560
3645
|
}
|
|
3561
3646
|
],
|
|
3562
|
-
rowLimit:
|
|
3647
|
+
rowLimit: BRAND_QUERY_ROW_LIMIT
|
|
3563
3648
|
}
|
|
3564
3649
|
});
|
|
3565
|
-
const pos = res.data.rows
|
|
3650
|
+
const pos = selectBrandPosition(res.data.rows ?? [], q.query.toLowerCase());
|
|
3566
3651
|
if (typeof pos === "number") {
|
|
3567
3652
|
return { foundOnPage1: pos <= PAGE_1_MAX_POSITION, position: Math.max(1, Math.round(pos)) };
|
|
3568
3653
|
}
|
|
@@ -3620,12 +3705,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
3620
3705
|
const path = options.previewPath ?? `reports/${slug}/draft.html`;
|
|
3621
3706
|
await mkdir3(dirname6(path), { recursive: true });
|
|
3622
3707
|
await writeFile10(path, html, "utf-8");
|
|
3623
|
-
return { reportRow: null, htmlPath: path, html, softFailures };
|
|
3708
|
+
return { reportRow: null, htmlPath: path, html, softFailures, queued: null, supersededIds: [] };
|
|
3624
3709
|
}
|
|
3625
3710
|
if (base === null) throw new Error("base required when previewOnly=false");
|
|
3626
3711
|
if (options.completeRowId) {
|
|
3627
|
-
await
|
|
3628
|
-
|
|
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
|
+
};
|
|
3629
3726
|
}
|
|
3630
3727
|
const reportId = `${siteRow.name} \u2014 ${reportType} \u2014 ${periodEnd.toISOString().slice(0, 10)}`;
|
|
3631
3728
|
const created = await createDraft(base, {
|
|
@@ -3642,13 +3739,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
3642
3739
|
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
3643
3740
|
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
3644
3741
|
});
|
|
3645
|
-
await
|
|
3646
|
-
|
|
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
|
+
};
|
|
3647
3756
|
}
|
|
3648
|
-
async function
|
|
3757
|
+
async function uploadDraftHtml(rowId, slug, periodEnd, html) {
|
|
3649
3758
|
const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;
|
|
3650
3759
|
await uploadAttachment(rowId, "Rendered HTML", html, htmlFilename, "text/html");
|
|
3651
|
-
await setDraftReady(base, rowId, true);
|
|
3652
3760
|
}
|
|
3653
3761
|
var NO_ENRICHMENT = { value: null, softFailed: false };
|
|
3654
3762
|
async function fetchGaUsers(siteRow, periodStart, periodEnd) {
|
|
@@ -3910,7 +4018,11 @@ async function sendOne(client, base, site, report) {
|
|
|
3910
4018
|
headerImageCid: cidName,
|
|
3911
4019
|
headerWidth: header.displayWidth,
|
|
3912
4020
|
headerHeight: header.displayHeight,
|
|
3913
|
-
headerBgColor: header.placeholderColor
|
|
4021
|
+
headerBgColor: header.placeholderColor,
|
|
4022
|
+
// Announcement-only: re-derive cadence + improvements from the site row so the SENT email
|
|
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.
|
|
4025
|
+
...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
|
|
3914
4026
|
});
|
|
3915
4027
|
const reportDate = report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date();
|
|
3916
4028
|
const subject = report.subjectOverride ?? `${site.name} \u2014 ${monthYear(reportDate)} ${report.reportType} Report`;
|