@lionrapid/core 0.4.0 → 0.6.0
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 +69 -0
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +193 -61
- 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 +599 -245
- package/dist/browser/lionrapid.auto.dev.js.map +4 -4
- package/dist/browser/lionrapid.auto.js +599 -245
- 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 +951 -196
- 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 +951 -196
- package/dist/browser/lionrapid.iife.dev.js.map +4 -4
- package/dist/browser/lionrapid.iife.js +951 -196
- 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 +951 -196
- 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 +16 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +43 -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 +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/lionRapid.d.ts +1 -2
- package/dist/lionRapid.d.ts.map +1 -1
- package/dist/lionRapid.js +5 -7
- package/dist/lionRapid.js.map +1 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.d.ts.map +1 -1
- package/dist/repositories/index.js +1 -0
- package/dist/repositories/index.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/repositories/static-repository.d.ts +40 -0
- package/dist/repositories/static-repository.d.ts.map +1 -0
- package/dist/repositories/static-repository.js +60 -0
- package/dist/repositories/static-repository.js.map +1 -0
- 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
|
@@ -28,8 +28,10 @@ var LionRapid = (() => {
|
|
|
28
28
|
Colors: () => Colors,
|
|
29
29
|
ContentValidator: () => ContentValidator,
|
|
30
30
|
EventEmitter: () => EventEmitter,
|
|
31
|
+
ExcludeFilter: () => ExcludeFilter,
|
|
31
32
|
HTMLSerializer: () => HTMLSerializer,
|
|
32
33
|
HandlerChain: () => HandlerChain,
|
|
34
|
+
LOCALE_TAG_REGEX: () => LOCALE_TAG_REGEX,
|
|
33
35
|
LionRapidBuilder: () => LionRapidBuilder,
|
|
34
36
|
LionRapidCore: () => LionRapidCore,
|
|
35
37
|
LocalStorageRepository: () => LocalStorageRepository,
|
|
@@ -40,24 +42,44 @@ var LionRapid = (() => {
|
|
|
40
42
|
MissingTranslationQueue: () => MissingTranslationQueue,
|
|
41
43
|
NetworkError: () => NetworkError,
|
|
42
44
|
NetworkRepository: () => NetworkRepository,
|
|
45
|
+
SAFE_ATTRS: () => SAFE_ATTRS,
|
|
46
|
+
SAFE_STYLE_PROPS: () => SAFE_STYLE_PROPS,
|
|
47
|
+
SAFE_TAGS: () => SAFE_TAGS,
|
|
43
48
|
ServerError: () => ServerError,
|
|
49
|
+
StaticRepository: () => StaticRepository,
|
|
44
50
|
TranslationError: () => TranslationError,
|
|
45
51
|
TranslationLockedError: () => TranslationLockedError,
|
|
46
52
|
TranslationNotFoundError: () => TranslationNotFoundError,
|
|
47
53
|
VERSION: () => VERSION,
|
|
48
54
|
VOID_TAGS: () => VOID_TAGS,
|
|
49
55
|
createPackageLogger: () => createPackageLogger,
|
|
56
|
+
escapeHtml: () => escapeHtml,
|
|
50
57
|
extractTagName: () => extractTagName,
|
|
58
|
+
generateAutoKey: () => generateAutoKey,
|
|
51
59
|
isClientError: () => isClientError,
|
|
52
60
|
isRetryableError: () => isRetryableError,
|
|
53
61
|
isTranslationError: () => isTranslationError,
|
|
62
|
+
localeDir: () => localeDir,
|
|
54
63
|
logger: () => logger,
|
|
64
|
+
matchLocale: () => matchLocale,
|
|
65
|
+
md5: () => md5,
|
|
55
66
|
measure: () => measure,
|
|
67
|
+
normalizeLocale: () => normalizeLocale,
|
|
68
|
+
parseAcceptLanguage: () => parseAcceptLanguage,
|
|
56
69
|
parseKeyPath: () => parseKeyPath,
|
|
57
70
|
prepareTranslationRequest: () => prepareTranslationRequest,
|
|
71
|
+
primarySubtag: () => primarySubtag,
|
|
58
72
|
readRawStyle: () => readRawStyle,
|
|
73
|
+
replaceBlockInners: () => replaceBlockInners,
|
|
59
74
|
resolveNestedKey: () => resolveNestedKey,
|
|
60
|
-
|
|
75
|
+
sanitizeContentUnit: () => sanitizeContentUnit,
|
|
76
|
+
sanitizeElement: () => sanitizeElement,
|
|
77
|
+
sanitizeOpeningTag: () => sanitizeOpeningTag,
|
|
78
|
+
sanitizeStyle: () => sanitizeStyle,
|
|
79
|
+
sanitizeStyleSheetText: () => sanitizeStyleSheetText,
|
|
80
|
+
sanitizeUrl: () => sanitizeUrl,
|
|
81
|
+
serializeOpeningTag: () => serializeOpeningTag,
|
|
82
|
+
splitBlocks: () => splitBlocks
|
|
61
83
|
});
|
|
62
84
|
|
|
63
85
|
// src/config/logger.config.ts
|
|
@@ -407,6 +429,57 @@ var LionRapid = (() => {
|
|
|
407
429
|
};
|
|
408
430
|
};
|
|
409
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
|
+
|
|
410
483
|
// src/base/event-emitter.ts
|
|
411
484
|
var EventEmitter = class {
|
|
412
485
|
constructor() {
|
|
@@ -695,7 +768,7 @@ var LionRapid = (() => {
|
|
|
695
768
|
complete: () => {
|
|
696
769
|
this.emit("language:change:success", {
|
|
697
770
|
context: this.createContext(requestId, [...baseTrace, "complete"]),
|
|
698
|
-
payload: { oldLocale, newLocale }
|
|
771
|
+
payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
|
|
699
772
|
});
|
|
700
773
|
},
|
|
701
774
|
error: (error) => {
|
|
@@ -1105,12 +1178,11 @@ var LionRapid = (() => {
|
|
|
1105
1178
|
}
|
|
1106
1179
|
|
|
1107
1180
|
// src/utils/context-utils.ts
|
|
1108
|
-
function createTranslationContext(parsedKey, currentLocale, defaultNamespace
|
|
1181
|
+
function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
|
|
1109
1182
|
const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
|
|
1110
1183
|
return {
|
|
1111
1184
|
locale: currentLocale,
|
|
1112
|
-
namespace: effectiveNamespace
|
|
1113
|
-
fallbackLocale: fallbackLocale || currentLocale
|
|
1185
|
+
namespace: effectiveNamespace
|
|
1114
1186
|
};
|
|
1115
1187
|
}
|
|
1116
1188
|
|
|
@@ -1227,7 +1299,7 @@ var LionRapid = (() => {
|
|
|
1227
1299
|
}
|
|
1228
1300
|
|
|
1229
1301
|
// src/utils/translation-flow.ts
|
|
1230
|
-
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace
|
|
1302
|
+
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
|
|
1231
1303
|
const startTime = performance.now();
|
|
1232
1304
|
const { defaultValue, params } = parseTranslationParams(
|
|
1233
1305
|
defaultValueOrParams,
|
|
@@ -1237,8 +1309,7 @@ var LionRapid = (() => {
|
|
|
1237
1309
|
const context = createTranslationContext(
|
|
1238
1310
|
parsedKey,
|
|
1239
1311
|
currentLocale,
|
|
1240
|
-
defaultNamespace
|
|
1241
|
-
fallbackLocale
|
|
1312
|
+
defaultNamespace
|
|
1242
1313
|
);
|
|
1243
1314
|
const individualCacheKey = createIndividualCacheKey(
|
|
1244
1315
|
context.locale,
|
|
@@ -1260,7 +1331,6 @@ var LionRapid = (() => {
|
|
|
1260
1331
|
locale: currentLocale,
|
|
1261
1332
|
namespace: context.namespace,
|
|
1262
1333
|
// ✅ Use effective namespace (explicit or default)
|
|
1263
|
-
fallbackLocale,
|
|
1264
1334
|
individualCacheKey,
|
|
1265
1335
|
// ✅ Ready-to-use cache key
|
|
1266
1336
|
namespaceCacheKey,
|
|
@@ -2376,6 +2446,183 @@ var LionRapid = (() => {
|
|
|
2376
2446
|
}
|
|
2377
2447
|
};
|
|
2378
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
|
+
|
|
2379
2626
|
// src/content/serializer/HTMLSerializer.ts
|
|
2380
2627
|
var HTMLSerializer = class {
|
|
2381
2628
|
serialize(unit) {
|
|
@@ -2385,24 +2632,25 @@ var LionRapid = (() => {
|
|
|
2385
2632
|
let lastIndex = 0;
|
|
2386
2633
|
let match;
|
|
2387
2634
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
2388
|
-
result += text.slice(lastIndex, match.index);
|
|
2635
|
+
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
2389
2636
|
const [fullMatch, closingSlash, indexStr] = match;
|
|
2390
2637
|
const styleList = styles[indexStr] ?? [];
|
|
2391
2638
|
const rawOpen = readRawStyle(styleList);
|
|
2392
|
-
|
|
2639
|
+
const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
|
|
2640
|
+
if (rawOpen && safeOpen !== null) {
|
|
2393
2641
|
const tagName = extractTagName(rawOpen);
|
|
2394
2642
|
const isVoid = VOID_TAGS.has(tagName);
|
|
2395
2643
|
if (closingSlash) {
|
|
2396
2644
|
if (!isVoid) result += `</${tagName}>`;
|
|
2397
2645
|
} else {
|
|
2398
|
-
result +=
|
|
2646
|
+
result += safeOpen;
|
|
2399
2647
|
}
|
|
2400
2648
|
} else {
|
|
2401
2649
|
result += fullMatch;
|
|
2402
2650
|
}
|
|
2403
2651
|
lastIndex = match.index + fullMatch.length;
|
|
2404
2652
|
}
|
|
2405
|
-
result += text.slice(lastIndex);
|
|
2653
|
+
result += escapeHtml(text.slice(lastIndex));
|
|
2406
2654
|
return result;
|
|
2407
2655
|
}
|
|
2408
2656
|
};
|
|
@@ -2641,10 +2889,9 @@ var LionRapid = (() => {
|
|
|
2641
2889
|
var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
|
|
2642
2890
|
var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
|
|
2643
2891
|
var LionRapidCore = class {
|
|
2644
|
-
constructor(eventEmitter, handlerChain, defaultLocale = "en",
|
|
2892
|
+
constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
|
|
2645
2893
|
__publicField(this, "handlerChain");
|
|
2646
2894
|
__publicField(this, "currentLocale");
|
|
2647
|
-
__publicField(this, "fallbackLocale");
|
|
2648
2895
|
__publicField(this, "defaultNamespace");
|
|
2649
2896
|
__publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
|
|
2650
2897
|
__publicField(this, "bus");
|
|
@@ -2662,7 +2909,6 @@ var LionRapid = (() => {
|
|
|
2662
2909
|
this.bus = eventEmitter;
|
|
2663
2910
|
this.handlerChain = handlerChain;
|
|
2664
2911
|
this.currentLocale = defaultLocale;
|
|
2665
|
-
this.fallbackLocale = fallbackLocale;
|
|
2666
2912
|
this.defaultNamespace = defaultNamespace;
|
|
2667
2913
|
this.cache = new CacheManager(
|
|
2668
2914
|
this.handlerChain,
|
|
@@ -2727,8 +2973,7 @@ var LionRapid = (() => {
|
|
|
2727
2973
|
this.currentLocale,
|
|
2728
2974
|
firstArg,
|
|
2729
2975
|
secondArg,
|
|
2730
|
-
this.defaultNamespace
|
|
2731
|
-
this.fallbackLocale
|
|
2976
|
+
this.defaultNamespace
|
|
2732
2977
|
);
|
|
2733
2978
|
logger.debug(`Translation requested: ${request.cleanKey}`);
|
|
2734
2979
|
const translateBus = this.bus.translation(request);
|
|
@@ -2778,8 +3023,7 @@ var LionRapid = (() => {
|
|
|
2778
3023
|
this.currentLocale,
|
|
2779
3024
|
firstArg,
|
|
2780
3025
|
secondArg,
|
|
2781
|
-
this.defaultNamespace
|
|
2782
|
-
this.fallbackLocale
|
|
3026
|
+
this.defaultNamespace
|
|
2783
3027
|
);
|
|
2784
3028
|
logger.debug(`HTML translation requested: ${request.cleanKey}`);
|
|
2785
3029
|
const translateBus = this.bus.translation(request);
|
|
@@ -2987,8 +3231,7 @@ var LionRapid = (() => {
|
|
|
2987
3231
|
this.currentLocale,
|
|
2988
3232
|
params,
|
|
2989
3233
|
defaultValue,
|
|
2990
|
-
this.defaultNamespace
|
|
2991
|
-
this.fallbackLocale
|
|
3234
|
+
this.defaultNamespace
|
|
2992
3235
|
);
|
|
2993
3236
|
const syncResult = this.handlerChain.handleSync(request);
|
|
2994
3237
|
const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
|
|
@@ -3109,8 +3352,7 @@ var LionRapid = (() => {
|
|
|
3109
3352
|
this.currentLocale,
|
|
3110
3353
|
void 0,
|
|
3111
3354
|
void 0,
|
|
3112
|
-
this.defaultNamespace
|
|
3113
|
-
this.fallbackLocale
|
|
3355
|
+
this.defaultNamespace
|
|
3114
3356
|
);
|
|
3115
3357
|
return this.handlerChain.isCached(request);
|
|
3116
3358
|
}
|
|
@@ -3220,6 +3462,155 @@ var LionRapid = (() => {
|
|
|
3220
3462
|
}
|
|
3221
3463
|
};
|
|
3222
3464
|
|
|
3465
|
+
// src/repositories/memory-repository.ts
|
|
3466
|
+
var MemoryRepository = class {
|
|
3467
|
+
constructor(config) {
|
|
3468
|
+
__publicField(this, "name", "MemoryRepository");
|
|
3469
|
+
__publicField(this, "type", "memory");
|
|
3470
|
+
__publicField(this, "config", {});
|
|
3471
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
3472
|
+
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
3473
|
+
__publicField(this, "maxSize", 1e3);
|
|
3474
|
+
this.config = config;
|
|
3475
|
+
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
3476
|
+
}
|
|
3477
|
+
get(key) {
|
|
3478
|
+
const value = this.cache.get(key);
|
|
3479
|
+
if (value !== void 0) {
|
|
3480
|
+
this.accessOrder.set(key, Date.now());
|
|
3481
|
+
return value;
|
|
3482
|
+
}
|
|
3483
|
+
return null;
|
|
3484
|
+
}
|
|
3485
|
+
set(key, value) {
|
|
3486
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
3487
|
+
this.evictOldest();
|
|
3488
|
+
}
|
|
3489
|
+
this.cache.set(key, value);
|
|
3490
|
+
this.accessOrder.set(key, Date.now());
|
|
3491
|
+
}
|
|
3492
|
+
remove(key) {
|
|
3493
|
+
this.cache.delete(key);
|
|
3494
|
+
this.accessOrder.delete(key);
|
|
3495
|
+
}
|
|
3496
|
+
clear() {
|
|
3497
|
+
this.cache.clear();
|
|
3498
|
+
this.accessOrder.clear();
|
|
3499
|
+
}
|
|
3500
|
+
/**
|
|
3501
|
+
* Clear all entries for a specific namespace
|
|
3502
|
+
* Removes both the namespace cache entry and all individual translation keys
|
|
3503
|
+
*/
|
|
3504
|
+
clearNamespace(locale, namespace) {
|
|
3505
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3506
|
+
const keyPrefix = `${locale}:${namespace}:`;
|
|
3507
|
+
this.cache.delete(namespaceCacheKey);
|
|
3508
|
+
this.accessOrder.delete(namespaceCacheKey);
|
|
3509
|
+
for (const key of this.cache.keys()) {
|
|
3510
|
+
if (key.startsWith(keyPrefix)) {
|
|
3511
|
+
this.cache.delete(key);
|
|
3512
|
+
this.accessOrder.delete(key);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
getNamespace(locale, namespace) {
|
|
3517
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3518
|
+
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
3519
|
+
if (!namespaceData) return null;
|
|
3520
|
+
try {
|
|
3521
|
+
return JSON.parse(namespaceData);
|
|
3522
|
+
} catch (error) {
|
|
3523
|
+
this.cache.delete(namespaceCacheKey);
|
|
3524
|
+
return null;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
setNamespace(locale, namespace, translations) {
|
|
3528
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3529
|
+
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
3530
|
+
}
|
|
3531
|
+
evictOldest() {
|
|
3532
|
+
let oldestKey = "";
|
|
3533
|
+
let oldestTime = Date.now();
|
|
3534
|
+
for (const [key, time] of this.accessOrder.entries()) {
|
|
3535
|
+
if (time < oldestTime) {
|
|
3536
|
+
oldestTime = time;
|
|
3537
|
+
oldestKey = key;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
if (oldestKey) {
|
|
3541
|
+
this.cache.delete(oldestKey);
|
|
3542
|
+
this.accessOrder.delete(oldestKey);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Get all cache entries for debugging
|
|
3547
|
+
*/
|
|
3548
|
+
getEntries() {
|
|
3549
|
+
const entries = {};
|
|
3550
|
+
for (const [key, value] of this.cache.entries()) {
|
|
3551
|
+
entries[key] = value;
|
|
3552
|
+
}
|
|
3553
|
+
return entries;
|
|
3554
|
+
}
|
|
3555
|
+
getConfig() {
|
|
3556
|
+
return this.config;
|
|
3557
|
+
}
|
|
3558
|
+
};
|
|
3559
|
+
|
|
3560
|
+
// src/repositories/static-repository.ts
|
|
3561
|
+
var StaticRepository = class {
|
|
3562
|
+
constructor(bundle, config = {}) {
|
|
3563
|
+
__publicField(this, "name", "StaticRepository");
|
|
3564
|
+
__publicField(this, "type", "memory");
|
|
3565
|
+
__publicField(this, "config");
|
|
3566
|
+
__publicField(this, "mem");
|
|
3567
|
+
this.config = {
|
|
3568
|
+
enabled: config.enabled ?? true,
|
|
3569
|
+
priority: config.priority ?? 0.5,
|
|
3570
|
+
// after the live memory (0), before storage
|
|
3571
|
+
options: {
|
|
3572
|
+
ttl: Number.MAX_SAFE_INTEGER,
|
|
3573
|
+
maxSize: Number.MAX_SAFE_INTEGER,
|
|
3574
|
+
...config.options
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
this.mem = new MemoryRepository({ options: { maxSize: Number.MAX_SAFE_INTEGER } });
|
|
3578
|
+
for (const [locale, namespaces] of Object.entries(bundle)) {
|
|
3579
|
+
for (const [namespace, translations] of Object.entries(namespaces)) {
|
|
3580
|
+
this.mem.setNamespace(locale, namespace, translations);
|
|
3581
|
+
const flat = flattenTranslations(
|
|
3582
|
+
translations,
|
|
3583
|
+
"",
|
|
3584
|
+
locale,
|
|
3585
|
+
namespace
|
|
3586
|
+
);
|
|
3587
|
+
for (const [key, value] of Object.entries(flat)) this.mem.set(key, value);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
get(key) {
|
|
3592
|
+
return this.mem.get(key);
|
|
3593
|
+
}
|
|
3594
|
+
getNamespace(locale, namespace) {
|
|
3595
|
+
return this.mem.getNamespace(locale, namespace);
|
|
3596
|
+
}
|
|
3597
|
+
getEntries() {
|
|
3598
|
+
return this.mem.getEntries();
|
|
3599
|
+
}
|
|
3600
|
+
getConfig() {
|
|
3601
|
+
return this.config;
|
|
3602
|
+
}
|
|
3603
|
+
// Read-only: the live memory layer owns fresh network data; this stays pristine.
|
|
3604
|
+
set() {
|
|
3605
|
+
}
|
|
3606
|
+
remove() {
|
|
3607
|
+
}
|
|
3608
|
+
clear() {
|
|
3609
|
+
}
|
|
3610
|
+
setNamespace() {
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
|
|
3223
3614
|
// src/sync/missing-translation-queue.ts
|
|
3224
3615
|
var COOLDOWN_MS = 6e4;
|
|
3225
3616
|
var MissingTranslationQueue = class {
|
|
@@ -3382,6 +3773,9 @@ var LionRapid = (() => {
|
|
|
3382
3773
|
};
|
|
3383
3774
|
|
|
3384
3775
|
// src/builder.ts
|
|
3776
|
+
function isLoudPreloadFailure(error) {
|
|
3777
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3778
|
+
}
|
|
3385
3779
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3386
3780
|
constructor(config) {
|
|
3387
3781
|
__publicField(this, "config");
|
|
@@ -3392,7 +3786,6 @@ var LionRapid = (() => {
|
|
|
3392
3786
|
__publicField(this, "syncQueue", null);
|
|
3393
3787
|
this.config = {
|
|
3394
3788
|
defaultLocale: config.defaultLocale || "en",
|
|
3395
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3396
3789
|
namespace: config.namespace || "app"
|
|
3397
3790
|
};
|
|
3398
3791
|
}
|
|
@@ -3428,6 +3821,15 @@ var LionRapid = (() => {
|
|
|
3428
3821
|
}
|
|
3429
3822
|
return this;
|
|
3430
3823
|
}
|
|
3824
|
+
/**
|
|
3825
|
+
* Offline resilience: seed a translation bundle (or the CLI's snapshot files)
|
|
3826
|
+
* as a read-only sync layer that resolves synchronously even with the server
|
|
3827
|
+
* down and nothing cached. Sits after the live memory layer (priority 0) at
|
|
3828
|
+
* `priority` (default 0.5), so fresh network data always shadows it.
|
|
3829
|
+
*/
|
|
3830
|
+
useStaticBundle(bundle, priority = 0.5) {
|
|
3831
|
+
return this.use(new StaticRepository(bundle, { priority }));
|
|
3832
|
+
}
|
|
3431
3833
|
async build() {
|
|
3432
3834
|
const eventEmitter = new EventEmitter();
|
|
3433
3835
|
const buildBus = eventEmitter.builder();
|
|
@@ -3484,7 +3886,6 @@ var LionRapid = (() => {
|
|
|
3484
3886
|
eventEmitter,
|
|
3485
3887
|
handlerChain,
|
|
3486
3888
|
this.config.defaultLocale,
|
|
3487
|
-
this.config.fallbackLocale,
|
|
3488
3889
|
this.config.namespace
|
|
3489
3890
|
);
|
|
3490
3891
|
for (const plugin of this.plugins) {
|
|
@@ -3515,7 +3916,17 @@ var LionRapid = (() => {
|
|
|
3515
3916
|
logger.debug("Preload targets", {
|
|
3516
3917
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3517
3918
|
});
|
|
3919
|
+
let warnedLoudly = false;
|
|
3920
|
+
const warnLoudlyOnce = (namespaceKey, error) => {
|
|
3921
|
+
if (!isLoudPreloadFailure(error) || warnedLoudly) return;
|
|
3922
|
+
warnedLoudly = true;
|
|
3923
|
+
console.warn(
|
|
3924
|
+
`[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
|
|
3925
|
+
error
|
|
3926
|
+
);
|
|
3927
|
+
};
|
|
3518
3928
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3929
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3519
3930
|
try {
|
|
3520
3931
|
const result = await networkHandler.loadNamespace(
|
|
3521
3932
|
preloadConfig.locale,
|
|
@@ -3524,19 +3935,19 @@ var LionRapid = (() => {
|
|
|
3524
3935
|
if (result && result.data && result.status === 200) {
|
|
3525
3936
|
const context = {
|
|
3526
3937
|
locale: preloadConfig.locale,
|
|
3527
|
-
namespace: preloadConfig.namespace
|
|
3528
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3938
|
+
namespace: preloadConfig.namespace
|
|
3529
3939
|
};
|
|
3530
3940
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3531
|
-
logger.debug(
|
|
3532
|
-
|
|
3533
|
-
|
|
3941
|
+
logger.debug(`Preloaded: ${namespaceKey}`);
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
if (result?.error) {
|
|
3945
|
+
warnLoudlyOnce(namespaceKey, result.error);
|
|
3946
|
+
logger.warn(`Preload failed: ${namespaceKey}`, result.error);
|
|
3534
3947
|
}
|
|
3535
3948
|
} catch (error) {
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
error
|
|
3539
|
-
);
|
|
3949
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3950
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3540
3951
|
}
|
|
3541
3952
|
});
|
|
3542
3953
|
await Promise.allSettled(promises);
|
|
@@ -3550,8 +3961,16 @@ var LionRapid = (() => {
|
|
|
3550
3961
|
__publicField(this, "name", "NetworkRepository");
|
|
3551
3962
|
__publicField(this, "type", "network");
|
|
3552
3963
|
__publicField(this, "config", {});
|
|
3553
|
-
/**
|
|
3554
|
-
|
|
3964
|
+
/**
|
|
3965
|
+
* Namespace snapshot per key "locale:namespace": the server version TOGETHER
|
|
3966
|
+
* with the payload it describes, persisted as ONE localStorage entry.
|
|
3967
|
+
*
|
|
3968
|
+
* Canonical 304 contract (same as edge KV bundle + WP transient cache):
|
|
3969
|
+
* never claim freshness you can't back. If-None-Match is only sent when the
|
|
3970
|
+
* snapshot is in hand; a 304 answers with that snapshot's payload; version
|
|
3971
|
+
* and payload can never desync because they live and die together.
|
|
3972
|
+
*/
|
|
3973
|
+
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3555
3974
|
/**
|
|
3556
3975
|
* Default endpoint templates with placeholder support
|
|
3557
3976
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3664,36 +4083,50 @@ var LionRapid = (() => {
|
|
|
3664
4083
|
logger.debug("API base URL is not set");
|
|
3665
4084
|
throw new NetworkError("API base URL is not set");
|
|
3666
4085
|
}
|
|
4086
|
+
try {
|
|
4087
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
4088
|
+
} catch (error) {
|
|
4089
|
+
this.handleCaughtError(error);
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Shared namespace fetch with the canonical 304 contract:
|
|
4094
|
+
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
4095
|
+
* - 304 answers with that snapshot's payload — never with "nothing".
|
|
4096
|
+
* - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
|
|
4097
|
+
* - A 200 stores version + payload atomically before either is trusted.
|
|
4098
|
+
*/
|
|
4099
|
+
async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
|
|
3667
4100
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3668
4101
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3669
4102
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
if (
|
|
3678
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3679
|
-
return
|
|
3680
|
-
}
|
|
3681
|
-
if (!response.ok) {
|
|
3682
|
-
this.handleHttpError(response, namespaceKey);
|
|
3683
|
-
}
|
|
3684
|
-
const data = await response.json();
|
|
3685
|
-
logger.debug("API response data:", data);
|
|
3686
|
-
if (!data.translations) {
|
|
3687
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3688
|
-
}
|
|
3689
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3690
|
-
if (version) {
|
|
3691
|
-
this.setVersion(namespaceKey, version);
|
|
4103
|
+
const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
|
|
4104
|
+
const extraHeaders = {};
|
|
4105
|
+
if (snapshot) {
|
|
4106
|
+
extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
|
|
4107
|
+
}
|
|
4108
|
+
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
4109
|
+
if (response.status === 304) {
|
|
4110
|
+
if (snapshot) {
|
|
4111
|
+
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
|
|
4112
|
+
return snapshot.translations;
|
|
3692
4113
|
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
4114
|
+
logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
|
|
4115
|
+
return this.fetchNamespaceRaw(locale, namespace, false);
|
|
4116
|
+
}
|
|
4117
|
+
if (!response.ok) {
|
|
4118
|
+
this.handleHttpError(response, namespaceKey);
|
|
3696
4119
|
}
|
|
4120
|
+
const data = await response.json();
|
|
4121
|
+
logger.debug("API response data:", data);
|
|
4122
|
+
if (!data.translations) {
|
|
4123
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
4124
|
+
}
|
|
4125
|
+
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
4126
|
+
if (version) {
|
|
4127
|
+
this.setSnapshot(namespaceKey, { version, translations: data.translations });
|
|
4128
|
+
}
|
|
4129
|
+
return data.translations;
|
|
3697
4130
|
}
|
|
3698
4131
|
/**
|
|
3699
4132
|
* Save translation to API (if supported)
|
|
@@ -3778,33 +4211,8 @@ var LionRapid = (() => {
|
|
|
3778
4211
|
logger.debug("API base URL is not set");
|
|
3779
4212
|
throw new NetworkError("API base URL is not set");
|
|
3780
4213
|
}
|
|
3781
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3782
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3783
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3784
4214
|
try {
|
|
3785
|
-
|
|
3786
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3787
|
-
if (cachedVersion) {
|
|
3788
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3789
|
-
}
|
|
3790
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3791
|
-
if (response.status === 304) {
|
|
3792
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3793
|
-
return null;
|
|
3794
|
-
}
|
|
3795
|
-
if (!response.ok) {
|
|
3796
|
-
this.handleHttpError(response, namespaceKey);
|
|
3797
|
-
}
|
|
3798
|
-
const data = await response.json();
|
|
3799
|
-
logger.debug("API response data:", data);
|
|
3800
|
-
if (!data.translations) {
|
|
3801
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3802
|
-
}
|
|
3803
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3804
|
-
if (version) {
|
|
3805
|
-
this.setVersion(namespaceKey, version);
|
|
3806
|
-
}
|
|
3807
|
-
return data.translations;
|
|
4215
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3808
4216
|
} catch (error) {
|
|
3809
4217
|
this.handleCaughtError(error);
|
|
3810
4218
|
}
|
|
@@ -3914,25 +4322,37 @@ var LionRapid = (() => {
|
|
|
3914
4322
|
getConfig() {
|
|
3915
4323
|
return this.config;
|
|
3916
4324
|
}
|
|
3917
|
-
/** Read persisted version
|
|
3918
|
-
|
|
3919
|
-
|
|
4325
|
+
/** Read a persisted snapshot (version + payload), falling back to memory.
|
|
4326
|
+
* Legacy bare-version entries and corrupt snapshots are deleted on sight —
|
|
4327
|
+
* a version without its payload must never drive a conditional request. */
|
|
4328
|
+
getSnapshot(namespaceKey) {
|
|
4329
|
+
const mem = this.namespaceSnapshots.get(namespaceKey);
|
|
3920
4330
|
if (mem) return mem;
|
|
3921
4331
|
try {
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
4332
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4333
|
+
const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4334
|
+
if (raw) {
|
|
4335
|
+
const parsed = JSON.parse(raw);
|
|
4336
|
+
if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
|
|
4337
|
+
this.namespaceSnapshots.set(namespaceKey, parsed);
|
|
4338
|
+
return parsed;
|
|
4339
|
+
}
|
|
4340
|
+
localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
3926
4341
|
}
|
|
3927
4342
|
} catch {
|
|
3928
4343
|
}
|
|
3929
4344
|
return void 0;
|
|
3930
4345
|
}
|
|
3931
|
-
/** Persist version
|
|
3932
|
-
|
|
3933
|
-
|
|
4346
|
+
/** Persist version + payload as ONE entry (memory + localStorage). On quota
|
|
4347
|
+
* failure the previous stored snapshot stays — still a consistent pair. */
|
|
4348
|
+
setSnapshot(namespaceKey, snapshot) {
|
|
4349
|
+
this.namespaceSnapshots.set(namespaceKey, snapshot);
|
|
3934
4350
|
try {
|
|
3935
|
-
localStorage.setItem(
|
|
4351
|
+
localStorage.setItem(
|
|
4352
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4353
|
+
JSON.stringify(snapshot)
|
|
4354
|
+
);
|
|
4355
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
3936
4356
|
} catch {
|
|
3937
4357
|
}
|
|
3938
4358
|
}
|
|
@@ -4021,105 +4441,13 @@ var LionRapid = (() => {
|
|
|
4021
4441
|
}
|
|
4022
4442
|
}
|
|
4023
4443
|
};
|
|
4024
|
-
/** localStorage key prefix for persisted
|
|
4025
|
-
__publicField(_NetworkRepository, "
|
|
4444
|
+
/** localStorage key prefix for persisted snapshots (version + payload) */
|
|
4445
|
+
__publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
4446
|
+
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4447
|
+
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4448
|
+
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4026
4449
|
var NetworkRepository = _NetworkRepository;
|
|
4027
4450
|
|
|
4028
|
-
// src/repositories/memory-repository.ts
|
|
4029
|
-
var MemoryRepository = class {
|
|
4030
|
-
constructor(config) {
|
|
4031
|
-
__publicField(this, "name", "MemoryRepository");
|
|
4032
|
-
__publicField(this, "type", "memory");
|
|
4033
|
-
__publicField(this, "config", {});
|
|
4034
|
-
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
4035
|
-
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
4036
|
-
__publicField(this, "maxSize", 1e3);
|
|
4037
|
-
this.config = config;
|
|
4038
|
-
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
4039
|
-
}
|
|
4040
|
-
get(key) {
|
|
4041
|
-
const value = this.cache.get(key);
|
|
4042
|
-
if (value !== void 0) {
|
|
4043
|
-
this.accessOrder.set(key, Date.now());
|
|
4044
|
-
return value;
|
|
4045
|
-
}
|
|
4046
|
-
return null;
|
|
4047
|
-
}
|
|
4048
|
-
set(key, value) {
|
|
4049
|
-
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
4050
|
-
this.evictOldest();
|
|
4051
|
-
}
|
|
4052
|
-
this.cache.set(key, value);
|
|
4053
|
-
this.accessOrder.set(key, Date.now());
|
|
4054
|
-
}
|
|
4055
|
-
remove(key) {
|
|
4056
|
-
this.cache.delete(key);
|
|
4057
|
-
this.accessOrder.delete(key);
|
|
4058
|
-
}
|
|
4059
|
-
clear() {
|
|
4060
|
-
this.cache.clear();
|
|
4061
|
-
this.accessOrder.clear();
|
|
4062
|
-
}
|
|
4063
|
-
/**
|
|
4064
|
-
* Clear all entries for a specific namespace
|
|
4065
|
-
* Removes both the namespace cache entry and all individual translation keys
|
|
4066
|
-
*/
|
|
4067
|
-
clearNamespace(locale, namespace) {
|
|
4068
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4069
|
-
const keyPrefix = `${locale}:${namespace}:`;
|
|
4070
|
-
this.cache.delete(namespaceCacheKey);
|
|
4071
|
-
this.accessOrder.delete(namespaceCacheKey);
|
|
4072
|
-
for (const key of this.cache.keys()) {
|
|
4073
|
-
if (key.startsWith(keyPrefix)) {
|
|
4074
|
-
this.cache.delete(key);
|
|
4075
|
-
this.accessOrder.delete(key);
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
4078
|
-
}
|
|
4079
|
-
getNamespace(locale, namespace) {
|
|
4080
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4081
|
-
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
4082
|
-
if (!namespaceData) return null;
|
|
4083
|
-
try {
|
|
4084
|
-
return JSON.parse(namespaceData);
|
|
4085
|
-
} catch (error) {
|
|
4086
|
-
this.cache.delete(namespaceCacheKey);
|
|
4087
|
-
return null;
|
|
4088
|
-
}
|
|
4089
|
-
}
|
|
4090
|
-
setNamespace(locale, namespace, translations) {
|
|
4091
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4092
|
-
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
4093
|
-
}
|
|
4094
|
-
evictOldest() {
|
|
4095
|
-
let oldestKey = "";
|
|
4096
|
-
let oldestTime = Date.now();
|
|
4097
|
-
for (const [key, time] of this.accessOrder.entries()) {
|
|
4098
|
-
if (time < oldestTime) {
|
|
4099
|
-
oldestTime = time;
|
|
4100
|
-
oldestKey = key;
|
|
4101
|
-
}
|
|
4102
|
-
}
|
|
4103
|
-
if (oldestKey) {
|
|
4104
|
-
this.cache.delete(oldestKey);
|
|
4105
|
-
this.accessOrder.delete(oldestKey);
|
|
4106
|
-
}
|
|
4107
|
-
}
|
|
4108
|
-
/**
|
|
4109
|
-
* Get all cache entries for debugging
|
|
4110
|
-
*/
|
|
4111
|
-
getEntries() {
|
|
4112
|
-
const entries = {};
|
|
4113
|
-
for (const [key, value] of this.cache.entries()) {
|
|
4114
|
-
entries[key] = value;
|
|
4115
|
-
}
|
|
4116
|
-
return entries;
|
|
4117
|
-
}
|
|
4118
|
-
getConfig() {
|
|
4119
|
-
return this.config;
|
|
4120
|
-
}
|
|
4121
|
-
};
|
|
4122
|
-
|
|
4123
4451
|
// src/content/validator/ContentValidator.ts
|
|
4124
4452
|
var ContentValidator = class {
|
|
4125
4453
|
/**
|
|
@@ -4273,6 +4601,433 @@ var LionRapid = (() => {
|
|
|
4273
4601
|
}
|
|
4274
4602
|
};
|
|
4275
4603
|
|
|
4604
|
+
// src/content/splitter/BlockSplitter.ts
|
|
4605
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
4606
|
+
"title",
|
|
4607
|
+
"p",
|
|
4608
|
+
"h1",
|
|
4609
|
+
"h2",
|
|
4610
|
+
"h3",
|
|
4611
|
+
"h4",
|
|
4612
|
+
"h5",
|
|
4613
|
+
"h6",
|
|
4614
|
+
"li",
|
|
4615
|
+
"td",
|
|
4616
|
+
"th",
|
|
4617
|
+
"label",
|
|
4618
|
+
"button",
|
|
4619
|
+
"figcaption",
|
|
4620
|
+
"dt",
|
|
4621
|
+
"dd",
|
|
4622
|
+
"summary",
|
|
4623
|
+
"caption"
|
|
4624
|
+
]);
|
|
4625
|
+
var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
|
|
4626
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
4627
|
+
"script",
|
|
4628
|
+
"style",
|
|
4629
|
+
"code",
|
|
4630
|
+
"pre",
|
|
4631
|
+
"textarea",
|
|
4632
|
+
"noscript",
|
|
4633
|
+
"svg"
|
|
4634
|
+
]);
|
|
4635
|
+
var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
|
|
4636
|
+
function hasNoTranslateAttr(attrsString) {
|
|
4637
|
+
if (attrsString.includes("data-no-translate")) return true;
|
|
4638
|
+
return /translate\s*=\s*["']no["']/.test(attrsString);
|
|
4639
|
+
}
|
|
4640
|
+
function containsBlockTag(innerHTML) {
|
|
4641
|
+
TAG_REGEX.lastIndex = 0;
|
|
4642
|
+
let match;
|
|
4643
|
+
while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
|
|
4644
|
+
const [, closingSlash, tagName, , selfClosingSlash] = match;
|
|
4645
|
+
if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
|
|
4646
|
+
return true;
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
return false;
|
|
4650
|
+
}
|
|
4651
|
+
function findCloseTag(html, pos, tagName) {
|
|
4652
|
+
const lowerTag = tagName.toLowerCase();
|
|
4653
|
+
let depth = 1;
|
|
4654
|
+
TAG_REGEX.lastIndex = pos;
|
|
4655
|
+
let match;
|
|
4656
|
+
while ((match = TAG_REGEX.exec(html)) !== null) {
|
|
4657
|
+
const [, closingSlash, matchedTag, , selfClosingSlash] = match;
|
|
4658
|
+
if (matchedTag.toLowerCase() !== lowerTag) continue;
|
|
4659
|
+
if (closingSlash) {
|
|
4660
|
+
depth--;
|
|
4661
|
+
if (depth === 0) return match.index;
|
|
4662
|
+
} else if (!selfClosingSlash) {
|
|
4663
|
+
depth++;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
return -1;
|
|
4667
|
+
}
|
|
4668
|
+
function splitBlocks(html) {
|
|
4669
|
+
const blocks = [];
|
|
4670
|
+
let pos = 0;
|
|
4671
|
+
let skipDepth = 0;
|
|
4672
|
+
let iter = 0;
|
|
4673
|
+
const maxIter = 1e5;
|
|
4674
|
+
const noTranslateEnds = [];
|
|
4675
|
+
while (pos < html.length && iter < maxIter) {
|
|
4676
|
+
iter++;
|
|
4677
|
+
const ltPos = html.indexOf("<", pos);
|
|
4678
|
+
if (ltPos === -1) break;
|
|
4679
|
+
if (html.startsWith("<!--", ltPos)) {
|
|
4680
|
+
const commentEnd = html.indexOf("-->", ltPos + 4);
|
|
4681
|
+
if (commentEnd === -1) break;
|
|
4682
|
+
pos = commentEnd + 3;
|
|
4683
|
+
continue;
|
|
4684
|
+
}
|
|
4685
|
+
TAG_REGEX.lastIndex = ltPos;
|
|
4686
|
+
const match = TAG_REGEX.exec(html);
|
|
4687
|
+
if (!match || match.index !== ltPos) {
|
|
4688
|
+
pos = ltPos + 1;
|
|
4689
|
+
continue;
|
|
4690
|
+
}
|
|
4691
|
+
const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
|
|
4692
|
+
const tag = tagName.toLowerCase();
|
|
4693
|
+
const afterMatch = match.index + fullMatch.length;
|
|
4694
|
+
if (SKIP_TAGS.has(tag)) {
|
|
4695
|
+
if (closingSlash) {
|
|
4696
|
+
if (skipDepth > 0) skipDepth--;
|
|
4697
|
+
} else if (!selfClosingSlash) {
|
|
4698
|
+
skipDepth++;
|
|
4699
|
+
}
|
|
4700
|
+
pos = afterMatch;
|
|
4701
|
+
continue;
|
|
4702
|
+
}
|
|
4703
|
+
if (skipDepth > 0) {
|
|
4704
|
+
pos = afterMatch;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
|
|
4708
|
+
noTranslateEnds.pop();
|
|
4709
|
+
}
|
|
4710
|
+
if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
|
|
4711
|
+
const regionEnd = findCloseTag(html, afterMatch, tagName);
|
|
4712
|
+
if (regionEnd !== -1) {
|
|
4713
|
+
noTranslateEnds.push(regionEnd);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
if (noTranslateEnds.length > 0) {
|
|
4717
|
+
pos = afterMatch;
|
|
4718
|
+
continue;
|
|
4719
|
+
}
|
|
4720
|
+
if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
|
|
4721
|
+
const closeTagStart = findCloseTag(html, afterMatch, tagName);
|
|
4722
|
+
if (closeTagStart === -1) {
|
|
4723
|
+
pos = afterMatch;
|
|
4724
|
+
continue;
|
|
4725
|
+
}
|
|
4726
|
+
const innerHTML = html.slice(afterMatch, closeTagStart);
|
|
4727
|
+
if (containsBlockTag(innerHTML)) {
|
|
4728
|
+
pos = afterMatch;
|
|
4729
|
+
continue;
|
|
4730
|
+
}
|
|
4731
|
+
blocks.push({
|
|
4732
|
+
tag,
|
|
4733
|
+
openTag: fullMatch,
|
|
4734
|
+
attrs: attrsString,
|
|
4735
|
+
inner: innerHTML,
|
|
4736
|
+
innerStart: afterMatch,
|
|
4737
|
+
innerEnd: closeTagStart
|
|
4738
|
+
});
|
|
4739
|
+
TAG_REGEX.lastIndex = closeTagStart;
|
|
4740
|
+
const closeMatch = TAG_REGEX.exec(html);
|
|
4741
|
+
pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
|
|
4742
|
+
continue;
|
|
4743
|
+
}
|
|
4744
|
+
pos = afterMatch;
|
|
4745
|
+
}
|
|
4746
|
+
return { blocks };
|
|
4747
|
+
}
|
|
4748
|
+
function replaceBlockInners(html, replacements) {
|
|
4749
|
+
const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
|
|
4750
|
+
let result = html;
|
|
4751
|
+
for (const { block, inner } of ordered) {
|
|
4752
|
+
result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
|
|
4753
|
+
}
|
|
4754
|
+
return result;
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
// src/browser/md5.ts
|
|
4758
|
+
function safeAdd(x, y) {
|
|
4759
|
+
const lsw = (x & 65535) + (y & 65535);
|
|
4760
|
+
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
4761
|
+
return msw << 16 | lsw & 65535;
|
|
4762
|
+
}
|
|
4763
|
+
function bitRotateLeft(num, cnt) {
|
|
4764
|
+
return num << cnt | num >>> 32 - cnt;
|
|
4765
|
+
}
|
|
4766
|
+
function md5cmn(q, a, b, x, s, t) {
|
|
4767
|
+
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
|
4768
|
+
}
|
|
4769
|
+
function md5ff(a, b, c, d, x, s, t) {
|
|
4770
|
+
return md5cmn(b & c | ~b & d, a, b, x, s, t);
|
|
4771
|
+
}
|
|
4772
|
+
function md5gg(a, b, c, d, x, s, t) {
|
|
4773
|
+
return md5cmn(b & d | c & ~d, a, b, x, s, t);
|
|
4774
|
+
}
|
|
4775
|
+
function md5hh(a, b, c, d, x, s, t) {
|
|
4776
|
+
return md5cmn(b ^ c ^ d, a, b, x, s, t);
|
|
4777
|
+
}
|
|
4778
|
+
function md5ii(a, b, c, d, x, s, t) {
|
|
4779
|
+
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
4780
|
+
}
|
|
4781
|
+
function binlMD5(x, len) {
|
|
4782
|
+
x[len >> 5] |= 128 << len % 32;
|
|
4783
|
+
x[(len + 64 >>> 9 << 4) + 14] = len;
|
|
4784
|
+
let a = 1732584193;
|
|
4785
|
+
let b = -271733879;
|
|
4786
|
+
let c = -1732584194;
|
|
4787
|
+
let d = 271733878;
|
|
4788
|
+
for (let i = 0; i < x.length; i += 16) {
|
|
4789
|
+
const olda = a;
|
|
4790
|
+
const oldb = b;
|
|
4791
|
+
const oldc = c;
|
|
4792
|
+
const oldd = d;
|
|
4793
|
+
a = md5ff(a, b, c, d, x[i], 7, -680876936);
|
|
4794
|
+
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
|
|
4795
|
+
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
|
|
4796
|
+
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
|
4797
|
+
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
|
|
4798
|
+
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
|
4799
|
+
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
|
4800
|
+
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
|
|
4801
|
+
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
|
4802
|
+
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
|
4803
|
+
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
|
|
4804
|
+
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
|
4805
|
+
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
|
4806
|
+
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
|
|
4807
|
+
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
|
4808
|
+
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
|
4809
|
+
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
|
|
4810
|
+
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
|
4811
|
+
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
|
|
4812
|
+
b = md5gg(b, c, d, a, x[i], 20, -373897302);
|
|
4813
|
+
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
|
|
4814
|
+
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
|
|
4815
|
+
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
|
|
4816
|
+
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
|
|
4817
|
+
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
|
|
4818
|
+
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
|
4819
|
+
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
|
|
4820
|
+
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
|
4821
|
+
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
|
4822
|
+
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
|
|
4823
|
+
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
|
4824
|
+
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
|
4825
|
+
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
|
|
4826
|
+
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
|
4827
|
+
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
|
4828
|
+
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
|
|
4829
|
+
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
|
4830
|
+
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
|
4831
|
+
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
|
|
4832
|
+
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
|
4833
|
+
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
|
|
4834
|
+
d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
|
|
4835
|
+
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
|
|
4836
|
+
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
|
|
4837
|
+
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
|
|
4838
|
+
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
|
|
4839
|
+
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
|
|
4840
|
+
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
|
|
4841
|
+
a = md5ii(a, b, c, d, x[i], 6, -198630844);
|
|
4842
|
+
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
|
4843
|
+
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
|
4844
|
+
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
|
|
4845
|
+
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
|
4846
|
+
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
|
4847
|
+
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
|
|
4848
|
+
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
|
4849
|
+
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
|
4850
|
+
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
|
|
4851
|
+
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
|
4852
|
+
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
|
4853
|
+
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
|
|
4854
|
+
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
|
4855
|
+
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
|
|
4856
|
+
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
|
|
4857
|
+
a = safeAdd(a, olda);
|
|
4858
|
+
b = safeAdd(b, oldb);
|
|
4859
|
+
c = safeAdd(c, oldc);
|
|
4860
|
+
d = safeAdd(d, oldd);
|
|
4861
|
+
}
|
|
4862
|
+
return [a, b, c, d];
|
|
4863
|
+
}
|
|
4864
|
+
function binl2hex(binarray) {
|
|
4865
|
+
const hexTab = "0123456789abcdef";
|
|
4866
|
+
let str = "";
|
|
4867
|
+
for (let i = 0; i < binarray.length * 4; i++) {
|
|
4868
|
+
str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
|
|
4869
|
+
}
|
|
4870
|
+
return str;
|
|
4871
|
+
}
|
|
4872
|
+
function utf8Bytes(str) {
|
|
4873
|
+
if (typeof TextEncoder !== "undefined") {
|
|
4874
|
+
return new TextEncoder().encode(str);
|
|
4875
|
+
}
|
|
4876
|
+
const out = [];
|
|
4877
|
+
for (let i = 0; i < str.length; i++) {
|
|
4878
|
+
const c = str.charCodeAt(i);
|
|
4879
|
+
if (c < 128) {
|
|
4880
|
+
out.push(c);
|
|
4881
|
+
} else if (c < 2048) {
|
|
4882
|
+
out.push(192 | c >> 6, 128 | c & 63);
|
|
4883
|
+
} else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
|
|
4884
|
+
const c2 = str.charCodeAt(++i);
|
|
4885
|
+
const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
|
|
4886
|
+
out.push(
|
|
4887
|
+
240 | cp >> 18,
|
|
4888
|
+
128 | cp >> 12 & 63,
|
|
4889
|
+
128 | cp >> 6 & 63,
|
|
4890
|
+
128 | cp & 63
|
|
4891
|
+
);
|
|
4892
|
+
} else {
|
|
4893
|
+
out.push(
|
|
4894
|
+
224 | c >> 12,
|
|
4895
|
+
128 | c >> 6 & 63,
|
|
4896
|
+
128 | c & 63
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
return new Uint8Array(out);
|
|
4901
|
+
}
|
|
4902
|
+
function bytes2binl(bytes) {
|
|
4903
|
+
const bin = [];
|
|
4904
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4905
|
+
bin[i >> 2] |= bytes[i] << i % 4 * 8;
|
|
4906
|
+
}
|
|
4907
|
+
return bin;
|
|
4908
|
+
}
|
|
4909
|
+
function md5(str) {
|
|
4910
|
+
const bytes = utf8Bytes(str);
|
|
4911
|
+
return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
|
|
4912
|
+
}
|
|
4913
|
+
function generateAutoKey(text) {
|
|
4914
|
+
const trimmed = text.trim();
|
|
4915
|
+
return "auto_" + md5(trimmed).substring(0, 12);
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4918
|
+
// src/browser/exclude-filter.ts
|
|
4919
|
+
var DEFAULT_PATTERNS = {
|
|
4920
|
+
emails: true,
|
|
4921
|
+
urls: true,
|
|
4922
|
+
numbers: true,
|
|
4923
|
+
phones: true
|
|
4924
|
+
};
|
|
4925
|
+
var PATTERNS = {
|
|
4926
|
+
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
4927
|
+
url: /^(https?:\/\/|www\.)[^\s]+$/i,
|
|
4928
|
+
number: /^[\d\s.,+-]+$/,
|
|
4929
|
+
phone: /^[+\d][\d\s().-]{6,}$/
|
|
4930
|
+
};
|
|
4931
|
+
var ExcludeFilter = class {
|
|
4932
|
+
constructor(patterns = {}, minTextLength = 2) {
|
|
4933
|
+
__publicField(this, "patterns");
|
|
4934
|
+
__publicField(this, "minTextLength");
|
|
4935
|
+
this.patterns = { ...DEFAULT_PATTERNS, ...patterns };
|
|
4936
|
+
this.minTextLength = minTextLength;
|
|
4937
|
+
}
|
|
4938
|
+
/**
|
|
4939
|
+
* Check if text should be excluded from translation
|
|
4940
|
+
*/
|
|
4941
|
+
shouldExclude(text) {
|
|
4942
|
+
const trimmed = text.trim();
|
|
4943
|
+
if (trimmed.length < this.minTextLength) {
|
|
4944
|
+
return true;
|
|
4945
|
+
}
|
|
4946
|
+
if (this.patterns.emails && PATTERNS.email.test(trimmed)) {
|
|
4947
|
+
return true;
|
|
4948
|
+
}
|
|
4949
|
+
if (this.patterns.urls && PATTERNS.url.test(trimmed)) {
|
|
4950
|
+
return true;
|
|
4951
|
+
}
|
|
4952
|
+
if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {
|
|
4953
|
+
return true;
|
|
4954
|
+
}
|
|
4955
|
+
if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {
|
|
4956
|
+
return true;
|
|
4957
|
+
}
|
|
4958
|
+
return false;
|
|
4959
|
+
}
|
|
4960
|
+
};
|
|
4961
|
+
|
|
4962
|
+
// src/browser/sanitizer.ts
|
|
4963
|
+
var REMOVE_TAGS = /* @__PURE__ */ new Set([
|
|
4964
|
+
"script",
|
|
4965
|
+
"iframe",
|
|
4966
|
+
"frame",
|
|
4967
|
+
"frameset",
|
|
4968
|
+
"object",
|
|
4969
|
+
"embed",
|
|
4970
|
+
"applet",
|
|
4971
|
+
"base",
|
|
4972
|
+
"meta",
|
|
4973
|
+
"link",
|
|
4974
|
+
"template",
|
|
4975
|
+
"portal"
|
|
4976
|
+
]);
|
|
4977
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
4978
|
+
"href",
|
|
4979
|
+
"src",
|
|
4980
|
+
"action",
|
|
4981
|
+
"formaction",
|
|
4982
|
+
"poster",
|
|
4983
|
+
"data",
|
|
4984
|
+
"xlink:href"
|
|
4985
|
+
]);
|
|
4986
|
+
function sanitizeElement(root) {
|
|
4987
|
+
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
4988
|
+
for (const el of all) {
|
|
4989
|
+
const tag = el.tagName.toLowerCase();
|
|
4990
|
+
if (REMOVE_TAGS.has(tag)) {
|
|
4991
|
+
el.remove();
|
|
4992
|
+
continue;
|
|
4993
|
+
}
|
|
4994
|
+
for (const name of el.getAttributeNames()) {
|
|
4995
|
+
const lower = name.toLowerCase();
|
|
4996
|
+
if (lower.startsWith("on") || lower === "srcdoc") {
|
|
4997
|
+
el.removeAttribute(name);
|
|
4998
|
+
continue;
|
|
4999
|
+
}
|
|
5000
|
+
if (URL_ATTRS2.has(lower)) {
|
|
5001
|
+
if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
|
|
5002
|
+
el.removeAttribute(name);
|
|
5003
|
+
}
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
if (lower === "style") {
|
|
5007
|
+
const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
|
|
5008
|
+
if (clean === null) el.removeAttribute(name);
|
|
5009
|
+
else el.setAttribute(name, clean);
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
function sanitizeStyleSheetText(css, baseHref) {
|
|
5015
|
+
let out = css.replace(/@import[^;]*(;|$)/gi, "");
|
|
5016
|
+
out = out.replace(
|
|
5017
|
+
/url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
|
|
5018
|
+
(full, _quote, target) => {
|
|
5019
|
+
try {
|
|
5020
|
+
const resolved = new URL(target, baseHref);
|
|
5021
|
+
if (resolved.protocol === "data:") return full;
|
|
5022
|
+
if (resolved.origin === new URL(baseHref).origin) return full;
|
|
5023
|
+
} catch {
|
|
5024
|
+
}
|
|
5025
|
+
return 'url("")';
|
|
5026
|
+
}
|
|
5027
|
+
);
|
|
5028
|
+
return out.replace(/expression\s*\(/gi, "blocked(");
|
|
5029
|
+
}
|
|
5030
|
+
|
|
4276
5031
|
// src/browser/storage/localstorage-repository.ts
|
|
4277
5032
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4278
5033
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|