@stacksjs/email 0.74.69 → 0.75.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.
@@ -126,8 +126,23 @@ export declare interface TemplateOptions {
126
126
  subject?: string
127
127
  inline?: boolean
128
128
  }
129
- /** Allowed types for email template variable values */
130
- export type TemplateVariableValue = string | number | boolean | undefined | null | SafeHtml;
129
+ /** A single value a template can print. */
130
+ export type TemplateScalar = string | number | boolean | undefined | null | SafeHtml;
131
+ /**
132
+ * Allowed types for email template variable values.
133
+ *
134
+ * Lists and records are for `.stx` templates, which receive the variables as
135
+ * props and can `@foreach` over a list of rows (a digest's top pages, an
136
+ * order's line items). The type used to stop at scalars, so a caller passing
137
+ * the rows its own `.stx` template loops over had to cast them past it.
138
+ *
139
+ * A plain `.html` template has only `{{ name }}` substitution and nothing to
140
+ * iterate with, so a list or record there renders as empty rather than as
141
+ * `[object Object]`.
142
+ */
143
+ export type TemplateVariableValue = | TemplateScalar
144
+ | readonly TemplateVariableValue[]
145
+ | { readonly [key: string]: TemplateVariableValue }
131
146
  /** Map of variable names to their values for template replacement */
132
147
  export type TemplateVariables = Record<string, TemplateVariableValue>;
133
148
  /**
package/dist/template.js CHANGED
@@ -1,4 +1,4 @@
1
- var {require}=import.meta;import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{fs}from"@stacksjs/storage";import{defaultsResourcesPath,resourcesPath,storagePath}from"@stacksjs/path";import{join,resolve as resolvePath}from"node:path";import{inlineCss,shouldInlineByDefault}from"./css-inliner";export class SafeHtml{value;__safeHtml=!0;constructor(value){this.value=value}}export function safe(html){return new SafeHtml(html)}function getDefaultVariables(){const primaryColor=config.app.primaryColor||"#3b82f6";return{appName:config.app.name||"Stacks",appUrl:config.app.url||"https://localhost",primaryColor,primaryColorDark:darkenColor(primaryColor,15),year:new Date().getFullYear()}}function darkenColor(hex,percent){const num=Number.parseInt(hex.replace("#",""),16),amt=Math.round(2.55*percent),R=Math.max(0,(num>>16)-amt),G=Math.max(0,(num>>8&255)-amt),B=Math.max(0,(num&255)-amt);return`#${(16777216+R*65536+G*256+B).toString(16).slice(1)}`}export function escapeHtml(input){return input.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function replaceVariables(html,variables){let result=html;for(const[key,value]of Object.entries(variables)){const escapedKey=key.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`\\{\\{\\s*${escapedKey}\\s*\\}\\}`,"g");result=result.replace(regex,renderTemplateValue(value))}return result}function renderTemplateValue(value){if(value===null||value===void 0)return"";if(value instanceof SafeHtml)return value.value;return escapeHtml(String(value))}const templateRoots=[(relativePath)=>resourcesPath(join("emails",relativePath)),(relativePath)=>defaultsResourcesPath(join("emails",relativePath))];let templateRegistry=null,templateRegistryLoaded=!1,templateRegistryCwd=null;function loadTemplateRegistry(){const cwd=process.cwd();if(templateRegistryLoaded&&templateRegistryCwd===cwd)return templateRegistry;templateRegistry=null;templateRegistryLoaded=!0;templateRegistryCwd=cwd;try{const dir=storagePath("framework/auto-imports"),module=require(`${dir}/emails.ts`);if(!module.emails)return null;templateRegistry=Object.fromEntries(Object.entries(module.emails).map(([name,file])=>[name,{path:resolvePath(dir,file),type:file.endsWith(".stx")?"stx":"html"}]))}catch{templateRegistry=null}return templateRegistry}export function resetTemplateRegistry(){templateRegistry=null;templateRegistryLoaded=!1;templateRegistryCwd=null}function resolveTemplatePath(templateName){const registry=loadTemplateRegistry();if(registry){const bare=templateName.replace(/\.(?:stx|html)$/,""),hit=registry[bare];if(hit){if(templateName.endsWith(".stx")&&hit.type!=="stx");else if(templateName.endsWith(".html")&&hit.type!=="html");else if(fs.existsSync(hit.path))return hit}}for(const root of templateRoots){if(templateName.endsWith(".stx")){const fullPath=root(templateName);if(fs.existsSync(fullPath))return{path:fullPath,type:"stx"};continue}if(templateName.endsWith(".html")){const fullPath=root(templateName);if(fs.existsSync(fullPath))return{path:fullPath,type:"html"};continue}const stxPath=root(`${templateName}.stx`);if(fs.existsSync(stxPath))return{path:stxPath,type:"stx"};const htmlPath=root(`${templateName}.html`);if(fs.existsSync(htmlPath))return{path:htmlPath,type:"html"}}return null}function loadHtmlTemplate(templatePath){const path=templatePath.endsWith(".html")?templatePath:`${templatePath}.html`;for(const root of templateRoots){const fullPath=root(path);if(fs.existsSync(fullPath))return fs.readFileSync(fullPath,"utf-8")}return null}function loadLayout(layoutName){return loadHtmlTemplate(`layouts/${layoutName}`)}export function htmlToText(html){return((value)=>value.replace(/&nbsp;/g," ").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;|&apos;/g,"'").replace(/&copy;/g,"(c)").replace(/&#(\d+);/g,(_,code)=>String.fromCodePoint(Number(code))).replace(/&#x([\da-f]+);/gi,(_,code)=>String.fromCodePoint(Number.parseInt(code,16))).replace(/&amp;/g,"&"))(html.replace(/<(head|style|script|title)\b[^>]*>[\s\S]*?<\/\1>/gi,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<a\b[^>]*?\bhref\s*=\s*(["'])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi,(_,_quote,href,inner)=>{const label=inner.replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim(),url=href.trim();if(!url||url==="#"||url.startsWith("javascript:"))return label;if(`mailto:${label}`===url)return label;if(!label||label===url)return url;return`${label} (${url})`}).replace(/<br\s*\/?>/gi,`
1
+ var {require}=import.meta;import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{fs}from"@stacksjs/storage";import{defaultsResourcesPath,resourcesPath,storagePath}from"@stacksjs/path";import{join,resolve as resolvePath}from"node:path";import{inlineCss,shouldInlineByDefault}from"./css-inliner";export class SafeHtml{value;__safeHtml=!0;constructor(value){this.value=value}}export function safe(html){return new SafeHtml(html)}function getDefaultVariables(){const primaryColor=config.app.primaryColor||"#3b82f6";return{appName:config.app.name||"Stacks",appUrl:config.app.url||"https://localhost",primaryColor,primaryColorDark:darkenColor(primaryColor,15),year:new Date().getFullYear()}}function darkenColor(hex,percent){const num=Number.parseInt(hex.replace("#",""),16),amt=Math.round(2.55*percent),R=Math.max(0,(num>>16)-amt),G=Math.max(0,(num>>8&255)-amt),B=Math.max(0,(num&255)-amt);return`#${(16777216+R*65536+G*256+B).toString(16).slice(1)}`}export function escapeHtml(input){return input.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function replaceVariables(html,variables){let result=html;for(const[key,value]of Object.entries(variables)){const escapedKey=key.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`\\{\\{\\s*${escapedKey}\\s*\\}\\}`,"g");result=result.replace(regex,renderTemplateValue(value))}return result}function renderTemplateValue(value){if(value===null||value===void 0)return"";if(value instanceof SafeHtml)return value.value;if(typeof value==="object")return"";return escapeHtml(String(value))}const templateRoots=[(relativePath)=>resourcesPath(join("emails",relativePath)),(relativePath)=>defaultsResourcesPath(join("emails",relativePath))];let templateRegistry=null,templateRegistryLoaded=!1,templateRegistryCwd=null;function loadTemplateRegistry(){const cwd=process.cwd();if(templateRegistryLoaded&&templateRegistryCwd===cwd)return templateRegistry;templateRegistry=null;templateRegistryLoaded=!0;templateRegistryCwd=cwd;try{const dir=storagePath("framework/auto-imports"),module=require(`${dir}/emails.ts`);if(!module.emails)return null;templateRegistry=Object.fromEntries(Object.entries(module.emails).map(([name,file])=>[name,{path:resolvePath(dir,file),type:file.endsWith(".stx")?"stx":"html"}]))}catch{templateRegistry=null}return templateRegistry}export function resetTemplateRegistry(){templateRegistry=null;templateRegistryLoaded=!1;templateRegistryCwd=null}function resolveTemplatePath(templateName){const registry=loadTemplateRegistry();if(registry){const bare=templateName.replace(/\.(?:stx|html)$/,""),hit=registry[bare];if(hit){if(templateName.endsWith(".stx")&&hit.type!=="stx");else if(templateName.endsWith(".html")&&hit.type!=="html");else if(fs.existsSync(hit.path))return hit}}for(const root of templateRoots){if(templateName.endsWith(".stx")){const fullPath=root(templateName);if(fs.existsSync(fullPath))return{path:fullPath,type:"stx"};continue}if(templateName.endsWith(".html")){const fullPath=root(templateName);if(fs.existsSync(fullPath))return{path:fullPath,type:"html"};continue}const stxPath=root(`${templateName}.stx`);if(fs.existsSync(stxPath))return{path:stxPath,type:"stx"};const htmlPath=root(`${templateName}.html`);if(fs.existsSync(htmlPath))return{path:htmlPath,type:"html"}}return null}function loadHtmlTemplate(templatePath){const path=templatePath.endsWith(".html")?templatePath:`${templatePath}.html`;for(const root of templateRoots){const fullPath=root(path);if(fs.existsSync(fullPath))return fs.readFileSync(fullPath,"utf-8")}return null}function loadLayout(layoutName){return loadHtmlTemplate(`layouts/${layoutName}`)}export function htmlToText(html){return((value)=>value.replace(/&nbsp;/g," ").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;|&apos;/g,"'").replace(/&copy;/g,"(c)").replace(/&#(\d+);/g,(_,code)=>String.fromCodePoint(Number(code))).replace(/&#x([\da-f]+);/gi,(_,code)=>String.fromCodePoint(Number.parseInt(code,16))).replace(/&amp;/g,"&"))(html.replace(/<(head|style|script|title)\b[^>]*>[\s\S]*?<\/\1>/gi,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<a\b[^>]*?\bhref\s*=\s*(["'])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi,(_,_quote,href,inner)=>{const label=inner.replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim(),url=href.trim();if(!url||url==="#"||url.startsWith("javascript:"))return label;if(`mailto:${label}`===url)return label;if(!label||label===url)return url;return`${label} (${url})`}).replace(/<br\s*\/?>/gi,`
2
2
  `).replace(/<\/(p|div|h[1-6]|li|tr|table)>/gi,`
3
3
  `).replace(/<\/td>/gi,"\t").replace(/<[^>]*>/g,"")).split(`
4
4
  `).map((line)=>line.replace(/[ \t]+/g," ").trim()).join(`
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/email",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.69",
5
+ "version": "0.75.0",
6
6
  "description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -61,26 +61,26 @@
61
61
  "prepublishOnly": "bun run build"
62
62
  },
63
63
  "dependencies": {
64
- "@stacksjs/config": "0.74.69",
65
- "@stacksjs/database": "0.74.69",
66
- "@stacksjs/events": "0.74.69",
67
- "@stacksjs/logging": "0.74.69",
64
+ "@stacksjs/config": "0.75.0",
65
+ "@stacksjs/database": "0.75.0",
66
+ "@stacksjs/events": "0.75.0",
67
+ "@stacksjs/logging": "0.75.0",
68
68
  "@stacksjs/mail": "^0.3.6",
69
- "@stacksjs/path": "0.74.69",
70
- "@stacksjs/storage": "0.74.69",
71
- "@stacksjs/strings": "0.74.69",
69
+ "@stacksjs/path": "0.75.0",
70
+ "@stacksjs/storage": "0.75.0",
71
+ "@stacksjs/strings": "0.75.0",
72
72
  "@stacksjs/stx": "^0.2.307",
73
73
  "@stacksjs/ts-cloud": "^0.16.0",
74
- "@stacksjs/utils": "0.74.69"
74
+ "@stacksjs/utils": "0.75.0"
75
75
  },
76
76
  "devDependencies": {
77
- "@stacksjs/cli": "0.74.69",
78
- "@stacksjs/error-handling": "0.74.69",
79
- "@stacksjs/types": "0.74.69",
77
+ "@stacksjs/cli": "0.75.0",
78
+ "@stacksjs/error-handling": "0.75.0",
79
+ "@stacksjs/types": "0.75.0",
80
80
  "better-dx": "^0.2.24"
81
81
  },
82
82
  "peerDependencies": {
83
- "@stacksjs/queue": "0.74.69"
83
+ "@stacksjs/queue": "0.75.0"
84
84
  },
85
85
  "peerDependenciesMeta": {
86
86
  "@stacksjs/queue": {