@lionrapid/core 0.5.0 → 0.6.1
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/dist/base/event-emitter.d.ts.map +1 -1
- package/dist/base/event-emitter.js +2 -1
- package/dist/base/event-emitter.js.map +1 -1
- package/dist/browser/attribute-extractor.d.ts.map +1 -1
- package/dist/browser/attribute-extractor.js +2 -1
- package/dist/browser/attribute-extractor.js.map +1 -1
- package/dist/browser/auto-init.d.ts +73 -0
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +193 -62
- package/dist/browser/auto-init.js.map +1 -1
- package/dist/browser/block-extractor.d.ts.map +1 -1
- package/dist/browser/block-extractor.js +3 -1
- package/dist/browser/block-extractor.js.map +1 -1
- package/dist/browser/editor/editor-api.d.ts +50 -6
- package/dist/browser/editor/editor-api.d.ts.map +1 -1
- package/dist/browser/editor/editor-api.js +121 -19
- package/dist/browser/editor/editor-api.js.map +1 -1
- package/dist/browser/editor/editor-init.js +4 -1
- package/dist/browser/editor/editor-init.js.map +1 -1
- package/dist/browser/editor/sidebar.d.ts +4 -3
- package/dist/browser/editor/sidebar.d.ts.map +1 -1
- package/dist/browser/editor/sidebar.js +93 -27
- package/dist/browser/editor/sidebar.js.map +1 -1
- package/dist/browser/editor/snapshot-viewer.d.ts.map +1 -1
- package/dist/browser/editor/snapshot-viewer.js +32 -3
- package/dist/browser/editor/snapshot-viewer.js.map +1 -1
- package/dist/browser/lionrapid.auto.dev.js +444 -149
- package/dist/browser/lionrapid.auto.dev.js.map +4 -4
- package/dist/browser/lionrapid.auto.js +444 -149
- package/dist/browser/lionrapid.auto.js.map +4 -4
- package/dist/browser/lionrapid.auto.min.js +4 -3
- package/dist/browser/lionrapid.auto.min.js.map +4 -4
- package/dist/browser/lionrapid.editor.js +1467 -1115
- package/dist/browser/lionrapid.editor.js.map +4 -4
- package/dist/browser/lionrapid.editor.min.js +4 -3
- package/dist/browser/lionrapid.editor.min.js.map +4 -4
- package/dist/browser/lionrapid.esm.js +590 -101
- package/dist/browser/lionrapid.esm.js.map +4 -4
- package/dist/browser/lionrapid.esm.min.js +3 -2
- package/dist/browser/lionrapid.esm.min.js.map +4 -4
- package/dist/browser/lionrapid.iife.dev.js +590 -101
- package/dist/browser/lionrapid.iife.dev.js.map +4 -4
- package/dist/browser/lionrapid.iife.js +590 -101
- package/dist/browser/lionrapid.iife.js.map +4 -4
- package/dist/browser/lionrapid.iife.min.js +3 -2
- package/dist/browser/lionrapid.iife.min.js.map +4 -4
- package/dist/browser/lionrapid.umd.js +590 -101
- package/dist/browser/lionrapid.umd.js.map +4 -4
- package/dist/browser/lionrapid.umd.min.js +3 -2
- package/dist/browser/lionrapid.umd.min.js.map +4 -4
- package/dist/browser/sanitizer.d.ts +37 -0
- package/dist/browser/sanitizer.d.ts.map +1 -0
- package/dist/browser/sanitizer.js +93 -0
- package/dist/browser/sanitizer.js.map +1 -0
- package/dist/builder.d.ts +8 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +33 -5
- package/dist/builder.js.map +1 -1
- package/dist/config/lionrapid.config.d.ts +0 -1
- package/dist/config/lionrapid.config.d.ts.map +1 -1
- package/dist/config/repository.config.d.ts +2 -2
- package/dist/config/repository.config.d.ts.map +1 -1
- package/dist/content/index.d.ts +3 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +5 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/sanitizer/HTMLSanitizer.d.ts +105 -0
- package/dist/content/sanitizer/HTMLSanitizer.d.ts.map +1 -0
- package/dist/content/sanitizer/HTMLSanitizer.js +257 -0
- package/dist/content/sanitizer/HTMLSanitizer.js.map +1 -0
- package/dist/content/serializer/HTMLSerializer.d.ts +6 -2
- package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -1
- package/dist/content/serializer/HTMLSerializer.js +16 -8
- package/dist/content/serializer/HTMLSerializer.js.map +1 -1
- package/dist/content/splitter/BlockSplitter.d.ts +66 -0
- package/dist/content/splitter/BlockSplitter.d.ts.map +1 -0
- package/dist/content/splitter/BlockSplitter.js +219 -0
- package/dist/content/splitter/BlockSplitter.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/lionRapid.d.ts +2 -2
- package/dist/lionRapid.d.ts.map +1 -1
- package/dist/lionRapid.js +19 -7
- package/dist/lionRapid.js.map +1 -1
- package/dist/repositories/network-repository.d.ts +30 -8
- package/dist/repositories/network-repository.d.ts.map +1 -1
- package/dist/repositories/network-repository.js +88 -74
- package/dist/repositories/network-repository.js.map +1 -1
- package/dist/types/lionRapid.types.d.ts +2 -0
- package/dist/types/lionRapid.types.d.ts.map +1 -1
- package/dist/types/translation.types.d.ts +0 -3
- package/dist/types/translation.types.d.ts.map +1 -1
- package/dist/utils/context-utils.d.ts +1 -2
- package/dist/utils/context-utils.d.ts.map +1 -1
- package/dist/utils/context-utils.js +3 -6
- package/dist/utils/context-utils.js.map +1 -1
- package/dist/utils/locale.d.ts +57 -0
- package/dist/utils/locale.d.ts.map +1 -0
- package/dist/utils/locale.js +116 -0
- package/dist/utils/locale.js.map +1 -0
- package/dist/utils/translation-flow.d.ts +1 -1
- package/dist/utils/translation-flow.d.ts.map +1 -1
- package/dist/utils/translation-flow.js +2 -3
- package/dist/utils/translation-flow.js.map +1 -1
- package/package.json +2 -1
|
@@ -349,6 +349,57 @@ var measure = (name) => {
|
|
|
349
349
|
};
|
|
350
350
|
};
|
|
351
351
|
|
|
352
|
+
// src/utils/locale.ts
|
|
353
|
+
var LOCALE_TAG_REGEX = /^[a-z]{2,3}(?:-[a-z0-9]{2,4})*$/i;
|
|
354
|
+
var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur", "yi"]);
|
|
355
|
+
function primarySubtag(tag) {
|
|
356
|
+
return tag.split("-")[0].toLowerCase();
|
|
357
|
+
}
|
|
358
|
+
function normalizeLocale(tag) {
|
|
359
|
+
const trimmed = tag.trim();
|
|
360
|
+
if (!trimmed) return trimmed;
|
|
361
|
+
const [lang, ...rest] = trimmed.split("-");
|
|
362
|
+
const out = [lang.toLowerCase()];
|
|
363
|
+
for (const sub of rest) {
|
|
364
|
+
if (sub.length === 4) {
|
|
365
|
+
out.push(sub[0].toUpperCase() + sub.slice(1).toLowerCase());
|
|
366
|
+
} else if (sub.length === 2) {
|
|
367
|
+
out.push(sub.toUpperCase());
|
|
368
|
+
} else {
|
|
369
|
+
out.push(sub.toLowerCase());
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return out.join("-");
|
|
373
|
+
}
|
|
374
|
+
function matchLocale(requested, available, fallback) {
|
|
375
|
+
if (available.length === 0) return fallback;
|
|
376
|
+
const avail = available.map((raw) => ({
|
|
377
|
+
raw,
|
|
378
|
+
lower: normalizeLocale(raw).toLowerCase()
|
|
379
|
+
}));
|
|
380
|
+
for (const req of requested) {
|
|
381
|
+
if (!req) continue;
|
|
382
|
+
const reqLower = normalizeLocale(req).toLowerCase();
|
|
383
|
+
const exact = avail.find((a) => a.lower === reqLower);
|
|
384
|
+
if (exact) return exact.raw;
|
|
385
|
+
const primary = primarySubtag(req);
|
|
386
|
+
const byPrimary = avail.find((a) => a.lower === primary);
|
|
387
|
+
if (byPrimary) return byPrimary.raw;
|
|
388
|
+
}
|
|
389
|
+
return fallback;
|
|
390
|
+
}
|
|
391
|
+
function parseAcceptLanguage(header) {
|
|
392
|
+
return header.split(",").map((part) => {
|
|
393
|
+
const [tag, ...params] = part.trim().split(";");
|
|
394
|
+
const qParam = params.find((p) => p.trim().startsWith("q="));
|
|
395
|
+
const q = qParam ? parseFloat(qParam.trim().slice(2)) : 1;
|
|
396
|
+
return { tag: tag.trim(), q: Number.isNaN(q) ? 0 : q };
|
|
397
|
+
}).filter((x) => x.tag.length > 0 && x.tag !== "*").sort((a, b) => b.q - a.q).map((x) => x.tag);
|
|
398
|
+
}
|
|
399
|
+
function localeDir(tag) {
|
|
400
|
+
return RTL_LANGUAGES.has(primarySubtag(tag)) ? "rtl" : "ltr";
|
|
401
|
+
}
|
|
402
|
+
|
|
352
403
|
// src/base/event-emitter.ts
|
|
353
404
|
var EventEmitter = class {
|
|
354
405
|
constructor() {
|
|
@@ -637,7 +688,7 @@ var EventEmitter = class {
|
|
|
637
688
|
complete: () => {
|
|
638
689
|
this.emit("language:change:success", {
|
|
639
690
|
context: this.createContext(requestId, [...baseTrace, "complete"]),
|
|
640
|
-
payload: { oldLocale, newLocale }
|
|
691
|
+
payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
|
|
641
692
|
});
|
|
642
693
|
},
|
|
643
694
|
error: (error) => {
|
|
@@ -1047,12 +1098,11 @@ function parseNamespaceKey(key) {
|
|
|
1047
1098
|
}
|
|
1048
1099
|
|
|
1049
1100
|
// src/utils/context-utils.ts
|
|
1050
|
-
function createTranslationContext(parsedKey, currentLocale, defaultNamespace
|
|
1101
|
+
function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
|
|
1051
1102
|
const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
|
|
1052
1103
|
return {
|
|
1053
1104
|
locale: currentLocale,
|
|
1054
|
-
namespace: effectiveNamespace
|
|
1055
|
-
fallbackLocale: fallbackLocale || currentLocale
|
|
1105
|
+
namespace: effectiveNamespace
|
|
1056
1106
|
};
|
|
1057
1107
|
}
|
|
1058
1108
|
|
|
@@ -1169,7 +1219,7 @@ function flattenTranslations(translations, prefix = "", locale, namespace) {
|
|
|
1169
1219
|
}
|
|
1170
1220
|
|
|
1171
1221
|
// src/utils/translation-flow.ts
|
|
1172
|
-
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace
|
|
1222
|
+
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
|
|
1173
1223
|
const startTime = performance.now();
|
|
1174
1224
|
const { defaultValue, params } = parseTranslationParams(
|
|
1175
1225
|
defaultValueOrParams,
|
|
@@ -1179,8 +1229,7 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1179
1229
|
const context = createTranslationContext(
|
|
1180
1230
|
parsedKey,
|
|
1181
1231
|
currentLocale,
|
|
1182
|
-
defaultNamespace
|
|
1183
|
-
fallbackLocale
|
|
1232
|
+
defaultNamespace
|
|
1184
1233
|
);
|
|
1185
1234
|
const individualCacheKey = createIndividualCacheKey(
|
|
1186
1235
|
context.locale,
|
|
@@ -1202,7 +1251,6 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1202
1251
|
locale: currentLocale,
|
|
1203
1252
|
namespace: context.namespace,
|
|
1204
1253
|
// ✅ Use effective namespace (explicit or default)
|
|
1205
|
-
fallbackLocale,
|
|
1206
1254
|
individualCacheKey,
|
|
1207
1255
|
// ✅ Ready-to-use cache key
|
|
1208
1256
|
namespaceCacheKey,
|
|
@@ -2318,6 +2366,183 @@ var MarkupParser = class {
|
|
|
2318
2366
|
}
|
|
2319
2367
|
};
|
|
2320
2368
|
|
|
2369
|
+
// src/content/sanitizer/HTMLSanitizer.ts
|
|
2370
|
+
var SAFE_TAGS = /* @__PURE__ */ new Set([
|
|
2371
|
+
"a",
|
|
2372
|
+
"abbr",
|
|
2373
|
+
"b",
|
|
2374
|
+
"bdi",
|
|
2375
|
+
"bdo",
|
|
2376
|
+
"br",
|
|
2377
|
+
"cite",
|
|
2378
|
+
"code",
|
|
2379
|
+
"del",
|
|
2380
|
+
"dfn",
|
|
2381
|
+
"em",
|
|
2382
|
+
"i",
|
|
2383
|
+
"img",
|
|
2384
|
+
"ins",
|
|
2385
|
+
"kbd",
|
|
2386
|
+
"mark",
|
|
2387
|
+
"q",
|
|
2388
|
+
"s",
|
|
2389
|
+
"samp",
|
|
2390
|
+
"small",
|
|
2391
|
+
"span",
|
|
2392
|
+
"strong",
|
|
2393
|
+
"sub",
|
|
2394
|
+
"sup",
|
|
2395
|
+
"time",
|
|
2396
|
+
"u",
|
|
2397
|
+
"var",
|
|
2398
|
+
"wbr"
|
|
2399
|
+
]);
|
|
2400
|
+
var SAFE_ATTRS = /* @__PURE__ */ new Set([
|
|
2401
|
+
"alt",
|
|
2402
|
+
"cite",
|
|
2403
|
+
"class",
|
|
2404
|
+
"datetime",
|
|
2405
|
+
"dir",
|
|
2406
|
+
"height",
|
|
2407
|
+
"href",
|
|
2408
|
+
"id",
|
|
2409
|
+
"lang",
|
|
2410
|
+
"loading",
|
|
2411
|
+
"rel",
|
|
2412
|
+
"role",
|
|
2413
|
+
"src",
|
|
2414
|
+
"target",
|
|
2415
|
+
"title",
|
|
2416
|
+
"width"
|
|
2417
|
+
]);
|
|
2418
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "cite"]);
|
|
2419
|
+
var SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
2420
|
+
"color",
|
|
2421
|
+
"background-color",
|
|
2422
|
+
"font-family",
|
|
2423
|
+
"font-size",
|
|
2424
|
+
"font-style",
|
|
2425
|
+
"font-weight",
|
|
2426
|
+
"letter-spacing",
|
|
2427
|
+
"line-height",
|
|
2428
|
+
"text-decoration",
|
|
2429
|
+
"text-decoration-color",
|
|
2430
|
+
"text-decoration-line",
|
|
2431
|
+
"text-decoration-style",
|
|
2432
|
+
"text-transform",
|
|
2433
|
+
"vertical-align",
|
|
2434
|
+
"white-space",
|
|
2435
|
+
"word-spacing"
|
|
2436
|
+
]);
|
|
2437
|
+
var DANGEROUS_CSS_VALUE = /url\s*\(|expression\s*\(|image-set\s*\(|element\s*\(|paint\s*\(|javascript:|\\|[<>{}@]/i;
|
|
2438
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
2439
|
+
var NAMED_ENTITIES = {
|
|
2440
|
+
amp: "&",
|
|
2441
|
+
lt: "<",
|
|
2442
|
+
gt: ">",
|
|
2443
|
+
quot: '"',
|
|
2444
|
+
apos: "'",
|
|
2445
|
+
nbsp: "\xA0",
|
|
2446
|
+
colon: ":",
|
|
2447
|
+
semi: ";",
|
|
2448
|
+
sol: "/",
|
|
2449
|
+
bsol: "\\",
|
|
2450
|
+
Tab: " ",
|
|
2451
|
+
NewLine: "\n"
|
|
2452
|
+
};
|
|
2453
|
+
function decodeEntities2(value) {
|
|
2454
|
+
return value.replace(
|
|
2455
|
+
/&(?:#[xX]([0-9a-fA-F]+);?|#(\d+);?|([a-zA-Z]+);)/g,
|
|
2456
|
+
(full, hex, dec, name) => {
|
|
2457
|
+
if (hex) return String.fromCodePoint(parseInt(hex, 16));
|
|
2458
|
+
if (dec) return String.fromCodePoint(parseInt(dec, 10));
|
|
2459
|
+
return NAMED_ENTITIES[name] ?? full;
|
|
2460
|
+
}
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
function escapeHtml(text) {
|
|
2464
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2465
|
+
}
|
|
2466
|
+
function sanitizeUrl(value) {
|
|
2467
|
+
const decoded = decodeEntities2(value);
|
|
2468
|
+
const probe = decoded.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
|
|
2469
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);
|
|
2470
|
+
if (!scheme) return value;
|
|
2471
|
+
return SAFE_SCHEMES.has(scheme[1]) ? value : null;
|
|
2472
|
+
}
|
|
2473
|
+
function sanitizeStyle(value) {
|
|
2474
|
+
return filterCssDeclarations(value, true);
|
|
2475
|
+
}
|
|
2476
|
+
function sanitizeCssDeclarations(value) {
|
|
2477
|
+
return filterCssDeclarations(value, false);
|
|
2478
|
+
}
|
|
2479
|
+
function filterCssDeclarations(value, requirePropAllowlist) {
|
|
2480
|
+
const decoded = decodeEntities2(value);
|
|
2481
|
+
const kept = [];
|
|
2482
|
+
for (const declaration of decoded.split(";")) {
|
|
2483
|
+
const colon = declaration.indexOf(":");
|
|
2484
|
+
if (colon === -1) continue;
|
|
2485
|
+
const prop = declaration.slice(0, colon).trim().toLowerCase();
|
|
2486
|
+
const val = declaration.slice(colon + 1).trim();
|
|
2487
|
+
if (!/^[a-z-]+$/.test(prop)) continue;
|
|
2488
|
+
if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;
|
|
2489
|
+
if (val === "" || DANGEROUS_CSS_VALUE.test(val)) continue;
|
|
2490
|
+
kept.push(`${prop}: ${val}`);
|
|
2491
|
+
}
|
|
2492
|
+
return kept.length > 0 ? kept.join("; ") : null;
|
|
2493
|
+
}
|
|
2494
|
+
var ATTR_PATTERN = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
|
|
2495
|
+
function sanitizeOpeningTag(rawOpen) {
|
|
2496
|
+
if (!rawOpen.startsWith("<")) return null;
|
|
2497
|
+
const tagName = extractTagName(rawOpen);
|
|
2498
|
+
if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
const end = rawOpen.endsWith("/>") ? rawOpen.length - 2 : rawOpen.endsWith(">") ? rawOpen.length - 1 : rawOpen.length;
|
|
2502
|
+
const attrSource = rawOpen.slice(1 + tagName.length, end);
|
|
2503
|
+
const attrs = {};
|
|
2504
|
+
for (const match of attrSource.matchAll(ATTR_PATTERN)) {
|
|
2505
|
+
const name = match[1].toLowerCase();
|
|
2506
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
2507
|
+
if (name in attrs) continue;
|
|
2508
|
+
if (name === "style") {
|
|
2509
|
+
const cleanStyle = sanitizeStyle(value);
|
|
2510
|
+
if (cleanStyle !== null) attrs[name] = cleanStyle;
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
const allowed = SAFE_ATTRS.has(name) || /^data-[a-z0-9-]*$/.test(name) || /^aria-[a-z0-9-]*$/.test(name);
|
|
2514
|
+
if (!allowed) continue;
|
|
2515
|
+
if (URL_ATTRS.has(name)) {
|
|
2516
|
+
if (sanitizeUrl(value) === null) continue;
|
|
2517
|
+
}
|
|
2518
|
+
attrs[name] = value;
|
|
2519
|
+
}
|
|
2520
|
+
return serializeOpeningTag(tagName, attrs);
|
|
2521
|
+
}
|
|
2522
|
+
function sanitizeContentUnit(unit) {
|
|
2523
|
+
let changed = false;
|
|
2524
|
+
const styles = {};
|
|
2525
|
+
for (const [index, styleList] of Object.entries(unit.styles)) {
|
|
2526
|
+
const raw = readRawStyle(styleList);
|
|
2527
|
+
if (raw === null) {
|
|
2528
|
+
styles[index] = styleList;
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
const safe = sanitizeOpeningTag(raw);
|
|
2532
|
+
if (safe === null) {
|
|
2533
|
+
changed = true;
|
|
2534
|
+
const rest = styleList.filter((s) => !s.startsWith("raw:"));
|
|
2535
|
+
if (rest.length > 0) styles[index] = rest;
|
|
2536
|
+
} else if (safe === raw) {
|
|
2537
|
+
styles[index] = styleList;
|
|
2538
|
+
} else {
|
|
2539
|
+
changed = true;
|
|
2540
|
+
styles[index] = styleList.map((s) => s.startsWith("raw:") ? `raw:${safe}` : s);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
return changed ? { text: unit.text, styles } : unit;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2321
2546
|
// src/content/serializer/HTMLSerializer.ts
|
|
2322
2547
|
var HTMLSerializer = class {
|
|
2323
2548
|
serialize(unit) {
|
|
@@ -2327,24 +2552,25 @@ var HTMLSerializer = class {
|
|
|
2327
2552
|
let lastIndex = 0;
|
|
2328
2553
|
let match;
|
|
2329
2554
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
2330
|
-
result += text.slice(lastIndex, match.index);
|
|
2555
|
+
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
2331
2556
|
const [fullMatch, closingSlash, indexStr] = match;
|
|
2332
2557
|
const styleList = styles[indexStr] ?? [];
|
|
2333
2558
|
const rawOpen = readRawStyle(styleList);
|
|
2334
|
-
|
|
2559
|
+
const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
|
|
2560
|
+
if (rawOpen && safeOpen !== null) {
|
|
2335
2561
|
const tagName = extractTagName(rawOpen);
|
|
2336
2562
|
const isVoid = VOID_TAGS.has(tagName);
|
|
2337
2563
|
if (closingSlash) {
|
|
2338
2564
|
if (!isVoid) result += `</${tagName}>`;
|
|
2339
2565
|
} else {
|
|
2340
|
-
result +=
|
|
2566
|
+
result += safeOpen;
|
|
2341
2567
|
}
|
|
2342
2568
|
} else {
|
|
2343
2569
|
result += fullMatch;
|
|
2344
2570
|
}
|
|
2345
2571
|
lastIndex = match.index + fullMatch.length;
|
|
2346
2572
|
}
|
|
2347
|
-
result += text.slice(lastIndex);
|
|
2573
|
+
result += escapeHtml(text.slice(lastIndex));
|
|
2348
2574
|
return result;
|
|
2349
2575
|
}
|
|
2350
2576
|
};
|
|
@@ -2583,10 +2809,9 @@ var CacheManager = class {
|
|
|
2583
2809
|
var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
|
|
2584
2810
|
var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
|
|
2585
2811
|
var LionRapidCore = class {
|
|
2586
|
-
constructor(eventEmitter, handlerChain, defaultLocale = "en",
|
|
2812
|
+
constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
|
|
2587
2813
|
__publicField(this, "handlerChain");
|
|
2588
2814
|
__publicField(this, "currentLocale");
|
|
2589
|
-
__publicField(this, "fallbackLocale");
|
|
2590
2815
|
__publicField(this, "defaultNamespace");
|
|
2591
2816
|
__publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
|
|
2592
2817
|
__publicField(this, "bus");
|
|
@@ -2594,6 +2819,7 @@ var LionRapidCore = class {
|
|
|
2594
2819
|
// 🔥 EMBEDDED: Core integration maps
|
|
2595
2820
|
__publicField(this, "formatters", /* @__PURE__ */ new Map());
|
|
2596
2821
|
__publicField(this, "processors", /* @__PURE__ */ new Map());
|
|
2822
|
+
__publicField(this, "warnedNoInterpolate", false);
|
|
2597
2823
|
// 📡 EVENT-DRIVEN: Just track for cleanup
|
|
2598
2824
|
__publicField(this, "eventPlugins", /* @__PURE__ */ new Set());
|
|
2599
2825
|
// 📦 CONTENT: Parser and serializer for ContentUnit processing
|
|
@@ -2604,7 +2830,6 @@ var LionRapidCore = class {
|
|
|
2604
2830
|
this.bus = eventEmitter;
|
|
2605
2831
|
this.handlerChain = handlerChain;
|
|
2606
2832
|
this.currentLocale = defaultLocale;
|
|
2607
|
-
this.fallbackLocale = fallbackLocale;
|
|
2608
2833
|
this.defaultNamespace = defaultNamespace;
|
|
2609
2834
|
this.cache = new CacheManager(
|
|
2610
2835
|
this.handlerChain,
|
|
@@ -2669,8 +2894,7 @@ var LionRapidCore = class {
|
|
|
2669
2894
|
this.currentLocale,
|
|
2670
2895
|
firstArg,
|
|
2671
2896
|
secondArg,
|
|
2672
|
-
this.defaultNamespace
|
|
2673
|
-
this.fallbackLocale
|
|
2897
|
+
this.defaultNamespace
|
|
2674
2898
|
);
|
|
2675
2899
|
logger.debug(`Translation requested: ${request.cleanKey}`);
|
|
2676
2900
|
const translateBus = this.bus.translation(request);
|
|
@@ -2720,8 +2944,7 @@ var LionRapidCore = class {
|
|
|
2720
2944
|
this.currentLocale,
|
|
2721
2945
|
firstArg,
|
|
2722
2946
|
secondArg,
|
|
2723
|
-
this.defaultNamespace
|
|
2724
|
-
this.fallbackLocale
|
|
2947
|
+
this.defaultNamespace
|
|
2725
2948
|
);
|
|
2726
2949
|
logger.debug(`HTML translation requested: ${request.cleanKey}`);
|
|
2727
2950
|
const translateBus = this.bus.translation(request);
|
|
@@ -2889,6 +3112,11 @@ var LionRapidCore = class {
|
|
|
2889
3112
|
resolvedLocale,
|
|
2890
3113
|
resolvedKey
|
|
2891
3114
|
);
|
|
3115
|
+
} else if (!this.warnedNoInterpolate && params && Object.keys(params).length > 0 && processedValue.includes("{")) {
|
|
3116
|
+
this.warnedNoInterpolate = true;
|
|
3117
|
+
console.warn(
|
|
3118
|
+
"[LionRapid] Translation params were passed but no interpolate formatter is registered \u2014 {placeholders} render literally. Install @lionrapid/formatters and add .use(coreFormattersPlugin) to the builder."
|
|
3119
|
+
);
|
|
2892
3120
|
}
|
|
2893
3121
|
if (this.processors.size > 0) {
|
|
2894
3122
|
for (const [name, processor] of this.processors.entries()) {
|
|
@@ -2929,8 +3157,7 @@ var LionRapidCore = class {
|
|
|
2929
3157
|
this.currentLocale,
|
|
2930
3158
|
params,
|
|
2931
3159
|
defaultValue,
|
|
2932
|
-
this.defaultNamespace
|
|
2933
|
-
this.fallbackLocale
|
|
3160
|
+
this.defaultNamespace
|
|
2934
3161
|
);
|
|
2935
3162
|
const syncResult = this.handlerChain.handleSync(request);
|
|
2936
3163
|
const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
|
|
@@ -3051,8 +3278,7 @@ var LionRapidCore = class {
|
|
|
3051
3278
|
this.currentLocale,
|
|
3052
3279
|
void 0,
|
|
3053
3280
|
void 0,
|
|
3054
|
-
this.defaultNamespace
|
|
3055
|
-
this.fallbackLocale
|
|
3281
|
+
this.defaultNamespace
|
|
3056
3282
|
);
|
|
3057
3283
|
return this.handlerChain.isCached(request);
|
|
3058
3284
|
}
|
|
@@ -3473,6 +3699,9 @@ var MissingTranslationQueue = class {
|
|
|
3473
3699
|
};
|
|
3474
3700
|
|
|
3475
3701
|
// src/builder.ts
|
|
3702
|
+
function isLoudPreloadFailure(error) {
|
|
3703
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3704
|
+
}
|
|
3476
3705
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3477
3706
|
constructor(config) {
|
|
3478
3707
|
__publicField(this, "config");
|
|
@@ -3483,7 +3712,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3483
3712
|
__publicField(this, "syncQueue", null);
|
|
3484
3713
|
this.config = {
|
|
3485
3714
|
defaultLocale: config.defaultLocale || "en",
|
|
3486
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3487
3715
|
namespace: config.namespace || "app"
|
|
3488
3716
|
};
|
|
3489
3717
|
}
|
|
@@ -3584,7 +3812,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3584
3812
|
eventEmitter,
|
|
3585
3813
|
handlerChain,
|
|
3586
3814
|
this.config.defaultLocale,
|
|
3587
|
-
this.config.fallbackLocale,
|
|
3588
3815
|
this.config.namespace
|
|
3589
3816
|
);
|
|
3590
3817
|
for (const plugin of this.plugins) {
|
|
@@ -3615,7 +3842,17 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3615
3842
|
logger.debug("Preload targets", {
|
|
3616
3843
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3617
3844
|
});
|
|
3845
|
+
let warnedLoudly = false;
|
|
3846
|
+
const warnLoudlyOnce = (namespaceKey, error) => {
|
|
3847
|
+
if (!isLoudPreloadFailure(error) || warnedLoudly) return;
|
|
3848
|
+
warnedLoudly = true;
|
|
3849
|
+
console.warn(
|
|
3850
|
+
`[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
|
|
3851
|
+
error
|
|
3852
|
+
);
|
|
3853
|
+
};
|
|
3618
3854
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3855
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3619
3856
|
try {
|
|
3620
3857
|
const result = await networkHandler.loadNamespace(
|
|
3621
3858
|
preloadConfig.locale,
|
|
@@ -3624,19 +3861,19 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3624
3861
|
if (result && result.data && result.status === 200) {
|
|
3625
3862
|
const context = {
|
|
3626
3863
|
locale: preloadConfig.locale,
|
|
3627
|
-
namespace: preloadConfig.namespace
|
|
3628
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3864
|
+
namespace: preloadConfig.namespace
|
|
3629
3865
|
};
|
|
3630
3866
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3631
|
-
logger.debug(
|
|
3632
|
-
|
|
3633
|
-
|
|
3867
|
+
logger.debug(`Preloaded: ${namespaceKey}`);
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
if (result?.error) {
|
|
3871
|
+
warnLoudlyOnce(namespaceKey, result.error);
|
|
3872
|
+
logger.warn(`Preload failed: ${namespaceKey}`, result.error);
|
|
3634
3873
|
}
|
|
3635
3874
|
} catch (error) {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
error
|
|
3639
|
-
);
|
|
3875
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3876
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3640
3877
|
}
|
|
3641
3878
|
});
|
|
3642
3879
|
await Promise.allSettled(promises);
|
|
@@ -3650,8 +3887,16 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3650
3887
|
__publicField(this, "name", "NetworkRepository");
|
|
3651
3888
|
__publicField(this, "type", "network");
|
|
3652
3889
|
__publicField(this, "config", {});
|
|
3653
|
-
/**
|
|
3654
|
-
|
|
3890
|
+
/**
|
|
3891
|
+
* Namespace snapshot per key "locale:namespace": the server version TOGETHER
|
|
3892
|
+
* with the payload it describes, persisted as ONE localStorage entry.
|
|
3893
|
+
*
|
|
3894
|
+
* Canonical 304 contract (same as edge KV bundle + WP transient cache):
|
|
3895
|
+
* never claim freshness you can't back. If-None-Match is only sent when the
|
|
3896
|
+
* snapshot is in hand; a 304 answers with that snapshot's payload; version
|
|
3897
|
+
* and payload can never desync because they live and die together.
|
|
3898
|
+
*/
|
|
3899
|
+
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3655
3900
|
/**
|
|
3656
3901
|
* Default endpoint templates with placeholder support
|
|
3657
3902
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3764,36 +4009,50 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3764
4009
|
logger.debug("API base URL is not set");
|
|
3765
4010
|
throw new NetworkError("API base URL is not set");
|
|
3766
4011
|
}
|
|
4012
|
+
try {
|
|
4013
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
4014
|
+
} catch (error) {
|
|
4015
|
+
this.handleCaughtError(error);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* Shared namespace fetch with the canonical 304 contract:
|
|
4020
|
+
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
4021
|
+
* - 304 answers with that snapshot's payload — never with "nothing".
|
|
4022
|
+
* - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
|
|
4023
|
+
* - A 200 stores version + payload atomically before either is trusted.
|
|
4024
|
+
*/
|
|
4025
|
+
async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
|
|
3767
4026
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3768
4027
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3769
4028
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
if (
|
|
3778
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3779
|
-
return
|
|
3780
|
-
}
|
|
3781
|
-
if (!response.ok) {
|
|
3782
|
-
this.handleHttpError(response, namespaceKey);
|
|
3783
|
-
}
|
|
3784
|
-
const data = await response.json();
|
|
3785
|
-
logger.debug("API response data:", data);
|
|
3786
|
-
if (!data.translations) {
|
|
3787
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3788
|
-
}
|
|
3789
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3790
|
-
if (version) {
|
|
3791
|
-
this.setVersion(namespaceKey, version);
|
|
4029
|
+
const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
|
|
4030
|
+
const extraHeaders = {};
|
|
4031
|
+
if (snapshot) {
|
|
4032
|
+
extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
|
|
4033
|
+
}
|
|
4034
|
+
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
4035
|
+
if (response.status === 304) {
|
|
4036
|
+
if (snapshot) {
|
|
4037
|
+
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
|
|
4038
|
+
return snapshot.translations;
|
|
3792
4039
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
4040
|
+
logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
|
|
4041
|
+
return this.fetchNamespaceRaw(locale, namespace, false);
|
|
4042
|
+
}
|
|
4043
|
+
if (!response.ok) {
|
|
4044
|
+
this.handleHttpError(response, namespaceKey);
|
|
3796
4045
|
}
|
|
4046
|
+
const data = await response.json();
|
|
4047
|
+
logger.debug("API response data:", data);
|
|
4048
|
+
if (!data.translations) {
|
|
4049
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
4050
|
+
}
|
|
4051
|
+
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
4052
|
+
if (version) {
|
|
4053
|
+
this.setSnapshot(namespaceKey, { version, translations: data.translations });
|
|
4054
|
+
}
|
|
4055
|
+
return data.translations;
|
|
3797
4056
|
}
|
|
3798
4057
|
/**
|
|
3799
4058
|
* Save translation to API (if supported)
|
|
@@ -3878,33 +4137,8 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3878
4137
|
logger.debug("API base URL is not set");
|
|
3879
4138
|
throw new NetworkError("API base URL is not set");
|
|
3880
4139
|
}
|
|
3881
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3882
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3883
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3884
4140
|
try {
|
|
3885
|
-
|
|
3886
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3887
|
-
if (cachedVersion) {
|
|
3888
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3889
|
-
}
|
|
3890
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3891
|
-
if (response.status === 304) {
|
|
3892
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3893
|
-
return null;
|
|
3894
|
-
}
|
|
3895
|
-
if (!response.ok) {
|
|
3896
|
-
this.handleHttpError(response, namespaceKey);
|
|
3897
|
-
}
|
|
3898
|
-
const data = await response.json();
|
|
3899
|
-
logger.debug("API response data:", data);
|
|
3900
|
-
if (!data.translations) {
|
|
3901
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3902
|
-
}
|
|
3903
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3904
|
-
if (version) {
|
|
3905
|
-
this.setVersion(namespaceKey, version);
|
|
3906
|
-
}
|
|
3907
|
-
return data.translations;
|
|
4141
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3908
4142
|
} catch (error) {
|
|
3909
4143
|
this.handleCaughtError(error);
|
|
3910
4144
|
}
|
|
@@ -4014,25 +4248,37 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4014
4248
|
getConfig() {
|
|
4015
4249
|
return this.config;
|
|
4016
4250
|
}
|
|
4017
|
-
/** Read persisted version
|
|
4018
|
-
|
|
4019
|
-
|
|
4251
|
+
/** Read a persisted snapshot (version + payload), falling back to memory.
|
|
4252
|
+
* Legacy bare-version entries and corrupt snapshots are deleted on sight —
|
|
4253
|
+
* a version without its payload must never drive a conditional request. */
|
|
4254
|
+
getSnapshot(namespaceKey) {
|
|
4255
|
+
const mem = this.namespaceSnapshots.get(namespaceKey);
|
|
4020
4256
|
if (mem) return mem;
|
|
4021
4257
|
try {
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4258
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4259
|
+
const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4260
|
+
if (raw) {
|
|
4261
|
+
const parsed = JSON.parse(raw);
|
|
4262
|
+
if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
|
|
4263
|
+
this.namespaceSnapshots.set(namespaceKey, parsed);
|
|
4264
|
+
return parsed;
|
|
4265
|
+
}
|
|
4266
|
+
localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4026
4267
|
}
|
|
4027
4268
|
} catch {
|
|
4028
4269
|
}
|
|
4029
4270
|
return void 0;
|
|
4030
4271
|
}
|
|
4031
|
-
/** Persist version
|
|
4032
|
-
|
|
4033
|
-
|
|
4272
|
+
/** Persist version + payload as ONE entry (memory + localStorage). On quota
|
|
4273
|
+
* failure the previous stored snapshot stays — still a consistent pair. */
|
|
4274
|
+
setSnapshot(namespaceKey, snapshot) {
|
|
4275
|
+
this.namespaceSnapshots.set(namespaceKey, snapshot);
|
|
4034
4276
|
try {
|
|
4035
|
-
localStorage.setItem(
|
|
4277
|
+
localStorage.setItem(
|
|
4278
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4279
|
+
JSON.stringify(snapshot)
|
|
4280
|
+
);
|
|
4281
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4036
4282
|
} catch {
|
|
4037
4283
|
}
|
|
4038
4284
|
}
|
|
@@ -4121,8 +4367,11 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4121
4367
|
}
|
|
4122
4368
|
}
|
|
4123
4369
|
};
|
|
4124
|
-
/** localStorage key prefix for persisted
|
|
4125
|
-
__publicField(_NetworkRepository, "
|
|
4370
|
+
/** localStorage key prefix for persisted snapshots (version + payload) */
|
|
4371
|
+
__publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
4372
|
+
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4373
|
+
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4374
|
+
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4126
4375
|
var NetworkRepository = _NetworkRepository;
|
|
4127
4376
|
|
|
4128
4377
|
// src/content/validator/ContentValidator.ts
|
|
@@ -4278,6 +4527,159 @@ var ContentValidator = class {
|
|
|
4278
4527
|
}
|
|
4279
4528
|
};
|
|
4280
4529
|
|
|
4530
|
+
// src/content/splitter/BlockSplitter.ts
|
|
4531
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
4532
|
+
"title",
|
|
4533
|
+
"p",
|
|
4534
|
+
"h1",
|
|
4535
|
+
"h2",
|
|
4536
|
+
"h3",
|
|
4537
|
+
"h4",
|
|
4538
|
+
"h5",
|
|
4539
|
+
"h6",
|
|
4540
|
+
"li",
|
|
4541
|
+
"td",
|
|
4542
|
+
"th",
|
|
4543
|
+
"label",
|
|
4544
|
+
"button",
|
|
4545
|
+
"figcaption",
|
|
4546
|
+
"dt",
|
|
4547
|
+
"dd",
|
|
4548
|
+
"summary",
|
|
4549
|
+
"caption"
|
|
4550
|
+
]);
|
|
4551
|
+
var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
|
|
4552
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
4553
|
+
"script",
|
|
4554
|
+
"style",
|
|
4555
|
+
"code",
|
|
4556
|
+
"pre",
|
|
4557
|
+
"textarea",
|
|
4558
|
+
"noscript",
|
|
4559
|
+
"svg"
|
|
4560
|
+
]);
|
|
4561
|
+
var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
|
|
4562
|
+
function hasNoTranslateAttr(attrsString) {
|
|
4563
|
+
if (attrsString.includes("data-no-translate")) return true;
|
|
4564
|
+
return /translate\s*=\s*["']no["']/.test(attrsString);
|
|
4565
|
+
}
|
|
4566
|
+
function containsBlockTag(innerHTML) {
|
|
4567
|
+
TAG_REGEX.lastIndex = 0;
|
|
4568
|
+
let match;
|
|
4569
|
+
while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
|
|
4570
|
+
const [, closingSlash, tagName, , selfClosingSlash] = match;
|
|
4571
|
+
if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
|
|
4572
|
+
return true;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return false;
|
|
4576
|
+
}
|
|
4577
|
+
function findCloseTag(html, pos, tagName) {
|
|
4578
|
+
const lowerTag = tagName.toLowerCase();
|
|
4579
|
+
let depth = 1;
|
|
4580
|
+
TAG_REGEX.lastIndex = pos;
|
|
4581
|
+
let match;
|
|
4582
|
+
while ((match = TAG_REGEX.exec(html)) !== null) {
|
|
4583
|
+
const [, closingSlash, matchedTag, , selfClosingSlash] = match;
|
|
4584
|
+
if (matchedTag.toLowerCase() !== lowerTag) continue;
|
|
4585
|
+
if (closingSlash) {
|
|
4586
|
+
depth--;
|
|
4587
|
+
if (depth === 0) return match.index;
|
|
4588
|
+
} else if (!selfClosingSlash) {
|
|
4589
|
+
depth++;
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
return -1;
|
|
4593
|
+
}
|
|
4594
|
+
function splitBlocks(html) {
|
|
4595
|
+
const blocks = [];
|
|
4596
|
+
let pos = 0;
|
|
4597
|
+
let skipDepth = 0;
|
|
4598
|
+
let iter = 0;
|
|
4599
|
+
const maxIter = 1e5;
|
|
4600
|
+
const noTranslateEnds = [];
|
|
4601
|
+
while (pos < html.length && iter < maxIter) {
|
|
4602
|
+
iter++;
|
|
4603
|
+
const ltPos = html.indexOf("<", pos);
|
|
4604
|
+
if (ltPos === -1) break;
|
|
4605
|
+
if (html.startsWith("<!--", ltPos)) {
|
|
4606
|
+
const commentEnd = html.indexOf("-->", ltPos + 4);
|
|
4607
|
+
if (commentEnd === -1) break;
|
|
4608
|
+
pos = commentEnd + 3;
|
|
4609
|
+
continue;
|
|
4610
|
+
}
|
|
4611
|
+
TAG_REGEX.lastIndex = ltPos;
|
|
4612
|
+
const match = TAG_REGEX.exec(html);
|
|
4613
|
+
if (!match || match.index !== ltPos) {
|
|
4614
|
+
pos = ltPos + 1;
|
|
4615
|
+
continue;
|
|
4616
|
+
}
|
|
4617
|
+
const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
|
|
4618
|
+
const tag = tagName.toLowerCase();
|
|
4619
|
+
const afterMatch = match.index + fullMatch.length;
|
|
4620
|
+
if (SKIP_TAGS.has(tag)) {
|
|
4621
|
+
if (closingSlash) {
|
|
4622
|
+
if (skipDepth > 0) skipDepth--;
|
|
4623
|
+
} else if (!selfClosingSlash) {
|
|
4624
|
+
skipDepth++;
|
|
4625
|
+
}
|
|
4626
|
+
pos = afterMatch;
|
|
4627
|
+
continue;
|
|
4628
|
+
}
|
|
4629
|
+
if (skipDepth > 0) {
|
|
4630
|
+
pos = afterMatch;
|
|
4631
|
+
continue;
|
|
4632
|
+
}
|
|
4633
|
+
while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
|
|
4634
|
+
noTranslateEnds.pop();
|
|
4635
|
+
}
|
|
4636
|
+
if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
|
|
4637
|
+
const regionEnd = findCloseTag(html, afterMatch, tagName);
|
|
4638
|
+
if (regionEnd !== -1) {
|
|
4639
|
+
noTranslateEnds.push(regionEnd);
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
if (noTranslateEnds.length > 0) {
|
|
4643
|
+
pos = afterMatch;
|
|
4644
|
+
continue;
|
|
4645
|
+
}
|
|
4646
|
+
if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
|
|
4647
|
+
const closeTagStart = findCloseTag(html, afterMatch, tagName);
|
|
4648
|
+
if (closeTagStart === -1) {
|
|
4649
|
+
pos = afterMatch;
|
|
4650
|
+
continue;
|
|
4651
|
+
}
|
|
4652
|
+
const innerHTML = html.slice(afterMatch, closeTagStart);
|
|
4653
|
+
if (containsBlockTag(innerHTML)) {
|
|
4654
|
+
pos = afterMatch;
|
|
4655
|
+
continue;
|
|
4656
|
+
}
|
|
4657
|
+
blocks.push({
|
|
4658
|
+
tag,
|
|
4659
|
+
openTag: fullMatch,
|
|
4660
|
+
attrs: attrsString,
|
|
4661
|
+
inner: innerHTML,
|
|
4662
|
+
innerStart: afterMatch,
|
|
4663
|
+
innerEnd: closeTagStart
|
|
4664
|
+
});
|
|
4665
|
+
TAG_REGEX.lastIndex = closeTagStart;
|
|
4666
|
+
const closeMatch = TAG_REGEX.exec(html);
|
|
4667
|
+
pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
|
|
4668
|
+
continue;
|
|
4669
|
+
}
|
|
4670
|
+
pos = afterMatch;
|
|
4671
|
+
}
|
|
4672
|
+
return { blocks };
|
|
4673
|
+
}
|
|
4674
|
+
function replaceBlockInners(html, replacements) {
|
|
4675
|
+
const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
|
|
4676
|
+
let result = html;
|
|
4677
|
+
for (const { block, inner } of ordered) {
|
|
4678
|
+
result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
|
|
4679
|
+
}
|
|
4680
|
+
return result;
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4281
4683
|
// src/browser/md5.ts
|
|
4282
4684
|
function safeAdd(x, y) {
|
|
4283
4685
|
const lsw = (x & 65535) + (y & 65535);
|
|
@@ -4483,6 +4885,75 @@ var ExcludeFilter = class {
|
|
|
4483
4885
|
}
|
|
4484
4886
|
};
|
|
4485
4887
|
|
|
4888
|
+
// src/browser/sanitizer.ts
|
|
4889
|
+
var REMOVE_TAGS = /* @__PURE__ */ new Set([
|
|
4890
|
+
"script",
|
|
4891
|
+
"iframe",
|
|
4892
|
+
"frame",
|
|
4893
|
+
"frameset",
|
|
4894
|
+
"object",
|
|
4895
|
+
"embed",
|
|
4896
|
+
"applet",
|
|
4897
|
+
"base",
|
|
4898
|
+
"meta",
|
|
4899
|
+
"link",
|
|
4900
|
+
"template",
|
|
4901
|
+
"portal"
|
|
4902
|
+
]);
|
|
4903
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
4904
|
+
"href",
|
|
4905
|
+
"src",
|
|
4906
|
+
"action",
|
|
4907
|
+
"formaction",
|
|
4908
|
+
"poster",
|
|
4909
|
+
"data",
|
|
4910
|
+
"xlink:href"
|
|
4911
|
+
]);
|
|
4912
|
+
function sanitizeElement(root) {
|
|
4913
|
+
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4914
|
+
for (const el of all) {
|
|
4915
|
+
const tag = el.tagName.toLowerCase();
|
|
4916
|
+
if (REMOVE_TAGS.has(tag)) {
|
|
4917
|
+
el.remove();
|
|
4918
|
+
continue;
|
|
4919
|
+
}
|
|
4920
|
+
for (const name of el.getAttributeNames()) {
|
|
4921
|
+
const lower = name.toLowerCase();
|
|
4922
|
+
if (lower.startsWith("on") || lower === "srcdoc") {
|
|
4923
|
+
el.removeAttribute(name);
|
|
4924
|
+
continue;
|
|
4925
|
+
}
|
|
4926
|
+
if (URL_ATTRS2.has(lower)) {
|
|
4927
|
+
if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
|
|
4928
|
+
el.removeAttribute(name);
|
|
4929
|
+
}
|
|
4930
|
+
continue;
|
|
4931
|
+
}
|
|
4932
|
+
if (lower === "style") {
|
|
4933
|
+
const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
|
|
4934
|
+
if (clean === null) el.removeAttribute(name);
|
|
4935
|
+
else el.setAttribute(name, clean);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
function sanitizeStyleSheetText(css, baseHref) {
|
|
4941
|
+
let out = css.replace(/@import[^;]*(;|$)/gi, "");
|
|
4942
|
+
out = out.replace(
|
|
4943
|
+
/url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
|
|
4944
|
+
(full, _quote, target) => {
|
|
4945
|
+
try {
|
|
4946
|
+
const resolved = new URL(target, baseHref);
|
|
4947
|
+
if (resolved.protocol === "data:") return full;
|
|
4948
|
+
if (resolved.origin === new URL(baseHref).origin) return full;
|
|
4949
|
+
} catch {
|
|
4950
|
+
}
|
|
4951
|
+
return 'url("")';
|
|
4952
|
+
}
|
|
4953
|
+
);
|
|
4954
|
+
return out.replace(/expression\s*\(/gi, "blocked(");
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4486
4957
|
// src/browser/storage/localstorage-repository.ts
|
|
4487
4958
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4488
4959
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|
|
@@ -4886,6 +5357,7 @@ export {
|
|
|
4886
5357
|
ExcludeFilter,
|
|
4887
5358
|
HTMLSerializer,
|
|
4888
5359
|
HandlerChain,
|
|
5360
|
+
LOCALE_TAG_REGEX,
|
|
4889
5361
|
LionRapidBuilder,
|
|
4890
5362
|
LionRapidCore,
|
|
4891
5363
|
LocalStorageRepository,
|
|
@@ -4896,6 +5368,9 @@ export {
|
|
|
4896
5368
|
MissingTranslationQueue,
|
|
4897
5369
|
NetworkError,
|
|
4898
5370
|
NetworkRepository,
|
|
5371
|
+
SAFE_ATTRS,
|
|
5372
|
+
SAFE_STYLE_PROPS,
|
|
5373
|
+
SAFE_TAGS,
|
|
4899
5374
|
ServerError,
|
|
4900
5375
|
StaticRepository,
|
|
4901
5376
|
TranslationError,
|
|
@@ -4904,18 +5379,32 @@ export {
|
|
|
4904
5379
|
VERSION,
|
|
4905
5380
|
VOID_TAGS,
|
|
4906
5381
|
createPackageLogger,
|
|
5382
|
+
escapeHtml,
|
|
4907
5383
|
extractTagName,
|
|
4908
5384
|
generateAutoKey,
|
|
4909
5385
|
isClientError,
|
|
4910
5386
|
isRetryableError,
|
|
4911
5387
|
isTranslationError,
|
|
5388
|
+
localeDir,
|
|
4912
5389
|
logger,
|
|
5390
|
+
matchLocale,
|
|
4913
5391
|
md5,
|
|
4914
5392
|
measure,
|
|
5393
|
+
normalizeLocale,
|
|
5394
|
+
parseAcceptLanguage,
|
|
4915
5395
|
parseKeyPath,
|
|
4916
5396
|
prepareTranslationRequest,
|
|
5397
|
+
primarySubtag,
|
|
4917
5398
|
readRawStyle,
|
|
5399
|
+
replaceBlockInners,
|
|
4918
5400
|
resolveNestedKey,
|
|
4919
|
-
|
|
5401
|
+
sanitizeContentUnit,
|
|
5402
|
+
sanitizeElement,
|
|
5403
|
+
sanitizeOpeningTag,
|
|
5404
|
+
sanitizeStyle,
|
|
5405
|
+
sanitizeStyleSheetText,
|
|
5406
|
+
sanitizeUrl,
|
|
5407
|
+
serializeOpeningTag,
|
|
5408
|
+
splitBlocks
|
|
4920
5409
|
};
|
|
4921
5410
|
//# sourceMappingURL=lionrapid.esm.js.map
|