@anthusai/papyrus 2.0.0-next.60 → 2.0.0-next.62
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.
|
@@ -17,6 +17,7 @@ const THEME_STYLE_KEYS = [
|
|
|
17
17
|
];
|
|
18
18
|
const THEME_KEYS = [
|
|
19
19
|
"accent", "text", "background", ...THEME_STYLE_KEYS, "logoUrl", "wordmarkImageUrl", "wordmarkAlt", "wordmarkWidth", "wordmarkHeight",
|
|
20
|
+
"footerWordmarkWidth",
|
|
20
21
|
];
|
|
21
22
|
const KNOWN_KEYS = new Set([
|
|
22
23
|
"name", "siteId", "siteOrigin", "basePath", "computeRoleArn", "sendingDomain", "fromAddress", "fromName", "instantAlerts", "theme", "lists",
|
|
@@ -140,6 +141,15 @@ function resolveTheme(raw) {
|
|
|
140
141
|
else if (theme.wordmarkWidth !== undefined || theme.wordmarkHeight !== undefined) {
|
|
141
142
|
fail("theme.wordmarkWidth and theme.wordmarkHeight need theme.wordmarkImageUrl");
|
|
142
143
|
}
|
|
144
|
+
if (theme.footerWordmarkWidth !== undefined) {
|
|
145
|
+
if (theme.wordmarkImageUrl === undefined)
|
|
146
|
+
fail("theme.footerWordmarkWidth needs theme.wordmarkImageUrl");
|
|
147
|
+
const width = theme.footerWordmarkWidth;
|
|
148
|
+
if (typeof width !== "number" || !Number.isInteger(width) || width < 1 || width > 400) {
|
|
149
|
+
fail("theme.footerWordmarkWidth must be a whole number of pixels from 1 to 400");
|
|
150
|
+
}
|
|
151
|
+
resolved.footerWordmarkWidth = width;
|
|
152
|
+
}
|
|
143
153
|
if (theme.wordmarkAlt !== undefined) {
|
|
144
154
|
const alt = text(theme.wordmarkAlt, "theme.wordmarkAlt", 200);
|
|
145
155
|
if (/[\u0000-\u001f\u007f]/.test(alt))
|
|
@@ -37,6 +37,8 @@ export type NewsletterTheme = {
|
|
|
37
37
|
wordmarkAlt?: string;
|
|
38
38
|
wordmarkWidth?: number;
|
|
39
39
|
wordmarkHeight?: number;
|
|
40
|
+
/** Draws the footer's wordmark at this width in pixels (1 to 400), smaller than the header's; the height keeps the aspect ratio. */
|
|
41
|
+
footerWordmarkWidth?: number;
|
|
40
42
|
labelFont?: string;
|
|
41
43
|
};
|
|
42
44
|
|
|
@@ -46,6 +48,7 @@ const THEME_STYLE_KEYS = [
|
|
|
46
48
|
] as const;
|
|
47
49
|
const THEME_KEYS = [
|
|
48
50
|
"accent", "text", "background", ...THEME_STYLE_KEYS, "logoUrl", "wordmarkImageUrl", "wordmarkAlt", "wordmarkWidth", "wordmarkHeight",
|
|
51
|
+
"footerWordmarkWidth",
|
|
49
52
|
];
|
|
50
53
|
|
|
51
54
|
export type NewsletterRule = { sections?: string[]; tags?: string[]; types?: string[] };
|
|
@@ -205,6 +208,14 @@ function resolveTheme(raw: unknown): NewsletterTheme {
|
|
|
205
208
|
} else if (theme.wordmarkWidth !== undefined || theme.wordmarkHeight !== undefined) {
|
|
206
209
|
fail("theme.wordmarkWidth and theme.wordmarkHeight need theme.wordmarkImageUrl");
|
|
207
210
|
}
|
|
211
|
+
if (theme.footerWordmarkWidth !== undefined) {
|
|
212
|
+
if (theme.wordmarkImageUrl === undefined) fail("theme.footerWordmarkWidth needs theme.wordmarkImageUrl");
|
|
213
|
+
const width = theme.footerWordmarkWidth;
|
|
214
|
+
if (typeof width !== "number" || !Number.isInteger(width) || width < 1 || width > 400) {
|
|
215
|
+
fail("theme.footerWordmarkWidth must be a whole number of pixels from 1 to 400");
|
|
216
|
+
}
|
|
217
|
+
resolved.footerWordmarkWidth = width;
|
|
218
|
+
}
|
|
208
219
|
if (theme.wordmarkAlt !== undefined) {
|
|
209
220
|
const alt = text(theme.wordmarkAlt, "theme.wordmarkAlt", 200);
|
|
210
221
|
if (/[\u0000-\u001f\u007f]/.test(alt)) fail("theme.wordmarkAlt must not contain control characters");
|
package/lib/newsletter-api.ts
CHANGED
|
@@ -238,6 +238,14 @@ function call(options: NewsletterApiOptions, functionName: string, event: Newsle
|
|
|
238
238
|
return (options.invoke ?? invokeNewsletterLambda)(functionName, event);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Next answers HEAD by running the route's GET export, but leaves `request.method` as "HEAD". A check for `=== "GET"` therefore
|
|
243
|
+
* sends a mail scanner's HEAD down the POST path. Every read-only branch asks this instead, so HEAD redirects exactly like GET.
|
|
244
|
+
*/
|
|
245
|
+
function isReadRequest(request: Pick<NextRequest, "method">): boolean {
|
|
246
|
+
return request.method === "GET" || request.method === "HEAD";
|
|
247
|
+
}
|
|
248
|
+
|
|
241
249
|
function textResponse(body: string, status: number): NextResponse {
|
|
242
250
|
return new NextResponse(body, { status, headers: { ...NO_STORE, "content-type": "text/plain; charset=utf-8" } });
|
|
243
251
|
}
|
|
@@ -266,7 +274,7 @@ export async function handleNewsletterConfirm(request: NextRequest, options: New
|
|
|
266
274
|
if (!plugin) return notFoundPage();
|
|
267
275
|
const to = redirector(request, environment, plugin);
|
|
268
276
|
if ("response" in to) return to.response;
|
|
269
|
-
if (request
|
|
277
|
+
if (isReadRequest(request)) {
|
|
270
278
|
const token = manageTokenFrom(request.nextUrl.searchParams.get("t"));
|
|
271
279
|
return token ? to.go("confirm", { token }, 302) : to.go("expired", {}, 302);
|
|
272
280
|
}
|
|
@@ -304,7 +312,7 @@ export async function handleNewsletterUnsubscribe(request: NextRequest, options:
|
|
|
304
312
|
const to = redirector(request, environment, plugin);
|
|
305
313
|
if ("response" in to) return to.response;
|
|
306
314
|
const query = request.nextUrl.searchParams;
|
|
307
|
-
if (request
|
|
315
|
+
if (isReadRequest(request)) {
|
|
308
316
|
const token = manageTokenFrom(query.get("t"));
|
|
309
317
|
const listId = listParameter(query.get("l"));
|
|
310
318
|
return token ? to.go("unsubscribe", { token, ...(listId ? { list: listId } : {}) }, 302) : to.go("expired", {}, 302);
|
|
@@ -344,7 +352,7 @@ export async function handleNewsletterPreferences(request: NextRequest, options:
|
|
|
344
352
|
if (!plugin) return notFoundPage();
|
|
345
353
|
const to = redirector(request, environment, plugin);
|
|
346
354
|
if ("response" in to) return to.response;
|
|
347
|
-
if (request
|
|
355
|
+
if (isReadRequest(request)) {
|
|
348
356
|
const token = manageTokenFrom(request.nextUrl.searchParams.get("t"));
|
|
349
357
|
return token ? to.go("preferences", { token }, 302) : to.go("expired", {}, 302);
|
|
350
358
|
}
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED