@at-flux/astroflare 1.0.2 → 1.0.4
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/LICENSE +1 -1
- package/README.md +98 -14
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/core.cjs +162 -138
- package/dist/core.d.cts +59 -2
- package/dist/core.d.ts +59 -2
- package/dist/core.js +147 -16
- package/dist/forms/index.cjs +7 -130
- package/dist/forms/index.d.cts +2 -52
- package/dist/forms/index.d.ts +2 -52
- package/dist/forms/index.js +99 -14
- package/dist/{chunk-DRYHJSYC.js → forms-8uIdWmMD.cjs} +81 -53
- package/dist/index-Bk-K9aDQ.d.ts +44 -0
- package/dist/index-CcH_yXr-.d.cts +44 -0
- package/dist/index.cjs +20 -141
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -17
- package/package.json +14 -8
- package/src/components/CollectionQuery.astro +107 -0
- package/src/components/ContactModalCta.astro +2 -0
- package/src/components/FilterPills.astro +164 -0
- package/src/components/IconButton.astro +5 -0
- package/src/components/InstagramProfileLink.astro +3 -0
- package/src/components/ListSummary.astro +63 -0
- package/src/components/MediaProtect.astro +41 -0
- package/src/components/Modal.astro +3 -0
- package/src/components/ModalTrigger.astro +2 -0
- package/src/components/Pager.astro +92 -0
- package/src/components/Section.astro +3 -0
- package/src/components/TagSummary.astro +71 -0
- package/src/components/ThemeToggle.astro +1 -0
- package/src/components/Tooltip.astro +70 -0
- package/src/components/collection-query-props.ts +45 -0
- package/src/runtime/collection-query.ts +156 -0
- package/src/runtime/media-protect.ts +70 -0
- package/src/styles/accessibility.css +1 -1
- package/src/styles/no-save.css +2 -2
- package/src/styles/prose.css +23 -11
- package/src/styles/scrollbar.css +10 -3
- package/dist/chunk-ALMPH3A2.js +0 -0
package/dist/core.js
CHANGED
|
@@ -1,17 +1,148 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "./chunk-DRYHJSYC.js";
|
|
10
|
-
export {
|
|
11
|
-
composeEmailAddress,
|
|
12
|
-
forms_exports as forms,
|
|
13
|
-
generateFormResultHtml,
|
|
14
|
-
generateFormSectionHtml,
|
|
15
|
-
renderEmailTemplate,
|
|
16
|
-
sendEmail
|
|
1
|
+
import { composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail, t as forms_exports } from "./forms/index.js";
|
|
2
|
+
//#region src/tag-colors.ts
|
|
3
|
+
const DEFAULT_OVERRIDES = {
|
|
4
|
+
shoots: "#dc3545",
|
|
5
|
+
tech: "#8a95a5",
|
|
6
|
+
ai: "#6b7b8d",
|
|
7
|
+
sites: "#7dd3c0",
|
|
8
|
+
flow: "#b367e0"
|
|
17
9
|
};
|
|
10
|
+
const hashStringToHue = (input) => {
|
|
11
|
+
let hash = 0;
|
|
12
|
+
for (let i = 0; i < input.length; i += 1) hash = hash * 31 + input.charCodeAt(i) >>> 0;
|
|
13
|
+
return hash % 360;
|
|
14
|
+
};
|
|
15
|
+
const hexToRgb = (hex) => {
|
|
16
|
+
const value = hex.replace("#", "").trim();
|
|
17
|
+
if (!/^[0-9a-fA-F]{6}$/.test(value)) return null;
|
|
18
|
+
return [
|
|
19
|
+
Number.parseInt(value.slice(0, 2), 16),
|
|
20
|
+
Number.parseInt(value.slice(2, 4), 16),
|
|
21
|
+
Number.parseInt(value.slice(4, 6), 16)
|
|
22
|
+
];
|
|
23
|
+
};
|
|
24
|
+
const rgbToHsl = (r, g, b) => {
|
|
25
|
+
const red = r / 255;
|
|
26
|
+
const green = g / 255;
|
|
27
|
+
const blue = b / 255;
|
|
28
|
+
const max = Math.max(red, green, blue);
|
|
29
|
+
const min = Math.min(red, green, blue);
|
|
30
|
+
const delta = max - min;
|
|
31
|
+
const lightness = (max + min) / 2;
|
|
32
|
+
const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
|
|
33
|
+
let hue = 0;
|
|
34
|
+
if (delta !== 0) if (max === red) hue = (green - blue) / delta % 6;
|
|
35
|
+
else if (max === green) hue = (blue - red) / delta + 2;
|
|
36
|
+
else hue = (red - green) / delta + 4;
|
|
37
|
+
return [
|
|
38
|
+
Math.round((hue * 60 + 360) % 360),
|
|
39
|
+
Math.round(saturation * 100),
|
|
40
|
+
Math.round(lightness * 100)
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
const resolveHue = (tag, options) => {
|
|
44
|
+
const key = tag.trim().toLowerCase();
|
|
45
|
+
const color = options.overrides?.[key] ?? DEFAULT_OVERRIDES[key];
|
|
46
|
+
if (!color) return hashStringToHue(key);
|
|
47
|
+
const rgb = hexToRgb(color);
|
|
48
|
+
if (!rgb) return hashStringToHue(key);
|
|
49
|
+
return rgbToHsl(...rgb)[0];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Build a deterministic HSL-based palette for a tag label.
|
|
53
|
+
* Uses known defaults and optional overrides before falling back to hashed hue.
|
|
54
|
+
*/
|
|
55
|
+
const getTagPalette = (tag, options = {}) => {
|
|
56
|
+
const hue = resolveHue(tag, options);
|
|
57
|
+
return {
|
|
58
|
+
bg: `hsla(${hue} 48% 58% / 0.12)`,
|
|
59
|
+
border: `hsla(${hue} 52% 58% / 0.34)`,
|
|
60
|
+
text: `hsl(${hue} 50% 66%)`
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/date-format.ts
|
|
65
|
+
const DEFAULT_OPTIONS = {
|
|
66
|
+
year: "numeric",
|
|
67
|
+
month: "long",
|
|
68
|
+
day: "numeric"
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Format dates consistently for cards and article metadata.
|
|
72
|
+
*/
|
|
73
|
+
const formatDisplayDate = (date, config = {}) => {
|
|
74
|
+
const { locale = "en-GB", options = DEFAULT_OPTIONS } = config;
|
|
75
|
+
return new Intl.DateTimeFormat(locale, options).format(new Date(date));
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/collection-query.ts
|
|
79
|
+
const toInt = (value, fallback) => {
|
|
80
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
81
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
82
|
+
};
|
|
83
|
+
const parseCollectionQuery = (searchParams, options = {}) => {
|
|
84
|
+
const { defaultPage = 1, defaultSize = 12, maxSize = 100 } = options;
|
|
85
|
+
const page = Math.max(1, toInt(searchParams.get("page"), defaultPage));
|
|
86
|
+
const size = Math.min(maxSize, Math.max(1, toInt(searchParams.get("size"), defaultSize)));
|
|
87
|
+
const fallbackOffset = (page - 1) * size;
|
|
88
|
+
const offset = Math.max(0, toInt(searchParams.get("offset"), fallbackOffset));
|
|
89
|
+
const rawFilters = searchParams.get("filters");
|
|
90
|
+
let filters = {};
|
|
91
|
+
if (rawFilters) try {
|
|
92
|
+
const parsed = JSON.parse(rawFilters);
|
|
93
|
+
filters = Object.fromEntries(Object.entries(parsed).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, String(value)]));
|
|
94
|
+
} catch {
|
|
95
|
+
filters = {};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
page,
|
|
99
|
+
size,
|
|
100
|
+
offset,
|
|
101
|
+
filters
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const paginateCollection = (list, query) => {
|
|
105
|
+
const totalItems = list.length;
|
|
106
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / query.size));
|
|
107
|
+
const start = (Math.min(totalPages, Math.max(1, Math.floor(query.offset / query.size) + 1)) - 1) * query.size;
|
|
108
|
+
const end = start + query.size;
|
|
109
|
+
return {
|
|
110
|
+
items: list.slice(start, end),
|
|
111
|
+
totalItems,
|
|
112
|
+
totalPages,
|
|
113
|
+
start,
|
|
114
|
+
end: Math.min(end, totalItems)
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const buildCollectionHref = (pathname, query, overrides = {}) => {
|
|
118
|
+
const next = {
|
|
119
|
+
...query,
|
|
120
|
+
...overrides
|
|
121
|
+
};
|
|
122
|
+
const normalizedOffset = (next.page - 1) * next.size;
|
|
123
|
+
const params = new URLSearchParams({
|
|
124
|
+
page: String(next.page),
|
|
125
|
+
size: String(next.size),
|
|
126
|
+
offset: String(next.offset ?? normalizedOffset)
|
|
127
|
+
});
|
|
128
|
+
if (Object.keys(next.filters ?? {}).length > 0) params.set("filters", JSON.stringify(next.filters));
|
|
129
|
+
return `${pathname}?${params.toString()}`;
|
|
130
|
+
};
|
|
131
|
+
const matchesCollectionFilters = (values, filters) => Object.entries(filters).every(([key, value]) => {
|
|
132
|
+
return (values[key] ?? []).includes(value);
|
|
133
|
+
});
|
|
134
|
+
const buildPageSequence = (totalPages, currentPage, maxButtons = 7) => {
|
|
135
|
+
if (totalPages <= maxButtons) return Array.from({ length: totalPages }, (_, index) => index + 1);
|
|
136
|
+
const innerSlots = Math.max(1, maxButtons - 2);
|
|
137
|
+
const left = Math.max(2, currentPage - Math.floor(innerSlots / 2));
|
|
138
|
+
const right = Math.min(totalPages - 1, left + innerSlots - 1);
|
|
139
|
+
const adjustedLeft = Math.max(2, right - innerSlots + 1);
|
|
140
|
+
const sequence = [1];
|
|
141
|
+
if (adjustedLeft > 2) sequence.push("…");
|
|
142
|
+
for (let page = adjustedLeft; page <= right; page += 1) sequence.push(page);
|
|
143
|
+
if (right < totalPages - 1) sequence.push("…");
|
|
144
|
+
sequence.push(totalPages);
|
|
145
|
+
return sequence;
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
export { buildCollectionHref, buildPageSequence, composeEmailAddress, formatDisplayDate, forms_exports as forms, generateFormResultHtml, generateFormSectionHtml, getTagPalette, matchesCollectionFilters, paginateCollection, parseCollectionQuery, renderEmailTemplate, sendEmail };
|
package/dist/forms/index.cjs
CHANGED
|
@@ -1,130 +1,7 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/forms/index.ts
|
|
21
|
-
var forms_exports = {};
|
|
22
|
-
__export(forms_exports, {
|
|
23
|
-
composeEmailAddress: () => composeEmailAddress,
|
|
24
|
-
generateFormResultHtml: () => generateFormResultHtml,
|
|
25
|
-
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
26
|
-
renderEmailTemplate: () => renderEmailTemplate,
|
|
27
|
-
sendEmail: () => sendEmail
|
|
28
|
-
});
|
|
29
|
-
module.exports = __toCommonJS(forms_exports);
|
|
30
|
-
var import_resend = require("resend");
|
|
31
|
-
async function sendEmail(config, payload) {
|
|
32
|
-
if (config.mock) {
|
|
33
|
-
console.info("[astroflare:forms] Mock email payload", payload);
|
|
34
|
-
return { id: "mock-email-id" };
|
|
35
|
-
}
|
|
36
|
-
const resend = new import_resend.Resend(config.apiKey);
|
|
37
|
-
const { data, error } = await resend.emails.send({
|
|
38
|
-
from: payload.from,
|
|
39
|
-
to: payload.to,
|
|
40
|
-
subject: payload.subject,
|
|
41
|
-
html: payload.html
|
|
42
|
-
});
|
|
43
|
-
if (error) {
|
|
44
|
-
console.error("[astroflare:forms] Resend API error", error);
|
|
45
|
-
throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
|
|
46
|
-
}
|
|
47
|
-
return data;
|
|
48
|
-
}
|
|
49
|
-
function composeEmailAddress(local, domain, tag) {
|
|
50
|
-
const taggedLocal = tag ? `${local}+${tag}` : local;
|
|
51
|
-
return `${taggedLocal}@${domain}`;
|
|
52
|
-
}
|
|
53
|
-
function generateFormSectionHtml(section) {
|
|
54
|
-
const itemsHtml = section.items.map((item) => {
|
|
55
|
-
const value = item.value ?? "";
|
|
56
|
-
const isHtml = /<[^>]+>/.test(value);
|
|
57
|
-
return `
|
|
58
|
-
<div style="margin-bottom:16px;">
|
|
59
|
-
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
60
|
-
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
61
|
-
</div>
|
|
62
|
-
`;
|
|
63
|
-
}).join("");
|
|
64
|
-
return `
|
|
65
|
-
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
66
|
-
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
67
|
-
<div style="color:#111827;">
|
|
68
|
-
${itemsHtml}
|
|
69
|
-
</div>
|
|
70
|
-
</section>
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
function generateFormResultHtml(sections) {
|
|
74
|
-
return sections.map(generateFormSectionHtml).join("\n");
|
|
75
|
-
}
|
|
76
|
-
async function renderEmailTemplate(args) {
|
|
77
|
-
const { title, contentHtml, brandName, footerText } = args;
|
|
78
|
-
const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
|
|
79
|
-
"en-GB"
|
|
80
|
-
)}.`;
|
|
81
|
-
return `<!DOCTYPE html>
|
|
82
|
-
<html lang="en">
|
|
83
|
-
<head>
|
|
84
|
-
<meta charset="utf-8">
|
|
85
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
86
|
-
<title>${title}</title>
|
|
87
|
-
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
88
|
-
</head>
|
|
89
|
-
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
90
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
91
|
-
<tr>
|
|
92
|
-
<td align="center" style="padding:24px;">
|
|
93
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
94
|
-
<tr>
|
|
95
|
-
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
96
|
-
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
97
|
-
${title}
|
|
98
|
-
</h1>
|
|
99
|
-
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
100
|
-
Contact form submission
|
|
101
|
-
</p>
|
|
102
|
-
</td>
|
|
103
|
-
</tr>
|
|
104
|
-
<tr>
|
|
105
|
-
<td style="padding:24px;">
|
|
106
|
-
${contentHtml}
|
|
107
|
-
</td>
|
|
108
|
-
</tr>
|
|
109
|
-
<tr>
|
|
110
|
-
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
111
|
-
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
112
|
-
${footer}
|
|
113
|
-
</p>
|
|
114
|
-
</td>
|
|
115
|
-
</tr>
|
|
116
|
-
</table>
|
|
117
|
-
</td>
|
|
118
|
-
</tr>
|
|
119
|
-
</table>
|
|
120
|
-
</body>
|
|
121
|
-
</html>`;
|
|
122
|
-
}
|
|
123
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
-
0 && (module.exports = {
|
|
125
|
-
composeEmailAddress,
|
|
126
|
-
generateFormResultHtml,
|
|
127
|
-
generateFormSectionHtml,
|
|
128
|
-
renderEmailTemplate,
|
|
129
|
-
sendEmail
|
|
130
|
-
});
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_forms = require("../forms-8uIdWmMD.cjs");
|
|
3
|
+
exports.composeEmailAddress = require_forms.composeEmailAddress;
|
|
4
|
+
exports.generateFormResultHtml = require_forms.generateFormResultHtml;
|
|
5
|
+
exports.generateFormSectionHtml = require_forms.generateFormSectionHtml;
|
|
6
|
+
exports.renderEmailTemplate = require_forms.renderEmailTemplate;
|
|
7
|
+
exports.sendEmail = require_forms.sendEmail;
|
package/dist/forms/index.d.cts
CHANGED
|
@@ -1,52 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
interface ResendConfig {
|
|
4
|
-
/**
|
|
5
|
-
* Resend API key.
|
|
6
|
-
*/
|
|
7
|
-
apiKey: string;
|
|
8
|
-
/**
|
|
9
|
-
* When true, do not send real email – log to console instead.
|
|
10
|
-
* Useful for local/dev environments.
|
|
11
|
-
*/
|
|
12
|
-
mock?: boolean;
|
|
13
|
-
}
|
|
14
|
-
interface EmailPayload {
|
|
15
|
-
from: string;
|
|
16
|
-
to: string;
|
|
17
|
-
subject: string;
|
|
18
|
-
html: string;
|
|
19
|
-
}
|
|
20
|
-
declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
|
|
21
|
-
id: string;
|
|
22
|
-
}>;
|
|
23
|
-
declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
|
|
24
|
-
interface FormSection {
|
|
25
|
-
title: string;
|
|
26
|
-
items: {
|
|
27
|
-
key: string;
|
|
28
|
-
value: string;
|
|
29
|
-
}[];
|
|
30
|
-
}
|
|
31
|
-
declare function generateFormSectionHtml(section: FormSection): string;
|
|
32
|
-
declare function generateFormResultHtml(sections: FormSection[]): string;
|
|
33
|
-
declare function renderEmailTemplate(args: {
|
|
34
|
-
title: string;
|
|
35
|
-
contentHtml: string;
|
|
36
|
-
brandName?: string;
|
|
37
|
-
footerText?: string;
|
|
38
|
-
}): Promise<string>;
|
|
39
|
-
|
|
40
|
-
type index_EmailPayload = EmailPayload;
|
|
41
|
-
type index_FormSection = FormSection;
|
|
42
|
-
type index_ResendConfig = ResendConfig;
|
|
43
|
-
declare const index_composeEmailAddress: typeof composeEmailAddress;
|
|
44
|
-
declare const index_generateFormResultHtml: typeof generateFormResultHtml;
|
|
45
|
-
declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
|
|
46
|
-
declare const index_renderEmailTemplate: typeof renderEmailTemplate;
|
|
47
|
-
declare const index_sendEmail: typeof sendEmail;
|
|
48
|
-
declare namespace index {
|
|
49
|
-
export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
|
|
1
|
+
import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, t as EmailPayload } from "../index-CcH_yXr-.cjs";
|
|
2
|
+
export { EmailPayload, FormSection, ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail };
|
package/dist/forms/index.d.ts
CHANGED
|
@@ -1,52 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
interface ResendConfig {
|
|
4
|
-
/**
|
|
5
|
-
* Resend API key.
|
|
6
|
-
*/
|
|
7
|
-
apiKey: string;
|
|
8
|
-
/**
|
|
9
|
-
* When true, do not send real email – log to console instead.
|
|
10
|
-
* Useful for local/dev environments.
|
|
11
|
-
*/
|
|
12
|
-
mock?: boolean;
|
|
13
|
-
}
|
|
14
|
-
interface EmailPayload {
|
|
15
|
-
from: string;
|
|
16
|
-
to: string;
|
|
17
|
-
subject: string;
|
|
18
|
-
html: string;
|
|
19
|
-
}
|
|
20
|
-
declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
|
|
21
|
-
id: string;
|
|
22
|
-
}>;
|
|
23
|
-
declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
|
|
24
|
-
interface FormSection {
|
|
25
|
-
title: string;
|
|
26
|
-
items: {
|
|
27
|
-
key: string;
|
|
28
|
-
value: string;
|
|
29
|
-
}[];
|
|
30
|
-
}
|
|
31
|
-
declare function generateFormSectionHtml(section: FormSection): string;
|
|
32
|
-
declare function generateFormResultHtml(sections: FormSection[]): string;
|
|
33
|
-
declare function renderEmailTemplate(args: {
|
|
34
|
-
title: string;
|
|
35
|
-
contentHtml: string;
|
|
36
|
-
brandName?: string;
|
|
37
|
-
footerText?: string;
|
|
38
|
-
}): Promise<string>;
|
|
39
|
-
|
|
40
|
-
type index_EmailPayload = EmailPayload;
|
|
41
|
-
type index_FormSection = FormSection;
|
|
42
|
-
type index_ResendConfig = ResendConfig;
|
|
43
|
-
declare const index_composeEmailAddress: typeof composeEmailAddress;
|
|
44
|
-
declare const index_generateFormResultHtml: typeof generateFormResultHtml;
|
|
45
|
-
declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
|
|
46
|
-
declare const index_renderEmailTemplate: typeof renderEmailTemplate;
|
|
47
|
-
declare const index_sendEmail: typeof sendEmail;
|
|
48
|
-
declare namespace index {
|
|
49
|
-
export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
|
|
1
|
+
import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, t as EmailPayload } from "../index-Bk-K9aDQ.js";
|
|
2
|
+
export { EmailPayload, FormSection, ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail };
|
package/dist/forms/index.js
CHANGED
|
@@ -1,14 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
1
|
+
import { t as __exportAll } from "../chunk-pbuEa-1d.js";
|
|
2
|
+
import { Resend } from "resend";
|
|
3
|
+
//#region src/forms/index.ts
|
|
4
|
+
var forms_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
composeEmailAddress: () => composeEmailAddress,
|
|
6
|
+
generateFormResultHtml: () => generateFormResultHtml,
|
|
7
|
+
generateFormSectionHtml: () => generateFormSectionHtml,
|
|
8
|
+
renderEmailTemplate: () => renderEmailTemplate,
|
|
9
|
+
sendEmail: () => sendEmail
|
|
10
|
+
});
|
|
11
|
+
async function sendEmail(config, payload) {
|
|
12
|
+
if (config.mock) {
|
|
13
|
+
console.info("[astroflare:forms] Mock email payload", payload);
|
|
14
|
+
return { id: "mock-email-id" };
|
|
15
|
+
}
|
|
16
|
+
const { data, error } = await new Resend(config.apiKey).emails.send({
|
|
17
|
+
from: payload.from,
|
|
18
|
+
to: payload.to,
|
|
19
|
+
subject: payload.subject,
|
|
20
|
+
html: payload.html
|
|
21
|
+
});
|
|
22
|
+
if (error) {
|
|
23
|
+
console.error("[astroflare:forms] Resend API error", error);
|
|
24
|
+
throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function composeEmailAddress(local, domain, tag) {
|
|
29
|
+
return `${tag ? `${local}+${tag}` : local}@${domain}`;
|
|
30
|
+
}
|
|
31
|
+
function generateFormSectionHtml(section) {
|
|
32
|
+
const itemsHtml = section.items.map((item) => {
|
|
33
|
+
const value = item.value ?? "";
|
|
34
|
+
const isHtml = /<[^>]+>/.test(value);
|
|
35
|
+
return `
|
|
36
|
+
<div style="margin-bottom:16px;">
|
|
37
|
+
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
38
|
+
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
}).join("");
|
|
42
|
+
return `
|
|
43
|
+
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
44
|
+
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
45
|
+
<div style="color:#111827;">
|
|
46
|
+
${itemsHtml}
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function generateFormResultHtml(sections) {
|
|
52
|
+
return sections.map(generateFormSectionHtml).join("\n");
|
|
53
|
+
}
|
|
54
|
+
async function renderEmailTemplate(args) {
|
|
55
|
+
const { title, contentHtml, brandName, footerText } = args;
|
|
56
|
+
return `<!DOCTYPE html>
|
|
57
|
+
<html lang="en">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="utf-8">
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
61
|
+
<title>${title}</title>
|
|
62
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
63
|
+
</head>
|
|
64
|
+
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
65
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
66
|
+
<tr>
|
|
67
|
+
<td align="center" style="padding:24px;">
|
|
68
|
+
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
69
|
+
<tr>
|
|
70
|
+
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
71
|
+
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
72
|
+
${title}
|
|
73
|
+
</h1>
|
|
74
|
+
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
75
|
+
Contact form submission
|
|
76
|
+
</p>
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td style="padding:24px;">
|
|
81
|
+
${contentHtml}
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
86
|
+
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
87
|
+
${footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString("en-GB")}.`}
|
|
88
|
+
</p>
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
</table>
|
|
92
|
+
</td>
|
|
93
|
+
</tr>
|
|
94
|
+
</table>
|
|
95
|
+
</body>
|
|
96
|
+
</html>`;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail, forms_exports as t };
|