@lionrapid/core 0.5.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 +64 -0
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +171 -59
- 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 +424 -147
- package/dist/browser/lionrapid.auto.dev.js.map +4 -4
- package/dist/browser/lionrapid.auto.js +424 -147
- 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 +584 -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 +584 -101
- package/dist/browser/lionrapid.iife.dev.js.map +4 -4
- package/dist/browser/lionrapid.iife.js +584 -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 +584 -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 +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/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
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
ExcludeFilter: () => ExcludeFilter,
|
|
43
43
|
HTMLSerializer: () => HTMLSerializer,
|
|
44
44
|
HandlerChain: () => HandlerChain,
|
|
45
|
+
LOCALE_TAG_REGEX: () => LOCALE_TAG_REGEX,
|
|
45
46
|
LionRapidBuilder: () => LionRapidBuilder,
|
|
46
47
|
LionRapidCore: () => LionRapidCore,
|
|
47
48
|
LocalStorageRepository: () => LocalStorageRepository,
|
|
@@ -52,6 +53,9 @@ __export(index_exports, {
|
|
|
52
53
|
MissingTranslationQueue: () => MissingTranslationQueue,
|
|
53
54
|
NetworkError: () => NetworkError,
|
|
54
55
|
NetworkRepository: () => NetworkRepository,
|
|
56
|
+
SAFE_ATTRS: () => SAFE_ATTRS,
|
|
57
|
+
SAFE_STYLE_PROPS: () => SAFE_STYLE_PROPS,
|
|
58
|
+
SAFE_TAGS: () => SAFE_TAGS,
|
|
55
59
|
ServerError: () => ServerError,
|
|
56
60
|
StaticRepository: () => StaticRepository,
|
|
57
61
|
TranslationError: () => TranslationError,
|
|
@@ -60,19 +64,33 @@ __export(index_exports, {
|
|
|
60
64
|
VERSION: () => VERSION,
|
|
61
65
|
VOID_TAGS: () => VOID_TAGS,
|
|
62
66
|
createPackageLogger: () => createPackageLogger,
|
|
67
|
+
escapeHtml: () => escapeHtml,
|
|
63
68
|
extractTagName: () => extractTagName,
|
|
64
69
|
generateAutoKey: () => generateAutoKey,
|
|
65
70
|
isClientError: () => isClientError,
|
|
66
71
|
isRetryableError: () => isRetryableError,
|
|
67
72
|
isTranslationError: () => isTranslationError,
|
|
73
|
+
localeDir: () => localeDir,
|
|
68
74
|
logger: () => logger,
|
|
75
|
+
matchLocale: () => matchLocale,
|
|
69
76
|
md5: () => md5,
|
|
70
77
|
measure: () => measure,
|
|
78
|
+
normalizeLocale: () => normalizeLocale,
|
|
79
|
+
parseAcceptLanguage: () => parseAcceptLanguage,
|
|
71
80
|
parseKeyPath: () => parseKeyPath,
|
|
72
81
|
prepareTranslationRequest: () => prepareTranslationRequest,
|
|
82
|
+
primarySubtag: () => primarySubtag,
|
|
73
83
|
readRawStyle: () => readRawStyle,
|
|
84
|
+
replaceBlockInners: () => replaceBlockInners,
|
|
74
85
|
resolveNestedKey: () => resolveNestedKey,
|
|
75
|
-
|
|
86
|
+
sanitizeContentUnit: () => sanitizeContentUnit,
|
|
87
|
+
sanitizeElement: () => sanitizeElement,
|
|
88
|
+
sanitizeOpeningTag: () => sanitizeOpeningTag,
|
|
89
|
+
sanitizeStyle: () => sanitizeStyle,
|
|
90
|
+
sanitizeStyleSheetText: () => sanitizeStyleSheetText,
|
|
91
|
+
sanitizeUrl: () => sanitizeUrl,
|
|
92
|
+
serializeOpeningTag: () => serializeOpeningTag,
|
|
93
|
+
splitBlocks: () => splitBlocks
|
|
76
94
|
});
|
|
77
95
|
module.exports = __toCommonJS(index_exports);
|
|
78
96
|
|
|
@@ -423,6 +441,57 @@ var measure = (name) => {
|
|
|
423
441
|
};
|
|
424
442
|
};
|
|
425
443
|
|
|
444
|
+
// src/utils/locale.ts
|
|
445
|
+
var LOCALE_TAG_REGEX = /^[a-z]{2,3}(?:-[a-z0-9]{2,4})*$/i;
|
|
446
|
+
var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur", "yi"]);
|
|
447
|
+
function primarySubtag(tag) {
|
|
448
|
+
return tag.split("-")[0].toLowerCase();
|
|
449
|
+
}
|
|
450
|
+
function normalizeLocale(tag) {
|
|
451
|
+
const trimmed = tag.trim();
|
|
452
|
+
if (!trimmed) return trimmed;
|
|
453
|
+
const [lang, ...rest] = trimmed.split("-");
|
|
454
|
+
const out = [lang.toLowerCase()];
|
|
455
|
+
for (const sub of rest) {
|
|
456
|
+
if (sub.length === 4) {
|
|
457
|
+
out.push(sub[0].toUpperCase() + sub.slice(1).toLowerCase());
|
|
458
|
+
} else if (sub.length === 2) {
|
|
459
|
+
out.push(sub.toUpperCase());
|
|
460
|
+
} else {
|
|
461
|
+
out.push(sub.toLowerCase());
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return out.join("-");
|
|
465
|
+
}
|
|
466
|
+
function matchLocale(requested, available, fallback) {
|
|
467
|
+
if (available.length === 0) return fallback;
|
|
468
|
+
const avail = available.map((raw) => ({
|
|
469
|
+
raw,
|
|
470
|
+
lower: normalizeLocale(raw).toLowerCase()
|
|
471
|
+
}));
|
|
472
|
+
for (const req of requested) {
|
|
473
|
+
if (!req) continue;
|
|
474
|
+
const reqLower = normalizeLocale(req).toLowerCase();
|
|
475
|
+
const exact = avail.find((a) => a.lower === reqLower);
|
|
476
|
+
if (exact) return exact.raw;
|
|
477
|
+
const primary = primarySubtag(req);
|
|
478
|
+
const byPrimary = avail.find((a) => a.lower === primary);
|
|
479
|
+
if (byPrimary) return byPrimary.raw;
|
|
480
|
+
}
|
|
481
|
+
return fallback;
|
|
482
|
+
}
|
|
483
|
+
function parseAcceptLanguage(header) {
|
|
484
|
+
return header.split(",").map((part) => {
|
|
485
|
+
const [tag, ...params] = part.trim().split(";");
|
|
486
|
+
const qParam = params.find((p) => p.trim().startsWith("q="));
|
|
487
|
+
const q = qParam ? parseFloat(qParam.trim().slice(2)) : 1;
|
|
488
|
+
return { tag: tag.trim(), q: Number.isNaN(q) ? 0 : q };
|
|
489
|
+
}).filter((x) => x.tag.length > 0 && x.tag !== "*").sort((a, b) => b.q - a.q).map((x) => x.tag);
|
|
490
|
+
}
|
|
491
|
+
function localeDir(tag) {
|
|
492
|
+
return RTL_LANGUAGES.has(primarySubtag(tag)) ? "rtl" : "ltr";
|
|
493
|
+
}
|
|
494
|
+
|
|
426
495
|
// src/base/event-emitter.ts
|
|
427
496
|
var EventEmitter = class {
|
|
428
497
|
constructor() {
|
|
@@ -711,7 +780,7 @@ var EventEmitter = class {
|
|
|
711
780
|
complete: () => {
|
|
712
781
|
this.emit("language:change:success", {
|
|
713
782
|
context: this.createContext(requestId, [...baseTrace, "complete"]),
|
|
714
|
-
payload: { oldLocale, newLocale }
|
|
783
|
+
payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
|
|
715
784
|
});
|
|
716
785
|
},
|
|
717
786
|
error: (error) => {
|
|
@@ -1121,12 +1190,11 @@ function parseNamespaceKey(key) {
|
|
|
1121
1190
|
}
|
|
1122
1191
|
|
|
1123
1192
|
// src/utils/context-utils.ts
|
|
1124
|
-
function createTranslationContext(parsedKey, currentLocale, defaultNamespace
|
|
1193
|
+
function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
|
|
1125
1194
|
const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
|
|
1126
1195
|
return {
|
|
1127
1196
|
locale: currentLocale,
|
|
1128
|
-
namespace: effectiveNamespace
|
|
1129
|
-
fallbackLocale: fallbackLocale || currentLocale
|
|
1197
|
+
namespace: effectiveNamespace
|
|
1130
1198
|
};
|
|
1131
1199
|
}
|
|
1132
1200
|
|
|
@@ -1243,7 +1311,7 @@ function flattenTranslations(translations, prefix = "", locale, namespace) {
|
|
|
1243
1311
|
}
|
|
1244
1312
|
|
|
1245
1313
|
// src/utils/translation-flow.ts
|
|
1246
|
-
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace
|
|
1314
|
+
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
|
|
1247
1315
|
const startTime = performance.now();
|
|
1248
1316
|
const { defaultValue, params } = parseTranslationParams(
|
|
1249
1317
|
defaultValueOrParams,
|
|
@@ -1253,8 +1321,7 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1253
1321
|
const context = createTranslationContext(
|
|
1254
1322
|
parsedKey,
|
|
1255
1323
|
currentLocale,
|
|
1256
|
-
defaultNamespace
|
|
1257
|
-
fallbackLocale
|
|
1324
|
+
defaultNamespace
|
|
1258
1325
|
);
|
|
1259
1326
|
const individualCacheKey = createIndividualCacheKey(
|
|
1260
1327
|
context.locale,
|
|
@@ -1276,7 +1343,6 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1276
1343
|
locale: currentLocale,
|
|
1277
1344
|
namespace: context.namespace,
|
|
1278
1345
|
// ✅ Use effective namespace (explicit or default)
|
|
1279
|
-
fallbackLocale,
|
|
1280
1346
|
individualCacheKey,
|
|
1281
1347
|
// ✅ Ready-to-use cache key
|
|
1282
1348
|
namespaceCacheKey,
|
|
@@ -2392,6 +2458,183 @@ var MarkupParser = class {
|
|
|
2392
2458
|
}
|
|
2393
2459
|
};
|
|
2394
2460
|
|
|
2461
|
+
// src/content/sanitizer/HTMLSanitizer.ts
|
|
2462
|
+
var SAFE_TAGS = /* @__PURE__ */ new Set([
|
|
2463
|
+
"a",
|
|
2464
|
+
"abbr",
|
|
2465
|
+
"b",
|
|
2466
|
+
"bdi",
|
|
2467
|
+
"bdo",
|
|
2468
|
+
"br",
|
|
2469
|
+
"cite",
|
|
2470
|
+
"code",
|
|
2471
|
+
"del",
|
|
2472
|
+
"dfn",
|
|
2473
|
+
"em",
|
|
2474
|
+
"i",
|
|
2475
|
+
"img",
|
|
2476
|
+
"ins",
|
|
2477
|
+
"kbd",
|
|
2478
|
+
"mark",
|
|
2479
|
+
"q",
|
|
2480
|
+
"s",
|
|
2481
|
+
"samp",
|
|
2482
|
+
"small",
|
|
2483
|
+
"span",
|
|
2484
|
+
"strong",
|
|
2485
|
+
"sub",
|
|
2486
|
+
"sup",
|
|
2487
|
+
"time",
|
|
2488
|
+
"u",
|
|
2489
|
+
"var",
|
|
2490
|
+
"wbr"
|
|
2491
|
+
]);
|
|
2492
|
+
var SAFE_ATTRS = /* @__PURE__ */ new Set([
|
|
2493
|
+
"alt",
|
|
2494
|
+
"cite",
|
|
2495
|
+
"class",
|
|
2496
|
+
"datetime",
|
|
2497
|
+
"dir",
|
|
2498
|
+
"height",
|
|
2499
|
+
"href",
|
|
2500
|
+
"id",
|
|
2501
|
+
"lang",
|
|
2502
|
+
"loading",
|
|
2503
|
+
"rel",
|
|
2504
|
+
"role",
|
|
2505
|
+
"src",
|
|
2506
|
+
"target",
|
|
2507
|
+
"title",
|
|
2508
|
+
"width"
|
|
2509
|
+
]);
|
|
2510
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "cite"]);
|
|
2511
|
+
var SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
2512
|
+
"color",
|
|
2513
|
+
"background-color",
|
|
2514
|
+
"font-family",
|
|
2515
|
+
"font-size",
|
|
2516
|
+
"font-style",
|
|
2517
|
+
"font-weight",
|
|
2518
|
+
"letter-spacing",
|
|
2519
|
+
"line-height",
|
|
2520
|
+
"text-decoration",
|
|
2521
|
+
"text-decoration-color",
|
|
2522
|
+
"text-decoration-line",
|
|
2523
|
+
"text-decoration-style",
|
|
2524
|
+
"text-transform",
|
|
2525
|
+
"vertical-align",
|
|
2526
|
+
"white-space",
|
|
2527
|
+
"word-spacing"
|
|
2528
|
+
]);
|
|
2529
|
+
var DANGEROUS_CSS_VALUE = /url\s*\(|expression\s*\(|image-set\s*\(|element\s*\(|paint\s*\(|javascript:|\\|[<>{}@]/i;
|
|
2530
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
2531
|
+
var NAMED_ENTITIES = {
|
|
2532
|
+
amp: "&",
|
|
2533
|
+
lt: "<",
|
|
2534
|
+
gt: ">",
|
|
2535
|
+
quot: '"',
|
|
2536
|
+
apos: "'",
|
|
2537
|
+
nbsp: "\xA0",
|
|
2538
|
+
colon: ":",
|
|
2539
|
+
semi: ";",
|
|
2540
|
+
sol: "/",
|
|
2541
|
+
bsol: "\\",
|
|
2542
|
+
Tab: " ",
|
|
2543
|
+
NewLine: "\n"
|
|
2544
|
+
};
|
|
2545
|
+
function decodeEntities2(value) {
|
|
2546
|
+
return value.replace(
|
|
2547
|
+
/&(?:#[xX]([0-9a-fA-F]+);?|#(\d+);?|([a-zA-Z]+);)/g,
|
|
2548
|
+
(full, hex, dec, name) => {
|
|
2549
|
+
if (hex) return String.fromCodePoint(parseInt(hex, 16));
|
|
2550
|
+
if (dec) return String.fromCodePoint(parseInt(dec, 10));
|
|
2551
|
+
return NAMED_ENTITIES[name] ?? full;
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
function escapeHtml(text) {
|
|
2556
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2557
|
+
}
|
|
2558
|
+
function sanitizeUrl(value) {
|
|
2559
|
+
const decoded = decodeEntities2(value);
|
|
2560
|
+
const probe = decoded.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
|
|
2561
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);
|
|
2562
|
+
if (!scheme) return value;
|
|
2563
|
+
return SAFE_SCHEMES.has(scheme[1]) ? value : null;
|
|
2564
|
+
}
|
|
2565
|
+
function sanitizeStyle(value) {
|
|
2566
|
+
return filterCssDeclarations(value, true);
|
|
2567
|
+
}
|
|
2568
|
+
function sanitizeCssDeclarations(value) {
|
|
2569
|
+
return filterCssDeclarations(value, false);
|
|
2570
|
+
}
|
|
2571
|
+
function filterCssDeclarations(value, requirePropAllowlist) {
|
|
2572
|
+
const decoded = decodeEntities2(value);
|
|
2573
|
+
const kept = [];
|
|
2574
|
+
for (const declaration of decoded.split(";")) {
|
|
2575
|
+
const colon = declaration.indexOf(":");
|
|
2576
|
+
if (colon === -1) continue;
|
|
2577
|
+
const prop = declaration.slice(0, colon).trim().toLowerCase();
|
|
2578
|
+
const val = declaration.slice(colon + 1).trim();
|
|
2579
|
+
if (!/^[a-z-]+$/.test(prop)) continue;
|
|
2580
|
+
if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;
|
|
2581
|
+
if (val === "" || DANGEROUS_CSS_VALUE.test(val)) continue;
|
|
2582
|
+
kept.push(`${prop}: ${val}`);
|
|
2583
|
+
}
|
|
2584
|
+
return kept.length > 0 ? kept.join("; ") : null;
|
|
2585
|
+
}
|
|
2586
|
+
var ATTR_PATTERN = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
|
|
2587
|
+
function sanitizeOpeningTag(rawOpen) {
|
|
2588
|
+
if (!rawOpen.startsWith("<")) return null;
|
|
2589
|
+
const tagName = extractTagName(rawOpen);
|
|
2590
|
+
if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {
|
|
2591
|
+
return null;
|
|
2592
|
+
}
|
|
2593
|
+
const end = rawOpen.endsWith("/>") ? rawOpen.length - 2 : rawOpen.endsWith(">") ? rawOpen.length - 1 : rawOpen.length;
|
|
2594
|
+
const attrSource = rawOpen.slice(1 + tagName.length, end);
|
|
2595
|
+
const attrs = {};
|
|
2596
|
+
for (const match of attrSource.matchAll(ATTR_PATTERN)) {
|
|
2597
|
+
const name = match[1].toLowerCase();
|
|
2598
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
2599
|
+
if (name in attrs) continue;
|
|
2600
|
+
if (name === "style") {
|
|
2601
|
+
const cleanStyle = sanitizeStyle(value);
|
|
2602
|
+
if (cleanStyle !== null) attrs[name] = cleanStyle;
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
const allowed = SAFE_ATTRS.has(name) || /^data-[a-z0-9-]*$/.test(name) || /^aria-[a-z0-9-]*$/.test(name);
|
|
2606
|
+
if (!allowed) continue;
|
|
2607
|
+
if (URL_ATTRS.has(name)) {
|
|
2608
|
+
if (sanitizeUrl(value) === null) continue;
|
|
2609
|
+
}
|
|
2610
|
+
attrs[name] = value;
|
|
2611
|
+
}
|
|
2612
|
+
return serializeOpeningTag(tagName, attrs);
|
|
2613
|
+
}
|
|
2614
|
+
function sanitizeContentUnit(unit) {
|
|
2615
|
+
let changed = false;
|
|
2616
|
+
const styles = {};
|
|
2617
|
+
for (const [index, styleList] of Object.entries(unit.styles)) {
|
|
2618
|
+
const raw = readRawStyle(styleList);
|
|
2619
|
+
if (raw === null) {
|
|
2620
|
+
styles[index] = styleList;
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
const safe = sanitizeOpeningTag(raw);
|
|
2624
|
+
if (safe === null) {
|
|
2625
|
+
changed = true;
|
|
2626
|
+
const rest = styleList.filter((s) => !s.startsWith("raw:"));
|
|
2627
|
+
if (rest.length > 0) styles[index] = rest;
|
|
2628
|
+
} else if (safe === raw) {
|
|
2629
|
+
styles[index] = styleList;
|
|
2630
|
+
} else {
|
|
2631
|
+
changed = true;
|
|
2632
|
+
styles[index] = styleList.map((s) => s.startsWith("raw:") ? `raw:${safe}` : s);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return changed ? { text: unit.text, styles } : unit;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2395
2638
|
// src/content/serializer/HTMLSerializer.ts
|
|
2396
2639
|
var HTMLSerializer = class {
|
|
2397
2640
|
serialize(unit) {
|
|
@@ -2401,24 +2644,25 @@ var HTMLSerializer = class {
|
|
|
2401
2644
|
let lastIndex = 0;
|
|
2402
2645
|
let match;
|
|
2403
2646
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
2404
|
-
result += text.slice(lastIndex, match.index);
|
|
2647
|
+
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
2405
2648
|
const [fullMatch, closingSlash, indexStr] = match;
|
|
2406
2649
|
const styleList = styles[indexStr] ?? [];
|
|
2407
2650
|
const rawOpen = readRawStyle(styleList);
|
|
2408
|
-
|
|
2651
|
+
const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
|
|
2652
|
+
if (rawOpen && safeOpen !== null) {
|
|
2409
2653
|
const tagName = extractTagName(rawOpen);
|
|
2410
2654
|
const isVoid = VOID_TAGS.has(tagName);
|
|
2411
2655
|
if (closingSlash) {
|
|
2412
2656
|
if (!isVoid) result += `</${tagName}>`;
|
|
2413
2657
|
} else {
|
|
2414
|
-
result +=
|
|
2658
|
+
result += safeOpen;
|
|
2415
2659
|
}
|
|
2416
2660
|
} else {
|
|
2417
2661
|
result += fullMatch;
|
|
2418
2662
|
}
|
|
2419
2663
|
lastIndex = match.index + fullMatch.length;
|
|
2420
2664
|
}
|
|
2421
|
-
result += text.slice(lastIndex);
|
|
2665
|
+
result += escapeHtml(text.slice(lastIndex));
|
|
2422
2666
|
return result;
|
|
2423
2667
|
}
|
|
2424
2668
|
};
|
|
@@ -2657,10 +2901,9 @@ var CacheManager = class {
|
|
|
2657
2901
|
var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
|
|
2658
2902
|
var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
|
|
2659
2903
|
var LionRapidCore = class {
|
|
2660
|
-
constructor(eventEmitter, handlerChain, defaultLocale = "en",
|
|
2904
|
+
constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
|
|
2661
2905
|
__publicField(this, "handlerChain");
|
|
2662
2906
|
__publicField(this, "currentLocale");
|
|
2663
|
-
__publicField(this, "fallbackLocale");
|
|
2664
2907
|
__publicField(this, "defaultNamespace");
|
|
2665
2908
|
__publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
|
|
2666
2909
|
__publicField(this, "bus");
|
|
@@ -2678,7 +2921,6 @@ var LionRapidCore = class {
|
|
|
2678
2921
|
this.bus = eventEmitter;
|
|
2679
2922
|
this.handlerChain = handlerChain;
|
|
2680
2923
|
this.currentLocale = defaultLocale;
|
|
2681
|
-
this.fallbackLocale = fallbackLocale;
|
|
2682
2924
|
this.defaultNamespace = defaultNamespace;
|
|
2683
2925
|
this.cache = new CacheManager(
|
|
2684
2926
|
this.handlerChain,
|
|
@@ -2743,8 +2985,7 @@ var LionRapidCore = class {
|
|
|
2743
2985
|
this.currentLocale,
|
|
2744
2986
|
firstArg,
|
|
2745
2987
|
secondArg,
|
|
2746
|
-
this.defaultNamespace
|
|
2747
|
-
this.fallbackLocale
|
|
2988
|
+
this.defaultNamespace
|
|
2748
2989
|
);
|
|
2749
2990
|
logger.debug(`Translation requested: ${request.cleanKey}`);
|
|
2750
2991
|
const translateBus = this.bus.translation(request);
|
|
@@ -2794,8 +3035,7 @@ var LionRapidCore = class {
|
|
|
2794
3035
|
this.currentLocale,
|
|
2795
3036
|
firstArg,
|
|
2796
3037
|
secondArg,
|
|
2797
|
-
this.defaultNamespace
|
|
2798
|
-
this.fallbackLocale
|
|
3038
|
+
this.defaultNamespace
|
|
2799
3039
|
);
|
|
2800
3040
|
logger.debug(`HTML translation requested: ${request.cleanKey}`);
|
|
2801
3041
|
const translateBus = this.bus.translation(request);
|
|
@@ -3003,8 +3243,7 @@ var LionRapidCore = class {
|
|
|
3003
3243
|
this.currentLocale,
|
|
3004
3244
|
params,
|
|
3005
3245
|
defaultValue,
|
|
3006
|
-
this.defaultNamespace
|
|
3007
|
-
this.fallbackLocale
|
|
3246
|
+
this.defaultNamespace
|
|
3008
3247
|
);
|
|
3009
3248
|
const syncResult = this.handlerChain.handleSync(request);
|
|
3010
3249
|
const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
|
|
@@ -3125,8 +3364,7 @@ var LionRapidCore = class {
|
|
|
3125
3364
|
this.currentLocale,
|
|
3126
3365
|
void 0,
|
|
3127
3366
|
void 0,
|
|
3128
|
-
this.defaultNamespace
|
|
3129
|
-
this.fallbackLocale
|
|
3367
|
+
this.defaultNamespace
|
|
3130
3368
|
);
|
|
3131
3369
|
return this.handlerChain.isCached(request);
|
|
3132
3370
|
}
|
|
@@ -3547,6 +3785,9 @@ var MissingTranslationQueue = class {
|
|
|
3547
3785
|
};
|
|
3548
3786
|
|
|
3549
3787
|
// src/builder.ts
|
|
3788
|
+
function isLoudPreloadFailure(error) {
|
|
3789
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3790
|
+
}
|
|
3550
3791
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3551
3792
|
constructor(config) {
|
|
3552
3793
|
__publicField(this, "config");
|
|
@@ -3557,7 +3798,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3557
3798
|
__publicField(this, "syncQueue", null);
|
|
3558
3799
|
this.config = {
|
|
3559
3800
|
defaultLocale: config.defaultLocale || "en",
|
|
3560
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3561
3801
|
namespace: config.namespace || "app"
|
|
3562
3802
|
};
|
|
3563
3803
|
}
|
|
@@ -3658,7 +3898,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3658
3898
|
eventEmitter,
|
|
3659
3899
|
handlerChain,
|
|
3660
3900
|
this.config.defaultLocale,
|
|
3661
|
-
this.config.fallbackLocale,
|
|
3662
3901
|
this.config.namespace
|
|
3663
3902
|
);
|
|
3664
3903
|
for (const plugin of this.plugins) {
|
|
@@ -3689,7 +3928,17 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3689
3928
|
logger.debug("Preload targets", {
|
|
3690
3929
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3691
3930
|
});
|
|
3931
|
+
let warnedLoudly = false;
|
|
3932
|
+
const warnLoudlyOnce = (namespaceKey, error) => {
|
|
3933
|
+
if (!isLoudPreloadFailure(error) || warnedLoudly) return;
|
|
3934
|
+
warnedLoudly = true;
|
|
3935
|
+
console.warn(
|
|
3936
|
+
`[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
|
|
3937
|
+
error
|
|
3938
|
+
);
|
|
3939
|
+
};
|
|
3692
3940
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3941
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3693
3942
|
try {
|
|
3694
3943
|
const result = await networkHandler.loadNamespace(
|
|
3695
3944
|
preloadConfig.locale,
|
|
@@ -3698,19 +3947,19 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3698
3947
|
if (result && result.data && result.status === 200) {
|
|
3699
3948
|
const context = {
|
|
3700
3949
|
locale: preloadConfig.locale,
|
|
3701
|
-
namespace: preloadConfig.namespace
|
|
3702
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3950
|
+
namespace: preloadConfig.namespace
|
|
3703
3951
|
};
|
|
3704
3952
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3705
|
-
logger.debug(
|
|
3706
|
-
|
|
3707
|
-
|
|
3953
|
+
logger.debug(`Preloaded: ${namespaceKey}`);
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
if (result?.error) {
|
|
3957
|
+
warnLoudlyOnce(namespaceKey, result.error);
|
|
3958
|
+
logger.warn(`Preload failed: ${namespaceKey}`, result.error);
|
|
3708
3959
|
}
|
|
3709
3960
|
} catch (error) {
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
error
|
|
3713
|
-
);
|
|
3961
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3962
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3714
3963
|
}
|
|
3715
3964
|
});
|
|
3716
3965
|
await Promise.allSettled(promises);
|
|
@@ -3724,8 +3973,16 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3724
3973
|
__publicField(this, "name", "NetworkRepository");
|
|
3725
3974
|
__publicField(this, "type", "network");
|
|
3726
3975
|
__publicField(this, "config", {});
|
|
3727
|
-
/**
|
|
3728
|
-
|
|
3976
|
+
/**
|
|
3977
|
+
* Namespace snapshot per key "locale:namespace": the server version TOGETHER
|
|
3978
|
+
* with the payload it describes, persisted as ONE localStorage entry.
|
|
3979
|
+
*
|
|
3980
|
+
* Canonical 304 contract (same as edge KV bundle + WP transient cache):
|
|
3981
|
+
* never claim freshness you can't back. If-None-Match is only sent when the
|
|
3982
|
+
* snapshot is in hand; a 304 answers with that snapshot's payload; version
|
|
3983
|
+
* and payload can never desync because they live and die together.
|
|
3984
|
+
*/
|
|
3985
|
+
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3729
3986
|
/**
|
|
3730
3987
|
* Default endpoint templates with placeholder support
|
|
3731
3988
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3838,36 +4095,50 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3838
4095
|
logger.debug("API base URL is not set");
|
|
3839
4096
|
throw new NetworkError("API base URL is not set");
|
|
3840
4097
|
}
|
|
4098
|
+
try {
|
|
4099
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
4100
|
+
} catch (error) {
|
|
4101
|
+
this.handleCaughtError(error);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Shared namespace fetch with the canonical 304 contract:
|
|
4106
|
+
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
4107
|
+
* - 304 answers with that snapshot's payload — never with "nothing".
|
|
4108
|
+
* - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
|
|
4109
|
+
* - A 200 stores version + payload atomically before either is trusted.
|
|
4110
|
+
*/
|
|
4111
|
+
async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
|
|
3841
4112
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3842
4113
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3843
4114
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
if (
|
|
3852
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3853
|
-
return
|
|
3854
|
-
}
|
|
3855
|
-
if (!response.ok) {
|
|
3856
|
-
this.handleHttpError(response, namespaceKey);
|
|
3857
|
-
}
|
|
3858
|
-
const data = await response.json();
|
|
3859
|
-
logger.debug("API response data:", data);
|
|
3860
|
-
if (!data.translations) {
|
|
3861
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3862
|
-
}
|
|
3863
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3864
|
-
if (version) {
|
|
3865
|
-
this.setVersion(namespaceKey, version);
|
|
4115
|
+
const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
|
|
4116
|
+
const extraHeaders = {};
|
|
4117
|
+
if (snapshot) {
|
|
4118
|
+
extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
|
|
4119
|
+
}
|
|
4120
|
+
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
4121
|
+
if (response.status === 304) {
|
|
4122
|
+
if (snapshot) {
|
|
4123
|
+
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
|
|
4124
|
+
return snapshot.translations;
|
|
3866
4125
|
}
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
4126
|
+
logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
|
|
4127
|
+
return this.fetchNamespaceRaw(locale, namespace, false);
|
|
4128
|
+
}
|
|
4129
|
+
if (!response.ok) {
|
|
4130
|
+
this.handleHttpError(response, namespaceKey);
|
|
4131
|
+
}
|
|
4132
|
+
const data = await response.json();
|
|
4133
|
+
logger.debug("API response data:", data);
|
|
4134
|
+
if (!data.translations) {
|
|
4135
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
3870
4136
|
}
|
|
4137
|
+
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
4138
|
+
if (version) {
|
|
4139
|
+
this.setSnapshot(namespaceKey, { version, translations: data.translations });
|
|
4140
|
+
}
|
|
4141
|
+
return data.translations;
|
|
3871
4142
|
}
|
|
3872
4143
|
/**
|
|
3873
4144
|
* Save translation to API (if supported)
|
|
@@ -3952,33 +4223,8 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3952
4223
|
logger.debug("API base URL is not set");
|
|
3953
4224
|
throw new NetworkError("API base URL is not set");
|
|
3954
4225
|
}
|
|
3955
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3956
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3957
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3958
4226
|
try {
|
|
3959
|
-
|
|
3960
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3961
|
-
if (cachedVersion) {
|
|
3962
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3963
|
-
}
|
|
3964
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3965
|
-
if (response.status === 304) {
|
|
3966
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3967
|
-
return null;
|
|
3968
|
-
}
|
|
3969
|
-
if (!response.ok) {
|
|
3970
|
-
this.handleHttpError(response, namespaceKey);
|
|
3971
|
-
}
|
|
3972
|
-
const data = await response.json();
|
|
3973
|
-
logger.debug("API response data:", data);
|
|
3974
|
-
if (!data.translations) {
|
|
3975
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3976
|
-
}
|
|
3977
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3978
|
-
if (version) {
|
|
3979
|
-
this.setVersion(namespaceKey, version);
|
|
3980
|
-
}
|
|
3981
|
-
return data.translations;
|
|
4227
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3982
4228
|
} catch (error) {
|
|
3983
4229
|
this.handleCaughtError(error);
|
|
3984
4230
|
}
|
|
@@ -4088,25 +4334,37 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4088
4334
|
getConfig() {
|
|
4089
4335
|
return this.config;
|
|
4090
4336
|
}
|
|
4091
|
-
/** Read persisted version
|
|
4092
|
-
|
|
4093
|
-
|
|
4337
|
+
/** Read a persisted snapshot (version + payload), falling back to memory.
|
|
4338
|
+
* Legacy bare-version entries and corrupt snapshots are deleted on sight —
|
|
4339
|
+
* a version without its payload must never drive a conditional request. */
|
|
4340
|
+
getSnapshot(namespaceKey) {
|
|
4341
|
+
const mem = this.namespaceSnapshots.get(namespaceKey);
|
|
4094
4342
|
if (mem) return mem;
|
|
4095
4343
|
try {
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4344
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4345
|
+
const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4346
|
+
if (raw) {
|
|
4347
|
+
const parsed = JSON.parse(raw);
|
|
4348
|
+
if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
|
|
4349
|
+
this.namespaceSnapshots.set(namespaceKey, parsed);
|
|
4350
|
+
return parsed;
|
|
4351
|
+
}
|
|
4352
|
+
localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4100
4353
|
}
|
|
4101
4354
|
} catch {
|
|
4102
4355
|
}
|
|
4103
4356
|
return void 0;
|
|
4104
4357
|
}
|
|
4105
|
-
/** Persist version
|
|
4106
|
-
|
|
4107
|
-
|
|
4358
|
+
/** Persist version + payload as ONE entry (memory + localStorage). On quota
|
|
4359
|
+
* failure the previous stored snapshot stays — still a consistent pair. */
|
|
4360
|
+
setSnapshot(namespaceKey, snapshot) {
|
|
4361
|
+
this.namespaceSnapshots.set(namespaceKey, snapshot);
|
|
4108
4362
|
try {
|
|
4109
|
-
localStorage.setItem(
|
|
4363
|
+
localStorage.setItem(
|
|
4364
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4365
|
+
JSON.stringify(snapshot)
|
|
4366
|
+
);
|
|
4367
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4110
4368
|
} catch {
|
|
4111
4369
|
}
|
|
4112
4370
|
}
|
|
@@ -4195,8 +4453,11 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4195
4453
|
}
|
|
4196
4454
|
}
|
|
4197
4455
|
};
|
|
4198
|
-
/** localStorage key prefix for persisted
|
|
4199
|
-
__publicField(_NetworkRepository, "
|
|
4456
|
+
/** localStorage key prefix for persisted snapshots (version + payload) */
|
|
4457
|
+
__publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
4458
|
+
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4459
|
+
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4460
|
+
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4200
4461
|
var NetworkRepository = _NetworkRepository;
|
|
4201
4462
|
|
|
4202
4463
|
// src/content/validator/ContentValidator.ts
|
|
@@ -4352,6 +4613,159 @@ var ContentValidator = class {
|
|
|
4352
4613
|
}
|
|
4353
4614
|
};
|
|
4354
4615
|
|
|
4616
|
+
// src/content/splitter/BlockSplitter.ts
|
|
4617
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
4618
|
+
"title",
|
|
4619
|
+
"p",
|
|
4620
|
+
"h1",
|
|
4621
|
+
"h2",
|
|
4622
|
+
"h3",
|
|
4623
|
+
"h4",
|
|
4624
|
+
"h5",
|
|
4625
|
+
"h6",
|
|
4626
|
+
"li",
|
|
4627
|
+
"td",
|
|
4628
|
+
"th",
|
|
4629
|
+
"label",
|
|
4630
|
+
"button",
|
|
4631
|
+
"figcaption",
|
|
4632
|
+
"dt",
|
|
4633
|
+
"dd",
|
|
4634
|
+
"summary",
|
|
4635
|
+
"caption"
|
|
4636
|
+
]);
|
|
4637
|
+
var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
|
|
4638
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
4639
|
+
"script",
|
|
4640
|
+
"style",
|
|
4641
|
+
"code",
|
|
4642
|
+
"pre",
|
|
4643
|
+
"textarea",
|
|
4644
|
+
"noscript",
|
|
4645
|
+
"svg"
|
|
4646
|
+
]);
|
|
4647
|
+
var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
|
|
4648
|
+
function hasNoTranslateAttr(attrsString) {
|
|
4649
|
+
if (attrsString.includes("data-no-translate")) return true;
|
|
4650
|
+
return /translate\s*=\s*["']no["']/.test(attrsString);
|
|
4651
|
+
}
|
|
4652
|
+
function containsBlockTag(innerHTML) {
|
|
4653
|
+
TAG_REGEX.lastIndex = 0;
|
|
4654
|
+
let match;
|
|
4655
|
+
while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
|
|
4656
|
+
const [, closingSlash, tagName, , selfClosingSlash] = match;
|
|
4657
|
+
if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
|
|
4658
|
+
return true;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
return false;
|
|
4662
|
+
}
|
|
4663
|
+
function findCloseTag(html, pos, tagName) {
|
|
4664
|
+
const lowerTag = tagName.toLowerCase();
|
|
4665
|
+
let depth = 1;
|
|
4666
|
+
TAG_REGEX.lastIndex = pos;
|
|
4667
|
+
let match;
|
|
4668
|
+
while ((match = TAG_REGEX.exec(html)) !== null) {
|
|
4669
|
+
const [, closingSlash, matchedTag, , selfClosingSlash] = match;
|
|
4670
|
+
if (matchedTag.toLowerCase() !== lowerTag) continue;
|
|
4671
|
+
if (closingSlash) {
|
|
4672
|
+
depth--;
|
|
4673
|
+
if (depth === 0) return match.index;
|
|
4674
|
+
} else if (!selfClosingSlash) {
|
|
4675
|
+
depth++;
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
return -1;
|
|
4679
|
+
}
|
|
4680
|
+
function splitBlocks(html) {
|
|
4681
|
+
const blocks = [];
|
|
4682
|
+
let pos = 0;
|
|
4683
|
+
let skipDepth = 0;
|
|
4684
|
+
let iter = 0;
|
|
4685
|
+
const maxIter = 1e5;
|
|
4686
|
+
const noTranslateEnds = [];
|
|
4687
|
+
while (pos < html.length && iter < maxIter) {
|
|
4688
|
+
iter++;
|
|
4689
|
+
const ltPos = html.indexOf("<", pos);
|
|
4690
|
+
if (ltPos === -1) break;
|
|
4691
|
+
if (html.startsWith("<!--", ltPos)) {
|
|
4692
|
+
const commentEnd = html.indexOf("-->", ltPos + 4);
|
|
4693
|
+
if (commentEnd === -1) break;
|
|
4694
|
+
pos = commentEnd + 3;
|
|
4695
|
+
continue;
|
|
4696
|
+
}
|
|
4697
|
+
TAG_REGEX.lastIndex = ltPos;
|
|
4698
|
+
const match = TAG_REGEX.exec(html);
|
|
4699
|
+
if (!match || match.index !== ltPos) {
|
|
4700
|
+
pos = ltPos + 1;
|
|
4701
|
+
continue;
|
|
4702
|
+
}
|
|
4703
|
+
const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
|
|
4704
|
+
const tag = tagName.toLowerCase();
|
|
4705
|
+
const afterMatch = match.index + fullMatch.length;
|
|
4706
|
+
if (SKIP_TAGS.has(tag)) {
|
|
4707
|
+
if (closingSlash) {
|
|
4708
|
+
if (skipDepth > 0) skipDepth--;
|
|
4709
|
+
} else if (!selfClosingSlash) {
|
|
4710
|
+
skipDepth++;
|
|
4711
|
+
}
|
|
4712
|
+
pos = afterMatch;
|
|
4713
|
+
continue;
|
|
4714
|
+
}
|
|
4715
|
+
if (skipDepth > 0) {
|
|
4716
|
+
pos = afterMatch;
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
|
|
4720
|
+
noTranslateEnds.pop();
|
|
4721
|
+
}
|
|
4722
|
+
if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
|
|
4723
|
+
const regionEnd = findCloseTag(html, afterMatch, tagName);
|
|
4724
|
+
if (regionEnd !== -1) {
|
|
4725
|
+
noTranslateEnds.push(regionEnd);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
if (noTranslateEnds.length > 0) {
|
|
4729
|
+
pos = afterMatch;
|
|
4730
|
+
continue;
|
|
4731
|
+
}
|
|
4732
|
+
if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
|
|
4733
|
+
const closeTagStart = findCloseTag(html, afterMatch, tagName);
|
|
4734
|
+
if (closeTagStart === -1) {
|
|
4735
|
+
pos = afterMatch;
|
|
4736
|
+
continue;
|
|
4737
|
+
}
|
|
4738
|
+
const innerHTML = html.slice(afterMatch, closeTagStart);
|
|
4739
|
+
if (containsBlockTag(innerHTML)) {
|
|
4740
|
+
pos = afterMatch;
|
|
4741
|
+
continue;
|
|
4742
|
+
}
|
|
4743
|
+
blocks.push({
|
|
4744
|
+
tag,
|
|
4745
|
+
openTag: fullMatch,
|
|
4746
|
+
attrs: attrsString,
|
|
4747
|
+
inner: innerHTML,
|
|
4748
|
+
innerStart: afterMatch,
|
|
4749
|
+
innerEnd: closeTagStart
|
|
4750
|
+
});
|
|
4751
|
+
TAG_REGEX.lastIndex = closeTagStart;
|
|
4752
|
+
const closeMatch = TAG_REGEX.exec(html);
|
|
4753
|
+
pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
|
|
4754
|
+
continue;
|
|
4755
|
+
}
|
|
4756
|
+
pos = afterMatch;
|
|
4757
|
+
}
|
|
4758
|
+
return { blocks };
|
|
4759
|
+
}
|
|
4760
|
+
function replaceBlockInners(html, replacements) {
|
|
4761
|
+
const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
|
|
4762
|
+
let result = html;
|
|
4763
|
+
for (const { block, inner } of ordered) {
|
|
4764
|
+
result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
|
|
4765
|
+
}
|
|
4766
|
+
return result;
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4355
4769
|
// src/browser/md5.ts
|
|
4356
4770
|
function safeAdd(x, y) {
|
|
4357
4771
|
const lsw = (x & 65535) + (y & 65535);
|
|
@@ -4557,6 +4971,75 @@ var ExcludeFilter = class {
|
|
|
4557
4971
|
}
|
|
4558
4972
|
};
|
|
4559
4973
|
|
|
4974
|
+
// src/browser/sanitizer.ts
|
|
4975
|
+
var REMOVE_TAGS = /* @__PURE__ */ new Set([
|
|
4976
|
+
"script",
|
|
4977
|
+
"iframe",
|
|
4978
|
+
"frame",
|
|
4979
|
+
"frameset",
|
|
4980
|
+
"object",
|
|
4981
|
+
"embed",
|
|
4982
|
+
"applet",
|
|
4983
|
+
"base",
|
|
4984
|
+
"meta",
|
|
4985
|
+
"link",
|
|
4986
|
+
"template",
|
|
4987
|
+
"portal"
|
|
4988
|
+
]);
|
|
4989
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
4990
|
+
"href",
|
|
4991
|
+
"src",
|
|
4992
|
+
"action",
|
|
4993
|
+
"formaction",
|
|
4994
|
+
"poster",
|
|
4995
|
+
"data",
|
|
4996
|
+
"xlink:href"
|
|
4997
|
+
]);
|
|
4998
|
+
function sanitizeElement(root) {
|
|
4999
|
+
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
5000
|
+
for (const el of all) {
|
|
5001
|
+
const tag = el.tagName.toLowerCase();
|
|
5002
|
+
if (REMOVE_TAGS.has(tag)) {
|
|
5003
|
+
el.remove();
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
for (const name of el.getAttributeNames()) {
|
|
5007
|
+
const lower = name.toLowerCase();
|
|
5008
|
+
if (lower.startsWith("on") || lower === "srcdoc") {
|
|
5009
|
+
el.removeAttribute(name);
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
if (URL_ATTRS2.has(lower)) {
|
|
5013
|
+
if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
|
|
5014
|
+
el.removeAttribute(name);
|
|
5015
|
+
}
|
|
5016
|
+
continue;
|
|
5017
|
+
}
|
|
5018
|
+
if (lower === "style") {
|
|
5019
|
+
const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
|
|
5020
|
+
if (clean === null) el.removeAttribute(name);
|
|
5021
|
+
else el.setAttribute(name, clean);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
function sanitizeStyleSheetText(css, baseHref) {
|
|
5027
|
+
let out = css.replace(/@import[^;]*(;|$)/gi, "");
|
|
5028
|
+
out = out.replace(
|
|
5029
|
+
/url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
|
|
5030
|
+
(full, _quote, target) => {
|
|
5031
|
+
try {
|
|
5032
|
+
const resolved = new URL(target, baseHref);
|
|
5033
|
+
if (resolved.protocol === "data:") return full;
|
|
5034
|
+
if (resolved.origin === new URL(baseHref).origin) return full;
|
|
5035
|
+
} catch {
|
|
5036
|
+
}
|
|
5037
|
+
return 'url("")';
|
|
5038
|
+
}
|
|
5039
|
+
);
|
|
5040
|
+
return out.replace(/expression\s*\(/gi, "blocked(");
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4560
5043
|
// src/browser/storage/localstorage-repository.ts
|
|
4561
5044
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4562
5045
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|