@lionrapid/core 0.3.1 → 0.4.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/browser/attribute-extractor.d.ts +54 -0
- package/dist/browser/attribute-extractor.d.ts.map +1 -0
- package/dist/browser/attribute-extractor.js +155 -0
- package/dist/browser/attribute-extractor.js.map +1 -0
- package/dist/browser/auto-init.d.ts +4 -0
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +6 -2
- package/dist/browser/auto-init.js.map +1 -1
- package/dist/browser/auto-translator.d.ts +23 -0
- package/dist/browser/auto-translator.d.ts.map +1 -1
- package/dist/browser/auto-translator.js +90 -5
- package/dist/browser/auto-translator.js.map +1 -1
- package/dist/browser/block-extractor.d.ts +8 -0
- package/dist/browser/block-extractor.d.ts.map +1 -1
- package/dist/browser/block-extractor.js +77 -0
- package/dist/browser/block-extractor.js.map +1 -1
- package/dist/browser/editor/editor-api.d.ts +22 -1
- package/dist/browser/editor/editor-api.d.ts.map +1 -1
- package/dist/browser/editor/editor-api.js +54 -19
- package/dist/browser/editor/editor-api.js.map +1 -1
- package/dist/browser/editor/editor-init.js +72 -3
- package/dist/browser/editor/editor-init.js.map +1 -1
- package/dist/browser/editor/editor-styles.d.ts +1 -1
- package/dist/browser/editor/editor-styles.d.ts.map +1 -1
- package/dist/browser/editor/editor-styles.js +38 -1
- package/dist/browser/editor/editor-styles.js.map +1 -1
- package/dist/browser/editor/inspector.d.ts +8 -0
- package/dist/browser/editor/inspector.d.ts.map +1 -1
- package/dist/browser/editor/inspector.js +14 -0
- package/dist/browser/editor/inspector.js.map +1 -1
- package/dist/browser/editor/live-preview.d.ts +1 -1
- package/dist/browser/editor/live-preview.d.ts.map +1 -1
- package/dist/browser/editor/live-preview.js +27 -16
- package/dist/browser/editor/live-preview.js.map +1 -1
- package/dist/browser/editor/locale-url.d.ts +18 -0
- package/dist/browser/editor/locale-url.d.ts.map +1 -0
- package/dist/browser/editor/locale-url.js +37 -0
- package/dist/browser/editor/locale-url.js.map +1 -0
- package/dist/browser/editor/sidebar.d.ts +6 -0
- package/dist/browser/editor/sidebar.d.ts.map +1 -1
- package/dist/browser/editor/sidebar.js +133 -4
- package/dist/browser/editor/sidebar.js.map +1 -1
- package/dist/browser/editor/toolbar.d.ts +8 -1
- package/dist/browser/editor/toolbar.d.ts.map +1 -1
- package/dist/browser/editor/toolbar.js +49 -4
- package/dist/browser/editor/toolbar.js.map +1 -1
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/lionrapid.auto.dev.js +320 -12
- package/dist/browser/lionrapid.auto.dev.js.map +4 -4
- package/dist/browser/lionrapid.auto.js +320 -12
- package/dist/browser/lionrapid.auto.js.map +4 -4
- package/dist/browser/lionrapid.auto.min.js +3 -3
- package/dist/browser/lionrapid.auto.min.js.map +4 -4
- package/dist/browser/lionrapid.editor.js +371 -36
- package/dist/browser/lionrapid.editor.js.map +3 -3
- package/dist/browser/lionrapid.editor.min.js +41 -4
- package/dist/browser/lionrapid.editor.min.js.map +4 -4
- package/dist/browser/lionrapid.esm.js +36 -2
- package/dist/browser/lionrapid.esm.js.map +2 -2
- package/dist/browser/lionrapid.esm.min.js +2 -2
- package/dist/browser/lionrapid.esm.min.js.map +3 -3
- package/dist/browser/lionrapid.iife.dev.js +36 -2
- package/dist/browser/lionrapid.iife.dev.js.map +2 -2
- package/dist/browser/lionrapid.iife.js +36 -2
- package/dist/browser/lionrapid.iife.js.map +2 -2
- package/dist/browser/lionrapid.iife.min.js +2 -2
- package/dist/browser/lionrapid.iife.min.js.map +3 -3
- package/dist/browser/lionrapid.umd.js +36 -2
- package/dist/browser/lionrapid.umd.js.map +2 -2
- package/dist/browser/lionrapid.umd.min.js +2 -2
- package/dist/browser/lionrapid.umd.min.js.map +3 -3
- package/dist/browser/md5.d.ts +3 -2
- package/dist/browser/md5.d.ts.map +1 -1
- package/dist/browser/md5.js +41 -6
- package/dist/browser/md5.js.map +1 -1
- package/dist/content/parser/MarkupParser.d.ts +0 -10
- package/dist/content/parser/MarkupParser.d.ts.map +1 -1
- package/dist/content/parser/MarkupParser.js +45 -3
- package/dist/content/parser/MarkupParser.js.map +1 -1
- package/dist/content/types.d.ts +30 -0
- package/dist/content/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -64,7 +64,7 @@ var LionRapidEditor = (() => {
|
|
|
64
64
|
color: #fff;
|
|
65
65
|
padding: 8px 12px;
|
|
66
66
|
border-radius: 8px;
|
|
67
|
-
margin-right: 8px;
|
|
67
|
+
margin-right: 8px; /* gap between panel (left) and the FAB button (right) */
|
|
68
68
|
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
|
69
69
|
white-space: nowrap;
|
|
70
70
|
}
|
|
@@ -174,6 +174,43 @@ var LionRapidEditor = (() => {
|
|
|
174
174
|
cursor: not-allowed;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
.lr-fab-browse {
|
|
178
|
+
background: none;
|
|
179
|
+
border: 1px solid rgba(255,255,255,0.3);
|
|
180
|
+
color: #fff;
|
|
181
|
+
padding: 4px 10px;
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.lr-fab-browse:hover {
|
|
188
|
+
background: rgba(255,255,255,0.1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.lr-fab-browse.active {
|
|
192
|
+
background: #f5a623;
|
|
193
|
+
border-color: #f5a623;
|
|
194
|
+
color: #1a1a2e;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.lr-link-nav {
|
|
199
|
+
background: none;
|
|
200
|
+
border: 1px solid #2d7f7f;
|
|
201
|
+
color: #2d7f7f;
|
|
202
|
+
padding: 6px 12px;
|
|
203
|
+
border-radius: 4px;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
font-size: 13px;
|
|
206
|
+
font-weight: 600;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.lr-link-nav:hover {
|
|
210
|
+
background: #2d7f7f;
|
|
211
|
+
color: #fff;
|
|
212
|
+
}
|
|
213
|
+
|
|
177
214
|
/* Workflow action buttons (Phase 3) */
|
|
178
215
|
.lr-workflow-actions {
|
|
179
216
|
display: flex;
|
|
@@ -748,15 +785,46 @@ var LionRapidEditor = (() => {
|
|
|
748
785
|
}
|
|
749
786
|
return str;
|
|
750
787
|
}
|
|
751
|
-
function
|
|
788
|
+
function utf8Bytes(str) {
|
|
789
|
+
if (typeof TextEncoder !== "undefined") {
|
|
790
|
+
return new TextEncoder().encode(str);
|
|
791
|
+
}
|
|
792
|
+
const out = [];
|
|
793
|
+
for (let i = 0; i < str.length; i++) {
|
|
794
|
+
const c = str.charCodeAt(i);
|
|
795
|
+
if (c < 128) {
|
|
796
|
+
out.push(c);
|
|
797
|
+
} else if (c < 2048) {
|
|
798
|
+
out.push(192 | c >> 6, 128 | c & 63);
|
|
799
|
+
} else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
|
|
800
|
+
const c2 = str.charCodeAt(++i);
|
|
801
|
+
const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
|
|
802
|
+
out.push(
|
|
803
|
+
240 | cp >> 18,
|
|
804
|
+
128 | cp >> 12 & 63,
|
|
805
|
+
128 | cp >> 6 & 63,
|
|
806
|
+
128 | cp & 63
|
|
807
|
+
);
|
|
808
|
+
} else {
|
|
809
|
+
out.push(
|
|
810
|
+
224 | c >> 12,
|
|
811
|
+
128 | c >> 6 & 63,
|
|
812
|
+
128 | c & 63
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return new Uint8Array(out);
|
|
817
|
+
}
|
|
818
|
+
function bytes2binl(bytes) {
|
|
752
819
|
const bin = [];
|
|
753
|
-
for (let i = 0; i <
|
|
754
|
-
bin[i >>
|
|
820
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
821
|
+
bin[i >> 2] |= bytes[i] << i % 4 * 8;
|
|
755
822
|
}
|
|
756
823
|
return bin;
|
|
757
824
|
}
|
|
758
825
|
function md5(str) {
|
|
759
|
-
|
|
826
|
+
const bytes = utf8Bytes(str);
|
|
827
|
+
return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
|
|
760
828
|
}
|
|
761
829
|
function generateAutoKey(text) {
|
|
762
830
|
const trimmed = text.trim();
|
|
@@ -838,6 +906,19 @@ var LionRapidEditor = (() => {
|
|
|
838
906
|
this.currentTarget = null;
|
|
839
907
|
this.overlay.classList.remove("active");
|
|
840
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Suspend/resume the inspector. When inactive, hover/click handlers
|
|
911
|
+
* early-return so page links navigate natively — this is what the toolbar's
|
|
912
|
+
* "Browse" mode flips so a PM can click through the site's nav to reach
|
|
913
|
+
* another page, instead of every click opening the edit drawer.
|
|
914
|
+
*/
|
|
915
|
+
setActive(active) {
|
|
916
|
+
this.active = active;
|
|
917
|
+
if (!active) this.clearHighlight();
|
|
918
|
+
}
|
|
919
|
+
isActive() {
|
|
920
|
+
return this.active;
|
|
921
|
+
}
|
|
841
922
|
handleMouseOver(e) {
|
|
842
923
|
if (!this.active) return;
|
|
843
924
|
const target = e.target;
|
|
@@ -917,7 +998,7 @@ var LionRapidEditor = (() => {
|
|
|
917
998
|
|
|
918
999
|
// src/browser/editor/toolbar.ts
|
|
919
1000
|
var Toolbar = class {
|
|
920
|
-
constructor(shadowRoot, languages, currentLocale, onLanguageChange, onExit, onCaptureSnapshot = null, captureOptions = {}) {
|
|
1001
|
+
constructor(shadowRoot, languages, currentLocale, onLanguageChange, onExit, onCaptureSnapshot = null, captureOptions = {}, onBrowseToggle = null) {
|
|
921
1002
|
__publicField(this, "el");
|
|
922
1003
|
__publicField(this, "langBtnEl");
|
|
923
1004
|
__publicField(this, "langMenuEl");
|
|
@@ -927,6 +1008,9 @@ var LionRapidEditor = (() => {
|
|
|
927
1008
|
__publicField(this, "onLanguageChange");
|
|
928
1009
|
__publicField(this, "onExit");
|
|
929
1010
|
__publicField(this, "onCaptureSnapshot");
|
|
1011
|
+
__publicField(this, "onBrowseToggle");
|
|
1012
|
+
__publicField(this, "browsing", false);
|
|
1013
|
+
__publicField(this, "browseBtnEl", null);
|
|
930
1014
|
// Drag state
|
|
931
1015
|
__publicField(this, "dragging", false);
|
|
932
1016
|
__publicField(this, "dragOffsetX", 0);
|
|
@@ -934,6 +1018,7 @@ var LionRapidEditor = (() => {
|
|
|
934
1018
|
this.onLanguageChange = onLanguageChange;
|
|
935
1019
|
this.onExit = onExit;
|
|
936
1020
|
this.onCaptureSnapshot = onCaptureSnapshot;
|
|
1021
|
+
this.onBrowseToggle = onBrowseToggle;
|
|
937
1022
|
this.currentLocale = currentLocale;
|
|
938
1023
|
const captureMode = captureOptions.mode ?? "capture";
|
|
939
1024
|
this.el = document.createElement("div");
|
|
@@ -946,7 +1031,6 @@ var LionRapidEditor = (() => {
|
|
|
946
1031
|
e.stopPropagation();
|
|
947
1032
|
this.toggle();
|
|
948
1033
|
});
|
|
949
|
-
this.el.appendChild(fabBtn);
|
|
950
1034
|
const panel = document.createElement("div");
|
|
951
1035
|
panel.className = "lr-fab-panel";
|
|
952
1036
|
const langWrap = document.createElement("div");
|
|
@@ -995,6 +1079,18 @@ var LionRapidEditor = (() => {
|
|
|
995
1079
|
});
|
|
996
1080
|
panel.appendChild(captureBtn);
|
|
997
1081
|
}
|
|
1082
|
+
if (this.onBrowseToggle) {
|
|
1083
|
+
const browseBtn = document.createElement("button");
|
|
1084
|
+
browseBtn.className = "lr-fab-browse";
|
|
1085
|
+
browseBtn.textContent = "Browse";
|
|
1086
|
+
browseBtn.title = "Pause editing so you can click links and navigate to another page";
|
|
1087
|
+
browseBtn.addEventListener("click", (e) => {
|
|
1088
|
+
e.stopPropagation();
|
|
1089
|
+
this.setBrowsing(!this.browsing);
|
|
1090
|
+
});
|
|
1091
|
+
this.browseBtnEl = browseBtn;
|
|
1092
|
+
panel.appendChild(browseBtn);
|
|
1093
|
+
}
|
|
998
1094
|
this.progressEl = document.createElement("span");
|
|
999
1095
|
this.progressEl.className = "lr-fab-progress";
|
|
1000
1096
|
panel.appendChild(this.progressEl);
|
|
@@ -1004,6 +1100,7 @@ var LionRapidEditor = (() => {
|
|
|
1004
1100
|
exitBtn.addEventListener("click", () => this.onExit());
|
|
1005
1101
|
panel.appendChild(exitBtn);
|
|
1006
1102
|
this.el.appendChild(panel);
|
|
1103
|
+
this.el.appendChild(fabBtn);
|
|
1007
1104
|
shadowRoot.appendChild(this.el);
|
|
1008
1105
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
1009
1106
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
@@ -1014,6 +1111,16 @@ var LionRapidEditor = (() => {
|
|
|
1014
1111
|
this.expanded = !this.expanded;
|
|
1015
1112
|
this.el.classList.toggle("expanded", this.expanded);
|
|
1016
1113
|
}
|
|
1114
|
+
setBrowsing(browsing) {
|
|
1115
|
+
this.browsing = browsing;
|
|
1116
|
+
if (this.browseBtnEl) {
|
|
1117
|
+
this.browseBtnEl.textContent = browsing ? "Editing" : "Browse";
|
|
1118
|
+
this.browseBtnEl.classList.toggle("active", browsing);
|
|
1119
|
+
this.browseBtnEl.title = browsing ? "Resume editing \u2014 clicks open the edit drawer again" : "Pause editing so you can click links and navigate to another page";
|
|
1120
|
+
}
|
|
1121
|
+
this.el.classList.toggle("lr-fab--browsing", browsing);
|
|
1122
|
+
if (this.onBrowseToggle) this.onBrowseToggle(browsing);
|
|
1123
|
+
}
|
|
1017
1124
|
updateProgress(translated, total) {
|
|
1018
1125
|
this.progressEl.textContent = `${translated}/${total}`;
|
|
1019
1126
|
}
|
|
@@ -1036,9 +1143,10 @@ var LionRapidEditor = (() => {
|
|
|
1036
1143
|
this.dragging = true;
|
|
1037
1144
|
const x = Math.max(0, Math.min(e.clientX - this.dragOffsetX, window.innerWidth - this.el.offsetWidth));
|
|
1038
1145
|
const y = Math.max(0, Math.min(e.clientY - this.dragOffsetY, window.innerHeight - this.el.offsetHeight));
|
|
1039
|
-
this.el.
|
|
1146
|
+
const rightOffset = window.innerWidth - x - this.el.offsetWidth;
|
|
1147
|
+
this.el.style.right = `${Math.max(0, rightOffset)}px`;
|
|
1040
1148
|
this.el.style.top = `${y}px`;
|
|
1041
|
-
this.el.style.
|
|
1149
|
+
this.el.style.left = "auto";
|
|
1042
1150
|
this.el.style.bottom = "auto";
|
|
1043
1151
|
}
|
|
1044
1152
|
handleMouseUp() {
|
|
@@ -1085,7 +1193,7 @@ var LionRapidEditor = (() => {
|
|
|
1085
1193
|
|
|
1086
1194
|
// src/browser/editor/live-preview.ts
|
|
1087
1195
|
var htmlSerializer = new HTMLSerializer();
|
|
1088
|
-
function applyLivePreview(element, text, styles, key, locale, namespace) {
|
|
1196
|
+
function applyLivePreview(element, text, styles, key, locale, namespace, sourceText, rowState) {
|
|
1089
1197
|
if (!element.hasAttribute("data-lr-original")) {
|
|
1090
1198
|
element.setAttribute("data-lr-original", element.innerHTML);
|
|
1091
1199
|
}
|
|
@@ -1096,11 +1204,17 @@ var LionRapidEditor = (() => {
|
|
|
1096
1204
|
cache.set(fullKey, text, styles);
|
|
1097
1205
|
}
|
|
1098
1206
|
const hasIndexedMarkers = /<\/?[0-9]+\/?>/.test(text);
|
|
1099
|
-
const
|
|
1207
|
+
const isMachineTranslated = rowState === "machine_translated";
|
|
1208
|
+
const fullyWrapped = sourceText ? sourceText.match(/^<(\d+)>([\s\S]*)<\/\1>$/) : null;
|
|
1100
1209
|
if (hasIndexedMarkers) {
|
|
1101
1210
|
const unit = { text, styles };
|
|
1102
1211
|
element.innerHTML = htmlSerializer.serialize(unit);
|
|
1103
|
-
} else if (
|
|
1212
|
+
} else if (isMachineTranslated && fullyWrapped && styles && Object.keys(styles).length > 0) {
|
|
1213
|
+
const idx = fullyWrapped[1];
|
|
1214
|
+
const wrappedText = `<${idx}>${text}</${idx}>`;
|
|
1215
|
+
const unit = { text: wrappedText, styles };
|
|
1216
|
+
element.innerHTML = htmlSerializer.serialize(unit);
|
|
1217
|
+
} else {
|
|
1104
1218
|
element.textContent = text;
|
|
1105
1219
|
}
|
|
1106
1220
|
element.setAttribute("data-lr-block", key);
|
|
@@ -1168,6 +1282,7 @@ var LionRapidEditor = (() => {
|
|
|
1168
1282
|
|
|
1169
1283
|
// src/browser/block-extractor.ts
|
|
1170
1284
|
var BLOCK_SELECTOR3 = "p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption";
|
|
1285
|
+
var INLINE_BLOCK_SELECTOR = "a";
|
|
1171
1286
|
var SKIP_TAGS2 = /* @__PURE__ */ new Set([
|
|
1172
1287
|
"SCRIPT",
|
|
1173
1288
|
"STYLE",
|
|
@@ -1256,8 +1371,60 @@ var LionRapidEditor = (() => {
|
|
|
1256
1371
|
}
|
|
1257
1372
|
results.push({ element: el, unit, key });
|
|
1258
1373
|
}
|
|
1374
|
+
this.extractOrphanInline(root, results, includeTranslated);
|
|
1259
1375
|
return results;
|
|
1260
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Second pass: extract inline elements (currently <a>) that have no block
|
|
1379
|
+
* ancestor. These would otherwise slip through both the block pass (not in
|
|
1380
|
+
* BLOCK_SELECTOR) and the inline-style capture (no parent block to host
|
|
1381
|
+
* them as `<1>...</1>`). Common shape: a `<div>` action group containing
|
|
1382
|
+
* only links.
|
|
1383
|
+
*/
|
|
1384
|
+
extractOrphanInline(root, results, includeTranslated) {
|
|
1385
|
+
const candidates = root.querySelectorAll(INLINE_BLOCK_SELECTOR);
|
|
1386
|
+
const elements = [];
|
|
1387
|
+
if (root instanceof Element && root.matches(INLINE_BLOCK_SELECTOR)) {
|
|
1388
|
+
elements.push(root);
|
|
1389
|
+
}
|
|
1390
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1391
|
+
elements.push(candidates[i]);
|
|
1392
|
+
}
|
|
1393
|
+
for (let i = 0; i < elements.length; i++) {
|
|
1394
|
+
const el = elements[i];
|
|
1395
|
+
if (this.isInsideSkipTag(el)) continue;
|
|
1396
|
+
if (!includeTranslated && this.hasSkipAttribute(el)) continue;
|
|
1397
|
+
if (this.matchesExcludeSelector(el)) continue;
|
|
1398
|
+
if (el.getAttribute("translate") === "no") continue;
|
|
1399
|
+
if (el.parentElement?.closest(BLOCK_SELECTOR3)) continue;
|
|
1400
|
+
if (el.parentElement?.closest(INLINE_BLOCK_SELECTOR)) continue;
|
|
1401
|
+
if (el.querySelector(BLOCK_SELECTOR3)) continue;
|
|
1402
|
+
const lrOriginal = el.getAttribute("data-lr-original");
|
|
1403
|
+
let unit;
|
|
1404
|
+
if (lrOriginal) {
|
|
1405
|
+
const temp = document.createElement(el.tagName);
|
|
1406
|
+
temp.innerHTML = lrOriginal;
|
|
1407
|
+
unit = parseBlockElement(temp);
|
|
1408
|
+
} else {
|
|
1409
|
+
unit = parseBlockElement(el);
|
|
1410
|
+
}
|
|
1411
|
+
const strippedText = unit.text.replace(/<\/?[0-9]+\/?>/g, "");
|
|
1412
|
+
if (this.excludeFilter.shouldExclude(strippedText)) continue;
|
|
1413
|
+
const lrBlock = el.getAttribute("data-lr-block");
|
|
1414
|
+
let key;
|
|
1415
|
+
if (lrBlock) {
|
|
1416
|
+
key = lrBlock;
|
|
1417
|
+
} else {
|
|
1418
|
+
const dataI18n = el.getAttribute("data-i18n");
|
|
1419
|
+
if (dataI18n) {
|
|
1420
|
+
key = dataI18n.includes(":") ? dataI18n : `common:${dataI18n}`;
|
|
1421
|
+
} else {
|
|
1422
|
+
key = generateAutoKey(unit.text);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
results.push({ element: el, unit, key });
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1261
1428
|
isInsideSkipTag(element) {
|
|
1262
1429
|
let current = element.parentElement;
|
|
1263
1430
|
while (current) {
|
|
@@ -1293,6 +1460,23 @@ var LionRapidEditor = (() => {
|
|
|
1293
1460
|
}
|
|
1294
1461
|
};
|
|
1295
1462
|
|
|
1463
|
+
// src/browser/editor/locale-url.ts
|
|
1464
|
+
function localizeUrl(href, defaultLocale) {
|
|
1465
|
+
let url;
|
|
1466
|
+
try {
|
|
1467
|
+
url = new URL(href, window.location.href);
|
|
1468
|
+
} catch {
|
|
1469
|
+
return href;
|
|
1470
|
+
}
|
|
1471
|
+
if (url.origin !== window.location.origin) return href;
|
|
1472
|
+
const currentMatch = window.location.pathname.match(/^\/([a-z]{2})(\/|$)/);
|
|
1473
|
+
const currentLocale = currentMatch ? currentMatch[1] : defaultLocale;
|
|
1474
|
+
if (currentLocale === defaultLocale) return url.href;
|
|
1475
|
+
if (/^\/[a-z]{2}(\/|$)/.test(url.pathname)) return url.href;
|
|
1476
|
+
url.pathname = "/" + currentLocale + url.pathname;
|
|
1477
|
+
return url.href;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1296
1480
|
// src/browser/editor/sidebar.ts
|
|
1297
1481
|
var htmlSerializer2 = new HTMLSerializer();
|
|
1298
1482
|
var Sidebar = class {
|
|
@@ -1361,6 +1545,39 @@ var LionRapidEditor = (() => {
|
|
|
1361
1545
|
this.refreshBlocksList();
|
|
1362
1546
|
this.emitProgress();
|
|
1363
1547
|
});
|
|
1548
|
+
if (typeof window !== "undefined") {
|
|
1549
|
+
window.__lr_dump_keys = () => {
|
|
1550
|
+
const blocks = this.blockExtractor.extract(document.body, true);
|
|
1551
|
+
const rows = blocks.map((b, i) => {
|
|
1552
|
+
const s = this.statusMap.get(b.key);
|
|
1553
|
+
return {
|
|
1554
|
+
i,
|
|
1555
|
+
key: b.key,
|
|
1556
|
+
text: b.unit.text,
|
|
1557
|
+
stylesJson: JSON.stringify(b.unit.styles),
|
|
1558
|
+
statusText: s?.text ?? "",
|
|
1559
|
+
isReady: !!s?.isReady,
|
|
1560
|
+
hasTranslation: !!s?.text
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1563
|
+
console.table(rows);
|
|
1564
|
+
return rows;
|
|
1565
|
+
};
|
|
1566
|
+
window.__lr_editor_edit = (key, text) => {
|
|
1567
|
+
const blocks = this.blockExtractor.extract(document.body, true);
|
|
1568
|
+
const block = blocks.find((b) => b.key === key);
|
|
1569
|
+
if (!block) throw new Error(`[__lr_editor_edit] no block on page with key ${key}`);
|
|
1570
|
+
this.unsavedKeys.add(key);
|
|
1571
|
+
this.api.queueSave({
|
|
1572
|
+
key,
|
|
1573
|
+
locale: this.locale,
|
|
1574
|
+
namespace: this.namespace,
|
|
1575
|
+
text,
|
|
1576
|
+
styles: block.unit.styles
|
|
1577
|
+
});
|
|
1578
|
+
};
|
|
1579
|
+
window.__lr_editor_flush = () => this.api.flush();
|
|
1580
|
+
}
|
|
1364
1581
|
shadowRoot.appendChild(this.el);
|
|
1365
1582
|
}
|
|
1366
1583
|
open(resolved) {
|
|
@@ -1395,12 +1612,33 @@ var LionRapidEditor = (() => {
|
|
|
1395
1612
|
for (const [key, status] of Object.entries(statuses)) {
|
|
1396
1613
|
this.statusMap.set(key, status);
|
|
1397
1614
|
}
|
|
1615
|
+
const divergent = [];
|
|
1616
|
+
let untranslatedCount = 0;
|
|
1398
1617
|
for (const block of blocks) {
|
|
1399
1618
|
const status = this.statusMap.get(block.key);
|
|
1400
1619
|
if (status && status.text && !status.isReady) {
|
|
1401
|
-
applyLivePreview(block.element, status.text, block.unit.styles, block.key, this.locale, this.namespace);
|
|
1620
|
+
applyLivePreview(block.element, status.text, block.unit.styles, block.key, this.locale, this.namespace, block.unit.text, status.rowState);
|
|
1621
|
+
} else if (!status || !status.text) {
|
|
1622
|
+
untranslatedCount++;
|
|
1623
|
+
const stamp = block.element.getAttribute("data-lr-block");
|
|
1624
|
+
if (stamp && stamp.startsWith("auto_")) {
|
|
1625
|
+
const recomputed = generateAutoKey(block.unit.text);
|
|
1626
|
+
if (recomputed !== stamp) {
|
|
1627
|
+
divergent.push({ stamped: stamp, recomputed, text: block.unit.text.slice(0, 80) });
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1402
1630
|
}
|
|
1403
1631
|
}
|
|
1632
|
+
if (divergent.length > 0) {
|
|
1633
|
+
console.warn(
|
|
1634
|
+
`[LionRapid Editor] ${divergent.length} block(s) where the client-side auto_ key does NOT match the server's data-lr-block stamp \u2014 JS and server key generation are diverging for these inputs. Sample (first 5):`,
|
|
1635
|
+
divergent.slice(0, 5)
|
|
1636
|
+
);
|
|
1637
|
+
} else if (untranslatedCount > 0) {
|
|
1638
|
+
console.debug(
|
|
1639
|
+
`[LionRapid Editor] ${untranslatedCount} block(s) untranslated (no server text yet) \u2014 normal for in-progress pages.`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1404
1642
|
this.emitProgress();
|
|
1405
1643
|
}
|
|
1406
1644
|
getTranslatedCount() {
|
|
@@ -1453,6 +1691,7 @@ var LionRapidEditor = (() => {
|
|
|
1453
1691
|
origEl.innerHTML = this.renderTextWithTags(unit.text);
|
|
1454
1692
|
return origEl;
|
|
1455
1693
|
});
|
|
1694
|
+
this.renderLinkNav(element);
|
|
1456
1695
|
const isSourceLocale = this.locale === this.defaultLocale;
|
|
1457
1696
|
const isLocked = isSourceLocale || status?.isLocked || status?.isReady || false;
|
|
1458
1697
|
const textarea = document.createElement("textarea");
|
|
@@ -1484,7 +1723,7 @@ var LionRapidEditor = (() => {
|
|
|
1484
1723
|
const text = textarea.value;
|
|
1485
1724
|
previewEl.innerHTML = this.renderPreview(text, unit.styles);
|
|
1486
1725
|
if (text) {
|
|
1487
|
-
applyLivePreview(element, text, unit.styles, key, this.locale, this.namespace);
|
|
1726
|
+
applyLivePreview(element, text, unit.styles, key, this.locale, this.namespace, unit.text, "edited");
|
|
1488
1727
|
}
|
|
1489
1728
|
this.unsavedKeys.add(key);
|
|
1490
1729
|
this.api.queueSave({
|
|
@@ -1652,6 +1891,33 @@ var LionRapidEditor = (() => {
|
|
|
1652
1891
|
}
|
|
1653
1892
|
this.emitProgress();
|
|
1654
1893
|
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Render an "Open linked page →" button when the selected block is or
|
|
1896
|
+
* contains a same-origin link. Cross-origin links are skipped — the editor
|
|
1897
|
+
* session only applies to this site. No-op when there's no usable link.
|
|
1898
|
+
*/
|
|
1899
|
+
renderLinkNav(element) {
|
|
1900
|
+
const anchor = element.matches?.("a[href]") ? element : element.querySelector?.("a[href]");
|
|
1901
|
+
if (!anchor) return;
|
|
1902
|
+
let dest;
|
|
1903
|
+
try {
|
|
1904
|
+
dest = new URL(anchor.href, window.location.href);
|
|
1905
|
+
} catch {
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (dest.origin !== window.location.origin) return;
|
|
1909
|
+
if (dest.pathname === window.location.pathname && !dest.hash) return;
|
|
1910
|
+
this.addField("Linked page", () => {
|
|
1911
|
+
const btn = document.createElement("button");
|
|
1912
|
+
btn.className = "lr-link-nav";
|
|
1913
|
+
btn.textContent = "Open linked page \u2192";
|
|
1914
|
+
btn.title = dest.pathname + dest.search;
|
|
1915
|
+
btn.addEventListener("click", () => {
|
|
1916
|
+
window.location.href = localizeUrl(dest.href, this.defaultLocale);
|
|
1917
|
+
});
|
|
1918
|
+
return btn;
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1655
1921
|
addField(label, contentFn) {
|
|
1656
1922
|
const field = document.createElement("div");
|
|
1657
1923
|
field.className = "lr-field";
|
|
@@ -1788,6 +2054,8 @@ var LionRapidEditor = (() => {
|
|
|
1788
2054
|
__publicField(this, "handleVisibility", () => {
|
|
1789
2055
|
if (document.visibilityState === "hidden") {
|
|
1790
2056
|
this.flush();
|
|
2057
|
+
} else if (document.visibilityState === "visible") {
|
|
2058
|
+
this.maybeRenewSession();
|
|
1791
2059
|
}
|
|
1792
2060
|
});
|
|
1793
2061
|
this.apiBase = apiBase.replace(/\/$/, "");
|
|
@@ -1851,14 +2119,11 @@ var LionRapidEditor = (() => {
|
|
|
1851
2119
|
const items = [...this.queue];
|
|
1852
2120
|
this.onStatus("saving");
|
|
1853
2121
|
try {
|
|
1854
|
-
const response = await
|
|
2122
|
+
const response = await this.authFetch(
|
|
1855
2123
|
`${this.apiBase}/api/integrations/targets/batch`,
|
|
1856
2124
|
{
|
|
1857
2125
|
method: "PUT",
|
|
1858
|
-
headers: {
|
|
1859
|
-
"Content-Type": "application/json",
|
|
1860
|
-
"Authorization": `Bearer ${this.sessionToken}`
|
|
1861
|
-
},
|
|
2126
|
+
headers: { "Content-Type": "application/json" },
|
|
1862
2127
|
body: JSON.stringify({
|
|
1863
2128
|
translations: items.map((t) => ({
|
|
1864
2129
|
key: `${t.locale}:${t.namespace}:${t.key}`,
|
|
@@ -1900,12 +2165,9 @@ var LionRapidEditor = (() => {
|
|
|
1900
2165
|
const batch = keys.slice(0, 200);
|
|
1901
2166
|
const params = new URLSearchParams({ locale, namespace, keys: batch.join(",") });
|
|
1902
2167
|
try {
|
|
1903
|
-
const response = await
|
|
2168
|
+
const response = await this.authFetch(
|
|
1904
2169
|
`${this.apiBase}/api/integrations/targets/status?${params}`,
|
|
1905
|
-
{
|
|
1906
|
-
headers: { "Authorization": `Bearer ${this.sessionToken}` },
|
|
1907
|
-
signal: this.statusAbort.signal
|
|
1908
|
-
}
|
|
2170
|
+
{ signal: this.statusAbort.signal }
|
|
1909
2171
|
);
|
|
1910
2172
|
if (response.status === 401) {
|
|
1911
2173
|
this.handleSessionExpired();
|
|
@@ -1932,9 +2194,7 @@ var LionRapidEditor = (() => {
|
|
|
1932
2194
|
if (this.workflowInfoPromise) return this.workflowInfoPromise;
|
|
1933
2195
|
this.workflowInfoPromise = (async () => {
|
|
1934
2196
|
try {
|
|
1935
|
-
const response = await
|
|
1936
|
-
headers: { "Authorization": `Bearer ${this.sessionToken}` }
|
|
1937
|
-
});
|
|
2197
|
+
const response = await this.authFetch(`${this.apiBase}/api/integrations/editor/workflow`);
|
|
1938
2198
|
if (response.status === 401) {
|
|
1939
2199
|
this.handleSessionExpired();
|
|
1940
2200
|
return null;
|
|
@@ -1957,15 +2217,30 @@ var LionRapidEditor = (() => {
|
|
|
1957
2217
|
// Session renewal
|
|
1958
2218
|
// ============================================
|
|
1959
2219
|
/**
|
|
1960
|
-
* Check if session is within
|
|
2220
|
+
* Check if session is within 5min of expiry, and renew if so.
|
|
2221
|
+
*
|
|
2222
|
+
* The threshold is intentionally generous because background tabs throttle
|
|
2223
|
+
* setInterval to once-per-minute (Chrome) or even less aggressively, and
|
|
2224
|
+
* a sleeping laptop pauses timers entirely. Renewing early gives us
|
|
2225
|
+
* headroom for those cases — the renewal interval still runs before the
|
|
2226
|
+
* 15min server-side TTL elapses even if it fires a minute or two late.
|
|
1961
2227
|
*/
|
|
1962
2228
|
async maybeRenewSession() {
|
|
1963
2229
|
const exp = this.getSessionExp();
|
|
1964
2230
|
if (!exp) return;
|
|
1965
2231
|
const timeLeft = exp * 1e3 - Date.now();
|
|
1966
|
-
if (timeLeft >
|
|
2232
|
+
if (timeLeft > 3e5) return;
|
|
1967
2233
|
await this.renewSession();
|
|
1968
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Attempt one renewal and signal whether the token actually changed.
|
|
2237
|
+
* Used by the 401-retry path in API calls.
|
|
2238
|
+
*/
|
|
2239
|
+
async tryRenew() {
|
|
2240
|
+
const before = this.sessionToken;
|
|
2241
|
+
await this.renewSession();
|
|
2242
|
+
return this.sessionToken !== before;
|
|
2243
|
+
}
|
|
1969
2244
|
async renewSession() {
|
|
1970
2245
|
try {
|
|
1971
2246
|
const response = await fetch(`${this.apiBase}/api/editor/renew`, {
|
|
@@ -2008,6 +2283,30 @@ var LionRapidEditor = (() => {
|
|
|
2008
2283
|
return null;
|
|
2009
2284
|
}
|
|
2010
2285
|
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Authenticated fetch with one-shot 401 retry. If the request comes back
|
|
2288
|
+
* 401, we attempt a session renewal and replay the request once with the
|
|
2289
|
+
* new token. If renewal fails (token past the server's 2min grace), the
|
|
2290
|
+
* caller still gets a 401 and falls through to handleSessionExpired.
|
|
2291
|
+
*
|
|
2292
|
+
* Always passes `init.signal` straight through so abortable callers
|
|
2293
|
+
* (e.g. fetchStatus) still cancel correctly.
|
|
2294
|
+
*/
|
|
2295
|
+
async authFetch(url, init = {}) {
|
|
2296
|
+
const buildHeaders = () => {
|
|
2297
|
+
const h = new Headers(init.headers);
|
|
2298
|
+
h.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
2299
|
+
return h;
|
|
2300
|
+
};
|
|
2301
|
+
let response = await fetch(url, { ...init, headers: buildHeaders() });
|
|
2302
|
+
if (response.status === 401) {
|
|
2303
|
+
const renewed = await this.tryRenew();
|
|
2304
|
+
if (renewed) {
|
|
2305
|
+
response = await fetch(url, { ...init, headers: buildHeaders() });
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return response;
|
|
2309
|
+
}
|
|
2011
2310
|
handleSessionExpired() {
|
|
2012
2311
|
document.cookie = "__lr_editor_active=; path=/; SameSite=Lax; Max-Age=0";
|
|
2013
2312
|
this.onStatus("error", "Session expired. Please reopen the editor from the dashboard.");
|
|
@@ -2163,12 +2462,9 @@ var LionRapidEditor = (() => {
|
|
|
2163
2462
|
*/
|
|
2164
2463
|
async transitionTarget(composite, action) {
|
|
2165
2464
|
try {
|
|
2166
|
-
const response = await
|
|
2465
|
+
const response = await this.authFetch(
|
|
2167
2466
|
`${this.apiBase}/api/integrations/targets/${encodeURIComponent(composite)}/${action}`,
|
|
2168
|
-
{
|
|
2169
|
-
method: "POST",
|
|
2170
|
-
headers: { "Authorization": `Bearer ${this.sessionToken}` }
|
|
2171
|
-
}
|
|
2467
|
+
{ method: "POST" }
|
|
2172
2468
|
);
|
|
2173
2469
|
if (response.status === 401) {
|
|
2174
2470
|
this.handleSessionExpired();
|
|
@@ -2450,6 +2746,20 @@ var LionRapidEditor = (() => {
|
|
|
2450
2746
|
}
|
|
2451
2747
|
|
|
2452
2748
|
// src/browser/editor/editor-init.ts
|
|
2749
|
+
if (typeof window !== "undefined") {
|
|
2750
|
+
window.__lr_dump_keys = () => {
|
|
2751
|
+
const extractor = new BlockExtractor();
|
|
2752
|
+
const blocks = extractor.extract(document.body, true);
|
|
2753
|
+
const rows = blocks.map((b, i) => ({
|
|
2754
|
+
i,
|
|
2755
|
+
key: b.key,
|
|
2756
|
+
text: b.unit.text,
|
|
2757
|
+
stylesJson: JSON.stringify(b.unit.styles)
|
|
2758
|
+
}));
|
|
2759
|
+
console.table(rows);
|
|
2760
|
+
return rows;
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2453
2763
|
var SESSION_KEY = "__lr_editor_session";
|
|
2454
2764
|
var PREVIEW_COOKIE = "__lr_editor_active";
|
|
2455
2765
|
function setPreviewCookie() {
|
|
@@ -2645,7 +2955,6 @@ var LionRapidEditor = (() => {
|
|
|
2645
2955
|
const stripped = url2.pathname.replace(/^\/[a-z]{2}(\/|$)/, "/");
|
|
2646
2956
|
const prefix = newLocale === defaultLocale ? "" : `/${newLocale}`;
|
|
2647
2957
|
url2.pathname = prefix + (stripped === "/" && prefix ? "" : stripped);
|
|
2648
|
-
url2.searchParams.set("__lr_editor", grant);
|
|
2649
2958
|
window.location.href = url2.toString();
|
|
2650
2959
|
return;
|
|
2651
2960
|
}
|
|
@@ -2699,11 +3008,37 @@ var LionRapidEditor = (() => {
|
|
|
2699
3008
|
},
|
|
2700
3009
|
// When in recapture mode, surface it to the toolbar so the button label
|
|
2701
3010
|
// can change from "Capture" to "Save re-capture".
|
|
2702
|
-
recaptureUuid ? { mode: "recapture" } : void 0
|
|
3011
|
+
recaptureUuid ? { mode: "recapture" } : void 0,
|
|
3012
|
+
// Browse-mode toggle. Suspending the inspector lets the PM click the
|
|
3013
|
+
// site's own links to reach another page; the session rides along via the
|
|
3014
|
+
// sessionStorage JWT + __lr_editor_active cookie, so the editor re-activates
|
|
3015
|
+
// on the destination. Disabled in snapshot viewer mode (static sandbox,
|
|
3016
|
+
// nothing to navigate to).
|
|
3017
|
+
snapshotUuid ? null : (browsing) => {
|
|
3018
|
+
inspector.setActive(!browsing);
|
|
3019
|
+
if (browsing) sidebar.close();
|
|
3020
|
+
}
|
|
2703
3021
|
);
|
|
2704
3022
|
sidebar.setProgressCallback((translated, total) => {
|
|
2705
3023
|
toolbar.updateProgress(translated, total);
|
|
2706
3024
|
});
|
|
3025
|
+
document.addEventListener("click", (e) => {
|
|
3026
|
+
if (e.defaultPrevented) return;
|
|
3027
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
3028
|
+
if (e.button !== 0) return;
|
|
3029
|
+
const target = e.target;
|
|
3030
|
+
if (!target?.closest) return;
|
|
3031
|
+
if (target.closest("#__lionrapid-editor")) return;
|
|
3032
|
+
const a = target.closest("a[href]");
|
|
3033
|
+
if (!a) return;
|
|
3034
|
+
if (a.target && a.target !== "_self") return;
|
|
3035
|
+
if (a.hasAttribute("download")) return;
|
|
3036
|
+
const localized = localizeUrl(a.href, defaultLocale);
|
|
3037
|
+
if (localized !== a.href) {
|
|
3038
|
+
e.preventDefault();
|
|
3039
|
+
window.location.href = localized;
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
2707
3042
|
sidebar.hydrateStatus().then(() => {
|
|
2708
3043
|
toolbar.updateProgress(sidebar.getTranslatedCount(), sidebar.getTotalCount());
|
|
2709
3044
|
});
|