@lionrapid/core 0.1.1 → 0.1.3-beta.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/editor/editor-api.d.ts +52 -0
- package/dist/browser/editor/editor-api.d.ts.map +1 -1
- package/dist/browser/editor/editor-api.js +136 -0
- package/dist/browser/editor/editor-api.js.map +1 -1
- package/dist/browser/editor/editor-init.js +145 -6
- 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 +29 -0
- package/dist/browser/editor/editor-styles.js.map +1 -1
- package/dist/browser/editor/inspector.d.ts +5 -1
- package/dist/browser/editor/inspector.d.ts.map +1 -1
- package/dist/browser/editor/inspector.js +32 -1
- package/dist/browser/editor/inspector.js.map +1 -1
- package/dist/browser/editor/snapshot-capture.d.ts +57 -0
- package/dist/browser/editor/snapshot-capture.d.ts.map +1 -0
- package/dist/browser/editor/snapshot-capture.js +249 -0
- package/dist/browser/editor/snapshot-capture.js.map +1 -0
- package/dist/browser/editor/snapshot-viewer.d.ts +35 -0
- package/dist/browser/editor/snapshot-viewer.d.ts.map +1 -0
- package/dist/browser/editor/snapshot-viewer.js +90 -0
- package/dist/browser/editor/snapshot-viewer.js.map +1 -0
- package/dist/browser/editor/toolbar.d.ts +7 -1
- package/dist/browser/editor/toolbar.d.ts.map +1 -1
- package/dist/browser/editor/toolbar.js +26 -1
- package/dist/browser/editor/toolbar.js.map +1 -1
- package/dist/browser/lionrapid.editor.js +532 -6
- package/dist/browser/lionrapid.editor.js.map +3 -3
- package/dist/browser/lionrapid.editor.min.js +32 -2
- package/dist/browser/lionrapid.editor.min.js.map +4 -4
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Shows overlay on hover, resolves key on click, opens sidebar.
|
|
4
4
|
*/
|
|
5
5
|
import { resolveKey } from './key-resolver.js';
|
|
6
|
+
import { parseBlockElement } from '../markup-parser.js';
|
|
6
7
|
const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA', 'NOSCRIPT', 'SVG']);
|
|
7
8
|
const BLOCK_SELECTOR = 'p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption';
|
|
8
9
|
export class Inspector {
|
|
@@ -11,8 +12,16 @@ export class Inspector {
|
|
|
11
12
|
onSelect;
|
|
12
13
|
rafId = null;
|
|
13
14
|
active = true;
|
|
14
|
-
|
|
15
|
+
rootElement;
|
|
16
|
+
/** When set, clicks resolve keys from this map (snapshot viewer mode)
|
|
17
|
+
* instead of re-computing from the (redacted) DOM text. */
|
|
18
|
+
snapshotKeyMap;
|
|
19
|
+
constructor(shadowRoot, onSelect, rootElement, snapshotKeyMap) {
|
|
15
20
|
this.onSelect = onSelect;
|
|
21
|
+
// Default scope is the live document body; snapshot viewer passes a
|
|
22
|
+
// sandbox wrapper so blocks outside it are ignored.
|
|
23
|
+
this.rootElement = rootElement || document.body;
|
|
24
|
+
this.snapshotKeyMap = snapshotKeyMap || null;
|
|
16
25
|
// Create overlay inside shadow DOM
|
|
17
26
|
this.overlay = document.createElement('div');
|
|
18
27
|
this.overlay.className = 'lr-overlay';
|
|
@@ -50,6 +59,9 @@ export class Inspector {
|
|
|
50
59
|
// Skip editor UI elements (inside shadow DOM won't trigger this, but be safe)
|
|
51
60
|
if (target.closest('#__lionrapid-editor'))
|
|
52
61
|
return;
|
|
62
|
+
// Skip anything outside the scope root (snapshot viewer mode)
|
|
63
|
+
if (!this.rootElement.contains(target))
|
|
64
|
+
return;
|
|
53
65
|
// Skip non-translatable elements
|
|
54
66
|
if (this.shouldSkip(target)) {
|
|
55
67
|
this.clearHighlight();
|
|
@@ -75,6 +87,9 @@ export class Inspector {
|
|
|
75
87
|
// Skip editor UI
|
|
76
88
|
if (target.closest('#__lionrapid-editor'))
|
|
77
89
|
return;
|
|
90
|
+
// Skip anything outside the scope root (snapshot viewer mode)
|
|
91
|
+
if (!this.rootElement.contains(target))
|
|
92
|
+
return;
|
|
78
93
|
// Find the block to resolve
|
|
79
94
|
const block = target.closest(BLOCK_SELECTOR) || target.closest('[data-lr-block]');
|
|
80
95
|
if (!block)
|
|
@@ -83,6 +98,22 @@ export class Inspector {
|
|
|
83
98
|
return;
|
|
84
99
|
e.preventDefault();
|
|
85
100
|
e.stopPropagation();
|
|
101
|
+
// Snapshot viewer mode: look up pre-computed key by data-lr-snap-idx
|
|
102
|
+
// instead of re-running resolveKey on the redacted text.
|
|
103
|
+
if (this.snapshotKeyMap) {
|
|
104
|
+
const idxAttr = block.getAttribute('data-lr-snap-idx');
|
|
105
|
+
if (idxAttr !== null) {
|
|
106
|
+
const idx = parseInt(idxAttr, 10);
|
|
107
|
+
const key = this.snapshotKeyMap.get(idx);
|
|
108
|
+
if (key) {
|
|
109
|
+
const unit = parseBlockElement(block);
|
|
110
|
+
this.onSelect({ key, unit, translatedUnit: null, element: block });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Block has no snap-idx — not a pre-extracted block, skip
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
86
117
|
const resolved = resolveKey(block);
|
|
87
118
|
if (resolved) {
|
|
88
119
|
this.onSelect(resolved);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../src/browser/editor/inspector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAe,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../src/browser/editor/inspector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Capture — Serializes the live DOM with PII redaction.
|
|
3
|
+
*
|
|
4
|
+
* Used by the visual editor to let an authenticated admin capture an auth page
|
|
5
|
+
* so a translator (without credentials) can later translate it against a
|
|
6
|
+
* frozen copy. The serialized HTML is uploaded to the server and retrieved in
|
|
7
|
+
* viewer mode (?__lr_snapshot={uuid}) where Inspector + Sidebar boot against
|
|
8
|
+
* the snapshot root instead of document.body.
|
|
9
|
+
*
|
|
10
|
+
* Redaction strategy (block-level; Phase 1):
|
|
11
|
+
* - Remove: <script>, <noscript>, <iframe>, <embed>, <object>, editor host
|
|
12
|
+
* - Clear all form <input>, <textarea>, <select> values
|
|
13
|
+
* - Replace [data-pii] / [data-sensitive] text with [REDACTED]
|
|
14
|
+
* - Strip all inline event handlers (onclick, onmouseover, etc.)
|
|
15
|
+
* - Replace text nodes that fully match an email/phone/number/URL with [REDACTED]
|
|
16
|
+
* - Strip CSRF/CSP meta tags and auth tokens from link/script URLs
|
|
17
|
+
*
|
|
18
|
+
* Fine-grained text-node redaction (mid-sentence PII) is deferred to Phase 4.
|
|
19
|
+
*/
|
|
20
|
+
export interface SnapshotTextEntry {
|
|
21
|
+
/** auto_xxxxxxxxxxxx — 12-char md5 hash, generated BEFORE redaction so it matches the live page */
|
|
22
|
+
key: string;
|
|
23
|
+
/** The source tag name (P, H1, LI, etc.) — useful for future analytics */
|
|
24
|
+
tagName: string;
|
|
25
|
+
/** Index of this block among all block elements in the snapshot DOM. Used by
|
|
26
|
+
* the viewer to map a clicked element back to its pre-computed key without
|
|
27
|
+
* re-running BlockExtractor on the redacted text. */
|
|
28
|
+
blockIndex: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SnapshotViewport {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
devicePixelRatio: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CapturedSnapshot {
|
|
36
|
+
html: string;
|
|
37
|
+
textEntries: SnapshotTextEntry[];
|
|
38
|
+
pageUrl: string;
|
|
39
|
+
title: string;
|
|
40
|
+
viewport: SnapshotViewport;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Capture the current page as a redacted snapshot.
|
|
44
|
+
*
|
|
45
|
+
* Pure function — does NOT mutate the live DOM. Clones first, then:
|
|
46
|
+
* 1. Extracts block keys BEFORE redaction (so auto_keys match the live page)
|
|
47
|
+
* 2. Redacts the clone (strips PII, scripts, forms, etc.)
|
|
48
|
+
* 3. Stamps each block element with a data-lr-snap-idx attribute so the
|
|
49
|
+
* viewer can map a click back to the pre-computed key
|
|
50
|
+
* 4. Serializes to HTML
|
|
51
|
+
*
|
|
52
|
+
* The textEntries carry pre-computed keys (no source text) and the HTML is
|
|
53
|
+
* safe to store. The viewer uses textEntries to look up the correct key by
|
|
54
|
+
* index when the translator clicks a block.
|
|
55
|
+
*/
|
|
56
|
+
export declare function captureSnapshot(): CapturedSnapshot;
|
|
57
|
+
//# sourceMappingURL=snapshot-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-capture.d.ts","sourceRoot":"","sources":["../../../src/browser/editor/snapshot-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,MAAM,WAAW,iBAAiB;IAChC,mGAAmG;IACnG,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB;;0DAEsD;IACtD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAqClD"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Capture — Serializes the live DOM with PII redaction.
|
|
3
|
+
*
|
|
4
|
+
* Used by the visual editor to let an authenticated admin capture an auth page
|
|
5
|
+
* so a translator (without credentials) can later translate it against a
|
|
6
|
+
* frozen copy. The serialized HTML is uploaded to the server and retrieved in
|
|
7
|
+
* viewer mode (?__lr_snapshot={uuid}) where Inspector + Sidebar boot against
|
|
8
|
+
* the snapshot root instead of document.body.
|
|
9
|
+
*
|
|
10
|
+
* Redaction strategy (block-level; Phase 1):
|
|
11
|
+
* - Remove: <script>, <noscript>, <iframe>, <embed>, <object>, editor host
|
|
12
|
+
* - Clear all form <input>, <textarea>, <select> values
|
|
13
|
+
* - Replace [data-pii] / [data-sensitive] text with [REDACTED]
|
|
14
|
+
* - Strip all inline event handlers (onclick, onmouseover, etc.)
|
|
15
|
+
* - Replace text nodes that fully match an email/phone/number/URL with [REDACTED]
|
|
16
|
+
* - Strip CSRF/CSP meta tags and auth tokens from link/script URLs
|
|
17
|
+
*
|
|
18
|
+
* Fine-grained text-node redaction (mid-sentence PII) is deferred to Phase 4.
|
|
19
|
+
*/
|
|
20
|
+
import { BlockExtractor } from '../block-extractor.js';
|
|
21
|
+
import { ExcludeFilter } from '../exclude-filter.js';
|
|
22
|
+
const REDACTED = '[REDACTED]';
|
|
23
|
+
/**
|
|
24
|
+
* Capture the current page as a redacted snapshot.
|
|
25
|
+
*
|
|
26
|
+
* Pure function — does NOT mutate the live DOM. Clones first, then:
|
|
27
|
+
* 1. Extracts block keys BEFORE redaction (so auto_keys match the live page)
|
|
28
|
+
* 2. Redacts the clone (strips PII, scripts, forms, etc.)
|
|
29
|
+
* 3. Stamps each block element with a data-lr-snap-idx attribute so the
|
|
30
|
+
* viewer can map a click back to the pre-computed key
|
|
31
|
+
* 4. Serializes to HTML
|
|
32
|
+
*
|
|
33
|
+
* The textEntries carry pre-computed keys (no source text) and the HTML is
|
|
34
|
+
* safe to store. The viewer uses textEntries to look up the correct key by
|
|
35
|
+
* index when the translator clicks a block.
|
|
36
|
+
*/
|
|
37
|
+
export function captureSnapshot() {
|
|
38
|
+
// 1. Clone the entire document element.
|
|
39
|
+
const clone = document.documentElement.cloneNode(true);
|
|
40
|
+
// 2. Extract blocks BEFORE redaction so keys match the live page.
|
|
41
|
+
// The real text is used for md5 hashing, producing the same auto_keys
|
|
42
|
+
// the edge proxy / auto-init generates on the live page.
|
|
43
|
+
const textEntries = extractTextEntries(clone);
|
|
44
|
+
// 3. Stamp each extracted block with a data attribute linking it to its
|
|
45
|
+
// entry index. The viewer uses this to resolve clicks to pre-computed
|
|
46
|
+
// keys without re-running BlockExtractor on the redacted text.
|
|
47
|
+
// (extractTextEntries stores the element reference so we can stamp it)
|
|
48
|
+
// 4. Redact the clone. Order: remove dangerous elements first so we don't
|
|
49
|
+
// waste time walking their subtrees.
|
|
50
|
+
removeDangerousElements(clone);
|
|
51
|
+
clearFormValues(clone);
|
|
52
|
+
redactSensitiveElements(clone);
|
|
53
|
+
stripInlineEventHandlers(clone);
|
|
54
|
+
redactTextNodePII(clone);
|
|
55
|
+
stripSensitiveMetaAndHeaders(clone);
|
|
56
|
+
// 5. Serialize.
|
|
57
|
+
const html = '<!DOCTYPE html>\n' + clone.outerHTML;
|
|
58
|
+
return {
|
|
59
|
+
html,
|
|
60
|
+
textEntries,
|
|
61
|
+
pageUrl: window.location.href,
|
|
62
|
+
title: document.title,
|
|
63
|
+
viewport: {
|
|
64
|
+
width: window.innerWidth,
|
|
65
|
+
height: window.innerHeight,
|
|
66
|
+
devicePixelRatio: window.devicePixelRatio || 1,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove elements that should never appear in a snapshot:
|
|
72
|
+
* - <script>, <noscript>: could re-execute or leak source
|
|
73
|
+
* - <iframe>, <embed>, <object>: third-party content, likely auth-bound
|
|
74
|
+
* - #__lionrapid-editor: the editor's own shadow host
|
|
75
|
+
* - <link rel="prefetch"> / <link rel="preload"> that may leak auth URLs
|
|
76
|
+
*/
|
|
77
|
+
function removeDangerousElements(root) {
|
|
78
|
+
const selectors = [
|
|
79
|
+
'script',
|
|
80
|
+
'noscript',
|
|
81
|
+
'iframe',
|
|
82
|
+
'embed',
|
|
83
|
+
'object',
|
|
84
|
+
'#__lionrapid-editor',
|
|
85
|
+
'link[rel="prefetch"]',
|
|
86
|
+
'link[rel="preload"]',
|
|
87
|
+
'link[rel="modulepreload"]',
|
|
88
|
+
];
|
|
89
|
+
for (const selector of selectors) {
|
|
90
|
+
const matches = root.querySelectorAll(selector);
|
|
91
|
+
for (let i = 0; i < matches.length; i++) {
|
|
92
|
+
matches[i].remove();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all form field values so no user input leaks.
|
|
98
|
+
* This covers <input> (all types), <textarea>, and <select>.
|
|
99
|
+
*/
|
|
100
|
+
function clearFormValues(root) {
|
|
101
|
+
const inputs = root.querySelectorAll('input, textarea, select');
|
|
102
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
103
|
+
const el = inputs[i];
|
|
104
|
+
// Clear live value
|
|
105
|
+
if ('value' in el)
|
|
106
|
+
el.value = '';
|
|
107
|
+
// Clear the serialized attribute — cloneNode copies the attribute,
|
|
108
|
+
// not the .value property, so we need to touch both.
|
|
109
|
+
if (el.hasAttribute('value'))
|
|
110
|
+
el.setAttribute('value', '');
|
|
111
|
+
// <textarea> content lives in text children, not an attribute
|
|
112
|
+
if (el.tagName === 'TEXTAREA')
|
|
113
|
+
el.textContent = '';
|
|
114
|
+
// Clear option selection on <select>
|
|
115
|
+
if (el.tagName === 'SELECT') {
|
|
116
|
+
const options = el.querySelectorAll('option');
|
|
117
|
+
for (let j = 0; j < options.length; j++) {
|
|
118
|
+
options[j].selected = false;
|
|
119
|
+
options[j].removeAttribute('selected');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Remove data-* attributes that often hold state
|
|
123
|
+
const attrs = Array.from(el.attributes);
|
|
124
|
+
for (const attr of attrs) {
|
|
125
|
+
if (attr.name.startsWith('data-value') || attr.name === 'data-initial') {
|
|
126
|
+
el.removeAttribute(attr.name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Replace the text content of elements flagged as sensitive with [REDACTED].
|
|
133
|
+
* Preserves the element itself so layout stays intact.
|
|
134
|
+
*/
|
|
135
|
+
function redactSensitiveElements(root) {
|
|
136
|
+
const sensitive = root.querySelectorAll('[data-pii], [data-sensitive]');
|
|
137
|
+
for (let i = 0; i < sensitive.length; i++) {
|
|
138
|
+
const el = sensitive[i];
|
|
139
|
+
el.textContent = REDACTED;
|
|
140
|
+
// Strip all attributes except structural ones so classes/ids still position it.
|
|
141
|
+
const attrs = Array.from(el.attributes);
|
|
142
|
+
for (const attr of attrs) {
|
|
143
|
+
if (attr.name !== 'class' && attr.name !== 'id' && attr.name !== 'style') {
|
|
144
|
+
el.removeAttribute(attr.name);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Walk every element and strip any attribute that starts with 'on' (onclick,
|
|
151
|
+
* onmouseover, onload, etc.). These could re-execute in the viewer.
|
|
152
|
+
*/
|
|
153
|
+
function stripInlineEventHandlers(root) {
|
|
154
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
155
|
+
let node = walker.nextNode();
|
|
156
|
+
while (node) {
|
|
157
|
+
const attrs = Array.from(node.attributes);
|
|
158
|
+
for (const attr of attrs) {
|
|
159
|
+
if (attr.name.toLowerCase().startsWith('on')) {
|
|
160
|
+
node.removeAttribute(attr.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
node = walker.nextNode();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Walk text nodes and replace any that WHOLLY match a PII pattern (the entire
|
|
168
|
+
* trimmed text is an email, phone, URL, or numeric sequence). Mid-sentence
|
|
169
|
+
* redaction is deferred to Phase 4 (pii-redactor.ts).
|
|
170
|
+
*/
|
|
171
|
+
function redactTextNodePII(root) {
|
|
172
|
+
const filter = new ExcludeFilter({ emails: true, urls: true, numbers: true, phones: true }, 1);
|
|
173
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
174
|
+
const nodesToRedact = [];
|
|
175
|
+
let node = walker.nextNode();
|
|
176
|
+
while (node) {
|
|
177
|
+
const trimmed = (node.nodeValue || '').trim();
|
|
178
|
+
// Require at least 5 chars to avoid false positives on things like page
|
|
179
|
+
// numbers ("1", "23"). ExcludeFilter with minTextLength=1 only flags on
|
|
180
|
+
// PII pattern match, not length, so length guard lives here.
|
|
181
|
+
if (trimmed.length >= 5 && filter.shouldExclude(trimmed)) {
|
|
182
|
+
nodesToRedact.push(node);
|
|
183
|
+
}
|
|
184
|
+
node = walker.nextNode();
|
|
185
|
+
}
|
|
186
|
+
for (const textNode of nodesToRedact) {
|
|
187
|
+
textNode.nodeValue = REDACTED;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Strip meta tags and link/script URLs that commonly carry auth material.
|
|
192
|
+
*/
|
|
193
|
+
function stripSensitiveMetaAndHeaders(root) {
|
|
194
|
+
// CSRF / CSP nonces
|
|
195
|
+
const sensitiveMetas = root.querySelectorAll('meta[name="csrf-token"], meta[name="csp-nonce"], meta[name="csrf-param"], meta[http-equiv="set-cookie"]');
|
|
196
|
+
for (let i = 0; i < sensitiveMetas.length; i++) {
|
|
197
|
+
sensitiveMetas[i].remove();
|
|
198
|
+
}
|
|
199
|
+
// Strip token-bearing query params from <link> and <a href> URLs
|
|
200
|
+
const TOKEN_PARAMS = ['token', 'auth', 'access_token', 'session', 'sid', 'key', 'api_key', 'apikey'];
|
|
201
|
+
const urlElements = root.querySelectorAll('link[href], a[href], img[src]');
|
|
202
|
+
for (let i = 0; i < urlElements.length; i++) {
|
|
203
|
+
const el = urlElements[i];
|
|
204
|
+
const attr = el.tagName === 'IMG' ? 'src' : 'href';
|
|
205
|
+
const raw = el.getAttribute(attr);
|
|
206
|
+
if (!raw)
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
const url = new URL(raw, window.location.href);
|
|
210
|
+
let changed = false;
|
|
211
|
+
for (const param of TOKEN_PARAMS) {
|
|
212
|
+
if (url.searchParams.has(param)) {
|
|
213
|
+
url.searchParams.set(param, REDACTED);
|
|
214
|
+
changed = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (changed)
|
|
218
|
+
el.setAttribute(attr, url.toString());
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Not a parseable URL (mailto:, tel:, javascript:, etc.) — skip
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Run BlockExtractor on the clone BEFORE redaction to produce text entries
|
|
227
|
+
* with content-hash keys that match the live page. Each extracted block
|
|
228
|
+
* element is stamped with `data-lr-snap-idx` so the viewer can map a click
|
|
229
|
+
* back to the pre-computed key without re-running BlockExtractor on the
|
|
230
|
+
* (now redacted) text.
|
|
231
|
+
*
|
|
232
|
+
* No source text is included in the returned entries — only the key, tag
|
|
233
|
+
* name, and block index — so the entries are safe to store even though
|
|
234
|
+
* extraction happened before redaction.
|
|
235
|
+
*/
|
|
236
|
+
function extractTextEntries(clone) {
|
|
237
|
+
const extractor = new BlockExtractor({ emails: true, urls: true, numbers: true, phones: true }, [], 2);
|
|
238
|
+
const blocks = extractor.extract(clone);
|
|
239
|
+
return blocks.map((block, index) => {
|
|
240
|
+
// Stamp the element so the viewer can resolve clicks by data attribute
|
|
241
|
+
block.element.setAttribute('data-lr-snap-idx', String(index));
|
|
242
|
+
return {
|
|
243
|
+
key: block.key,
|
|
244
|
+
tagName: block.element.tagName,
|
|
245
|
+
blockIndex: index,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=snapshot-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-capture.js","sourceRoot":"","sources":["../../../src/browser/editor/snapshot-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Viewer — Renders a saved snapshot in the current page so the
|
|
3
|
+
* Inspector + Sidebar can be pointed at it for translation.
|
|
4
|
+
*
|
|
5
|
+
* When the editor loads with `?__lr_snapshot={uuid}`, the normal page is
|
|
6
|
+
* replaced with the snapshot HTML inside a sandbox wrapper:
|
|
7
|
+
*
|
|
8
|
+
* <div id="__lr_snapshot_root"> ← scoped root passed to Inspector
|
|
9
|
+
* <html>...snapshot...</html>
|
|
10
|
+
* </div>
|
|
11
|
+
*
|
|
12
|
+
* Auto-init retranslation is NOT re-run against the snapshot — translations
|
|
13
|
+
* saved via the sidebar flow through the normal batch endpoint and will
|
|
14
|
+
* appear on the real auth page on next load.
|
|
15
|
+
*/
|
|
16
|
+
import type { EditorAPI } from './editor-api.js';
|
|
17
|
+
export interface SnapshotViewerResult {
|
|
18
|
+
/** Scope root to pass to Inspector */
|
|
19
|
+
root: HTMLElement;
|
|
20
|
+
/** Snapshot metadata for display in the toolbar / sidebar */
|
|
21
|
+
pageUrl: string;
|
|
22
|
+
title: string;
|
|
23
|
+
/** Pre-computed key map: blockIndex → key. The Inspector uses this to resolve
|
|
24
|
+
* clicks without re-running BlockExtractor on the redacted HTML. */
|
|
25
|
+
keyMap: Map<number, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch a snapshot from the server and mount it into the current page.
|
|
29
|
+
* Returns the root element Inspector should scope against.
|
|
30
|
+
*
|
|
31
|
+
* On failure (404, network error, deleted snapshot) returns null and displays
|
|
32
|
+
* a user-visible notice via the caller's notice helper.
|
|
33
|
+
*/
|
|
34
|
+
export declare function activateSnapshotViewer(snapshotUuid: string, api: EditorAPI): Promise<SnapshotViewerResult | null>;
|
|
35
|
+
//# sourceMappingURL=snapshot-viewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-viewer.d.ts","sourceRoot":"","sources":["../../../src/browser/editor/snapshot-viewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd;yEACqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAsEtC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Viewer — Renders a saved snapshot in the current page so the
|
|
3
|
+
* Inspector + Sidebar can be pointed at it for translation.
|
|
4
|
+
*
|
|
5
|
+
* When the editor loads with `?__lr_snapshot={uuid}`, the normal page is
|
|
6
|
+
* replaced with the snapshot HTML inside a sandbox wrapper:
|
|
7
|
+
*
|
|
8
|
+
* <div id="__lr_snapshot_root"> ← scoped root passed to Inspector
|
|
9
|
+
* <html>...snapshot...</html>
|
|
10
|
+
* </div>
|
|
11
|
+
*
|
|
12
|
+
* Auto-init retranslation is NOT re-run against the snapshot — translations
|
|
13
|
+
* saved via the sidebar flow through the normal batch endpoint and will
|
|
14
|
+
* appear on the real auth page on next load.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Fetch a snapshot from the server and mount it into the current page.
|
|
18
|
+
* Returns the root element Inspector should scope against.
|
|
19
|
+
*
|
|
20
|
+
* On failure (404, network error, deleted snapshot) returns null and displays
|
|
21
|
+
* a user-visible notice via the caller's notice helper.
|
|
22
|
+
*/
|
|
23
|
+
export async function activateSnapshotViewer(snapshotUuid, api) {
|
|
24
|
+
const snapshot = await api.getSnapshot(snapshotUuid);
|
|
25
|
+
if (!snapshot)
|
|
26
|
+
return null;
|
|
27
|
+
// Replace the entire body with a sandbox wrapper holding the snapshot HTML.
|
|
28
|
+
// We keep document.head alone so the editor's Shadow DOM host and existing
|
|
29
|
+
// stylesheets (for rendering) still work.
|
|
30
|
+
//
|
|
31
|
+
// The snapshot HTML is a full document (starts with <!DOCTYPE>), so we
|
|
32
|
+
// parse it with DOMParser and extract the <body> children. The snapshot's
|
|
33
|
+
// <head> <link> and <style> tags are appended to the real head so the
|
|
34
|
+
// snapshot renders with its original styles.
|
|
35
|
+
const parser = new DOMParser();
|
|
36
|
+
const doc = parser.parseFromString(snapshot.html, 'text/html');
|
|
37
|
+
// Copy over stylesheets and inline <style> tags from the snapshot head.
|
|
38
|
+
// We prefix them with a data attribute so the cleanup code can remove them
|
|
39
|
+
// if the viewer is ever unmounted (out of scope for Phase 1).
|
|
40
|
+
const snapshotStyles = doc.head.querySelectorAll('link[rel="stylesheet"], style');
|
|
41
|
+
for (let i = 0; i < snapshotStyles.length; i++) {
|
|
42
|
+
const clone = snapshotStyles[i].cloneNode(true);
|
|
43
|
+
clone.setAttribute('data-lr-snapshot-style', '');
|
|
44
|
+
document.head.appendChild(clone);
|
|
45
|
+
}
|
|
46
|
+
// Copy <title> so the tab title reflects the captured page
|
|
47
|
+
if (snapshot.title) {
|
|
48
|
+
document.title = `[Snapshot] ${snapshot.title}`;
|
|
49
|
+
}
|
|
50
|
+
// Clear the current body content (but preserve the editor's shadow host
|
|
51
|
+
// if it's already mounted — it's attached to #__lionrapid-editor).
|
|
52
|
+
const editorHost = document.getElementById('__lionrapid-editor');
|
|
53
|
+
while (document.body.firstChild) {
|
|
54
|
+
if (document.body.firstChild === editorHost) {
|
|
55
|
+
// Skip past the editor host without removing it
|
|
56
|
+
const next = document.body.firstChild.nextSibling;
|
|
57
|
+
if (!next)
|
|
58
|
+
break;
|
|
59
|
+
document.body.removeChild(next);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
document.body.removeChild(document.body.firstChild);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Build sandbox wrapper and inject snapshot body
|
|
66
|
+
const root = document.createElement('div');
|
|
67
|
+
root.id = '__lr_snapshot_root';
|
|
68
|
+
root.setAttribute('data-lr-snapshot-uuid', snapshotUuid);
|
|
69
|
+
root.innerHTML = doc.body.innerHTML;
|
|
70
|
+
// Insert wrapper before the editor host so the editor stays on top
|
|
71
|
+
if (editorHost && editorHost.parentNode === document.body) {
|
|
72
|
+
document.body.insertBefore(root, editorHost);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
document.body.appendChild(root);
|
|
76
|
+
}
|
|
77
|
+
// Build key map from textEntries: blockIndex → auto_key
|
|
78
|
+
const keyMap = new Map();
|
|
79
|
+
const entries = snapshot.textEntries || [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
keyMap.set(entry.blockIndex, entry.key);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
root,
|
|
85
|
+
pageUrl: snapshot.pageUrl,
|
|
86
|
+
title: snapshot.title,
|
|
87
|
+
keyMap,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=snapshot-viewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-viewer.js","sourceRoot":"","sources":["../../../src/browser/editor/snapshot-viewer.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type OnLanguageChange = (locale: string) => void;
|
|
7
7
|
export type OnExit = () => void;
|
|
8
|
+
export type OnCaptureSnapshot = () => void;
|
|
9
|
+
export interface ToolbarCaptureOptions {
|
|
10
|
+
/** 'capture' = new snapshot, 'recapture' = overwrite an existing snapshot's version */
|
|
11
|
+
mode?: 'capture' | 'recapture';
|
|
12
|
+
}
|
|
8
13
|
export declare class Toolbar {
|
|
9
14
|
private el;
|
|
10
15
|
private langBtnEl;
|
|
@@ -14,10 +19,11 @@ export declare class Toolbar {
|
|
|
14
19
|
private currentLocale;
|
|
15
20
|
private onLanguageChange;
|
|
16
21
|
private onExit;
|
|
22
|
+
private onCaptureSnapshot;
|
|
17
23
|
private dragging;
|
|
18
24
|
private dragOffsetX;
|
|
19
25
|
private dragOffsetY;
|
|
20
|
-
constructor(shadowRoot: ShadowRoot, languages: string[], currentLocale: string, onLanguageChange: OnLanguageChange, onExit: OnExit);
|
|
26
|
+
constructor(shadowRoot: ShadowRoot, languages: string[], currentLocale: string, onLanguageChange: OnLanguageChange, onExit: OnExit, onCaptureSnapshot?: OnCaptureSnapshot | null, captureOptions?: ToolbarCaptureOptions);
|
|
21
27
|
toggle(): void;
|
|
22
28
|
updateProgress(translated: number, total: number): void;
|
|
23
29
|
getCurrentLocale(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../../../src/browser/editor/toolbar.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AACxD,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../../../src/browser/editor/toolbar.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AACxD,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;AAChC,MAAM,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IACpC,uFAAuF;IACvF,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CAChC;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAA2B;IAGpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;gBAGtB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EAAE,EACnB,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,EAAE,MAAM,EACd,iBAAiB,GAAE,iBAAiB,GAAG,IAAW,EAClD,cAAc,GAAE,qBAA0B;IAyG5C,MAAM,IAAI,IAAI;IAKd,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvD,gBAAgB,IAAI,MAAM;IAI1B,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,aAAa;CAQtB"}
|
|
@@ -12,14 +12,17 @@ export class Toolbar {
|
|
|
12
12
|
currentLocale;
|
|
13
13
|
onLanguageChange;
|
|
14
14
|
onExit;
|
|
15
|
+
onCaptureSnapshot;
|
|
15
16
|
// Drag state
|
|
16
17
|
dragging = false;
|
|
17
18
|
dragOffsetX = 0;
|
|
18
19
|
dragOffsetY = 0;
|
|
19
|
-
constructor(shadowRoot, languages, currentLocale, onLanguageChange, onExit) {
|
|
20
|
+
constructor(shadowRoot, languages, currentLocale, onLanguageChange, onExit, onCaptureSnapshot = null, captureOptions = {}) {
|
|
20
21
|
this.onLanguageChange = onLanguageChange;
|
|
21
22
|
this.onExit = onExit;
|
|
23
|
+
this.onCaptureSnapshot = onCaptureSnapshot;
|
|
22
24
|
this.currentLocale = currentLocale;
|
|
25
|
+
const captureMode = captureOptions.mode ?? 'capture';
|
|
23
26
|
this.el = document.createElement('div');
|
|
24
27
|
this.el.className = 'lr-fab';
|
|
25
28
|
// FAB button (always visible)
|
|
@@ -70,6 +73,28 @@ export class Toolbar {
|
|
|
70
73
|
}
|
|
71
74
|
langWrap.appendChild(this.langMenuEl);
|
|
72
75
|
panel.appendChild(langWrap);
|
|
76
|
+
// Capture Snapshot button (only in capture mode, not viewer mode).
|
|
77
|
+
// Label/title change when recapture mode is active so the PM knows this
|
|
78
|
+
// click will overwrite an existing snapshot's version instead of making
|
|
79
|
+
// a new one.
|
|
80
|
+
if (this.onCaptureSnapshot) {
|
|
81
|
+
const captureBtn = document.createElement('button');
|
|
82
|
+
captureBtn.className = 'lr-fab-snapshot';
|
|
83
|
+
if (captureMode === 'recapture') {
|
|
84
|
+
captureBtn.textContent = 'Save re-capture';
|
|
85
|
+
captureBtn.title = 'Save this page as a new version of the existing snapshot';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
captureBtn.textContent = 'Capture';
|
|
89
|
+
captureBtn.title = 'Capture a redacted snapshot of this page for translators';
|
|
90
|
+
}
|
|
91
|
+
captureBtn.addEventListener('click', (e) => {
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
if (this.onCaptureSnapshot)
|
|
94
|
+
this.onCaptureSnapshot();
|
|
95
|
+
});
|
|
96
|
+
panel.appendChild(captureBtn);
|
|
97
|
+
}
|
|
73
98
|
// Progress
|
|
74
99
|
this.progressEl = document.createElement('span');
|
|
75
100
|
this.progressEl.className = 'lr-fab-progress';
|