@solid-email/render 0.1.0 → 0.1.2

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.
@@ -2,6 +2,7 @@ import { renderToString, renderToStringAsync } from "solid-js/web/dist/server.js
2
2
  import * as html from "prettier/plugins/html";
3
3
  import { format } from "prettier/standalone";
4
4
  import { convert } from "html-to-text";
5
+ import { ssr } from "solid-js/web";
5
6
  //#region src/shared/utils/pretty.ts
6
7
  function getHtmlNode(path) {
7
8
  const topNode = path.node;
@@ -130,6 +131,230 @@ function renderSync(node, options) {
130
131
  return renderSyncOutput(removeSolidResourceScripts(renderToString(normalizeRenderable(node))), options);
131
132
  }
132
133
  //#endregion
133
- export { plainTextSelectors, pretty, render, renderSync, toPlainText };
134
+ //#region src/shared/slots.ts
135
+ const MARKER_PREFIX = "__SM_";
136
+ const CONTENT_START = `${MARKER_PREFIX}CNT_`;
137
+ const CONTENT_END = `${MARKER_PREFIX}CNE_`;
138
+ const ATTR_PREFIX = `${MARKER_PREFIX}ATR_`;
139
+ function encodeName(name) {
140
+ return encodeURIComponent(name).replace(/[!'()*_]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
141
+ }
142
+ function decodeName(encoded) {
143
+ return decodeURIComponent(encoded);
144
+ }
145
+ function makeContentMarker(name, defaultValue) {
146
+ const encoded = encodeName(name);
147
+ const start = `${CONTENT_START}${encoded}__`;
148
+ if (defaultValue !== void 0) return `${start}${defaultValue}${CONTENT_END}${encoded}__`;
149
+ return `${start}${CONTENT_END}${encoded}__`;
150
+ }
151
+ function makeAttrMarker(name) {
152
+ return `${ATTR_PREFIX}${encodeName(name)}__`;
153
+ }
154
+ function escapeRegex$1(str) {
155
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
156
+ }
157
+ function buildSlotLookup(html) {
158
+ const contentSlots = /* @__PURE__ */ new Map();
159
+ const attrSlots = /* @__PURE__ */ new Map();
160
+ const nameChars = "(?:[A-Za-z0-9.~-]|%[0-9A-Fa-f]{2})+";
161
+ const tokenRegex = new RegExp(`${escapeRegex$1(CONTENT_START)}(${nameChars})__|${escapeRegex$1(CONTENT_END)}(${nameChars})__`, "g");
162
+ const stack = [];
163
+ let match;
164
+ while (true) {
165
+ match = tokenRegex.exec(html);
166
+ if (match === null) break;
167
+ const startName = match[1];
168
+ if (startName !== void 0) {
169
+ stack.push({
170
+ encodedName: startName,
171
+ name: decodeName(startName),
172
+ startIndex: match.index,
173
+ contentStartIndex: tokenRegex.lastIndex
174
+ });
175
+ continue;
176
+ }
177
+ const endName = match[2];
178
+ if (endName === void 0) continue;
179
+ let openIndex = -1;
180
+ for (let index = stack.length - 1; index >= 0; index -= 1) if (stack[index]?.encodedName === endName) {
181
+ openIndex = index;
182
+ break;
183
+ }
184
+ if (openIndex === -1) continue;
185
+ const [open] = stack.splice(openIndex, 1);
186
+ if (open === void 0) continue;
187
+ const innerContent = html.slice(open.contentStartIndex, match.index);
188
+ const occurrence = {
189
+ full: html.slice(open.startIndex, tokenRegex.lastIndex),
190
+ hasDefault: innerContent.length > 0,
191
+ defaultValue: innerContent
192
+ };
193
+ const existing = contentSlots.get(open.name);
194
+ if (existing) existing.push(occurrence);
195
+ else contentSlots.set(open.name, [occurrence]);
196
+ }
197
+ const attrRegex = new RegExp(`${escapeRegex$1(ATTR_PREFIX)}(${nameChars})__`, "g");
198
+ while (true) {
199
+ match = attrRegex.exec(html);
200
+ if (match === null) break;
201
+ const name = decodeName(match[1] ?? "");
202
+ const existing = attrSlots.get(name);
203
+ if (existing) existing.push(match[0]);
204
+ else attrSlots.set(name, [match[0]]);
205
+ }
206
+ return {
207
+ content: contentSlots,
208
+ attr: attrSlots
209
+ };
210
+ }
211
+ function Slot(props) {
212
+ const encoded = encodeName(props.name);
213
+ const start = ssr(`${CONTENT_START}${encoded}__`);
214
+ const end = ssr(`${CONTENT_END}${encoded}__`);
215
+ if (props.children !== void 0 && props.children !== null) return [
216
+ start,
217
+ props.children,
218
+ end
219
+ ];
220
+ return [start, end];
221
+ }
222
+ function slot(name) {
223
+ return makeAttrMarker(name);
224
+ }
225
+ function defineSlots() {
226
+ return {
227
+ content: (name, defaultValue) => makeContentMarker(name, defaultValue),
228
+ attr: (name) => makeAttrMarker(name)
229
+ };
230
+ }
231
+ //#endregion
232
+ //#region src/shared/compile.ts
233
+ var CompiledTemplate = class {
234
+ html;
235
+ options;
236
+ contentSlots;
237
+ attrSlots;
238
+ markerRegex;
239
+ constructor(html, options) {
240
+ this.html = html;
241
+ this.options = options;
242
+ const lookup = buildSlotLookup(html);
243
+ this.contentSlots = lookup.content;
244
+ this.attrSlots = lookup.attr;
245
+ this.markerRegex = this.buildMarkerRegex();
246
+ }
247
+ async render(data, options) {
248
+ let result = this.html;
249
+ result = await this.replaceSlots(result, data);
250
+ return renderOutput(result, options ?? this.options);
251
+ }
252
+ renderSync(data, options) {
253
+ if ((options ?? this.options)?.pretty) throw new Error("renderSync does not support pretty output; use render.");
254
+ let result = this.html;
255
+ result = this.replaceSlotsSync(result, data);
256
+ return renderSyncOutput(result, options ?? this.options);
257
+ }
258
+ buildMarkerRegex() {
259
+ const markers = /* @__PURE__ */ new Set();
260
+ for (const occurrences of this.contentSlots.values()) for (const occ of occurrences) markers.add(occ.full);
261
+ for (const attrMarkers of this.attrSlots.values()) for (const marker of attrMarkers) markers.add(marker);
262
+ if (markers.size === 0) return /$^/g;
263
+ return new RegExp(Array.from(markers).sort((a, b) => b.length - a.length).map(escapeRegex).join("|"), "g");
264
+ }
265
+ validateSlots(data) {
266
+ const allSlotNames = /* @__PURE__ */ new Set([...this.contentSlots.keys(), ...this.attrSlots.keys()]);
267
+ for (const name of allSlotNames) if (data[name] === void 0) {
268
+ if (!(this.contentSlots.get(name)?.some((occ) => occ.hasDefault) ?? false)) console.warn(`[solid-email] Slot "${name}" has no default and was not provided in render data. It will render as empty.`);
269
+ }
270
+ }
271
+ async replaceSlots(result, data) {
272
+ return this.replaceSlotsInFragment(result, data, true);
273
+ }
274
+ async replaceSlotsInFragment(result, data, validate) {
275
+ if (validate) this.validateSlots(data);
276
+ const replacements = /* @__PURE__ */ new Map();
277
+ for (const [name, occurrences] of this.contentSlots) {
278
+ const value = data[name];
279
+ const rendered = value !== void 0 ? await renderSlotValueAsync(value) : void 0;
280
+ for (const occ of occurrences) {
281
+ if (!result.includes(occ.full)) continue;
282
+ const replacement = rendered ?? await this.replaceSlotsInFragment(occ.defaultValue, data, false);
283
+ replacements.set(occ.full, replacement);
284
+ }
285
+ }
286
+ for (const [name, markers] of this.attrSlots) {
287
+ const value = data[name];
288
+ const replacement = renderAttrValue(name, value);
289
+ for (const marker of markers) replacements.set(marker, replacement);
290
+ }
291
+ if (replacements.size === 0) return result;
292
+ return result.replace(this.markerRegex, (marker) => replacements.get(marker) ?? marker);
293
+ }
294
+ replaceSlotsSync(result, data) {
295
+ return this.replaceSlotsInFragmentSync(result, data, true);
296
+ }
297
+ replaceSlotsInFragmentSync(result, data, validate) {
298
+ if (validate) this.validateSlots(data);
299
+ const replacements = /* @__PURE__ */ new Map();
300
+ for (const [name, occurrences] of this.contentSlots) {
301
+ const value = data[name];
302
+ const rendered = value !== void 0 ? renderSlotValueSync(value) : void 0;
303
+ for (const occ of occurrences) {
304
+ if (!result.includes(occ.full)) continue;
305
+ const replacement = rendered ?? this.replaceSlotsInFragmentSync(occ.defaultValue, data, false);
306
+ replacements.set(occ.full, replacement);
307
+ }
308
+ }
309
+ for (const [name, markers] of this.attrSlots) {
310
+ const value = data[name];
311
+ const replacement = renderAttrValue(name, value);
312
+ for (const marker of markers) replacements.set(marker, replacement);
313
+ }
314
+ if (replacements.size === 0) return result;
315
+ return result.replace(this.markerRegex, (marker) => replacements.get(marker) ?? marker);
316
+ }
317
+ };
318
+ async function renderSlotValueAsync(value) {
319
+ if (value == null) return "";
320
+ if (Array.isArray(value)) return Promise.all(value.map(renderSlotValueAsync)).then((results) => results.join(""));
321
+ if (typeof value === "boolean") return value ? "true" : "";
322
+ if (typeof value === "string") return escapeHtml(value);
323
+ if (typeof value === "number") return String(value);
324
+ return removeSolidResourceScripts(await renderToStringAsync(() => value));
325
+ }
326
+ function renderSlotValueSync(value) {
327
+ if (value == null) return "";
328
+ if (Array.isArray(value)) return value.map(renderSlotValueSync).join("");
329
+ if (typeof value === "boolean") return value ? "true" : "";
330
+ if (typeof value === "string") return escapeHtml(value);
331
+ if (typeof value === "number") return String(value);
332
+ return removeSolidResourceScripts(renderToString(() => value));
333
+ }
334
+ function renderAttrValue(name, value) {
335
+ if (value == null) return "";
336
+ if (typeof value === "boolean") return value ? "true" : "";
337
+ if (typeof value === "string") return escapeAttr(value);
338
+ if (typeof value === "number") return String(value);
339
+ throw new TypeError(`Attribute slot "${name}" only accepts string, number, boolean, null, or undefined. Use <Slot name="${name}" /> for JSX/content values.`);
340
+ }
341
+ function escapeHtml(str) {
342
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
343
+ }
344
+ function escapeAttr(str) {
345
+ return str.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
346
+ }
347
+ function escapeRegex(str) {
348
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
349
+ }
350
+ async function compile(node, options) {
351
+ return new CompiledTemplate(removeSolidResourceScripts(await renderToStringAsync(normalizeRenderable(node))), options);
352
+ }
353
+ function compileSync(node, options) {
354
+ if (options?.pretty) throw new Error("compileSync does not support pretty output; use compile.");
355
+ return new CompiledTemplate(removeSolidResourceScripts(renderToString(normalizeRenderable(node))), options);
356
+ }
357
+ //#endregion
358
+ export { CompiledTemplate, Slot, buildSlotLookup, compile, compileSync, decodeName, defineSlots, makeAttrMarker, makeContentMarker, normalizeRenderable, plainTextSelectors, pretty, removeSolidResourceScripts, render, renderDocument, renderOutput, renderSync, renderSyncOutput, slot, toPlainText };
134
359
 
135
360
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/shared/render.ts"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type?: 'element' | 'text' | 'ieConditionalComment';\n kind?: 'element' | 'text' | 'ieConditionalComment' | 'root';\n name?: string;\n sourceSpan?: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction getHtmlNode(path: {\n node?: HtmlNode;\n stack?: Array<Record<string, unknown>>;\n}) {\n const topNode = path.node;\n if (topNode) {\n return topNode;\n }\n\n return path.stack?.[path.stack.length - 1] as unknown as HtmlNode | undefined;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'line') {\n return callback(doc.soft ? '' : ' ');\n }\n\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n\n const nextDoc = { ...doc } as Record<string, unknown>;\n for (const [key, value] of Object.entries(nextDoc)) {\n if (value && typeof value === 'object') {\n nextDoc[key] = recursivelyMapDoc(value as builders.Doc, callback);\n }\n }\n\n return nextDoc as unknown as builders.Doc;\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nconst htmlPrinter = modifiedHtml.printers?.html;\nif (htmlPrinter?.print) {\n const previousPrint = htmlPrinter.print;\n htmlPrinter.print = (path, options, print, args) => {\n const node = getHtmlNode(path as Parameters<typeof previousPrint>[0]);\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (\n node?.type === 'ieConditionalComment' ||\n node?.kind === 'ieConditionalComment'\n ) {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\n// Text-export note: images and\n// preview-only nodes are skipped, and links render without duplicated hrefs.\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n wordwrap: false,\n ...options,\n selectors: [...plainTextSelectors, ...(options?.selectors ?? [])],\n });\n}\n","import type { JSX } from 'solid-js';\nimport {\n renderToString,\n renderToStringAsync,\n} from 'solid-js/web/dist/server.js';\nimport type { Options, RenderSyncOptions } from './options';\nimport { pretty } from './utils/pretty';\nimport { toPlainText } from './utils/to-plain-text';\n\nexport type Renderable = JSX.Element | (() => JSX.Element);\n\nconst doctype =\n '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">';\n\nfunction normalizeRenderable(node: Renderable) {\n return typeof node === 'function' ? (node as () => JSX.Element) : () => node;\n}\n\nfunction decodeSerializedString(value: string): string {\n try {\n const jsonSafe = value.replace(\n /\\\\x([0-9A-Fa-f]{2})/g,\n (_match, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)),\n );\n const parsed: unknown = JSON.parse(`\"${jsonSafe}\"`);\n return typeof parsed === 'string' ? parsed : value;\n } catch {\n return value;\n }\n}\n\nfunction removeSolidResourceScripts(html: string): string {\n const errorMatch = /Object\\.assign\\(new Error\\(\"((?:\\\\.|[^\"\\\\])*)\"\\)/.exec(\n html,\n );\n if (errorMatch) {\n throw new Error(decodeSerializedString(errorMatch[1] ?? ''));\n }\n return html.replace(\n /<script>self\\.\\$R=self\\.\\$R\\|\\|\\[\\];[\\s\\S]*?<\\/script>/g,\n '',\n );\n}\n\nfunction renderDocument(html: string): string {\n return `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n}\n\nfunction renderSyncOutput(html: string, options?: RenderSyncOptions): string {\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n return renderDocument(html);\n}\n\nasync function renderOutput(html: string, options?: Options): Promise<string> {\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n const document = renderDocument(html);\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n}\n\n// API note: render accepts either a component function or an already\n// constructed node. Keep that API while rendering through Solid SSR.\nexport async function render(\n node: Renderable,\n options?: Options,\n): Promise<string> {\n const html = removeSolidResourceScripts(\n await renderToStringAsync(normalizeRenderable(node)),\n );\n\n return renderOutput(html, options);\n}\n\nexport function renderSync(\n node: Renderable,\n options?: RenderSyncOptions,\n): string {\n if (options?.pretty) {\n throw new Error('renderSync does not support pretty output; use render.');\n }\n\n const html = removeSolidResourceScripts(\n renderToString(normalizeRenderable(node)),\n );\n\n return renderSyncOutput(html, options);\n}\n"],"mappings":";;;;;AAiBA,SAAS,YAAY,MAGlB;CACD,MAAM,UAAU,KAAK;CACrB,IAAI,SACF,OAAO;CAGT,OAAO,KAAK,QAAQ,KAAK,MAAM,SAAS;AAC1C;AAEA,SAAS,kBACP,KACA,UACc;CACd,IAAI,MAAM,QAAQ,GAAG,GACnB,OAAO,IAAI,KAAK,aAAa,kBAAkB,UAAU,QAAQ,CAAC;CAGpE,IAAI,OAAO,QAAQ,UAAU;EAC3B,IAAI,IAAI,SAAS,QACf,OAAO,SAAS,IAAI,OAAO,KAAK,GAAG;EAGrC,IAAI,IAAI,SAAS,SACf,OAAO;GACL,GAAG;GACH,UAAU,kBAAkB,IAAI,UAAU,QAAQ;GAClD,gBAAgB,kBACd,IAAI,gBACJ,QACF;EACF;EAGF,IAAI,cAAc,KAChB,OAAO;GACL,GAAG;GACH,UAAU,kBAAkB,IAAI,UAAU,QAAQ;EACpD;EAGF,IAAI,WAAW,KACb,OAAO;GACL,GAAG;GACH,OAAO,kBAAkB,IAAI,OAAO,QAAQ;EAC9C;EAGF,IAAI,IAAI,SAAS,YACf,OAAO;GACL,GAAG;GACH,eAAe,kBAAkB,IAAI,eAAe,QAAQ;GAC5D,cAAc,kBAAkB,IAAI,cAAc,QAAQ;EAC5D;EAGF,MAAM,UAAU,EAAE,GAAG,IAAI;EACzB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,SAAS,OAAO,UAAU,UAC5B,QAAQ,OAAO,kBAAkB,OAAuB,QAAQ;EAIpE,OAAO;CACT;CAEA,OAAO,SAAS,GAAG;AACrB;AAEA,MAAM,eAAe,EAAE,GAAG,KAAK;AAC/B,MAAM,cAAc,aAAa,UAAU;AAC3C,IAAI,aAAa,OAAO;CACtB,MAAM,gBAAgB,YAAY;CAClC,YAAY,SAAS,MAAM,SAAS,OAAO,SAAS;EAClD,MAAM,OAAO,YAAY,IAA2C;EAEpE,MAAM,oBAAoB,cAAc,MAAM,SAAS,OAAO,IAAI;EAElE,IACE,MAAM,SAAS,0BACf,MAAM,SAAS,wBAUf,OARuB,kBAAkB,oBAAoB,QAAQ;GACnE,IAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,QAC1C,OAAO,IAAI,OAAO,KAAK;GAGzB,OAAO;EACT,CAEoB;EAGtB,OAAO;CACT;AACF;AAEA,MAAM,WAAoB;CACxB,WAAW;CACX,UAAU;CACV,SAAS,CAAC,YAAY;CACtB,iBAAiB;CACjB,QAAQ;AACV;AAEA,MAAa,UAAU,KAAa,UAAmB,CAAC,MAAM;CAC5D,OAAO,OAAO,IAAI,WAAW,MAAM,EAAE,GAAG;EACtC,GAAG;EACH,GAAG;CACL,CAAC;AACH;;;ACzHA,MAAa,qBAA2C;CACtD;EAAE,UAAU;EAAO,QAAQ;CAAO;CAClC;EAAE,UAAU;EAA4B,QAAQ;CAAO;CACvD;EACE,UAAU;EACV,SAAS;GAAE,cAAc;GAAO,0BAA0B;EAAK;CACjE;AACF;AAEA,SAAgB,YAAY,MAAc,SAA6B;CACrE,OAAO,QAAQ,MAAM;EACnB,UAAU;EACV,GAAG;EACH,WAAW,CAAC,GAAG,oBAAoB,GAAI,SAAS,aAAa,CAAC,CAAE;CAClE,CAAC;AACH;;;ACZA,MAAM,UACJ;AAEF,SAAS,oBAAoB,MAAkB;CAC7C,OAAO,OAAO,SAAS,aAAc,aAAmC;AAC1E;AAEA,SAAS,uBAAuB,OAAuB;CACrD,IAAI;EACF,MAAM,WAAW,MAAM,QACrB,yBACC,QAAQ,QAAgB,OAAO,aAAa,OAAO,SAAS,KAAK,EAAE,CAAC,CACvE;EACA,MAAM,SAAkB,KAAK,MAAM,IAAI,SAAS,EAAE;EAClD,OAAO,OAAO,WAAW,WAAW,SAAS;CAC/C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,2BAA2B,MAAsB;CACxD,MAAM,aAAa,mDAAmD,KACpE,IACF;CACA,IAAI,YACF,MAAM,IAAI,MAAM,uBAAuB,WAAW,MAAM,EAAE,CAAC;CAE7D,OAAO,KAAK,QACV,2DACA,EACF;AACF;AAEA,SAAS,eAAe,MAAsB;CAC5C,OAAO,GAAG,UAAU,KAAK,QAAQ,iBAAiB,EAAE;AACtD;AAEA,SAAS,iBAAiB,MAAc,SAAqC;CAC3E,IAAI,SAAS,WACX,OAAO,YAAY,MAAM,QAAQ,iBAAiB;CAGpD,OAAO,eAAe,IAAI;AAC5B;AAEA,eAAe,aAAa,MAAc,SAAoC;CAC5E,IAAI,SAAS,WACX,OAAO,YAAY,MAAM,QAAQ,iBAAiB;CAGpD,MAAM,WAAW,eAAe,IAAI;CAEpC,IAAI,SAAS,QACX,OAAO,OAAO,QAAQ;CAGxB,OAAO;AACT;AAIA,eAAsB,OACpB,MACA,SACiB;CAKjB,OAAO,aAJM,2BACX,MAAM,oBAAoB,oBAAoB,IAAI,CAAC,CAG9B,GAAG,OAAO;AACnC;AAEA,SAAgB,WACd,MACA,SACQ;CACR,IAAI,SAAS,QACX,MAAM,IAAI,MAAM,wDAAwD;CAO1E,OAAO,iBAJM,2BACX,eAAe,oBAAoB,IAAI,CAAC,CAGf,GAAG,OAAO;AACvC"}
1
+ {"version":3,"file":"index.mjs","names":["escapeRegex"],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/shared/render.ts","../../src/shared/slots.ts","../../src/shared/compile.ts"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type?: 'element' | 'text' | 'ieConditionalComment';\n kind?: 'element' | 'text' | 'ieConditionalComment' | 'root';\n name?: string;\n sourceSpan?: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction getHtmlNode(path: {\n node?: HtmlNode;\n stack?: Array<Record<string, unknown>>;\n}) {\n const topNode = path.node;\n if (topNode) {\n return topNode;\n }\n\n return path.stack?.[path.stack.length - 1] as unknown as HtmlNode | undefined;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'line') {\n return callback(doc.soft ? '' : ' ');\n }\n\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n\n const nextDoc = { ...doc } as Record<string, unknown>;\n for (const [key, value] of Object.entries(nextDoc)) {\n if (value && typeof value === 'object') {\n nextDoc[key] = recursivelyMapDoc(value as builders.Doc, callback);\n }\n }\n\n return nextDoc as unknown as builders.Doc;\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nconst htmlPrinter = modifiedHtml.printers?.html;\nif (htmlPrinter?.print) {\n const previousPrint = htmlPrinter.print;\n htmlPrinter.print = (path, options, print, args) => {\n const node = getHtmlNode(path as Parameters<typeof previousPrint>[0]);\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (\n node?.type === 'ieConditionalComment' ||\n node?.kind === 'ieConditionalComment'\n ) {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\n// Text-export note: images and\n// preview-only nodes are skipped, and links render without duplicated hrefs.\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n wordwrap: false,\n ...options,\n selectors: [...plainTextSelectors, ...(options?.selectors ?? [])],\n });\n}\n","import type { JSX } from 'solid-js';\nimport {\n renderToString,\n renderToStringAsync,\n} from 'solid-js/web/dist/server.js';\nimport type { Options, RenderSyncOptions } from './options';\nimport { pretty } from './utils/pretty';\nimport { toPlainText } from './utils/to-plain-text';\n\nexport type Renderable = JSX.Element | (() => JSX.Element);\n\nconst doctype =\n '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">';\n\nexport function normalizeRenderable(node: Renderable) {\n return typeof node === 'function' ? (node as () => JSX.Element) : () => node;\n}\n\nfunction decodeSerializedString(value: string): string {\n try {\n const jsonSafe = value.replace(\n /\\\\x([0-9A-Fa-f]{2})/g,\n (_match, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)),\n );\n const parsed: unknown = JSON.parse(`\"${jsonSafe}\"`);\n return typeof parsed === 'string' ? parsed : value;\n } catch {\n return value;\n }\n}\n\nexport function removeSolidResourceScripts(html: string): string {\n const errorMatch = /Object\\.assign\\(new Error\\(\"((?:\\\\.|[^\"\\\\])*)\"\\)/.exec(\n html,\n );\n if (errorMatch) {\n throw new Error(decodeSerializedString(errorMatch[1] ?? ''));\n }\n return html.replace(\n /<script>self\\.\\$R=self\\.\\$R\\|\\|\\[\\];[\\s\\S]*?<\\/script>/g,\n '',\n );\n}\n\nexport function renderDocument(html: string): string {\n return `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n}\n\nexport function renderSyncOutput(\n html: string,\n options?: RenderSyncOptions,\n): string {\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n return renderDocument(html);\n}\n\nexport async function renderOutput(\n html: string,\n options?: Options,\n): Promise<string> {\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n const document = renderDocument(html);\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n}\n\n// API note: render accepts either a component function or an already\n// constructed node. Keep that API while rendering through Solid SSR.\nexport async function render(\n node: Renderable,\n options?: Options,\n): Promise<string> {\n const html = removeSolidResourceScripts(\n await renderToStringAsync(normalizeRenderable(node)),\n );\n\n return renderOutput(html, options);\n}\n\nexport function renderSync(\n node: Renderable,\n options?: RenderSyncOptions,\n): string {\n if (options?.pretty) {\n throw new Error('renderSync does not support pretty output; use render.');\n }\n\n const html = removeSolidResourceScripts(\n renderToString(normalizeRenderable(node)),\n );\n\n return renderSyncOutput(html, options);\n}\n","import type { JSX } from 'solid-js';\nimport { ssr } from 'solid-js/web';\n\nconst MARKER_PREFIX = '__SM_';\nconst CONTENT_START = `${MARKER_PREFIX}CNT_`;\nconst CONTENT_END = `${MARKER_PREFIX}CNE_`;\nconst ATTR_PREFIX = `${MARKER_PREFIX}ATR_`;\n\nexport type SlotPrimitive = string | number | boolean | null | undefined;\nexport type SlotValue = SlotPrimitive | JSX.Element | SlotValue[];\nexport type SlotRecord = Record<string, SlotValue>;\n\nfunction encodeName(name: string): string {\n return encodeURIComponent(name).replace(\n /[!'()*_]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nexport function decodeName(encoded: string): string {\n return decodeURIComponent(encoded);\n}\n\nexport function makeContentMarker(name: string, defaultValue?: string): string {\n const encoded = encodeName(name);\n const start = `${CONTENT_START}${encoded}__`;\n if (defaultValue !== undefined) {\n return `${start}${defaultValue}${CONTENT_END}${encoded}__`;\n }\n return `${start}${CONTENT_END}${encoded}__`;\n}\n\nexport function makeAttrMarker(name: string): string {\n return `${ATTR_PREFIX}${encodeName(name)}__`;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport interface SlotOccurrence {\n full: string;\n hasDefault: boolean;\n defaultValue: string;\n}\n\nexport function buildSlotLookup(html: string): {\n content: Map<string, SlotOccurrence[]>;\n attr: Map<string, string[]>;\n} {\n const contentSlots = new Map<string, SlotOccurrence[]>();\n const attrSlots = new Map<string, string[]>();\n\n const nameChars = '(?:[A-Za-z0-9.~-]|%[0-9A-Fa-f]{2})+';\n\n const tokenRegex = new RegExp(\n `${escapeRegex(CONTENT_START)}(${nameChars})__|${escapeRegex(CONTENT_END)}(${nameChars})__`,\n 'g',\n );\n const stack: Array<{\n encodedName: string;\n name: string;\n startIndex: number;\n contentStartIndex: number;\n }> = [];\n let match: RegExpExecArray | null;\n while (true) {\n match = tokenRegex.exec(html);\n if (match === null) break;\n\n const startName = match[1];\n if (startName !== undefined) {\n stack.push({\n encodedName: startName,\n name: decodeName(startName),\n startIndex: match.index,\n contentStartIndex: tokenRegex.lastIndex,\n });\n continue;\n }\n\n const endName = match[2];\n if (endName === undefined) continue;\n\n let openIndex = -1;\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n if (stack[index]?.encodedName === endName) {\n openIndex = index;\n break;\n }\n }\n if (openIndex === -1) continue;\n\n const [open] = stack.splice(openIndex, 1);\n if (open === undefined) continue;\n\n const innerContent = html.slice(open.contentStartIndex, match.index);\n const occurrence = {\n full: html.slice(open.startIndex, tokenRegex.lastIndex),\n hasDefault: innerContent.length > 0,\n defaultValue: innerContent,\n };\n const existing = contentSlots.get(open.name);\n if (existing) {\n existing.push(occurrence);\n } else {\n contentSlots.set(open.name, [occurrence]);\n }\n }\n\n const attrRegex = new RegExp(\n `${escapeRegex(ATTR_PREFIX)}(${nameChars})__`,\n 'g',\n );\n while (true) {\n match = attrRegex.exec(html);\n if (match === null) break;\n const name = decodeName(match[1] ?? '');\n const existing = attrSlots.get(name);\n if (existing) {\n existing.push(match[0]);\n } else {\n attrSlots.set(name, [match[0]]);\n }\n }\n\n return { content: contentSlots, attr: attrSlots };\n}\n\nexport function Slot(props: {\n name: string;\n children?: JSX.Element;\n}): JSX.Element {\n const encoded = encodeName(props.name);\n const start = ssr(`${CONTENT_START}${encoded}__`) as unknown as JSX.Element;\n const end = ssr(`${CONTENT_END}${encoded}__`) as unknown as JSX.Element;\n if (props.children !== undefined && props.children !== null) {\n return [start, props.children, end] as unknown as JSX.Element;\n }\n return [start, end] as unknown as JSX.Element;\n}\n\nexport function slot(name: string): string {\n return makeAttrMarker(name);\n}\n\nexport interface SlotDefinition<T extends SlotRecord> {\n content: <K extends keyof T & string>(\n name: K,\n defaultValue?: string,\n ) => string;\n attr: <K extends keyof T & string>(name: K) => string;\n}\n\nexport function defineSlots<T extends SlotRecord>(): SlotDefinition<T> {\n return {\n content: (name, defaultValue) =>\n makeContentMarker(name as string, defaultValue),\n attr: (name) => makeAttrMarker(name as string),\n };\n}\n","import type { JSX } from 'solid-js';\nimport {\n renderToString,\n renderToStringAsync,\n} from 'solid-js/web/dist/server.js';\nimport type { Options, RenderSyncOptions } from './options';\nimport type { Renderable } from './render';\nimport {\n normalizeRenderable,\n removeSolidResourceScripts,\n renderOutput,\n renderSyncOutput,\n} from './render';\nimport { buildSlotLookup, type SlotOccurrence, type SlotValue } from './slots';\n\nexport type { SlotRecord, SlotValue } from './slots';\n\nexport class CompiledTemplate<\n TSlots extends Record<string, SlotValue> = Record<string, SlotValue>,\n> {\n private readonly html: string;\n private readonly options?: Options;\n private readonly contentSlots: Map<string, SlotOccurrence[]>;\n private readonly attrSlots: Map<string, string[]>;\n private readonly markerRegex: RegExp;\n\n constructor(html: string, options?: Options) {\n this.html = html;\n this.options = options;\n const lookup = buildSlotLookup(html);\n this.contentSlots = lookup.content;\n this.attrSlots = lookup.attr;\n this.markerRegex = this.buildMarkerRegex();\n }\n\n async render(data: TSlots, options?: Options): Promise<string> {\n let result = this.html;\n\n result = await this.replaceSlots(result, data);\n\n return renderOutput(result, options ?? this.options);\n }\n\n renderSync(data: TSlots, options?: RenderSyncOptions): string {\n if ((options ?? this.options)?.pretty) {\n throw new Error('renderSync does not support pretty output; use render.');\n }\n\n let result = this.html;\n\n result = this.replaceSlotsSync(result, data);\n\n return renderSyncOutput(\n result,\n options ?? (this.options as RenderSyncOptions),\n );\n }\n\n private buildMarkerRegex(): RegExp {\n const markers = new Set<string>();\n for (const occurrences of this.contentSlots.values()) {\n for (const occ of occurrences) {\n markers.add(occ.full);\n }\n }\n for (const attrMarkers of this.attrSlots.values()) {\n for (const marker of attrMarkers) {\n markers.add(marker);\n }\n }\n\n if (markers.size === 0) {\n return /$^/g;\n }\n\n return new RegExp(\n Array.from(markers)\n .sort((a, b) => b.length - a.length)\n .map(escapeRegex)\n .join('|'),\n 'g',\n );\n }\n\n private validateSlots(data: TSlots): void {\n const allSlotNames = new Set([\n ...this.contentSlots.keys(),\n ...this.attrSlots.keys(),\n ]);\n\n for (const name of allSlotNames) {\n const value = (data as Record<string, unknown>)[name];\n\n if (value === undefined) {\n const hasDefault =\n this.contentSlots.get(name)?.some((occ) => occ.hasDefault) ?? false;\n if (!hasDefault) {\n console.warn(\n `[solid-email] Slot \"${name}\" has no default and was not provided in render data. It will render as empty.`,\n );\n }\n }\n }\n }\n\n private async replaceSlots(result: string, data: TSlots): Promise<string> {\n return this.replaceSlotsInFragment(result, data, true);\n }\n\n private async replaceSlotsInFragment(\n result: string,\n data: TSlots,\n validate: boolean,\n ): Promise<string> {\n if (validate) this.validateSlots(data);\n const replacements = new Map<string, string>();\n\n for (const [name, occurrences] of this.contentSlots) {\n const value = data[name as keyof TSlots] as SlotValue | undefined;\n const rendered =\n value !== undefined ? await renderSlotValueAsync(value) : undefined;\n for (const occ of occurrences) {\n if (!result.includes(occ.full)) continue;\n const replacement =\n rendered ??\n (await this.replaceSlotsInFragment(occ.defaultValue, data, false));\n replacements.set(occ.full, replacement);\n }\n }\n\n for (const [name, markers] of this.attrSlots) {\n const value = data[name as keyof TSlots] as SlotValue | undefined;\n const replacement = renderAttrValue(name, value);\n for (const marker of markers) {\n replacements.set(marker, replacement);\n }\n }\n\n if (replacements.size === 0) return result;\n\n return result.replace(\n this.markerRegex,\n (marker) => replacements.get(marker) ?? marker,\n );\n }\n\n private replaceSlotsSync(result: string, data: TSlots): string {\n return this.replaceSlotsInFragmentSync(result, data, true);\n }\n\n private replaceSlotsInFragmentSync(\n result: string,\n data: TSlots,\n validate: boolean,\n ): string {\n if (validate) this.validateSlots(data);\n const replacements = new Map<string, string>();\n\n for (const [name, occurrences] of this.contentSlots) {\n const value = data[name as keyof TSlots] as SlotValue | undefined;\n const rendered =\n value !== undefined ? renderSlotValueSync(value) : undefined;\n for (const occ of occurrences) {\n if (!result.includes(occ.full)) continue;\n const replacement =\n rendered ??\n this.replaceSlotsInFragmentSync(occ.defaultValue, data, false);\n replacements.set(occ.full, replacement);\n }\n }\n\n for (const [name, markers] of this.attrSlots) {\n const value = data[name as keyof TSlots] as SlotValue | undefined;\n const replacement = renderAttrValue(name, value);\n for (const marker of markers) {\n replacements.set(marker, replacement);\n }\n }\n\n if (replacements.size === 0) return result;\n\n return result.replace(\n this.markerRegex,\n (marker) => replacements.get(marker) ?? marker,\n );\n }\n}\n\nasync function renderSlotValueAsync(value: SlotValue): Promise<string> {\n if (value == null) return '';\n if (Array.isArray(value))\n return Promise.all(value.map(renderSlotValueAsync)).then((results) =>\n results.join(''),\n );\n if (typeof value === 'boolean') return value ? 'true' : '';\n if (typeof value === 'string') return escapeHtml(value);\n if (typeof value === 'number') return String(value);\n return removeSolidResourceScripts(\n await renderToStringAsync(() => value as JSX.Element),\n );\n}\n\nfunction renderSlotValueSync(value: SlotValue): string {\n if (value == null) return '';\n if (Array.isArray(value)) return value.map(renderSlotValueSync).join('');\n if (typeof value === 'boolean') return value ? 'true' : '';\n if (typeof value === 'string') return escapeHtml(value);\n if (typeof value === 'number') return String(value);\n return removeSolidResourceScripts(renderToString(() => value as JSX.Element));\n}\n\nfunction renderAttrValue(name: string, value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'boolean') return value ? 'true' : '';\n if (typeof value === 'string') return escapeAttr(value);\n if (typeof value === 'number') return String(value);\n throw new TypeError(\n `Attribute slot \"${name}\" only accepts string, number, boolean, null, or undefined. Use <Slot name=\"${name}\" /> for JSX/content values.`,\n );\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n}\n\nfunction escapeAttr(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport async function compile<\n TSlots extends Record<string, SlotValue> = Record<string, SlotValue>,\n>(node: Renderable, options?: Options): Promise<CompiledTemplate<TSlots>> {\n const html = removeSolidResourceScripts(\n await renderToStringAsync(normalizeRenderable(node)),\n );\n return new CompiledTemplate<TSlots>(html, options);\n}\n\nexport function compileSync<\n TSlots extends Record<string, SlotValue> = Record<string, SlotValue>,\n>(node: Renderable, options?: RenderSyncOptions): CompiledTemplate<TSlots> {\n if (options?.pretty) {\n throw new Error('compileSync does not support pretty output; use compile.');\n }\n const html = removeSolidResourceScripts(\n renderToString(normalizeRenderable(node)),\n );\n return new CompiledTemplate<TSlots>(html, options);\n}\n"],"mappings":"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"}
@@ -26,6 +26,7 @@ let prettier_plugins_html = require("prettier/plugins/html");
26
26
  prettier_plugins_html = __toESM(prettier_plugins_html, 1);
27
27
  let prettier_standalone = require("prettier/standalone");
28
28
  let html_to_text = require("html-to-text");
29
+ let solid_js_web = require("solid-js/web");
29
30
  //#region src/shared/utils/pretty.ts
30
31
  function getHtmlNode(path) {
31
32
  const topNode = path.node;
@@ -154,8 +155,247 @@ function renderSync(node, options) {
154
155
  return renderSyncOutput(removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(normalizeRenderable(node))), options);
155
156
  }
156
157
  //#endregion
158
+ //#region src/shared/slots.ts
159
+ const MARKER_PREFIX = "__SM_";
160
+ const CONTENT_START = `${MARKER_PREFIX}CNT_`;
161
+ const CONTENT_END = `${MARKER_PREFIX}CNE_`;
162
+ const ATTR_PREFIX = `${MARKER_PREFIX}ATR_`;
163
+ function encodeName(name) {
164
+ return encodeURIComponent(name).replace(/[!'()*_]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
165
+ }
166
+ function decodeName(encoded) {
167
+ return decodeURIComponent(encoded);
168
+ }
169
+ function makeContentMarker(name, defaultValue) {
170
+ const encoded = encodeName(name);
171
+ const start = `${CONTENT_START}${encoded}__`;
172
+ if (defaultValue !== void 0) return `${start}${defaultValue}${CONTENT_END}${encoded}__`;
173
+ return `${start}${CONTENT_END}${encoded}__`;
174
+ }
175
+ function makeAttrMarker(name) {
176
+ return `${ATTR_PREFIX}${encodeName(name)}__`;
177
+ }
178
+ function escapeRegex$1(str) {
179
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
180
+ }
181
+ function buildSlotLookup(html) {
182
+ const contentSlots = /* @__PURE__ */ new Map();
183
+ const attrSlots = /* @__PURE__ */ new Map();
184
+ const nameChars = "(?:[A-Za-z0-9.~-]|%[0-9A-Fa-f]{2})+";
185
+ const tokenRegex = new RegExp(`${escapeRegex$1(CONTENT_START)}(${nameChars})__|${escapeRegex$1(CONTENT_END)}(${nameChars})__`, "g");
186
+ const stack = [];
187
+ let match;
188
+ while (true) {
189
+ match = tokenRegex.exec(html);
190
+ if (match === null) break;
191
+ const startName = match[1];
192
+ if (startName !== void 0) {
193
+ stack.push({
194
+ encodedName: startName,
195
+ name: decodeName(startName),
196
+ startIndex: match.index,
197
+ contentStartIndex: tokenRegex.lastIndex
198
+ });
199
+ continue;
200
+ }
201
+ const endName = match[2];
202
+ if (endName === void 0) continue;
203
+ let openIndex = -1;
204
+ for (let index = stack.length - 1; index >= 0; index -= 1) if (stack[index]?.encodedName === endName) {
205
+ openIndex = index;
206
+ break;
207
+ }
208
+ if (openIndex === -1) continue;
209
+ const [open] = stack.splice(openIndex, 1);
210
+ if (open === void 0) continue;
211
+ const innerContent = html.slice(open.contentStartIndex, match.index);
212
+ const occurrence = {
213
+ full: html.slice(open.startIndex, tokenRegex.lastIndex),
214
+ hasDefault: innerContent.length > 0,
215
+ defaultValue: innerContent
216
+ };
217
+ const existing = contentSlots.get(open.name);
218
+ if (existing) existing.push(occurrence);
219
+ else contentSlots.set(open.name, [occurrence]);
220
+ }
221
+ const attrRegex = new RegExp(`${escapeRegex$1(ATTR_PREFIX)}(${nameChars})__`, "g");
222
+ while (true) {
223
+ match = attrRegex.exec(html);
224
+ if (match === null) break;
225
+ const name = decodeName(match[1] ?? "");
226
+ const existing = attrSlots.get(name);
227
+ if (existing) existing.push(match[0]);
228
+ else attrSlots.set(name, [match[0]]);
229
+ }
230
+ return {
231
+ content: contentSlots,
232
+ attr: attrSlots
233
+ };
234
+ }
235
+ function Slot(props) {
236
+ const encoded = encodeName(props.name);
237
+ const start = (0, solid_js_web.ssr)(`${CONTENT_START}${encoded}__`);
238
+ const end = (0, solid_js_web.ssr)(`${CONTENT_END}${encoded}__`);
239
+ if (props.children !== void 0 && props.children !== null) return [
240
+ start,
241
+ props.children,
242
+ end
243
+ ];
244
+ return [start, end];
245
+ }
246
+ function slot(name) {
247
+ return makeAttrMarker(name);
248
+ }
249
+ function defineSlots() {
250
+ return {
251
+ content: (name, defaultValue) => makeContentMarker(name, defaultValue),
252
+ attr: (name) => makeAttrMarker(name)
253
+ };
254
+ }
255
+ //#endregion
256
+ //#region src/shared/compile.ts
257
+ var CompiledTemplate = class {
258
+ html;
259
+ options;
260
+ contentSlots;
261
+ attrSlots;
262
+ markerRegex;
263
+ constructor(html, options) {
264
+ this.html = html;
265
+ this.options = options;
266
+ const lookup = buildSlotLookup(html);
267
+ this.contentSlots = lookup.content;
268
+ this.attrSlots = lookup.attr;
269
+ this.markerRegex = this.buildMarkerRegex();
270
+ }
271
+ async render(data, options) {
272
+ let result = this.html;
273
+ result = await this.replaceSlots(result, data);
274
+ return renderOutput(result, options ?? this.options);
275
+ }
276
+ renderSync(data, options) {
277
+ if ((options ?? this.options)?.pretty) throw new Error("renderSync does not support pretty output; use render.");
278
+ let result = this.html;
279
+ result = this.replaceSlotsSync(result, data);
280
+ return renderSyncOutput(result, options ?? this.options);
281
+ }
282
+ buildMarkerRegex() {
283
+ const markers = /* @__PURE__ */ new Set();
284
+ for (const occurrences of this.contentSlots.values()) for (const occ of occurrences) markers.add(occ.full);
285
+ for (const attrMarkers of this.attrSlots.values()) for (const marker of attrMarkers) markers.add(marker);
286
+ if (markers.size === 0) return /$^/g;
287
+ return new RegExp(Array.from(markers).sort((a, b) => b.length - a.length).map(escapeRegex).join("|"), "g");
288
+ }
289
+ validateSlots(data) {
290
+ const allSlotNames = /* @__PURE__ */ new Set([...this.contentSlots.keys(), ...this.attrSlots.keys()]);
291
+ for (const name of allSlotNames) if (data[name] === void 0) {
292
+ if (!(this.contentSlots.get(name)?.some((occ) => occ.hasDefault) ?? false)) console.warn(`[solid-email] Slot "${name}" has no default and was not provided in render data. It will render as empty.`);
293
+ }
294
+ }
295
+ async replaceSlots(result, data) {
296
+ return this.replaceSlotsInFragment(result, data, true);
297
+ }
298
+ async replaceSlotsInFragment(result, data, validate) {
299
+ if (validate) this.validateSlots(data);
300
+ const replacements = /* @__PURE__ */ new Map();
301
+ for (const [name, occurrences] of this.contentSlots) {
302
+ const value = data[name];
303
+ const rendered = value !== void 0 ? await renderSlotValueAsync(value) : void 0;
304
+ for (const occ of occurrences) {
305
+ if (!result.includes(occ.full)) continue;
306
+ const replacement = rendered ?? await this.replaceSlotsInFragment(occ.defaultValue, data, false);
307
+ replacements.set(occ.full, replacement);
308
+ }
309
+ }
310
+ for (const [name, markers] of this.attrSlots) {
311
+ const value = data[name];
312
+ const replacement = renderAttrValue(name, value);
313
+ for (const marker of markers) replacements.set(marker, replacement);
314
+ }
315
+ if (replacements.size === 0) return result;
316
+ return result.replace(this.markerRegex, (marker) => replacements.get(marker) ?? marker);
317
+ }
318
+ replaceSlotsSync(result, data) {
319
+ return this.replaceSlotsInFragmentSync(result, data, true);
320
+ }
321
+ replaceSlotsInFragmentSync(result, data, validate) {
322
+ if (validate) this.validateSlots(data);
323
+ const replacements = /* @__PURE__ */ new Map();
324
+ for (const [name, occurrences] of this.contentSlots) {
325
+ const value = data[name];
326
+ const rendered = value !== void 0 ? renderSlotValueSync(value) : void 0;
327
+ for (const occ of occurrences) {
328
+ if (!result.includes(occ.full)) continue;
329
+ const replacement = rendered ?? this.replaceSlotsInFragmentSync(occ.defaultValue, data, false);
330
+ replacements.set(occ.full, replacement);
331
+ }
332
+ }
333
+ for (const [name, markers] of this.attrSlots) {
334
+ const value = data[name];
335
+ const replacement = renderAttrValue(name, value);
336
+ for (const marker of markers) replacements.set(marker, replacement);
337
+ }
338
+ if (replacements.size === 0) return result;
339
+ return result.replace(this.markerRegex, (marker) => replacements.get(marker) ?? marker);
340
+ }
341
+ };
342
+ async function renderSlotValueAsync(value) {
343
+ if (value == null) return "";
344
+ if (Array.isArray(value)) return Promise.all(value.map(renderSlotValueAsync)).then((results) => results.join(""));
345
+ if (typeof value === "boolean") return value ? "true" : "";
346
+ if (typeof value === "string") return escapeHtml(value);
347
+ if (typeof value === "number") return String(value);
348
+ return removeSolidResourceScripts(await (0, solid_js_web_dist_server_js.renderToStringAsync)(() => value));
349
+ }
350
+ function renderSlotValueSync(value) {
351
+ if (value == null) return "";
352
+ if (Array.isArray(value)) return value.map(renderSlotValueSync).join("");
353
+ if (typeof value === "boolean") return value ? "true" : "";
354
+ if (typeof value === "string") return escapeHtml(value);
355
+ if (typeof value === "number") return String(value);
356
+ return removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(() => value));
357
+ }
358
+ function renderAttrValue(name, value) {
359
+ if (value == null) return "";
360
+ if (typeof value === "boolean") return value ? "true" : "";
361
+ if (typeof value === "string") return escapeAttr(value);
362
+ if (typeof value === "number") return String(value);
363
+ throw new TypeError(`Attribute slot "${name}" only accepts string, number, boolean, null, or undefined. Use <Slot name="${name}" /> for JSX/content values.`);
364
+ }
365
+ function escapeHtml(str) {
366
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
367
+ }
368
+ function escapeAttr(str) {
369
+ return str.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
370
+ }
371
+ function escapeRegex(str) {
372
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
373
+ }
374
+ async function compile(node, options) {
375
+ return new CompiledTemplate(removeSolidResourceScripts(await (0, solid_js_web_dist_server_js.renderToStringAsync)(normalizeRenderable(node))), options);
376
+ }
377
+ function compileSync(node, options) {
378
+ if (options?.pretty) throw new Error("compileSync does not support pretty output; use compile.");
379
+ return new CompiledTemplate(removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(normalizeRenderable(node))), options);
380
+ }
381
+ //#endregion
382
+ exports.CompiledTemplate = CompiledTemplate;
383
+ exports.Slot = Slot;
384
+ exports.buildSlotLookup = buildSlotLookup;
385
+ exports.compile = compile;
386
+ exports.compileSync = compileSync;
387
+ exports.decodeName = decodeName;
388
+ exports.defineSlots = defineSlots;
389
+ exports.makeAttrMarker = makeAttrMarker;
390
+ exports.makeContentMarker = makeContentMarker;
391
+ exports.normalizeRenderable = normalizeRenderable;
157
392
  exports.plainTextSelectors = plainTextSelectors;
158
393
  exports.pretty = pretty;
394
+ exports.removeSolidResourceScripts = removeSolidResourceScripts;
159
395
  exports.render = render;
396
+ exports.renderDocument = renderDocument;
397
+ exports.renderOutput = renderOutput;
160
398
  exports.renderSync = renderSync;
399
+ exports.renderSyncOutput = renderSyncOutput;
400
+ exports.slot = slot;
161
401
  exports.toPlainText = toPlainText;
@@ -20,9 +20,61 @@ type RenderSyncOptions = {
20
20
  //#endregion
21
21
  //#region src/shared/render.d.ts
22
22
  type Renderable = JSX.Element | (() => JSX.Element);
23
+ declare function normalizeRenderable(node: Renderable): () => JSX.Element;
24
+ declare function removeSolidResourceScripts(html: string): string;
25
+ declare function renderDocument(html: string): string;
26
+ declare function renderSyncOutput(html: string, options?: RenderSyncOptions): string;
27
+ declare function renderOutput(html: string, options?: Options): Promise<string>;
23
28
  declare function render(node: Renderable, options?: Options): Promise<string>;
24
29
  declare function renderSync(node: Renderable, options?: RenderSyncOptions): string;
25
30
  //#endregion
31
+ //#region src/shared/slots.d.ts
32
+ type SlotPrimitive = string | number | boolean | null | undefined;
33
+ type SlotValue = SlotPrimitive | JSX.Element | SlotValue[];
34
+ type SlotRecord = Record<string, SlotValue>;
35
+ declare function decodeName(encoded: string): string;
36
+ declare function makeContentMarker(name: string, defaultValue?: string): string;
37
+ declare function makeAttrMarker(name: string): string;
38
+ interface SlotOccurrence {
39
+ full: string;
40
+ hasDefault: boolean;
41
+ defaultValue: string;
42
+ }
43
+ declare function buildSlotLookup(html: string): {
44
+ content: Map<string, SlotOccurrence[]>;
45
+ attr: Map<string, string[]>;
46
+ };
47
+ declare function Slot(props: {
48
+ name: string;
49
+ children?: JSX.Element;
50
+ }): JSX.Element;
51
+ declare function slot(name: string): string;
52
+ interface SlotDefinition<T extends SlotRecord> {
53
+ content: <K extends keyof T & string>(name: K, defaultValue?: string) => string;
54
+ attr: <K extends keyof T & string>(name: K) => string;
55
+ }
56
+ declare function defineSlots<T extends SlotRecord>(): SlotDefinition<T>;
57
+ //#endregion
58
+ //#region src/shared/compile.d.ts
59
+ declare class CompiledTemplate<TSlots extends Record<string, SlotValue> = Record<string, SlotValue>> {
60
+ private readonly html;
61
+ private readonly options?;
62
+ private readonly contentSlots;
63
+ private readonly attrSlots;
64
+ private readonly markerRegex;
65
+ constructor(html: string, options?: Options);
66
+ render(data: TSlots, options?: Options): Promise<string>;
67
+ renderSync(data: TSlots, options?: RenderSyncOptions): string;
68
+ private buildMarkerRegex;
69
+ private validateSlots;
70
+ private replaceSlots;
71
+ private replaceSlotsInFragment;
72
+ private replaceSlotsSync;
73
+ private replaceSlotsInFragmentSync;
74
+ }
75
+ declare function compile<TSlots extends Record<string, SlotValue> = Record<string, SlotValue>>(node: Renderable, options?: Options): Promise<CompiledTemplate<TSlots>>;
76
+ declare function compileSync<TSlots extends Record<string, SlotValue> = Record<string, SlotValue>>(node: Renderable, options?: RenderSyncOptions): CompiledTemplate<TSlots>;
77
+ //#endregion
26
78
  //#region src/shared/utils/pretty.d.ts
27
79
  declare const pretty: (str: string, options?: Options$1) => Promise<string>;
28
80
  //#endregion
@@ -30,5 +82,5 @@ declare const pretty: (str: string, options?: Options$1) => Promise<string>;
30
82
  declare const plainTextSelectors: SelectorDefinition[];
31
83
  declare function toPlainText(html: string, options?: HtmlToTextOptions): string;
32
84
  //#endregion
33
- export { Options, RenderSyncOptions, Renderable, plainTextSelectors, pretty, render, renderSync, toPlainText };
85
+ export { CompiledTemplate, Options, RenderSyncOptions, Renderable, Slot, SlotDefinition, SlotOccurrence, SlotPrimitive, SlotRecord, SlotValue, buildSlotLookup, compile, compileSync, decodeName, defineSlots, makeAttrMarker, makeContentMarker, normalizeRenderable, plainTextSelectors, pretty, removeSolidResourceScripts, render, renderDocument, renderOutput, renderSync, renderSyncOutput, slot, toPlainText };
34
86
  //# sourceMappingURL=index.d.cts.map