@maizzle/framework 5.0.0-beta.29 → 5.0.0-beta.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "5.0.0-beta.29",
3
+ "version": "5.0.0-beta.30",
4
4
  "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -97,9 +97,9 @@
97
97
  "@biomejs/biome": "1.9.2",
98
98
  "@types/js-beautify": "^1.14.3",
99
99
  "@types/markdown-it": "^14.1.2",
100
- "@vitest/coverage-v8": "^2.1.1",
100
+ "@vitest/coverage-v8": "^2.1.5",
101
101
  "supertest": "^7.0.0",
102
- "vitest": "^2.1.1"
102
+ "vitest": "^2.1.5"
103
103
  },
104
104
  "engines": {
105
105
  "node": ">=18.20"
@@ -30,7 +30,7 @@ const posthtmlPlugin = options => tree => {
30
30
  { heads: '{{', tails: '}}' },
31
31
  { heads: '{%', tails: '%}' },
32
32
  ],
33
- whitelist: [...defaultSafelist, ...get(options, 'whitelist', [])]
33
+ whitelist: [...defaultSafelist, ...get(options, 'safelist', [])]
34
34
  }
35
35
 
36
36
  options = merge(options, defaultOptions)
@@ -2,6 +2,7 @@ import juice from 'juice'
2
2
  import postcss from 'postcss'
3
3
  import get from 'lodash-es/get.js'
4
4
  import has from 'lodash-es/has.js'
5
+ import { defu as merge } from 'defu'
5
6
  import * as cheerio from 'cheerio/slim'
6
7
  import remove from 'lodash-es/remove.js'
7
8
  import { render } from 'posthtml-render'
@@ -33,6 +34,26 @@ export async function inline(html = '', options = {}) {
33
34
  options.removeInlinedSelectors = get(options, 'removeInlinedSelectors', true)
34
35
  options.resolveCalc = get(options, 'resolveCalc', true)
35
36
  options.preferUnitlessValues = get(options, 'preferUnitlessValues', true)
37
+ options.safelist = new Set([
38
+ ...get(options, 'safelist', []),
39
+ ...[
40
+ '.body', // Gmail
41
+ '.gmail', // Gmail
42
+ '.apple', // Apple Mail
43
+ '.ios', // Mail on iOS
44
+ '.ox-', // Open-Xchange
45
+ '.outlook', // Outlook.com
46
+ '[data-ogs', // Outlook.com
47
+ '.bloop_container', // Airmail
48
+ '.Singleton', // Apple Mail 10
49
+ '.unused', // Notes 8
50
+ '.moz-text-html', // Thunderbird
51
+ '.mail-detail-content', // Comcast, Libero webmail
52
+ 'edo', // Edison (all)
53
+ '#msgBody', // Freenet uses #msgBody
54
+ '.lang' // Fenced code blocks
55
+ ],
56
+ ])
36
57
 
37
58
  juice.styleToAttribute = get(options, 'styleToAttribute', {})
38
59
  juice.applyWidthAttributes = get(options, 'applyWidthAttributes', true)
@@ -106,26 +127,8 @@ export async function inline(html = '', options = {}) {
106
127
  }
107
128
  )
108
129
 
109
- const preservedClasses = new Set([
110
- '.body', // Gmail
111
- '.gmail', // Gmail
112
- '.apple', // Apple Mail
113
- '.ios', // Mail on iOS
114
- '.ox-', // Open-Xchange
115
- '.outlook', // Outlook.com
116
- '[data-ogs', // Outlook.com
117
- '.bloop_container', // Airmail
118
- '.Singleton', // Apple Mail 10
119
- '.unused', // Notes 8
120
- '.moz-text-html', // Thunderbird
121
- '.mail-detail-content', // Comcast, Libero webmail
122
- 'edo', // Edison (all)
123
- '#msgBody', // Freenet uses #msgBody
124
- '.lang' // Fenced code blocks
125
- ])
126
-
127
130
  // Precompile a single regex to match any substring from the preservedClasses set
128
- const combinedPattern = Array.from(preservedClasses)
131
+ const combinedPattern = Array.from(options.safelist)
129
132
  .map(pattern => pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')) // Escape special regex chars
130
133
  .join('|') // Combine all patterns into a single regex pattern with 'OR' (|)
131
134
 
@@ -137,7 +140,7 @@ export async function inline(html = '', options = {}) {
137
140
  root.walkAtRules(rule => {
138
141
  if (['media', 'supports'].includes(rule.name)) {
139
142
  rule.walkRules(rule => {
140
- preservedClasses.add(rule.selector)
143
+ options.safelist.add(rule.selector)
141
144
  })
142
145
  }
143
146
  })
@@ -185,12 +188,12 @@ export async function inline(html = '', options = {}) {
185
188
  // Preserve pseudo selectors
186
189
  // TODO: revisit pseudos list
187
190
  if ([':hover', ':active', ':focus', ':visited', ':link', ':before', ':after'].some(i => selector.includes(i))) {
188
- preservedClasses.add(selector)
191
+ options.safelist.add(selector)
189
192
  }
190
193
 
191
194
  if (options.removeInlinedSelectors) {
192
195
  // Remove the rule in the <style> tag as long as it's not a preserved class
193
- if (!preservedClasses.has(selector) && !combinedRegex.test(selector)) {
196
+ if (!options.safelist.has(selector) && !combinedRegex.test(selector)) {
194
197
  rule.remove()
195
198
  }
196
199
 
@@ -249,7 +252,7 @@ export async function inline(html = '', options = {}) {
249
252
  // If the class has been inlined in the style attribute...
250
253
  if (has(inlineStyles, prop)) {
251
254
  // Try to remove the classes that have been inlined
252
- if (![...preservedClasses].some(item => item.endsWith(name) || item.startsWith(name))) {
255
+ if (![...options.safelist].some(item => item.includes(name))) {
253
256
  remove(classList, classToRemove => name.includes(classToRemove))
254
257
  }
255
258