@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.
- package/README.md +82 -0
- package/dist/browser/index.cjs +617 -4
- package/dist/browser/index.d.cts +65 -2
- package/dist/browser/index.d.cts.map +1 -1
- package/dist/browser/index.d.mts +65 -2
- package/dist/browser/index.d.mts.map +1 -1
- package/dist/browser/index.mjs +603 -5
- package/dist/browser/index.mjs.map +1 -1
- package/dist/edge/index.cjs +617 -4
- package/dist/edge/index.d.cts +65 -2
- package/dist/edge/index.d.cts.map +1 -1
- package/dist/edge/index.d.mts +65 -2
- package/dist/edge/index.d.mts.map +1 -1
- package/dist/edge/index.mjs +603 -5
- package/dist/edge/index.mjs.map +1 -1
- package/dist/node/index.cjs +617 -4
- package/dist/node/index.d.cts +65 -2
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.mts +65 -2
- package/dist/node/index.d.mts.map +1 -1
- package/dist/node/index.mjs +603 -5
- package/dist/node/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/edge/index.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
110
|
-
|
|
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
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
273
|
+
}
|
|
274
|
+
function escapeAttr(str) {
|
|
275
|
+
return str.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
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;
|