@solid-email/render 0.1.1 → 0.1.3

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.
@@ -25,7 +25,8 @@ let solid_js_web_dist_server_js = require("solid-js/web/dist/server.js");
25
25
  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
- let html_to_text = require("html-to-text");
28
+ let _solid_email_html_to_text = require("@solid-email/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;
@@ -106,12 +107,74 @@ const plainTextSelectors = [
106
107
  }
107
108
  }
108
109
  ];
109
- function toPlainText(html, options) {
110
- return (0, html_to_text.convert)(html, {
110
+ let defaultConverter;
111
+ let defaultSelectorSnapshot;
112
+ const OPTION_SNAPSHOT_KEYS = [
113
+ "baseElements",
114
+ "decodeEntities",
115
+ "encodeCharacters",
116
+ "formatters",
117
+ "limits",
118
+ "longWordSplit",
119
+ "preserveNewlines",
120
+ "selectors",
121
+ "whitespaceCharacters",
122
+ "wordwrap"
123
+ ];
124
+ const customConverterCache = /* @__PURE__ */ new WeakMap();
125
+ function plainSelectorsChanged(snapshot) {
126
+ return snapshot.length !== plainTextSelectors.length || snapshot.some((selector, index) => selector !== plainTextSelectors[index]);
127
+ }
128
+ function getDefaultConverter() {
129
+ const changed = !defaultSelectorSnapshot || plainSelectorsChanged(defaultSelectorSnapshot);
130
+ if (!defaultConverter || changed) {
131
+ defaultSelectorSnapshot = [...plainTextSelectors];
132
+ defaultConverter = (0, _solid_email_html_to_text.compile)({
133
+ wordwrap: false,
134
+ selectors: defaultSelectorSnapshot
135
+ });
136
+ }
137
+ return defaultConverter;
138
+ }
139
+ function createOptionsSnapshot(options, plainSelectorSnapshot, customSelectorSnapshot) {
140
+ return {
141
+ baseElements: options.baseElements,
142
+ decodeEntities: options.decodeEntities,
143
+ encodeCharacters: options.encodeCharacters,
144
+ formatters: options.formatters,
145
+ limits: options.limits,
146
+ longWordSplit: options.longWordSplit,
147
+ preserveNewlines: options.preserveNewlines,
148
+ customSelectorSnapshot,
149
+ plainSelectorSnapshot,
150
+ selectors: options.selectors,
151
+ whitespaceCharacters: options.whitespaceCharacters,
152
+ wordwrap: options.wordwrap
153
+ };
154
+ }
155
+ function customOptionsChanged(options, snapshot) {
156
+ return plainSelectorsChanged(snapshot.plainSelectorSnapshot) || OPTION_SNAPSHOT_KEYS.some((key) => options[key] !== snapshot[key]) || options.selectors?.length !== snapshot.customSelectorSnapshot?.length || (options.selectors?.some((selector, index) => selector !== snapshot.customSelectorSnapshot?.[index]) ?? false);
157
+ }
158
+ function getCustomConverter(options) {
159
+ const cached = customConverterCache.get(options);
160
+ if (cached && !customOptionsChanged(options, cached.snapshot)) return cached.converter;
161
+ const plainSelectorSnapshot = [...plainTextSelectors];
162
+ const customSelectorSnapshot = options.selectors ? [...options.selectors] : void 0;
163
+ const selectors = [...plainSelectorSnapshot, ...customSelectorSnapshot ?? []];
164
+ const converter = (0, _solid_email_html_to_text.compile)({
111
165
  wordwrap: false,
112
166
  ...options,
113
- selectors: [...plainTextSelectors, ...options?.selectors ?? []]
167
+ selectors
114
168
  });
169
+ customConverterCache.set(options, {
170
+ converter,
171
+ snapshot: createOptionsSnapshot(options, plainSelectorSnapshot, customSelectorSnapshot)
172
+ });
173
+ return converter;
174
+ }
175
+ function toPlainText(html, options) {
176
+ if (!options) return getDefaultConverter()(html);
177
+ return getCustomConverter(options)(html);
115
178
  }
116
179
  //#endregion
117
180
  //#region src/shared/render.ts
@@ -154,8 +217,558 @@ function renderSync(node, options) {
154
217
  return renderSyncOutput(removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(normalizeRenderable(node))), options);
155
218
  }
156
219
  //#endregion
220
+ //#region src/shared/slot-values.ts
221
+ async function renderSlotValueAsync(value) {
222
+ if (value == null) return "";
223
+ if (Array.isArray(value)) return Promise.all(value.map(renderSlotValueAsync)).then((results) => results.join(""));
224
+ if (typeof value === "boolean") return value ? "true" : "";
225
+ if (typeof value === "string") return escapeHtml(value);
226
+ if (typeof value === "number") return String(value);
227
+ return removeSolidResourceScripts(await (0, solid_js_web_dist_server_js.renderToStringAsync)(() => value));
228
+ }
229
+ function renderSlotValueSync(value) {
230
+ if (value == null) return "";
231
+ if (Array.isArray(value)) return value.map(renderSlotValueSync).join("");
232
+ if (typeof value === "boolean") return value ? "true" : "";
233
+ if (typeof value === "string") return escapeHtml(value);
234
+ if (typeof value === "number") return String(value);
235
+ return removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(() => value));
236
+ }
237
+ async function renderSlotValueTextAsync(value, options) {
238
+ if (value == null) return "";
239
+ if (Array.isArray(value)) return (await Promise.all(value.map((item) => renderSlotValueTextAsync(item, options)))).join("");
240
+ if (typeof value === "boolean") return value ? "true" : "";
241
+ if (typeof value === "string") return value;
242
+ if (typeof value === "number") return String(value);
243
+ return toPlainText(removeSolidResourceScripts(await (0, solid_js_web_dist_server_js.renderToStringAsync)(() => value)), options);
244
+ }
245
+ function renderSlotValueTextSync(value, options) {
246
+ if (value == null) return "";
247
+ if (Array.isArray(value)) return value.map((item) => renderSlotValueTextSync(item, options)).join("");
248
+ if (typeof value === "boolean") return value ? "true" : "";
249
+ if (typeof value === "string") return value;
250
+ if (typeof value === "number") return String(value);
251
+ return toPlainText(removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(() => value)), options);
252
+ }
253
+ function renderAttrValue(name, value) {
254
+ if (value == null) return "";
255
+ if (typeof value === "boolean") return value ? "true" : "";
256
+ if (typeof value === "string") return escapeAttr(value);
257
+ if (typeof value === "number") return String(value);
258
+ throw new TypeError(`Attribute slot "${name}" only accepts string, number, boolean, null, or undefined. Use <Slot name="${name}" /> for JSX/content values.`);
259
+ }
260
+ function renderTextAttrValue(name, value) {
261
+ if (value == null) return "";
262
+ if (typeof value === "boolean") return value ? "true" : "";
263
+ if (typeof value === "string") return value;
264
+ if (typeof value === "number") return String(value);
265
+ throw new TypeError(`Attribute slot "${name}" only accepts string, number, boolean, null, or undefined. Use <Slot name="${name}" /> for JSX/content values.`);
266
+ }
267
+ function renderTextLinkHrefValue(name, value) {
268
+ const href = renderTextAttrValue(name, value).replace(/^mailto:/, "");
269
+ return href.startsWith("#") ? "" : href;
270
+ }
271
+ function escapeHtml(str) {
272
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
273
+ }
274
+ function escapeAttr(str) {
275
+ return str.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
276
+ }
277
+ //#endregion
278
+ //#region src/shared/slot-replacer.ts
279
+ function buildMarkerRegex(lookup) {
280
+ const markers = /* @__PURE__ */ new Set();
281
+ for (const occurrences of lookup.content.values()) for (const occ of occurrences) markers.add(occ.full);
282
+ for (const attrMarkers of lookup.attr.values()) for (const marker of attrMarkers) markers.add(marker);
283
+ if (markers.size === 0) return /$^/g;
284
+ return new RegExp(Array.from(markers).sort((a, b) => b.length - a.length).map(escapeRegex$2).join("|"), "g");
285
+ }
286
+ function validateSlots(data, lookup) {
287
+ const allSlotNames = /* @__PURE__ */ new Set([...lookup.content.keys(), ...lookup.attr.keys()]);
288
+ for (const name of allSlotNames) if (data[name] === void 0) {
289
+ if (!(lookup.content.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.`);
290
+ }
291
+ }
292
+ async function replaceSlots({ result, data, lookup, markerRegex, validate }) {
293
+ if (validate) validateSlots(data, lookup);
294
+ const replacements = /* @__PURE__ */ new Map();
295
+ for (const [name, occurrences] of lookup.content) {
296
+ const value = data[name];
297
+ const rendered = value !== void 0 ? await renderSlotValueAsync(value) : void 0;
298
+ for (const occ of occurrences) {
299
+ if (!result.includes(occ.full)) continue;
300
+ const replacement = rendered ?? await replaceSlots({
301
+ result: occ.defaultValue,
302
+ data,
303
+ lookup,
304
+ markerRegex,
305
+ validate: false
306
+ });
307
+ replacements.set(occ.full, replacement);
308
+ }
309
+ }
310
+ for (const [name, markers] of lookup.attr) {
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(markerRegex, (marker) => replacements.get(marker) ?? marker);
317
+ }
318
+ function replaceSlotsSync({ result, data, lookup, markerRegex, validate }) {
319
+ if (validate) validateSlots(data, lookup);
320
+ const replacements = /* @__PURE__ */ new Map();
321
+ for (const [name, occurrences] of lookup.content) {
322
+ const value = data[name];
323
+ const rendered = value !== void 0 ? renderSlotValueSync(value) : void 0;
324
+ for (const occ of occurrences) {
325
+ if (!result.includes(occ.full)) continue;
326
+ const replacement = rendered ?? replaceSlotsSync({
327
+ result: occ.defaultValue,
328
+ data,
329
+ lookup,
330
+ markerRegex,
331
+ validate: false
332
+ });
333
+ replacements.set(occ.full, replacement);
334
+ }
335
+ }
336
+ for (const [name, markers] of lookup.attr) {
337
+ const value = data[name];
338
+ const replacement = renderAttrValue(name, value);
339
+ for (const marker of markers) replacements.set(marker, replacement);
340
+ }
341
+ if (replacements.size === 0) return result;
342
+ return result.replace(markerRegex, (marker) => replacements.get(marker) ?? marker);
343
+ }
344
+ function escapeRegex$2(str) {
345
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
346
+ }
347
+ //#endregion
348
+ //#region src/shared/slots.ts
349
+ const MARKER_PREFIX = "__SM_";
350
+ const CONTENT_START$1 = `${MARKER_PREFIX}CNT_`;
351
+ const CONTENT_END$1 = `${MARKER_PREFIX}CNE_`;
352
+ const ATTR_PREFIX$1 = `${MARKER_PREFIX}ATR_`;
353
+ function encodeName(name) {
354
+ return encodeURIComponent(name).replace(/[!'()*_]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
355
+ }
356
+ function decodeName(encoded) {
357
+ return decodeURIComponent(encoded);
358
+ }
359
+ function makeContentMarker(name, defaultValue) {
360
+ const encoded = encodeName(name);
361
+ const start = `${CONTENT_START$1}${encoded}__`;
362
+ if (defaultValue !== void 0) return `${start}${defaultValue}${CONTENT_END$1}${encoded}__`;
363
+ return `${start}${CONTENT_END$1}${encoded}__`;
364
+ }
365
+ function makeAttrMarker(name) {
366
+ return `${ATTR_PREFIX$1}${encodeName(name)}__`;
367
+ }
368
+ function escapeRegex$1(str) {
369
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
370
+ }
371
+ function buildSlotLookup(html) {
372
+ const contentSlots = /* @__PURE__ */ new Map();
373
+ const attrSlots = /* @__PURE__ */ new Map();
374
+ const nameChars = "(?:[A-Za-z0-9.~-]|%[0-9A-Fa-f]{2})+";
375
+ const tokenRegex = new RegExp(`${escapeRegex$1(CONTENT_START$1)}(${nameChars})__|${escapeRegex$1(CONTENT_END$1)}(${nameChars})__`, "g");
376
+ const stack = [];
377
+ let match;
378
+ while (true) {
379
+ match = tokenRegex.exec(html);
380
+ if (match === null) break;
381
+ const startName = match[1];
382
+ if (startName !== void 0) {
383
+ stack.push({
384
+ encodedName: startName,
385
+ name: decodeName(startName),
386
+ startIndex: match.index,
387
+ contentStartIndex: tokenRegex.lastIndex
388
+ });
389
+ continue;
390
+ }
391
+ const endName = match[2];
392
+ if (endName === void 0) continue;
393
+ let openIndex = -1;
394
+ for (let index = stack.length - 1; index >= 0; index -= 1) if (stack[index]?.encodedName === endName) {
395
+ openIndex = index;
396
+ break;
397
+ }
398
+ if (openIndex === -1) continue;
399
+ const [open] = stack.splice(openIndex, 1);
400
+ if (open === void 0) continue;
401
+ const innerContent = html.slice(open.contentStartIndex, match.index);
402
+ const occurrence = {
403
+ full: html.slice(open.startIndex, tokenRegex.lastIndex),
404
+ hasDefault: innerContent.length > 0,
405
+ defaultValue: innerContent
406
+ };
407
+ const existing = contentSlots.get(open.name);
408
+ if (existing) existing.push(occurrence);
409
+ else contentSlots.set(open.name, [occurrence]);
410
+ }
411
+ const attrRegex = new RegExp(`${escapeRegex$1(ATTR_PREFIX$1)}(${nameChars})__`, "g");
412
+ while (true) {
413
+ match = attrRegex.exec(html);
414
+ if (match === null) break;
415
+ const name = decodeName(match[1] ?? "");
416
+ const existing = attrSlots.get(name);
417
+ if (existing) existing.push(match[0]);
418
+ else attrSlots.set(name, [match[0]]);
419
+ }
420
+ return {
421
+ content: contentSlots,
422
+ attr: attrSlots
423
+ };
424
+ }
425
+ function Slot(props) {
426
+ const encoded = encodeName(props.name);
427
+ const start = (0, solid_js_web.ssr)(`${CONTENT_START$1}${encoded}__`);
428
+ const end = (0, solid_js_web.ssr)(`${CONTENT_END$1}${encoded}__`);
429
+ if (props.children !== void 0 && props.children !== null) return [
430
+ start,
431
+ props.children,
432
+ end
433
+ ];
434
+ return [start, end];
435
+ }
436
+ function slot(name) {
437
+ return makeAttrMarker(name);
438
+ }
439
+ function defineSlots() {
440
+ return {
441
+ content: (name, defaultValue) => makeContentMarker(name, defaultValue),
442
+ attr: (name) => makeAttrMarker(name)
443
+ };
444
+ }
445
+ //#endregion
446
+ //#region src/shared/text-template.ts
447
+ const MARKER_NAME_CHARS = "(?:[A-Za-z0-9.~-]|%[0-9A-Fa-f]{2})+";
448
+ const CONTENT_START = "__SM_CNT_";
449
+ const CONTENT_END = "__SM_CNE_";
450
+ const ATTR_PREFIX = "__SM_ATR_";
451
+ const TEXT_SKIP_START = "__SM_TXS_";
452
+ const TEXT_SKIP_END = "__SM_TXE_";
453
+ const LINK_HREF_PREFIX = "__SM_LNK_";
454
+ function createPlainTextTemplate({ html, options, contentSlots, attrSlots }) {
455
+ const marked = markTextTemplateSlots(html, options);
456
+ const parsed = parseTextTemplate(toPlainText(marked.html, options));
457
+ return {
458
+ nodes: parsed.nodes,
459
+ usable: canUsePlainTextTemplate({
460
+ parsed,
461
+ supportedAttrMarkerCounts: marked.supportedAttrMarkerCounts,
462
+ expectedConditionalCount: marked.conditionalCount,
463
+ expectedLinkMarkerCounts: marked.expectedLinkMarkerCounts,
464
+ contentSlots,
465
+ attrSlots
466
+ })
467
+ };
468
+ }
469
+ async function renderTextTemplate(nodes, data, options) {
470
+ const chunks = [];
471
+ for (const node of nodes) switch (node.type) {
472
+ case "text":
473
+ chunks.push(node.value);
474
+ break;
475
+ case "contentSlot": {
476
+ const value = data[node.name];
477
+ chunks.push(value !== void 0 ? await renderSlotValueTextAsync(value, options) : await renderTextTemplate(node.fallback, data, options));
478
+ break;
479
+ }
480
+ case "attrSlot": {
481
+ const value = data[node.name];
482
+ chunks.push(renderTextAttrValue(node.name, value));
483
+ break;
484
+ }
485
+ case "linkHrefSlot": {
486
+ const value = data[node.name];
487
+ chunks.push(renderTextLinkHrefValue(node.name, value));
488
+ break;
489
+ }
490
+ case "conditionalSkip": {
491
+ const value = data[node.slotName];
492
+ if (renderTextAttrValue(node.slotName, value) !== "true") chunks.push(await renderTextTemplate(node.children, data, options));
493
+ break;
494
+ }
495
+ }
496
+ return chunks.join("");
497
+ }
498
+ function renderTextTemplateSync(nodes, data, options) {
499
+ const chunks = [];
500
+ for (const node of nodes) switch (node.type) {
501
+ case "text":
502
+ chunks.push(node.value);
503
+ break;
504
+ case "contentSlot": {
505
+ const value = data[node.name];
506
+ chunks.push(value !== void 0 ? renderSlotValueTextSync(value, options) : renderTextTemplateSync(node.fallback, data, options));
507
+ break;
508
+ }
509
+ case "attrSlot": {
510
+ const value = data[node.name];
511
+ chunks.push(renderTextAttrValue(node.name, value));
512
+ break;
513
+ }
514
+ case "linkHrefSlot": {
515
+ const value = data[node.name];
516
+ chunks.push(renderTextLinkHrefValue(node.name, value));
517
+ break;
518
+ }
519
+ case "conditionalSkip": {
520
+ const value = data[node.slotName];
521
+ if (renderTextAttrValue(node.slotName, value) !== "true") chunks.push(renderTextTemplateSync(node.children, data, options));
522
+ break;
523
+ }
524
+ }
525
+ return chunks.join("");
526
+ }
527
+ function canUsePlainTextTemplate({ parsed, supportedAttrMarkerCounts, expectedConditionalCount, expectedLinkMarkerCounts, contentSlots, attrSlots }) {
528
+ if (!parsed.valid) return false;
529
+ const remainingSupportedAttrMarkers = new Map(supportedAttrMarkerCounts);
530
+ for (const attrMarkers of attrSlots.values()) for (const marker of attrMarkers) {
531
+ const remaining = remainingSupportedAttrMarkers.get(marker) ?? 0;
532
+ if (remaining < 1) return false;
533
+ remainingSupportedAttrMarkers.set(marker, remaining - 1);
534
+ }
535
+ if (parsed.conditionalCount !== expectedConditionalCount) return false;
536
+ if (parsed.attrMarkerCounts.size > 0) return false;
537
+ for (const [marker, count] of expectedLinkMarkerCounts) if ((parsed.linkMarkerCounts.get(marker) ?? 0) < count) return false;
538
+ for (const [name, occurrences] of contentSlots) if ((parsed.contentSlotCounts.get(name) ?? 0) < occurrences.length) return false;
539
+ return true;
540
+ }
541
+ function markTextTemplateSlots(html, options) {
542
+ const supportedAttrMarkerCounts = /* @__PURE__ */ new Map();
543
+ const expectedLinkMarkerCounts = /* @__PURE__ */ new Map();
544
+ let conditionalCount = 0;
545
+ const incrementSupported = (marker) => {
546
+ incrementCount(supportedAttrMarkerCounts, marker);
547
+ };
548
+ const dataSkipRegex = new RegExp(`<([A-Za-z][\\w:-]*)([^>]*)\\sdata-skip-in-text=(["'])(__SM_ATR_(${MARKER_NAME_CHARS})__)\\3([^>]*)>([\\s\\S]*?)</\\1>`, "g");
549
+ let markedHtml = html.replace(dataSkipRegex, (_match, tagName, beforeAttrs, _quote, marker, encodedName, afterAttrs, children) => {
550
+ incrementSupported(marker);
551
+ conditionalCount += 1;
552
+ return `<${tagName}${beforeAttrs}${afterAttrs}>${TEXT_SKIP_START}${encodedName}__${children}${TEXT_SKIP_END}${encodedName}__</${tagName}>`;
553
+ });
554
+ if (!options) {
555
+ const linkHrefRegex = new RegExp(`<a([^>]*)\\shref=(["'])(__SM_ATR_(${MARKER_NAME_CHARS})__)\\2([^>]*)>([\\s\\S]*?)</a>`, "g");
556
+ markedHtml = markedHtml.replace(linkHrefRegex, (match, beforeAttrs, quote, marker, encodedName, afterAttrs, children) => {
557
+ const sameContentMarker = `${CONTENT_START}${encodedName}__`;
558
+ if (children.includes(sameContentMarker)) return match;
559
+ incrementSupported(marker);
560
+ incrementCount(expectedLinkMarkerCounts, marker);
561
+ return `<a${beforeAttrs} href=${quote}${LINK_HREF_PREFIX}${encodedName}__${quote}${afterAttrs}>${children}</a>`;
562
+ });
563
+ }
564
+ return {
565
+ html: markedHtml,
566
+ supportedAttrMarkerCounts,
567
+ expectedLinkMarkerCounts,
568
+ conditionalCount
569
+ };
570
+ }
571
+ function parseTextTemplate(text) {
572
+ const tokenRegex = new RegExp(`${escapeRegex(CONTENT_START)}(${MARKER_NAME_CHARS})__|${escapeRegex(CONTENT_END)}(${MARKER_NAME_CHARS})__|${escapeRegex(ATTR_PREFIX)}(${MARKER_NAME_CHARS})__|${escapeRegex(LINK_HREF_PREFIX)}(${MARKER_NAME_CHARS})__|${escapeRegex(TEXT_SKIP_START)}(${MARKER_NAME_CHARS})__|${escapeRegex(TEXT_SKIP_END)}(${MARKER_NAME_CHARS})__`, "g");
573
+ const root = {
574
+ kind: "root",
575
+ nodes: []
576
+ };
577
+ const stack = [root];
578
+ const contentSlotCounts = /* @__PURE__ */ new Map();
579
+ const attrMarkerCounts = /* @__PURE__ */ new Map();
580
+ const linkMarkerCounts = /* @__PURE__ */ new Map();
581
+ let conditionalCount = 0;
582
+ let valid = true;
583
+ let offset = 0;
584
+ let match;
585
+ const currentNodes = () => stack[stack.length - 1]?.nodes ?? root.nodes;
586
+ const addText = (value) => {
587
+ if (!value) return;
588
+ currentNodes().push({
589
+ type: "text",
590
+ value
591
+ });
592
+ };
593
+ while (true) {
594
+ match = tokenRegex.exec(text);
595
+ if (match === null) break;
596
+ addText(text.slice(offset, match.index));
597
+ offset = tokenRegex.lastIndex;
598
+ const [token, contentStart, contentEnd, attrName, linkName, conditionalStart, conditionalEnd] = match;
599
+ if (contentStart !== void 0) {
600
+ stack.push({
601
+ kind: "contentSlot",
602
+ encodedName: contentStart,
603
+ name: decodeName(contentStart),
604
+ nodes: []
605
+ });
606
+ continue;
607
+ }
608
+ if (contentEnd !== void 0) {
609
+ const frame = stack[stack.length - 1];
610
+ if (frame?.kind !== "contentSlot" || frame.encodedName !== contentEnd || !frame.name) {
611
+ valid = false;
612
+ addText(token);
613
+ continue;
614
+ }
615
+ stack.pop();
616
+ currentNodes().push({
617
+ type: "contentSlot",
618
+ name: frame.name,
619
+ fallback: frame.nodes
620
+ });
621
+ incrementCount(contentSlotCounts, frame.name);
622
+ continue;
623
+ }
624
+ if (attrName !== void 0) {
625
+ const marker = `${ATTR_PREFIX}${attrName}__`;
626
+ currentNodes().push({
627
+ type: "attrSlot",
628
+ name: decodeName(attrName),
629
+ marker
630
+ });
631
+ incrementCount(attrMarkerCounts, marker);
632
+ continue;
633
+ }
634
+ if (linkName !== void 0) {
635
+ const marker = `${ATTR_PREFIX}${linkName}__`;
636
+ currentNodes().push({
637
+ type: "linkHrefSlot",
638
+ name: decodeName(linkName),
639
+ marker
640
+ });
641
+ incrementCount(linkMarkerCounts, marker);
642
+ continue;
643
+ }
644
+ if (conditionalStart !== void 0) {
645
+ stack.push({
646
+ kind: "conditionalSkip",
647
+ encodedName: conditionalStart,
648
+ name: decodeName(conditionalStart),
649
+ nodes: []
650
+ });
651
+ continue;
652
+ }
653
+ if (conditionalEnd !== void 0) {
654
+ const frame = stack[stack.length - 1];
655
+ if (frame?.kind !== "conditionalSkip" || frame.encodedName !== conditionalEnd || !frame.name) {
656
+ valid = false;
657
+ addText(token);
658
+ continue;
659
+ }
660
+ stack.pop();
661
+ currentNodes().push({
662
+ type: "conditionalSkip",
663
+ slotName: frame.name,
664
+ children: frame.nodes
665
+ });
666
+ conditionalCount += 1;
667
+ }
668
+ }
669
+ addText(text.slice(offset));
670
+ return {
671
+ nodes: root.nodes,
672
+ contentSlotCounts,
673
+ attrMarkerCounts,
674
+ linkMarkerCounts,
675
+ conditionalCount,
676
+ valid: valid && stack.length === 1
677
+ };
678
+ }
679
+ function incrementCount(map, key) {
680
+ map.set(key, (map.get(key) ?? 0) + 1);
681
+ }
682
+ function escapeRegex(str) {
683
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
684
+ }
685
+ //#endregion
686
+ //#region src/shared/compile.ts
687
+ var CompiledTemplate = class {
688
+ html;
689
+ htmlToTextOptions;
690
+ slotLookup;
691
+ markerRegex;
692
+ plainTextTemplate;
693
+ constructor(html, options = {}) {
694
+ this.html = html;
695
+ this.htmlToTextOptions = options.htmlToTextOptions;
696
+ this.slotLookup = buildSlotLookup(html);
697
+ this.markerRegex = buildMarkerRegex(this.slotLookup);
698
+ if (options.withPlainText) this.plainTextTemplate = createPlainTextTemplate({
699
+ html,
700
+ options: options.htmlToTextOptions,
701
+ contentSlots: this.slotLookup.content,
702
+ attrSlots: this.slotLookup.attr
703
+ });
704
+ }
705
+ async render(data, options) {
706
+ if (options?.plainText) return this.renderPlainText(data);
707
+ return renderOutput(await this.replaceHtmlSlots(data), options?.pretty ? { pretty: true } : void 0);
708
+ }
709
+ renderSync(data, options) {
710
+ if (options?.pretty) throw new Error("renderSync does not support pretty output; use render.");
711
+ if (options?.plainText) return this.renderPlainTextSync(data);
712
+ return renderSyncOutput(this.replaceHtmlSlotsSync(data));
713
+ }
714
+ async replaceHtmlSlots(data) {
715
+ return replaceSlots({
716
+ result: this.html,
717
+ data,
718
+ lookup: this.slotLookup,
719
+ markerRegex: this.markerRegex,
720
+ validate: true
721
+ });
722
+ }
723
+ replaceHtmlSlotsSync(data) {
724
+ return replaceSlotsSync({
725
+ result: this.html,
726
+ data,
727
+ lookup: this.slotLookup,
728
+ markerRegex: this.markerRegex,
729
+ validate: true
730
+ });
731
+ }
732
+ async renderPlainText(data) {
733
+ if (this.plainTextTemplate?.usable) {
734
+ validateSlots(data, this.slotLookup);
735
+ return renderTextTemplate(this.plainTextTemplate.nodes, data, this.htmlToTextOptions);
736
+ }
737
+ return toPlainText(await this.replaceHtmlSlots(data), this.htmlToTextOptions);
738
+ }
739
+ renderPlainTextSync(data) {
740
+ if (this.plainTextTemplate?.usable) {
741
+ validateSlots(data, this.slotLookup);
742
+ return renderTextTemplateSync(this.plainTextTemplate.nodes, data, this.htmlToTextOptions);
743
+ }
744
+ return toPlainText(this.replaceHtmlSlotsSync(data), this.htmlToTextOptions);
745
+ }
746
+ };
747
+ async function compile(node, options) {
748
+ return new CompiledTemplate(removeSolidResourceScripts(await (0, solid_js_web_dist_server_js.renderToStringAsync)(normalizeRenderable(node))), options);
749
+ }
750
+ function compileSync(node, options) {
751
+ if (options?.pretty) throw new Error("compileSync does not support pretty output; use compile.");
752
+ return new CompiledTemplate(removeSolidResourceScripts((0, solid_js_web_dist_server_js.renderToString)(normalizeRenderable(node))), options);
753
+ }
754
+ //#endregion
755
+ exports.CompiledTemplate = CompiledTemplate;
756
+ exports.Slot = Slot;
757
+ exports.buildSlotLookup = buildSlotLookup;
758
+ exports.compile = compile;
759
+ exports.compileSync = compileSync;
760
+ exports.decodeName = decodeName;
761
+ exports.defineSlots = defineSlots;
762
+ exports.makeAttrMarker = makeAttrMarker;
763
+ exports.makeContentMarker = makeContentMarker;
764
+ exports.normalizeRenderable = normalizeRenderable;
157
765
  exports.plainTextSelectors = plainTextSelectors;
158
766
  exports.pretty = pretty;
767
+ exports.removeSolidResourceScripts = removeSolidResourceScripts;
159
768
  exports.render = render;
769
+ exports.renderDocument = renderDocument;
770
+ exports.renderOutput = renderOutput;
160
771
  exports.renderSync = renderSync;
772
+ exports.renderSyncOutput = renderSyncOutput;
773
+ exports.slot = slot;
161
774
  exports.toPlainText = toPlainText;