@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
|
@@ -31,6 +31,7 @@ var LionRapid = (() => {
|
|
|
31
31
|
ExcludeFilter: () => ExcludeFilter,
|
|
32
32
|
HTMLSerializer: () => HTMLSerializer,
|
|
33
33
|
HandlerChain: () => HandlerChain,
|
|
34
|
+
LOCALE_TAG_REGEX: () => LOCALE_TAG_REGEX,
|
|
34
35
|
LionRapidBuilder: () => LionRapidBuilder,
|
|
35
36
|
LionRapidCore: () => LionRapidCore,
|
|
36
37
|
LocalStorageRepository: () => LocalStorageRepository,
|
|
@@ -41,6 +42,9 @@ var LionRapid = (() => {
|
|
|
41
42
|
MissingTranslationQueue: () => MissingTranslationQueue,
|
|
42
43
|
NetworkError: () => NetworkError,
|
|
43
44
|
NetworkRepository: () => NetworkRepository,
|
|
45
|
+
SAFE_ATTRS: () => SAFE_ATTRS,
|
|
46
|
+
SAFE_STYLE_PROPS: () => SAFE_STYLE_PROPS,
|
|
47
|
+
SAFE_TAGS: () => SAFE_TAGS,
|
|
44
48
|
ServerError: () => ServerError,
|
|
45
49
|
StaticRepository: () => StaticRepository,
|
|
46
50
|
TranslationError: () => TranslationError,
|
|
@@ -49,19 +53,33 @@ var LionRapid = (() => {
|
|
|
49
53
|
VERSION: () => VERSION,
|
|
50
54
|
VOID_TAGS: () => VOID_TAGS,
|
|
51
55
|
createPackageLogger: () => createPackageLogger,
|
|
56
|
+
escapeHtml: () => escapeHtml,
|
|
52
57
|
extractTagName: () => extractTagName,
|
|
53
58
|
generateAutoKey: () => generateAutoKey,
|
|
54
59
|
isClientError: () => isClientError,
|
|
55
60
|
isRetryableError: () => isRetryableError,
|
|
56
61
|
isTranslationError: () => isTranslationError,
|
|
62
|
+
localeDir: () => localeDir,
|
|
57
63
|
logger: () => logger,
|
|
64
|
+
matchLocale: () => matchLocale,
|
|
58
65
|
md5: () => md5,
|
|
59
66
|
measure: () => measure,
|
|
67
|
+
normalizeLocale: () => normalizeLocale,
|
|
68
|
+
parseAcceptLanguage: () => parseAcceptLanguage,
|
|
60
69
|
parseKeyPath: () => parseKeyPath,
|
|
61
70
|
prepareTranslationRequest: () => prepareTranslationRequest,
|
|
71
|
+
primarySubtag: () => primarySubtag,
|
|
62
72
|
readRawStyle: () => readRawStyle,
|
|
73
|
+
replaceBlockInners: () => replaceBlockInners,
|
|
63
74
|
resolveNestedKey: () => resolveNestedKey,
|
|
64
|
-
|
|
75
|
+
sanitizeContentUnit: () => sanitizeContentUnit,
|
|
76
|
+
sanitizeElement: () => sanitizeElement,
|
|
77
|
+
sanitizeOpeningTag: () => sanitizeOpeningTag,
|
|
78
|
+
sanitizeStyle: () => sanitizeStyle,
|
|
79
|
+
sanitizeStyleSheetText: () => sanitizeStyleSheetText,
|
|
80
|
+
sanitizeUrl: () => sanitizeUrl,
|
|
81
|
+
serializeOpeningTag: () => serializeOpeningTag,
|
|
82
|
+
splitBlocks: () => splitBlocks
|
|
65
83
|
});
|
|
66
84
|
|
|
67
85
|
// src/config/logger.config.ts
|
|
@@ -411,6 +429,57 @@ var LionRapid = (() => {
|
|
|
411
429
|
};
|
|
412
430
|
};
|
|
413
431
|
|
|
432
|
+
// src/utils/locale.ts
|
|
433
|
+
var LOCALE_TAG_REGEX = /^[a-z]{2,3}(?:-[a-z0-9]{2,4})*$/i;
|
|
434
|
+
var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur", "yi"]);
|
|
435
|
+
function primarySubtag(tag) {
|
|
436
|
+
return tag.split("-")[0].toLowerCase();
|
|
437
|
+
}
|
|
438
|
+
function normalizeLocale(tag) {
|
|
439
|
+
const trimmed = tag.trim();
|
|
440
|
+
if (!trimmed) return trimmed;
|
|
441
|
+
const [lang, ...rest] = trimmed.split("-");
|
|
442
|
+
const out = [lang.toLowerCase()];
|
|
443
|
+
for (const sub of rest) {
|
|
444
|
+
if (sub.length === 4) {
|
|
445
|
+
out.push(sub[0].toUpperCase() + sub.slice(1).toLowerCase());
|
|
446
|
+
} else if (sub.length === 2) {
|
|
447
|
+
out.push(sub.toUpperCase());
|
|
448
|
+
} else {
|
|
449
|
+
out.push(sub.toLowerCase());
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return out.join("-");
|
|
453
|
+
}
|
|
454
|
+
function matchLocale(requested, available, fallback) {
|
|
455
|
+
if (available.length === 0) return fallback;
|
|
456
|
+
const avail = available.map((raw) => ({
|
|
457
|
+
raw,
|
|
458
|
+
lower: normalizeLocale(raw).toLowerCase()
|
|
459
|
+
}));
|
|
460
|
+
for (const req of requested) {
|
|
461
|
+
if (!req) continue;
|
|
462
|
+
const reqLower = normalizeLocale(req).toLowerCase();
|
|
463
|
+
const exact = avail.find((a) => a.lower === reqLower);
|
|
464
|
+
if (exact) return exact.raw;
|
|
465
|
+
const primary = primarySubtag(req);
|
|
466
|
+
const byPrimary = avail.find((a) => a.lower === primary);
|
|
467
|
+
if (byPrimary) return byPrimary.raw;
|
|
468
|
+
}
|
|
469
|
+
return fallback;
|
|
470
|
+
}
|
|
471
|
+
function parseAcceptLanguage(header) {
|
|
472
|
+
return header.split(",").map((part) => {
|
|
473
|
+
const [tag, ...params] = part.trim().split(";");
|
|
474
|
+
const qParam = params.find((p) => p.trim().startsWith("q="));
|
|
475
|
+
const q = qParam ? parseFloat(qParam.trim().slice(2)) : 1;
|
|
476
|
+
return { tag: tag.trim(), q: Number.isNaN(q) ? 0 : q };
|
|
477
|
+
}).filter((x) => x.tag.length > 0 && x.tag !== "*").sort((a, b) => b.q - a.q).map((x) => x.tag);
|
|
478
|
+
}
|
|
479
|
+
function localeDir(tag) {
|
|
480
|
+
return RTL_LANGUAGES.has(primarySubtag(tag)) ? "rtl" : "ltr";
|
|
481
|
+
}
|
|
482
|
+
|
|
414
483
|
// src/base/event-emitter.ts
|
|
415
484
|
var EventEmitter = class {
|
|
416
485
|
constructor() {
|
|
@@ -699,7 +768,7 @@ var LionRapid = (() => {
|
|
|
699
768
|
complete: () => {
|
|
700
769
|
this.emit("language:change:success", {
|
|
701
770
|
context: this.createContext(requestId, [...baseTrace, "complete"]),
|
|
702
|
-
payload: { oldLocale, newLocale }
|
|
771
|
+
payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
|
|
703
772
|
});
|
|
704
773
|
},
|
|
705
774
|
error: (error) => {
|
|
@@ -1109,12 +1178,11 @@ var LionRapid = (() => {
|
|
|
1109
1178
|
}
|
|
1110
1179
|
|
|
1111
1180
|
// src/utils/context-utils.ts
|
|
1112
|
-
function createTranslationContext(parsedKey, currentLocale, defaultNamespace
|
|
1181
|
+
function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
|
|
1113
1182
|
const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
|
|
1114
1183
|
return {
|
|
1115
1184
|
locale: currentLocale,
|
|
1116
|
-
namespace: effectiveNamespace
|
|
1117
|
-
fallbackLocale: fallbackLocale || currentLocale
|
|
1185
|
+
namespace: effectiveNamespace
|
|
1118
1186
|
};
|
|
1119
1187
|
}
|
|
1120
1188
|
|
|
@@ -1231,7 +1299,7 @@ var LionRapid = (() => {
|
|
|
1231
1299
|
}
|
|
1232
1300
|
|
|
1233
1301
|
// src/utils/translation-flow.ts
|
|
1234
|
-
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace
|
|
1302
|
+
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
|
|
1235
1303
|
const startTime = performance.now();
|
|
1236
1304
|
const { defaultValue, params } = parseTranslationParams(
|
|
1237
1305
|
defaultValueOrParams,
|
|
@@ -1241,8 +1309,7 @@ var LionRapid = (() => {
|
|
|
1241
1309
|
const context = createTranslationContext(
|
|
1242
1310
|
parsedKey,
|
|
1243
1311
|
currentLocale,
|
|
1244
|
-
defaultNamespace
|
|
1245
|
-
fallbackLocale
|
|
1312
|
+
defaultNamespace
|
|
1246
1313
|
);
|
|
1247
1314
|
const individualCacheKey = createIndividualCacheKey(
|
|
1248
1315
|
context.locale,
|
|
@@ -1264,7 +1331,6 @@ var LionRapid = (() => {
|
|
|
1264
1331
|
locale: currentLocale,
|
|
1265
1332
|
namespace: context.namespace,
|
|
1266
1333
|
// ✅ Use effective namespace (explicit or default)
|
|
1267
|
-
fallbackLocale,
|
|
1268
1334
|
individualCacheKey,
|
|
1269
1335
|
// ✅ Ready-to-use cache key
|
|
1270
1336
|
namespaceCacheKey,
|
|
@@ -2380,6 +2446,183 @@ var LionRapid = (() => {
|
|
|
2380
2446
|
}
|
|
2381
2447
|
};
|
|
2382
2448
|
|
|
2449
|
+
// src/content/sanitizer/HTMLSanitizer.ts
|
|
2450
|
+
var SAFE_TAGS = /* @__PURE__ */ new Set([
|
|
2451
|
+
"a",
|
|
2452
|
+
"abbr",
|
|
2453
|
+
"b",
|
|
2454
|
+
"bdi",
|
|
2455
|
+
"bdo",
|
|
2456
|
+
"br",
|
|
2457
|
+
"cite",
|
|
2458
|
+
"code",
|
|
2459
|
+
"del",
|
|
2460
|
+
"dfn",
|
|
2461
|
+
"em",
|
|
2462
|
+
"i",
|
|
2463
|
+
"img",
|
|
2464
|
+
"ins",
|
|
2465
|
+
"kbd",
|
|
2466
|
+
"mark",
|
|
2467
|
+
"q",
|
|
2468
|
+
"s",
|
|
2469
|
+
"samp",
|
|
2470
|
+
"small",
|
|
2471
|
+
"span",
|
|
2472
|
+
"strong",
|
|
2473
|
+
"sub",
|
|
2474
|
+
"sup",
|
|
2475
|
+
"time",
|
|
2476
|
+
"u",
|
|
2477
|
+
"var",
|
|
2478
|
+
"wbr"
|
|
2479
|
+
]);
|
|
2480
|
+
var SAFE_ATTRS = /* @__PURE__ */ new Set([
|
|
2481
|
+
"alt",
|
|
2482
|
+
"cite",
|
|
2483
|
+
"class",
|
|
2484
|
+
"datetime",
|
|
2485
|
+
"dir",
|
|
2486
|
+
"height",
|
|
2487
|
+
"href",
|
|
2488
|
+
"id",
|
|
2489
|
+
"lang",
|
|
2490
|
+
"loading",
|
|
2491
|
+
"rel",
|
|
2492
|
+
"role",
|
|
2493
|
+
"src",
|
|
2494
|
+
"target",
|
|
2495
|
+
"title",
|
|
2496
|
+
"width"
|
|
2497
|
+
]);
|
|
2498
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "cite"]);
|
|
2499
|
+
var SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
2500
|
+
"color",
|
|
2501
|
+
"background-color",
|
|
2502
|
+
"font-family",
|
|
2503
|
+
"font-size",
|
|
2504
|
+
"font-style",
|
|
2505
|
+
"font-weight",
|
|
2506
|
+
"letter-spacing",
|
|
2507
|
+
"line-height",
|
|
2508
|
+
"text-decoration",
|
|
2509
|
+
"text-decoration-color",
|
|
2510
|
+
"text-decoration-line",
|
|
2511
|
+
"text-decoration-style",
|
|
2512
|
+
"text-transform",
|
|
2513
|
+
"vertical-align",
|
|
2514
|
+
"white-space",
|
|
2515
|
+
"word-spacing"
|
|
2516
|
+
]);
|
|
2517
|
+
var DANGEROUS_CSS_VALUE = /url\s*\(|expression\s*\(|image-set\s*\(|element\s*\(|paint\s*\(|javascript:|\\|[<>{}@]/i;
|
|
2518
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
2519
|
+
var NAMED_ENTITIES = {
|
|
2520
|
+
amp: "&",
|
|
2521
|
+
lt: "<",
|
|
2522
|
+
gt: ">",
|
|
2523
|
+
quot: '"',
|
|
2524
|
+
apos: "'",
|
|
2525
|
+
nbsp: "\xA0",
|
|
2526
|
+
colon: ":",
|
|
2527
|
+
semi: ";",
|
|
2528
|
+
sol: "/",
|
|
2529
|
+
bsol: "\\",
|
|
2530
|
+
Tab: " ",
|
|
2531
|
+
NewLine: "\n"
|
|
2532
|
+
};
|
|
2533
|
+
function decodeEntities2(value) {
|
|
2534
|
+
return value.replace(
|
|
2535
|
+
/&(?:#[xX]([0-9a-fA-F]+);?|#(\d+);?|([a-zA-Z]+);)/g,
|
|
2536
|
+
(full, hex, dec, name) => {
|
|
2537
|
+
if (hex) return String.fromCodePoint(parseInt(hex, 16));
|
|
2538
|
+
if (dec) return String.fromCodePoint(parseInt(dec, 10));
|
|
2539
|
+
return NAMED_ENTITIES[name] ?? full;
|
|
2540
|
+
}
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
function escapeHtml(text) {
|
|
2544
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2545
|
+
}
|
|
2546
|
+
function sanitizeUrl(value) {
|
|
2547
|
+
const decoded = decodeEntities2(value);
|
|
2548
|
+
const probe = decoded.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
|
|
2549
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);
|
|
2550
|
+
if (!scheme) return value;
|
|
2551
|
+
return SAFE_SCHEMES.has(scheme[1]) ? value : null;
|
|
2552
|
+
}
|
|
2553
|
+
function sanitizeStyle(value) {
|
|
2554
|
+
return filterCssDeclarations(value, true);
|
|
2555
|
+
}
|
|
2556
|
+
function sanitizeCssDeclarations(value) {
|
|
2557
|
+
return filterCssDeclarations(value, false);
|
|
2558
|
+
}
|
|
2559
|
+
function filterCssDeclarations(value, requirePropAllowlist) {
|
|
2560
|
+
const decoded = decodeEntities2(value);
|
|
2561
|
+
const kept = [];
|
|
2562
|
+
for (const declaration of decoded.split(";")) {
|
|
2563
|
+
const colon = declaration.indexOf(":");
|
|
2564
|
+
if (colon === -1) continue;
|
|
2565
|
+
const prop = declaration.slice(0, colon).trim().toLowerCase();
|
|
2566
|
+
const val = declaration.slice(colon + 1).trim();
|
|
2567
|
+
if (!/^[a-z-]+$/.test(prop)) continue;
|
|
2568
|
+
if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;
|
|
2569
|
+
if (val === "" || DANGEROUS_CSS_VALUE.test(val)) continue;
|
|
2570
|
+
kept.push(`${prop}: ${val}`);
|
|
2571
|
+
}
|
|
2572
|
+
return kept.length > 0 ? kept.join("; ") : null;
|
|
2573
|
+
}
|
|
2574
|
+
var ATTR_PATTERN = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
|
|
2575
|
+
function sanitizeOpeningTag(rawOpen) {
|
|
2576
|
+
if (!rawOpen.startsWith("<")) return null;
|
|
2577
|
+
const tagName = extractTagName(rawOpen);
|
|
2578
|
+
if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
const end = rawOpen.endsWith("/>") ? rawOpen.length - 2 : rawOpen.endsWith(">") ? rawOpen.length - 1 : rawOpen.length;
|
|
2582
|
+
const attrSource = rawOpen.slice(1 + tagName.length, end);
|
|
2583
|
+
const attrs = {};
|
|
2584
|
+
for (const match of attrSource.matchAll(ATTR_PATTERN)) {
|
|
2585
|
+
const name = match[1].toLowerCase();
|
|
2586
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
2587
|
+
if (name in attrs) continue;
|
|
2588
|
+
if (name === "style") {
|
|
2589
|
+
const cleanStyle = sanitizeStyle(value);
|
|
2590
|
+
if (cleanStyle !== null) attrs[name] = cleanStyle;
|
|
2591
|
+
continue;
|
|
2592
|
+
}
|
|
2593
|
+
const allowed = SAFE_ATTRS.has(name) || /^data-[a-z0-9-]*$/.test(name) || /^aria-[a-z0-9-]*$/.test(name);
|
|
2594
|
+
if (!allowed) continue;
|
|
2595
|
+
if (URL_ATTRS.has(name)) {
|
|
2596
|
+
if (sanitizeUrl(value) === null) continue;
|
|
2597
|
+
}
|
|
2598
|
+
attrs[name] = value;
|
|
2599
|
+
}
|
|
2600
|
+
return serializeOpeningTag(tagName, attrs);
|
|
2601
|
+
}
|
|
2602
|
+
function sanitizeContentUnit(unit) {
|
|
2603
|
+
let changed = false;
|
|
2604
|
+
const styles = {};
|
|
2605
|
+
for (const [index, styleList] of Object.entries(unit.styles)) {
|
|
2606
|
+
const raw = readRawStyle(styleList);
|
|
2607
|
+
if (raw === null) {
|
|
2608
|
+
styles[index] = styleList;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
const safe = sanitizeOpeningTag(raw);
|
|
2612
|
+
if (safe === null) {
|
|
2613
|
+
changed = true;
|
|
2614
|
+
const rest = styleList.filter((s) => !s.startsWith("raw:"));
|
|
2615
|
+
if (rest.length > 0) styles[index] = rest;
|
|
2616
|
+
} else if (safe === raw) {
|
|
2617
|
+
styles[index] = styleList;
|
|
2618
|
+
} else {
|
|
2619
|
+
changed = true;
|
|
2620
|
+
styles[index] = styleList.map((s) => s.startsWith("raw:") ? `raw:${safe}` : s);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return changed ? { text: unit.text, styles } : unit;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2383
2626
|
// src/content/serializer/HTMLSerializer.ts
|
|
2384
2627
|
var HTMLSerializer = class {
|
|
2385
2628
|
serialize(unit) {
|
|
@@ -2389,24 +2632,25 @@ var LionRapid = (() => {
|
|
|
2389
2632
|
let lastIndex = 0;
|
|
2390
2633
|
let match;
|
|
2391
2634
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
2392
|
-
result += text.slice(lastIndex, match.index);
|
|
2635
|
+
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
2393
2636
|
const [fullMatch, closingSlash, indexStr] = match;
|
|
2394
2637
|
const styleList = styles[indexStr] ?? [];
|
|
2395
2638
|
const rawOpen = readRawStyle(styleList);
|
|
2396
|
-
|
|
2639
|
+
const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
|
|
2640
|
+
if (rawOpen && safeOpen !== null) {
|
|
2397
2641
|
const tagName = extractTagName(rawOpen);
|
|
2398
2642
|
const isVoid = VOID_TAGS.has(tagName);
|
|
2399
2643
|
if (closingSlash) {
|
|
2400
2644
|
if (!isVoid) result += `</${tagName}>`;
|
|
2401
2645
|
} else {
|
|
2402
|
-
result +=
|
|
2646
|
+
result += safeOpen;
|
|
2403
2647
|
}
|
|
2404
2648
|
} else {
|
|
2405
2649
|
result += fullMatch;
|
|
2406
2650
|
}
|
|
2407
2651
|
lastIndex = match.index + fullMatch.length;
|
|
2408
2652
|
}
|
|
2409
|
-
result += text.slice(lastIndex);
|
|
2653
|
+
result += escapeHtml(text.slice(lastIndex));
|
|
2410
2654
|
return result;
|
|
2411
2655
|
}
|
|
2412
2656
|
};
|
|
@@ -2645,10 +2889,9 @@ var LionRapid = (() => {
|
|
|
2645
2889
|
var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
|
|
2646
2890
|
var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
|
|
2647
2891
|
var LionRapidCore = class {
|
|
2648
|
-
constructor(eventEmitter, handlerChain, defaultLocale = "en",
|
|
2892
|
+
constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
|
|
2649
2893
|
__publicField(this, "handlerChain");
|
|
2650
2894
|
__publicField(this, "currentLocale");
|
|
2651
|
-
__publicField(this, "fallbackLocale");
|
|
2652
2895
|
__publicField(this, "defaultNamespace");
|
|
2653
2896
|
__publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
|
|
2654
2897
|
__publicField(this, "bus");
|
|
@@ -2656,6 +2899,7 @@ var LionRapid = (() => {
|
|
|
2656
2899
|
// 🔥 EMBEDDED: Core integration maps
|
|
2657
2900
|
__publicField(this, "formatters", /* @__PURE__ */ new Map());
|
|
2658
2901
|
__publicField(this, "processors", /* @__PURE__ */ new Map());
|
|
2902
|
+
__publicField(this, "warnedNoInterpolate", false);
|
|
2659
2903
|
// 📡 EVENT-DRIVEN: Just track for cleanup
|
|
2660
2904
|
__publicField(this, "eventPlugins", /* @__PURE__ */ new Set());
|
|
2661
2905
|
// 📦 CONTENT: Parser and serializer for ContentUnit processing
|
|
@@ -2666,7 +2910,6 @@ var LionRapid = (() => {
|
|
|
2666
2910
|
this.bus = eventEmitter;
|
|
2667
2911
|
this.handlerChain = handlerChain;
|
|
2668
2912
|
this.currentLocale = defaultLocale;
|
|
2669
|
-
this.fallbackLocale = fallbackLocale;
|
|
2670
2913
|
this.defaultNamespace = defaultNamespace;
|
|
2671
2914
|
this.cache = new CacheManager(
|
|
2672
2915
|
this.handlerChain,
|
|
@@ -2731,8 +2974,7 @@ var LionRapid = (() => {
|
|
|
2731
2974
|
this.currentLocale,
|
|
2732
2975
|
firstArg,
|
|
2733
2976
|
secondArg,
|
|
2734
|
-
this.defaultNamespace
|
|
2735
|
-
this.fallbackLocale
|
|
2977
|
+
this.defaultNamespace
|
|
2736
2978
|
);
|
|
2737
2979
|
logger.debug(`Translation requested: ${request.cleanKey}`);
|
|
2738
2980
|
const translateBus = this.bus.translation(request);
|
|
@@ -2782,8 +3024,7 @@ var LionRapid = (() => {
|
|
|
2782
3024
|
this.currentLocale,
|
|
2783
3025
|
firstArg,
|
|
2784
3026
|
secondArg,
|
|
2785
|
-
this.defaultNamespace
|
|
2786
|
-
this.fallbackLocale
|
|
3027
|
+
this.defaultNamespace
|
|
2787
3028
|
);
|
|
2788
3029
|
logger.debug(`HTML translation requested: ${request.cleanKey}`);
|
|
2789
3030
|
const translateBus = this.bus.translation(request);
|
|
@@ -2951,6 +3192,11 @@ var LionRapid = (() => {
|
|
|
2951
3192
|
resolvedLocale,
|
|
2952
3193
|
resolvedKey
|
|
2953
3194
|
);
|
|
3195
|
+
} else if (!this.warnedNoInterpolate && params && Object.keys(params).length > 0 && processedValue.includes("{")) {
|
|
3196
|
+
this.warnedNoInterpolate = true;
|
|
3197
|
+
console.warn(
|
|
3198
|
+
"[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."
|
|
3199
|
+
);
|
|
2954
3200
|
}
|
|
2955
3201
|
if (this.processors.size > 0) {
|
|
2956
3202
|
for (const [name, processor] of this.processors.entries()) {
|
|
@@ -2991,8 +3237,7 @@ var LionRapid = (() => {
|
|
|
2991
3237
|
this.currentLocale,
|
|
2992
3238
|
params,
|
|
2993
3239
|
defaultValue,
|
|
2994
|
-
this.defaultNamespace
|
|
2995
|
-
this.fallbackLocale
|
|
3240
|
+
this.defaultNamespace
|
|
2996
3241
|
);
|
|
2997
3242
|
const syncResult = this.handlerChain.handleSync(request);
|
|
2998
3243
|
const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
|
|
@@ -3113,8 +3358,7 @@ var LionRapid = (() => {
|
|
|
3113
3358
|
this.currentLocale,
|
|
3114
3359
|
void 0,
|
|
3115
3360
|
void 0,
|
|
3116
|
-
this.defaultNamespace
|
|
3117
|
-
this.fallbackLocale
|
|
3361
|
+
this.defaultNamespace
|
|
3118
3362
|
);
|
|
3119
3363
|
return this.handlerChain.isCached(request);
|
|
3120
3364
|
}
|
|
@@ -3535,6 +3779,9 @@ var LionRapid = (() => {
|
|
|
3535
3779
|
};
|
|
3536
3780
|
|
|
3537
3781
|
// src/builder.ts
|
|
3782
|
+
function isLoudPreloadFailure(error) {
|
|
3783
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3784
|
+
}
|
|
3538
3785
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3539
3786
|
constructor(config) {
|
|
3540
3787
|
__publicField(this, "config");
|
|
@@ -3545,7 +3792,6 @@ var LionRapid = (() => {
|
|
|
3545
3792
|
__publicField(this, "syncQueue", null);
|
|
3546
3793
|
this.config = {
|
|
3547
3794
|
defaultLocale: config.defaultLocale || "en",
|
|
3548
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3549
3795
|
namespace: config.namespace || "app"
|
|
3550
3796
|
};
|
|
3551
3797
|
}
|
|
@@ -3646,7 +3892,6 @@ var LionRapid = (() => {
|
|
|
3646
3892
|
eventEmitter,
|
|
3647
3893
|
handlerChain,
|
|
3648
3894
|
this.config.defaultLocale,
|
|
3649
|
-
this.config.fallbackLocale,
|
|
3650
3895
|
this.config.namespace
|
|
3651
3896
|
);
|
|
3652
3897
|
for (const plugin of this.plugins) {
|
|
@@ -3677,7 +3922,17 @@ var LionRapid = (() => {
|
|
|
3677
3922
|
logger.debug("Preload targets", {
|
|
3678
3923
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3679
3924
|
});
|
|
3925
|
+
let warnedLoudly = false;
|
|
3926
|
+
const warnLoudlyOnce = (namespaceKey, error) => {
|
|
3927
|
+
if (!isLoudPreloadFailure(error) || warnedLoudly) return;
|
|
3928
|
+
warnedLoudly = true;
|
|
3929
|
+
console.warn(
|
|
3930
|
+
`[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
|
|
3931
|
+
error
|
|
3932
|
+
);
|
|
3933
|
+
};
|
|
3680
3934
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3935
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3681
3936
|
try {
|
|
3682
3937
|
const result = await networkHandler.loadNamespace(
|
|
3683
3938
|
preloadConfig.locale,
|
|
@@ -3686,19 +3941,19 @@ var LionRapid = (() => {
|
|
|
3686
3941
|
if (result && result.data && result.status === 200) {
|
|
3687
3942
|
const context = {
|
|
3688
3943
|
locale: preloadConfig.locale,
|
|
3689
|
-
namespace: preloadConfig.namespace
|
|
3690
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3944
|
+
namespace: preloadConfig.namespace
|
|
3691
3945
|
};
|
|
3692
3946
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3693
|
-
logger.debug(
|
|
3694
|
-
|
|
3695
|
-
|
|
3947
|
+
logger.debug(`Preloaded: ${namespaceKey}`);
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
if (result?.error) {
|
|
3951
|
+
warnLoudlyOnce(namespaceKey, result.error);
|
|
3952
|
+
logger.warn(`Preload failed: ${namespaceKey}`, result.error);
|
|
3696
3953
|
}
|
|
3697
3954
|
} catch (error) {
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
error
|
|
3701
|
-
);
|
|
3955
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3956
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3702
3957
|
}
|
|
3703
3958
|
});
|
|
3704
3959
|
await Promise.allSettled(promises);
|
|
@@ -3712,8 +3967,16 @@ var LionRapid = (() => {
|
|
|
3712
3967
|
__publicField(this, "name", "NetworkRepository");
|
|
3713
3968
|
__publicField(this, "type", "network");
|
|
3714
3969
|
__publicField(this, "config", {});
|
|
3715
|
-
/**
|
|
3716
|
-
|
|
3970
|
+
/**
|
|
3971
|
+
* Namespace snapshot per key "locale:namespace": the server version TOGETHER
|
|
3972
|
+
* with the payload it describes, persisted as ONE localStorage entry.
|
|
3973
|
+
*
|
|
3974
|
+
* Canonical 304 contract (same as edge KV bundle + WP transient cache):
|
|
3975
|
+
* never claim freshness you can't back. If-None-Match is only sent when the
|
|
3976
|
+
* snapshot is in hand; a 304 answers with that snapshot's payload; version
|
|
3977
|
+
* and payload can never desync because they live and die together.
|
|
3978
|
+
*/
|
|
3979
|
+
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3717
3980
|
/**
|
|
3718
3981
|
* Default endpoint templates with placeholder support
|
|
3719
3982
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3826,36 +4089,50 @@ var LionRapid = (() => {
|
|
|
3826
4089
|
logger.debug("API base URL is not set");
|
|
3827
4090
|
throw new NetworkError("API base URL is not set");
|
|
3828
4091
|
}
|
|
4092
|
+
try {
|
|
4093
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
4094
|
+
} catch (error) {
|
|
4095
|
+
this.handleCaughtError(error);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
/**
|
|
4099
|
+
* Shared namespace fetch with the canonical 304 contract:
|
|
4100
|
+
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
4101
|
+
* - 304 answers with that snapshot's payload — never with "nothing".
|
|
4102
|
+
* - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
|
|
4103
|
+
* - A 200 stores version + payload atomically before either is trusted.
|
|
4104
|
+
*/
|
|
4105
|
+
async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
|
|
3829
4106
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3830
4107
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3831
4108
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
if (
|
|
3840
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3841
|
-
return
|
|
3842
|
-
}
|
|
3843
|
-
if (!response.ok) {
|
|
3844
|
-
this.handleHttpError(response, namespaceKey);
|
|
3845
|
-
}
|
|
3846
|
-
const data = await response.json();
|
|
3847
|
-
logger.debug("API response data:", data);
|
|
3848
|
-
if (!data.translations) {
|
|
3849
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3850
|
-
}
|
|
3851
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3852
|
-
if (version) {
|
|
3853
|
-
this.setVersion(namespaceKey, version);
|
|
4109
|
+
const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
|
|
4110
|
+
const extraHeaders = {};
|
|
4111
|
+
if (snapshot) {
|
|
4112
|
+
extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
|
|
4113
|
+
}
|
|
4114
|
+
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
4115
|
+
if (response.status === 304) {
|
|
4116
|
+
if (snapshot) {
|
|
4117
|
+
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
|
|
4118
|
+
return snapshot.translations;
|
|
3854
4119
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
4120
|
+
logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
|
|
4121
|
+
return this.fetchNamespaceRaw(locale, namespace, false);
|
|
4122
|
+
}
|
|
4123
|
+
if (!response.ok) {
|
|
4124
|
+
this.handleHttpError(response, namespaceKey);
|
|
3858
4125
|
}
|
|
4126
|
+
const data = await response.json();
|
|
4127
|
+
logger.debug("API response data:", data);
|
|
4128
|
+
if (!data.translations) {
|
|
4129
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
4130
|
+
}
|
|
4131
|
+
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
4132
|
+
if (version) {
|
|
4133
|
+
this.setSnapshot(namespaceKey, { version, translations: data.translations });
|
|
4134
|
+
}
|
|
4135
|
+
return data.translations;
|
|
3859
4136
|
}
|
|
3860
4137
|
/**
|
|
3861
4138
|
* Save translation to API (if supported)
|
|
@@ -3940,33 +4217,8 @@ var LionRapid = (() => {
|
|
|
3940
4217
|
logger.debug("API base URL is not set");
|
|
3941
4218
|
throw new NetworkError("API base URL is not set");
|
|
3942
4219
|
}
|
|
3943
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3944
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3945
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3946
4220
|
try {
|
|
3947
|
-
|
|
3948
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3949
|
-
if (cachedVersion) {
|
|
3950
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3951
|
-
}
|
|
3952
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3953
|
-
if (response.status === 304) {
|
|
3954
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3955
|
-
return null;
|
|
3956
|
-
}
|
|
3957
|
-
if (!response.ok) {
|
|
3958
|
-
this.handleHttpError(response, namespaceKey);
|
|
3959
|
-
}
|
|
3960
|
-
const data = await response.json();
|
|
3961
|
-
logger.debug("API response data:", data);
|
|
3962
|
-
if (!data.translations) {
|
|
3963
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3964
|
-
}
|
|
3965
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3966
|
-
if (version) {
|
|
3967
|
-
this.setVersion(namespaceKey, version);
|
|
3968
|
-
}
|
|
3969
|
-
return data.translations;
|
|
4221
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3970
4222
|
} catch (error) {
|
|
3971
4223
|
this.handleCaughtError(error);
|
|
3972
4224
|
}
|
|
@@ -4076,25 +4328,37 @@ var LionRapid = (() => {
|
|
|
4076
4328
|
getConfig() {
|
|
4077
4329
|
return this.config;
|
|
4078
4330
|
}
|
|
4079
|
-
/** Read persisted version
|
|
4080
|
-
|
|
4081
|
-
|
|
4331
|
+
/** Read a persisted snapshot (version + payload), falling back to memory.
|
|
4332
|
+
* Legacy bare-version entries and corrupt snapshots are deleted on sight —
|
|
4333
|
+
* a version without its payload must never drive a conditional request. */
|
|
4334
|
+
getSnapshot(namespaceKey) {
|
|
4335
|
+
const mem = this.namespaceSnapshots.get(namespaceKey);
|
|
4082
4336
|
if (mem) return mem;
|
|
4083
4337
|
try {
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4338
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4339
|
+
const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4340
|
+
if (raw) {
|
|
4341
|
+
const parsed = JSON.parse(raw);
|
|
4342
|
+
if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
|
|
4343
|
+
this.namespaceSnapshots.set(namespaceKey, parsed);
|
|
4344
|
+
return parsed;
|
|
4345
|
+
}
|
|
4346
|
+
localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4088
4347
|
}
|
|
4089
4348
|
} catch {
|
|
4090
4349
|
}
|
|
4091
4350
|
return void 0;
|
|
4092
4351
|
}
|
|
4093
|
-
/** Persist version
|
|
4094
|
-
|
|
4095
|
-
|
|
4352
|
+
/** Persist version + payload as ONE entry (memory + localStorage). On quota
|
|
4353
|
+
* failure the previous stored snapshot stays — still a consistent pair. */
|
|
4354
|
+
setSnapshot(namespaceKey, snapshot) {
|
|
4355
|
+
this.namespaceSnapshots.set(namespaceKey, snapshot);
|
|
4096
4356
|
try {
|
|
4097
|
-
localStorage.setItem(
|
|
4357
|
+
localStorage.setItem(
|
|
4358
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4359
|
+
JSON.stringify(snapshot)
|
|
4360
|
+
);
|
|
4361
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4098
4362
|
} catch {
|
|
4099
4363
|
}
|
|
4100
4364
|
}
|
|
@@ -4183,8 +4447,11 @@ var LionRapid = (() => {
|
|
|
4183
4447
|
}
|
|
4184
4448
|
}
|
|
4185
4449
|
};
|
|
4186
|
-
/** localStorage key prefix for persisted
|
|
4187
|
-
__publicField(_NetworkRepository, "
|
|
4450
|
+
/** localStorage key prefix for persisted snapshots (version + payload) */
|
|
4451
|
+
__publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
4452
|
+
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4453
|
+
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4454
|
+
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4188
4455
|
var NetworkRepository = _NetworkRepository;
|
|
4189
4456
|
|
|
4190
4457
|
// src/content/validator/ContentValidator.ts
|
|
@@ -4340,6 +4607,159 @@ var LionRapid = (() => {
|
|
|
4340
4607
|
}
|
|
4341
4608
|
};
|
|
4342
4609
|
|
|
4610
|
+
// src/content/splitter/BlockSplitter.ts
|
|
4611
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
4612
|
+
"title",
|
|
4613
|
+
"p",
|
|
4614
|
+
"h1",
|
|
4615
|
+
"h2",
|
|
4616
|
+
"h3",
|
|
4617
|
+
"h4",
|
|
4618
|
+
"h5",
|
|
4619
|
+
"h6",
|
|
4620
|
+
"li",
|
|
4621
|
+
"td",
|
|
4622
|
+
"th",
|
|
4623
|
+
"label",
|
|
4624
|
+
"button",
|
|
4625
|
+
"figcaption",
|
|
4626
|
+
"dt",
|
|
4627
|
+
"dd",
|
|
4628
|
+
"summary",
|
|
4629
|
+
"caption"
|
|
4630
|
+
]);
|
|
4631
|
+
var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
|
|
4632
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
4633
|
+
"script",
|
|
4634
|
+
"style",
|
|
4635
|
+
"code",
|
|
4636
|
+
"pre",
|
|
4637
|
+
"textarea",
|
|
4638
|
+
"noscript",
|
|
4639
|
+
"svg"
|
|
4640
|
+
]);
|
|
4641
|
+
var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
|
|
4642
|
+
function hasNoTranslateAttr(attrsString) {
|
|
4643
|
+
if (attrsString.includes("data-no-translate")) return true;
|
|
4644
|
+
return /translate\s*=\s*["']no["']/.test(attrsString);
|
|
4645
|
+
}
|
|
4646
|
+
function containsBlockTag(innerHTML) {
|
|
4647
|
+
TAG_REGEX.lastIndex = 0;
|
|
4648
|
+
let match;
|
|
4649
|
+
while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
|
|
4650
|
+
const [, closingSlash, tagName, , selfClosingSlash] = match;
|
|
4651
|
+
if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
|
|
4652
|
+
return true;
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
return false;
|
|
4656
|
+
}
|
|
4657
|
+
function findCloseTag(html, pos, tagName) {
|
|
4658
|
+
const lowerTag = tagName.toLowerCase();
|
|
4659
|
+
let depth = 1;
|
|
4660
|
+
TAG_REGEX.lastIndex = pos;
|
|
4661
|
+
let match;
|
|
4662
|
+
while ((match = TAG_REGEX.exec(html)) !== null) {
|
|
4663
|
+
const [, closingSlash, matchedTag, , selfClosingSlash] = match;
|
|
4664
|
+
if (matchedTag.toLowerCase() !== lowerTag) continue;
|
|
4665
|
+
if (closingSlash) {
|
|
4666
|
+
depth--;
|
|
4667
|
+
if (depth === 0) return match.index;
|
|
4668
|
+
} else if (!selfClosingSlash) {
|
|
4669
|
+
depth++;
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
return -1;
|
|
4673
|
+
}
|
|
4674
|
+
function splitBlocks(html) {
|
|
4675
|
+
const blocks = [];
|
|
4676
|
+
let pos = 0;
|
|
4677
|
+
let skipDepth = 0;
|
|
4678
|
+
let iter = 0;
|
|
4679
|
+
const maxIter = 1e5;
|
|
4680
|
+
const noTranslateEnds = [];
|
|
4681
|
+
while (pos < html.length && iter < maxIter) {
|
|
4682
|
+
iter++;
|
|
4683
|
+
const ltPos = html.indexOf("<", pos);
|
|
4684
|
+
if (ltPos === -1) break;
|
|
4685
|
+
if (html.startsWith("<!--", ltPos)) {
|
|
4686
|
+
const commentEnd = html.indexOf("-->", ltPos + 4);
|
|
4687
|
+
if (commentEnd === -1) break;
|
|
4688
|
+
pos = commentEnd + 3;
|
|
4689
|
+
continue;
|
|
4690
|
+
}
|
|
4691
|
+
TAG_REGEX.lastIndex = ltPos;
|
|
4692
|
+
const match = TAG_REGEX.exec(html);
|
|
4693
|
+
if (!match || match.index !== ltPos) {
|
|
4694
|
+
pos = ltPos + 1;
|
|
4695
|
+
continue;
|
|
4696
|
+
}
|
|
4697
|
+
const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
|
|
4698
|
+
const tag = tagName.toLowerCase();
|
|
4699
|
+
const afterMatch = match.index + fullMatch.length;
|
|
4700
|
+
if (SKIP_TAGS.has(tag)) {
|
|
4701
|
+
if (closingSlash) {
|
|
4702
|
+
if (skipDepth > 0) skipDepth--;
|
|
4703
|
+
} else if (!selfClosingSlash) {
|
|
4704
|
+
skipDepth++;
|
|
4705
|
+
}
|
|
4706
|
+
pos = afterMatch;
|
|
4707
|
+
continue;
|
|
4708
|
+
}
|
|
4709
|
+
if (skipDepth > 0) {
|
|
4710
|
+
pos = afterMatch;
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4713
|
+
while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
|
|
4714
|
+
noTranslateEnds.pop();
|
|
4715
|
+
}
|
|
4716
|
+
if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
|
|
4717
|
+
const regionEnd = findCloseTag(html, afterMatch, tagName);
|
|
4718
|
+
if (regionEnd !== -1) {
|
|
4719
|
+
noTranslateEnds.push(regionEnd);
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
if (noTranslateEnds.length > 0) {
|
|
4723
|
+
pos = afterMatch;
|
|
4724
|
+
continue;
|
|
4725
|
+
}
|
|
4726
|
+
if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
|
|
4727
|
+
const closeTagStart = findCloseTag(html, afterMatch, tagName);
|
|
4728
|
+
if (closeTagStart === -1) {
|
|
4729
|
+
pos = afterMatch;
|
|
4730
|
+
continue;
|
|
4731
|
+
}
|
|
4732
|
+
const innerHTML = html.slice(afterMatch, closeTagStart);
|
|
4733
|
+
if (containsBlockTag(innerHTML)) {
|
|
4734
|
+
pos = afterMatch;
|
|
4735
|
+
continue;
|
|
4736
|
+
}
|
|
4737
|
+
blocks.push({
|
|
4738
|
+
tag,
|
|
4739
|
+
openTag: fullMatch,
|
|
4740
|
+
attrs: attrsString,
|
|
4741
|
+
inner: innerHTML,
|
|
4742
|
+
innerStart: afterMatch,
|
|
4743
|
+
innerEnd: closeTagStart
|
|
4744
|
+
});
|
|
4745
|
+
TAG_REGEX.lastIndex = closeTagStart;
|
|
4746
|
+
const closeMatch = TAG_REGEX.exec(html);
|
|
4747
|
+
pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
|
|
4748
|
+
continue;
|
|
4749
|
+
}
|
|
4750
|
+
pos = afterMatch;
|
|
4751
|
+
}
|
|
4752
|
+
return { blocks };
|
|
4753
|
+
}
|
|
4754
|
+
function replaceBlockInners(html, replacements) {
|
|
4755
|
+
const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
|
|
4756
|
+
let result = html;
|
|
4757
|
+
for (const { block, inner } of ordered) {
|
|
4758
|
+
result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
|
|
4759
|
+
}
|
|
4760
|
+
return result;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4343
4763
|
// src/browser/md5.ts
|
|
4344
4764
|
function safeAdd(x, y) {
|
|
4345
4765
|
const lsw = (x & 65535) + (y & 65535);
|
|
@@ -4545,6 +4965,75 @@ var LionRapid = (() => {
|
|
|
4545
4965
|
}
|
|
4546
4966
|
};
|
|
4547
4967
|
|
|
4968
|
+
// src/browser/sanitizer.ts
|
|
4969
|
+
var REMOVE_TAGS = /* @__PURE__ */ new Set([
|
|
4970
|
+
"script",
|
|
4971
|
+
"iframe",
|
|
4972
|
+
"frame",
|
|
4973
|
+
"frameset",
|
|
4974
|
+
"object",
|
|
4975
|
+
"embed",
|
|
4976
|
+
"applet",
|
|
4977
|
+
"base",
|
|
4978
|
+
"meta",
|
|
4979
|
+
"link",
|
|
4980
|
+
"template",
|
|
4981
|
+
"portal"
|
|
4982
|
+
]);
|
|
4983
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
4984
|
+
"href",
|
|
4985
|
+
"src",
|
|
4986
|
+
"action",
|
|
4987
|
+
"formaction",
|
|
4988
|
+
"poster",
|
|
4989
|
+
"data",
|
|
4990
|
+
"xlink:href"
|
|
4991
|
+
]);
|
|
4992
|
+
function sanitizeElement(root) {
|
|
4993
|
+
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4994
|
+
for (const el of all) {
|
|
4995
|
+
const tag = el.tagName.toLowerCase();
|
|
4996
|
+
if (REMOVE_TAGS.has(tag)) {
|
|
4997
|
+
el.remove();
|
|
4998
|
+
continue;
|
|
4999
|
+
}
|
|
5000
|
+
for (const name of el.getAttributeNames()) {
|
|
5001
|
+
const lower = name.toLowerCase();
|
|
5002
|
+
if (lower.startsWith("on") || lower === "srcdoc") {
|
|
5003
|
+
el.removeAttribute(name);
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
if (URL_ATTRS2.has(lower)) {
|
|
5007
|
+
if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
|
|
5008
|
+
el.removeAttribute(name);
|
|
5009
|
+
}
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
if (lower === "style") {
|
|
5013
|
+
const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
|
|
5014
|
+
if (clean === null) el.removeAttribute(name);
|
|
5015
|
+
else el.setAttribute(name, clean);
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
function sanitizeStyleSheetText(css, baseHref) {
|
|
5021
|
+
let out = css.replace(/@import[^;]*(;|$)/gi, "");
|
|
5022
|
+
out = out.replace(
|
|
5023
|
+
/url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
|
|
5024
|
+
(full, _quote, target) => {
|
|
5025
|
+
try {
|
|
5026
|
+
const resolved = new URL(target, baseHref);
|
|
5027
|
+
if (resolved.protocol === "data:") return full;
|
|
5028
|
+
if (resolved.origin === new URL(baseHref).origin) return full;
|
|
5029
|
+
} catch {
|
|
5030
|
+
}
|
|
5031
|
+
return 'url("")';
|
|
5032
|
+
}
|
|
5033
|
+
);
|
|
5034
|
+
return out.replace(/expression\s*\(/gi, "blocked(");
|
|
5035
|
+
}
|
|
5036
|
+
|
|
4548
5037
|
// src/browser/storage/localstorage-repository.ts
|
|
4549
5038
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4550
5039
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|