@maizzle/framework 5.0.0-beta.21 → 5.0.0-beta.23
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 +3 -3
- package/src/commands/build.js +6 -3
- package/src/generators/plaintext.js +19 -15
- package/src/server/index.js +1 -1
- package/src/server/routes/index.js +51 -13
- package/src/server/views/index.html +137 -14
- package/src/transformers/inline.js +1 -1
- package/types/css/inline.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maizzle/framework",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.23",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@csstools/css-calc": "^1.2.4",
|
|
52
52
|
"@maizzle/cli": "next",
|
|
53
|
-
"cheerio": "
|
|
53
|
+
"cheerio": "1.0.0-rc.12",
|
|
54
54
|
"chokidar": "^3.6.0",
|
|
55
55
|
"cli-table3": "^0.6.5",
|
|
56
56
|
"color-shorthand-hex-to-six-digit": "^5.0.16",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"html-crush": "^6.0.18",
|
|
63
63
|
"is-url-superb": "^6.1.0",
|
|
64
64
|
"istextorbinary": "^9.5.0",
|
|
65
|
-
"juice": "^10.0.
|
|
65
|
+
"juice": "^10.0.1",
|
|
66
66
|
"lodash-es": "^4.17.21",
|
|
67
67
|
"morphdom": "^2.7.4",
|
|
68
68
|
"ora": "^8.0.1",
|
package/src/commands/build.js
CHANGED
|
@@ -221,12 +221,15 @@ export default async (config = {}) => {
|
|
|
221
221
|
if (Boolean(plaintextConfig) || !isEmpty(plaintextConfig)) {
|
|
222
222
|
const posthtmlOptions = get(rendered.config, 'posthtml.options', {})
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
await writePlaintextFile(
|
|
225
|
+
await generatePlaintext(rendered.html, merge(plaintextConfig, posthtmlOptions)),
|
|
226
|
+
rendered.config
|
|
227
|
+
)
|
|
227
228
|
.catch(error => {
|
|
228
229
|
throw new Error(`Error writing plaintext file: ${error}`)
|
|
229
230
|
})
|
|
231
|
+
|
|
232
|
+
rendered.html = await handlePlaintextTags(rendered.html, posthtmlOptions)
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
/**
|
|
@@ -154,17 +154,11 @@ export async function writePlaintextFile(plaintext = '', config = {}) {
|
|
|
154
154
|
const plaintextExtension = get(plaintextConfig, 'output.extension', 'txt')
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* If `plaintext: true` (either from Front Matter or from config)
|
|
157
|
+
* If `plaintext: true` (either from Front Matter or from config),
|
|
158
|
+
* output plaintext file in the same location as the HTML file.
|
|
158
159
|
*/
|
|
159
160
|
if (plaintextConfig === true) {
|
|
160
|
-
|
|
161
|
-
if (typeof config.permalink === 'string') {
|
|
162
|
-
// Output plaintext at the `permalink` path
|
|
163
|
-
plaintextOutputPath = config.permalink
|
|
164
|
-
} else {
|
|
165
|
-
// Output plaintext at the same directory as the HTML file
|
|
166
|
-
plaintextOutputPath = get(config, 'build.output.path')
|
|
167
|
-
}
|
|
161
|
+
plaintextOutputPath = get(config, 'build.output.path')
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
/**
|
|
@@ -179,12 +173,22 @@ export async function writePlaintextFile(plaintext = '', config = {}) {
|
|
|
179
173
|
// No need to handle if it's an object, since we already set it to that initially
|
|
180
174
|
|
|
181
175
|
/**
|
|
182
|
-
* If `
|
|
176
|
+
* If the template has a `permalink` key set in the FM, always output plaintext file there
|
|
177
|
+
*/
|
|
178
|
+
if (typeof config.permalink === 'string') {
|
|
179
|
+
plaintextOutputPath = config.permalink
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* If `plaintextOutputPath` is a file path, output file there.
|
|
184
|
+
*
|
|
185
|
+
* The file will be output relative to the project root, and the extension
|
|
186
|
+
* doesn't matter, it will be replaced with `plaintextExtension`.
|
|
183
187
|
*/
|
|
184
188
|
if (path.extname(plaintextOutputPath)) {
|
|
185
189
|
// Ensure the target directory exists
|
|
186
190
|
await lstat(plaintextOutputPath).catch(async () => {
|
|
187
|
-
await mkdir(plaintextOutputPath, { recursive: true })
|
|
191
|
+
await mkdir(path.dirname(plaintextOutputPath), { recursive: true })
|
|
188
192
|
})
|
|
189
193
|
|
|
190
194
|
// Ensure correct extension is used
|
|
@@ -193,19 +197,19 @@ export async function writePlaintextFile(plaintext = '', config = {}) {
|
|
|
193
197
|
path.basename(plaintextOutputPath, path.extname(plaintextOutputPath)) + '.' + plaintextExtension
|
|
194
198
|
)
|
|
195
199
|
|
|
196
|
-
console.log('plaintextOutputPath', plaintextOutputPath);
|
|
197
|
-
|
|
198
200
|
return writeFile(plaintextOutputPath, plaintext)
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
|
-
* If `plaintextOutputPath` is a directory path, output file there
|
|
204
|
+
* If `plaintextOutputPath` is a directory path, output file there using the Template's name.
|
|
205
|
+
*
|
|
206
|
+
* The file will be output relative to the `build.output.path` directory.
|
|
203
207
|
*/
|
|
204
208
|
const templateFileName = get(config, 'build.current.path.name')
|
|
205
209
|
|
|
206
210
|
plaintextOutputPath = path.join(
|
|
207
|
-
path.dirname(plaintextOutputPath),
|
|
208
211
|
get(config, 'build.current.path.dir'),
|
|
212
|
+
plaintextOutputPath,
|
|
209
213
|
templateFileName + '.' + plaintextExtension
|
|
210
214
|
)
|
|
211
215
|
|
package/src/server/index.js
CHANGED
|
@@ -7,27 +7,65 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import expressions from 'posthtml-expressions'
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
function groupFilesByDirectories(globs, files) {
|
|
11
|
+
const result = {}
|
|
12
|
+
let current = {}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
globs.forEach(glob => {
|
|
15
|
+
const rootPath = glob.split(/[\*\!\{\}]/)[0].replace(/\/+$/, '')
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
files.forEach(file => {
|
|
18
|
+
if (file.startsWith(rootPath)) {
|
|
19
|
+
const relativePath = file.slice(rootPath.length + 1)
|
|
20
|
+
const parts = relativePath.split('/')
|
|
21
|
+
current = result[rootPath] = result[rootPath] || {}
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
24
|
+
current = current[parts[i]] = current[parts[i]] || {}
|
|
25
|
+
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const fileName = parts[parts.length - 1]
|
|
28
|
+
current[fileName] = {
|
|
29
|
+
name: fileName,
|
|
30
|
+
href: file,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
25
33
|
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function flattenPaths(paths, parentPath = '', currentDepth = 0) {
|
|
40
|
+
const flatArray = []
|
|
41
|
+
|
|
42
|
+
for (const [key, value] of Object.entries(paths)) {
|
|
43
|
+
const fullPath = parentPath ? `${parentPath}/${key}` : key
|
|
44
|
+
|
|
45
|
+
if (value && typeof value === 'object' && !value.name) {
|
|
46
|
+
// If it's a folder, add it with the current depth and recurse into its contents
|
|
47
|
+
flatArray.push({ name: key, path: fullPath, depth: currentDepth, type: 'folder' })
|
|
48
|
+
flatArray.push(...flattenPaths(value, fullPath, currentDepth + 1))
|
|
49
|
+
} else if (value && typeof value === 'object' && value.name) {
|
|
50
|
+
// If it's a file, add it with the current depth
|
|
51
|
+
flatArray.push({ name: value.name, href: value.href, path: fullPath, depth: currentDepth, type: 'file' })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return flatArray
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
route.get(['/', '/index.html'], async (req, res) => {
|
|
59
|
+
const view = await fs.readFile(path.join(__dirname, '../views', 'index.html'), 'utf8')
|
|
60
|
+
|
|
61
|
+
const content = new Set(req.maizzleConfig.build.content)
|
|
62
|
+
|
|
63
|
+
const groupedByDir = groupFilesByDirectories(content, req.templatePaths)
|
|
26
64
|
|
|
27
65
|
const { html } = await posthtml()
|
|
28
66
|
.use(expressions({
|
|
29
67
|
locals: {
|
|
30
|
-
|
|
68
|
+
paths: flattenPaths(groupedByDir)
|
|
31
69
|
}
|
|
32
70
|
}))
|
|
33
71
|
.process(view)
|
|
@@ -4,21 +4,144 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maizzle | Templates</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" media="screen">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
padding: 7rem 6.25rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (max-width: 425px) {
|
|
16
|
+
body {
|
|
17
|
+
padding: 3rem 1.5rem;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* General styling */
|
|
22
|
+
ul {
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
list-style-type: none;
|
|
26
|
+
font-family: Inter, Arial, Helvetica, sans-serif;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Folder styling */
|
|
30
|
+
li > strong {
|
|
31
|
+
font-size: 1.875rem;
|
|
32
|
+
line-height: 2.25rem;
|
|
33
|
+
color: #0F172A;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
li.folder.root {
|
|
37
|
+
padding-bottom: 1.5rem;
|
|
38
|
+
padding-top: 3rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
li.folder.root:first-child {
|
|
42
|
+
padding-top: 0 !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
li.folder.nested {
|
|
46
|
+
padding-top: 1.5rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
li.folder.nested > strong {
|
|
50
|
+
font-size: 1.25rem;
|
|
51
|
+
line-height: 1.75rem;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
li > strong > span {
|
|
56
|
+
color: #94A3B8;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* File styling */
|
|
60
|
+
li > a {
|
|
61
|
+
color: #4f46e5;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
font-size: 1.25rem;
|
|
64
|
+
line-height: 1.75rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
li > a:hover {
|
|
68
|
+
text-decoration: underline;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Indentation based on depth */
|
|
72
|
+
li {
|
|
73
|
+
padding-left: 1.5rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
li.folder.nested {
|
|
77
|
+
padding-top: 2.25rem;
|
|
78
|
+
padding-bottom: 0.625rem;
|
|
79
|
+
border-left: 1px solid #64748B;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
li.folder.root {
|
|
83
|
+
padding-top: 3rem;
|
|
84
|
+
border: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
li.file {
|
|
88
|
+
border-left: 1px solid #64748B;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
li.file.nested {
|
|
92
|
+
line-height: 2.25rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
li.file.nested a {
|
|
96
|
+
padding-bottom: 0.75rem;
|
|
97
|
+
margin-left: -1.5rem;
|
|
98
|
+
padding-left: 1.5rem;
|
|
99
|
+
border-left: 1px solid #cbd5e1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
li.file.nested:last-child a {
|
|
103
|
+
padding-bottom: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.insignia {
|
|
107
|
+
width: 64rem;
|
|
108
|
+
position: fixed;
|
|
109
|
+
z-index: -1;
|
|
110
|
+
right: -3rem;
|
|
111
|
+
bottom: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@media (max-width: 425px) {
|
|
115
|
+
.insignia {
|
|
116
|
+
width: 100%;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
7
120
|
</head>
|
|
8
121
|
<body>
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
122
|
+
<ul>
|
|
123
|
+
<each loop="item in paths">
|
|
124
|
+
<if condition="item.type === 'folder'">
|
|
125
|
+
<li
|
|
126
|
+
style="padding-left: calc(1.5rem * {{ item.depth }});"
|
|
127
|
+
class="folder {{ item.depth > 0 ? 'nested' : 'root' }}"
|
|
128
|
+
>
|
|
129
|
+
<strong>{{{ item.name }}}</strong>
|
|
130
|
+
</li>
|
|
131
|
+
</if>
|
|
132
|
+
<if condition="item.type === 'file'">
|
|
133
|
+
<li
|
|
134
|
+
style="padding-left: calc(1.5rem * {{ item.depth }});"
|
|
135
|
+
class="file {{ item.depth > 1 ? 'nested' : '' }}"
|
|
136
|
+
>
|
|
137
|
+
<a href="{{ item.href }}">
|
|
138
|
+
{{ item.name }}
|
|
139
|
+
</a>
|
|
140
|
+
</li>
|
|
141
|
+
</if>
|
|
142
|
+
</each>
|
|
143
|
+
</ul>
|
|
144
|
+
|
|
145
|
+
<svg fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 993 483" class="insignia"><mask id="a" style="mask-type:alpha;" maskUnits="userSpaceOnUse" x="0" y="0" width="993" height="483"><path fill="#D9D9D9" d="M0 0h993v483H0z"></path></mask><g mask="url(#a)" stroke="#e2e8f0" stroke-miterlimit="10"><path d="M954.124 81.816c0-45.163-39.678-81.774-88.624-81.774-48.945 0-88.624 36.611-88.624 81.774v436.13c0 45.163 39.679 81.775 88.624 81.775 48.946 0 88.624-36.612 88.624-81.775V81.816ZM583.379 81.816c0-45.163-39.678-81.774-88.624-81.774-48.945 0-88.624 36.611-88.624 81.774v436.13c0 45.163 39.679 81.775 88.624 81.775 48.946 0 88.624-36.612 88.624-81.775V81.816Z"></path><path d="M935.39 132.185c30.161-35.57 23.362-86.965-15.187-114.795S825.954-4.165 795.794 31.404L425.713 467.848c-30.161 35.57-23.361 86.965 15.187 114.795 38.549 27.829 94.249 21.555 124.41-14.014l370.08-436.444ZM564.288 132.196c30.161-35.569 23.362-86.964-15.187-114.794S454.852-4.154 424.692 31.416L54.611 467.86c-30.16 35.569-23.361 86.965 15.187 114.794 38.549 27.83 94.249 21.556 124.41-14.013l370.08-436.445Z"></path></g></svg>
|
|
23
146
|
</body>
|
|
24
147
|
</html>
|
|
@@ -2,11 +2,11 @@ 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 * as cheerio from 'cheerio/slim'
|
|
6
5
|
import remove from 'lodash-es/remove.js'
|
|
7
6
|
import { render } from 'posthtml-render'
|
|
8
7
|
import { calc } from '@csstools/css-calc'
|
|
9
8
|
import isEmpty from 'lodash-es/isEmpty.js'
|
|
9
|
+
import * as cheerio from 'cheerio/lib/slim'
|
|
10
10
|
import safeParser from 'postcss-safe-parser'
|
|
11
11
|
import isObject from 'lodash-es/isObject.js'
|
|
12
12
|
import { parser as parse } from 'posthtml-parser'
|
package/types/css/inline.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export default interface InlineCSSConfig {
|
|
|
85
85
|
* Prefer HTML `width` and `height` attributes over inline CSS.
|
|
86
86
|
* The inline CSS `width` and `height` will be removed.
|
|
87
87
|
*
|
|
88
|
+
* Applies to elements set in `widthElements` and `heightElements`.
|
|
89
|
+
*
|
|
88
90
|
* @example
|
|
89
91
|
* ```
|
|
90
92
|
* export default {
|