@stacksjs/email 0.72.6 → 0.72.7

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.
@@ -18,6 +18,18 @@
18
18
  * ```
19
19
  */
20
20
  export declare function safe(html: string): SafeHtml;
21
+ /**
22
+ * HTML-escape every char that could break out of a text node into an
23
+ * attribute / tag context. The five-char set is the OWASP minimum for
24
+ * "in a text node or quoted attribute"; we don't need the more
25
+ * paranoid (forward-slash, equals, etc.) set because the template uses
26
+ * `{{ … }}` only inside the document body, never inside `<script>` /
27
+ * `<style>` / unquoted attribute slots.
28
+ *
29
+ * Constructed once at module load — a `replace` chain is faster than a
30
+ * single regex for short strings.
31
+ */
32
+ export declare function escapeHtml(input: string): string;
21
33
  /**
22
34
  * Render an email template with optional layout
23
35
  *
package/dist/template.js CHANGED
@@ -1,4 +1,4 @@
1
- import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{fs}from"@stacksjs/storage";import{defaultsResourcesPath,resourcesPath}from"@stacksjs/path";import{join}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)}`}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))];function resolveTemplatePath(templateName){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}`)}function htmlToText(html){return html.replace(/<br\s*\/?>/gi,`
1
+ import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{fs}from"@stacksjs/storage";import{defaultsResourcesPath,resourcesPath}from"@stacksjs/path";import{join}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))];function resolveTemplatePath(templateName){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}`)}function htmlToText(html){return html.replace(/<br\s*\/?>/gi,`
2
2
  `).replace(/<\/(p|div|h[1-6]|li|tr)>/gi,`
3
3
  `).replace(/<\/td>/gi,"\t").replace(/<[^>]*>/g,"").replace(/&nbsp;/g," ").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&copy;/g,"(c)").replace(/\n\s*\n\s*\n/g,`
4
4
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/email",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.6",
5
+ "version": "0.72.7",
6
6
  "description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -64,10 +64,10 @@
64
64
  "postal-mime": "^2.7.6"
65
65
  },
66
66
  "devDependencies": {
67
- "@stacksjs/cli": "0.72.6",
68
- "@stacksjs/config": "0.72.6",
67
+ "@stacksjs/cli": "0.72.7",
68
+ "@stacksjs/config": "0.72.7",
69
69
  "better-dx": "^0.2.23",
70
- "@stacksjs/error-handling": "0.72.6",
71
- "@stacksjs/types": "0.72.6"
70
+ "@stacksjs/error-handling": "0.72.7",
71
+ "@stacksjs/types": "0.72.7"
72
72
  }
73
73
  }