@stringpush/sdk 0.1.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/README.md +134 -0
- package/dist/chunk-FROJCNV7.umd.cjs +153 -0
- package/dist/chunk-FROJCNV7.umd.cjs.map +1 -0
- package/dist/chunk-X3WTVBZ6.mjs +153 -0
- package/dist/chunk-X3WTVBZ6.mjs.map +1 -0
- package/dist/edit-launcher-DB2DJSQJ.umd.cjs +250 -0
- package/dist/edit-launcher-DB2DJSQJ.umd.cjs.map +1 -0
- package/dist/edit-launcher-PTZ5BIO2.mjs +250 -0
- package/dist/edit-launcher-PTZ5BIO2.mjs.map +1 -0
- package/dist/index.d.cts +78 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.mjs +260 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.cjs +260 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/overlay-7KC2RRGB.mjs +1039 -0
- package/dist/overlay-7KC2RRGB.mjs.map +1 -0
- package/dist/overlay-MLOXYRPA.umd.cjs +1039 -0
- package/dist/overlay-MLOXYRPA.umd.cjs.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/overlay/constants.ts
|
|
2
|
+
var OVERLAY_ROOT_ID = "translation-overlay-root";
|
|
3
|
+
var EDIT_PANEL_HOST_ID = "translation-edit-panel-host";
|
|
4
|
+
var OVERLAY_CHUNK_MARKER = "translation-overlay-chunk-v1";
|
|
5
|
+
|
|
6
|
+
// src/overlay/overlay-api.ts
|
|
7
|
+
var _runtimecore = require('@stringpush/runtime-core');
|
|
8
|
+
var OverlayApiError = class extends Error {
|
|
9
|
+
constructor(message, status, body) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.body = body;
|
|
13
|
+
this.name = "OverlayApiError";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
get isVersionConflict() {
|
|
18
|
+
return this.status === 409 && typeof this.body === "object" && this.body !== null && "error" in this.body && _optionalChain([this, 'access', _ => _.body, 'access', _2 => _2.error, 'optionalAccess', _3 => _3.code]) === "VERSION_CONFLICT";
|
|
19
|
+
}
|
|
20
|
+
get isKeyNotFound() {
|
|
21
|
+
return this.status === 404 && typeof this.body === "object" && this.body !== null && "error" in this.body && _optionalChain([this, 'access', _4 => _4.body, 'access', _5 => _5.error, 'optionalAccess', _6 => _6.code]) === "NOT_FOUND";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function overlayHeaders(config) {
|
|
25
|
+
const headers = {
|
|
26
|
+
Authorization: `Bearer ${config.editToken}`,
|
|
27
|
+
Accept: "application/json",
|
|
28
|
+
"Content-Type": "application/json"
|
|
29
|
+
};
|
|
30
|
+
const origin = _runtimecore.resolveRequestOrigin.call(void 0, config.origin);
|
|
31
|
+
if (origin) {
|
|
32
|
+
headers.Origin = origin;
|
|
33
|
+
}
|
|
34
|
+
return headers;
|
|
35
|
+
}
|
|
36
|
+
async function overlayFetch(config, path, init = {}) {
|
|
37
|
+
const fetchFn = _nullishCoalesce(config.fetch, () => ( globalThis.fetch.bind(globalThis)));
|
|
38
|
+
const url = `${config.apiBaseUrl.replace(/\/$/, "")}${path}`;
|
|
39
|
+
return fetchFn(url, {
|
|
40
|
+
...init,
|
|
41
|
+
headers: {
|
|
42
|
+
...overlayHeaders(config),
|
|
43
|
+
...init.headers
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function parseJson(response) {
|
|
48
|
+
return await response.json();
|
|
49
|
+
}
|
|
50
|
+
async function fetchOverlayLocales(config) {
|
|
51
|
+
const response = await overlayFetch(config, "/v1/overlay/locales");
|
|
52
|
+
const body = await parseJson(
|
|
53
|
+
response
|
|
54
|
+
);
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new OverlayApiError(
|
|
57
|
+
`Failed to load locales (HTTP ${response.status})`,
|
|
58
|
+
response.status,
|
|
59
|
+
body
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return body.data.map((row) => ({
|
|
63
|
+
id: row.id,
|
|
64
|
+
code: row.code
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
async function fetchOverlayKeyValues(config, keyPath) {
|
|
68
|
+
const params = new URLSearchParams({ key: keyPath });
|
|
69
|
+
const response = await overlayFetch(config, `/v1/overlay/keys/values?${params}`);
|
|
70
|
+
const body = await parseJson(response);
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new OverlayApiError(
|
|
73
|
+
`Failed to load key values (HTTP ${response.status})`,
|
|
74
|
+
response.status,
|
|
75
|
+
body
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return body;
|
|
79
|
+
}
|
|
80
|
+
async function createOverlayKey(config, input) {
|
|
81
|
+
const response = await overlayFetch(config, "/v1/overlay/keys", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
keyPath: input.keyPath,
|
|
85
|
+
defaultMessage: _nullishCoalesce(input.defaultMessage, () => ( null))
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
const body = await parseJson(response);
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new OverlayApiError(
|
|
91
|
+
`Failed to create translation key (HTTP ${response.status})`,
|
|
92
|
+
response.status,
|
|
93
|
+
body
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const record = body;
|
|
97
|
+
const keyPath = record.namespace ? `${record.namespace}.${record.key}` : record.key;
|
|
98
|
+
return { id: record.id, keyPath };
|
|
99
|
+
}
|
|
100
|
+
async function patchOverlayValue(config, input) {
|
|
101
|
+
const headers = {};
|
|
102
|
+
if (input.version !== null) {
|
|
103
|
+
headers["If-Match"] = String(input.version);
|
|
104
|
+
}
|
|
105
|
+
const response = await overlayFetch(config, "/v1/overlay/values", {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
headers,
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
keyId: input.keyId,
|
|
110
|
+
localeId: input.localeId,
|
|
111
|
+
value: input.value
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
const body = await parseJson(response);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new OverlayApiError(
|
|
117
|
+
`Failed to save translation (HTTP ${response.status})`,
|
|
118
|
+
response.status,
|
|
119
|
+
body
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return body;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/overlay/locale-display.ts
|
|
126
|
+
var LOCALE_FLAGS = {
|
|
127
|
+
en: "\u{1F1FA}\u{1F1F8}",
|
|
128
|
+
fr: "\u{1F1EB}\u{1F1F7}",
|
|
129
|
+
de: "\u{1F1E9}\u{1F1EA}",
|
|
130
|
+
"de-DE": "\u{1F1E9}\u{1F1EA}",
|
|
131
|
+
es: "\u{1F1EA}\u{1F1F8}"
|
|
132
|
+
};
|
|
133
|
+
var LOCALE_NAMES = {
|
|
134
|
+
en: "English",
|
|
135
|
+
fr: "French",
|
|
136
|
+
de: "German",
|
|
137
|
+
"de-DE": "German",
|
|
138
|
+
es: "Spanish"
|
|
139
|
+
};
|
|
140
|
+
function overlayLocaleFlag(code) {
|
|
141
|
+
return _nullishCoalesce(LOCALE_FLAGS[code], () => ( "\u{1F310}"));
|
|
142
|
+
}
|
|
143
|
+
function overlayLocaleLabel(code) {
|
|
144
|
+
const name = _nullishCoalesce(LOCALE_NAMES[code], () => ( code.toUpperCase()));
|
|
145
|
+
return `${name} (${code})`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/overlay/edit-panel.ts
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
// src/overlay/edit-panel-remote.ts
|
|
152
|
+
var activeHandler = null;
|
|
153
|
+
function registerEditPanelRemoteHandler(handler) {
|
|
154
|
+
activeHandler = handler;
|
|
155
|
+
}
|
|
156
|
+
function notifyEditPanelRemoteUpdate(update) {
|
|
157
|
+
if (!activeHandler || activeHandler.keyPath !== update.keyPath) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return activeHandler.applyRemoteUpdate(update);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/overlay/edit-panel.ts
|
|
164
|
+
var PANEL_STYLES = `
|
|
165
|
+
:host {
|
|
166
|
+
all: initial;
|
|
167
|
+
font-family: system-ui, -apple-system, Segoe UI, sans-serif;
|
|
168
|
+
--sp-bg: #ffffff;
|
|
169
|
+
--sp-text: #0f172a;
|
|
170
|
+
--sp-muted: #64748b;
|
|
171
|
+
--sp-border: #e2e8f0;
|
|
172
|
+
--sp-accent: #4338ca;
|
|
173
|
+
--sp-accent-hover: #3730a3;
|
|
174
|
+
--sp-warning-bg: #fef3c7;
|
|
175
|
+
--sp-warning-text: #92400e;
|
|
176
|
+
--sp-warning-border: #fcd34d;
|
|
177
|
+
--sp-badge-draft-bg: #fef3c6;
|
|
178
|
+
--sp-badge-draft-text: #973c00;
|
|
179
|
+
--sp-badge-default-bg: #f1f5f9;
|
|
180
|
+
--sp-badge-default-text: #475569;
|
|
181
|
+
}
|
|
182
|
+
.panel {
|
|
183
|
+
position: fixed;
|
|
184
|
+
top: 0;
|
|
185
|
+
right: 0;
|
|
186
|
+
width: 400px;
|
|
187
|
+
max-width: 100vw;
|
|
188
|
+
height: 100vh;
|
|
189
|
+
background: var(--sp-bg);
|
|
190
|
+
color: var(--sp-text);
|
|
191
|
+
box-shadow: -8px 0 32px rgba(15, 23, 42, 0.12);
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
z-index: 2147483647;
|
|
195
|
+
}
|
|
196
|
+
.header {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: flex-start;
|
|
199
|
+
justify-content: space-between;
|
|
200
|
+
gap: 12px;
|
|
201
|
+
padding: 16px 16px 12px;
|
|
202
|
+
border-bottom: 1px solid var(--sp-border);
|
|
203
|
+
}
|
|
204
|
+
.header-main {
|
|
205
|
+
min-width: 0;
|
|
206
|
+
}
|
|
207
|
+
.brand {
|
|
208
|
+
margin: 0 0 8px;
|
|
209
|
+
font-size: 11px;
|
|
210
|
+
font-weight: 700;
|
|
211
|
+
letter-spacing: 0.08em;
|
|
212
|
+
text-transform: uppercase;
|
|
213
|
+
color: var(--sp-accent);
|
|
214
|
+
}
|
|
215
|
+
.title {
|
|
216
|
+
margin: 0;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
font-weight: 600;
|
|
219
|
+
}
|
|
220
|
+
.key-path {
|
|
221
|
+
margin: 4px 0 0;
|
|
222
|
+
font: 12px/1.4 ui-monospace, Menlo, monospace;
|
|
223
|
+
color: var(--sp-muted);
|
|
224
|
+
word-break: break-all;
|
|
225
|
+
}
|
|
226
|
+
.close-btn {
|
|
227
|
+
border: none;
|
|
228
|
+
background: transparent;
|
|
229
|
+
font-size: 22px;
|
|
230
|
+
line-height: 1;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
color: var(--sp-muted);
|
|
233
|
+
padding: 2px 4px;
|
|
234
|
+
}
|
|
235
|
+
.body {
|
|
236
|
+
flex: 1;
|
|
237
|
+
overflow: auto;
|
|
238
|
+
padding: 12px 16px;
|
|
239
|
+
}
|
|
240
|
+
.locale-row {
|
|
241
|
+
margin-bottom: 16px;
|
|
242
|
+
padding-bottom: 16px;
|
|
243
|
+
border-bottom: 1px solid var(--sp-border);
|
|
244
|
+
}
|
|
245
|
+
.locale-row:last-child {
|
|
246
|
+
border-bottom: none;
|
|
247
|
+
margin-bottom: 0;
|
|
248
|
+
padding-bottom: 0;
|
|
249
|
+
}
|
|
250
|
+
.locale-header {
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: space-between;
|
|
254
|
+
gap: 8px;
|
|
255
|
+
margin-bottom: 8px;
|
|
256
|
+
}
|
|
257
|
+
.locale-label {
|
|
258
|
+
display: inline-flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: 6px;
|
|
261
|
+
font-size: 13px;
|
|
262
|
+
font-weight: 600;
|
|
263
|
+
color: var(--sp-text);
|
|
264
|
+
}
|
|
265
|
+
.locale-flag {
|
|
266
|
+
font-size: 15px;
|
|
267
|
+
line-height: 1;
|
|
268
|
+
}
|
|
269
|
+
.locale-badges {
|
|
270
|
+
display: inline-flex;
|
|
271
|
+
gap: 4px;
|
|
272
|
+
}
|
|
273
|
+
.badge {
|
|
274
|
+
display: inline-flex;
|
|
275
|
+
align-items: center;
|
|
276
|
+
padding: 2px 8px;
|
|
277
|
+
border-radius: 999px;
|
|
278
|
+
font-size: 10px;
|
|
279
|
+
font-weight: 700;
|
|
280
|
+
letter-spacing: 0.02em;
|
|
281
|
+
}
|
|
282
|
+
.badge-default {
|
|
283
|
+
background: var(--sp-badge-default-bg);
|
|
284
|
+
color: var(--sp-badge-default-text);
|
|
285
|
+
}
|
|
286
|
+
.badge-draft {
|
|
287
|
+
background: var(--sp-badge-draft-bg);
|
|
288
|
+
color: var(--sp-badge-draft-text);
|
|
289
|
+
}
|
|
290
|
+
.locale-row textarea {
|
|
291
|
+
width: 100%;
|
|
292
|
+
min-height: 80px;
|
|
293
|
+
box-sizing: border-box;
|
|
294
|
+
border: 1px solid #cbd5e1;
|
|
295
|
+
border-radius: 8px;
|
|
296
|
+
padding: 10px 12px;
|
|
297
|
+
font: 14px/1.45 inherit;
|
|
298
|
+
resize: vertical;
|
|
299
|
+
color: var(--sp-text);
|
|
300
|
+
}
|
|
301
|
+
.locale-row textarea:focus-visible {
|
|
302
|
+
outline: 2px solid var(--sp-accent);
|
|
303
|
+
outline-offset: 1px;
|
|
304
|
+
border-color: var(--sp-accent);
|
|
305
|
+
}
|
|
306
|
+
.banner {
|
|
307
|
+
margin: 0 16px 12px;
|
|
308
|
+
padding: 12px 14px;
|
|
309
|
+
border-radius: 8px;
|
|
310
|
+
border: 1px solid var(--sp-warning-border);
|
|
311
|
+
background: var(--sp-warning-bg);
|
|
312
|
+
color: var(--sp-warning-text);
|
|
313
|
+
font-size: 13px;
|
|
314
|
+
line-height: 1.45;
|
|
315
|
+
}
|
|
316
|
+
.banner p {
|
|
317
|
+
margin: 0;
|
|
318
|
+
}
|
|
319
|
+
.banner button {
|
|
320
|
+
margin-top: 10px;
|
|
321
|
+
border: 1px solid #d97706;
|
|
322
|
+
background: #fff;
|
|
323
|
+
border-radius: 6px;
|
|
324
|
+
padding: 6px 12px;
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
font-size: 12px;
|
|
327
|
+
font-weight: 600;
|
|
328
|
+
color: var(--sp-warning-text);
|
|
329
|
+
}
|
|
330
|
+
.footer {
|
|
331
|
+
display: flex;
|
|
332
|
+
gap: 8px;
|
|
333
|
+
justify-content: flex-end;
|
|
334
|
+
padding: 12px 16px 16px;
|
|
335
|
+
border-top: 1px solid var(--sp-border);
|
|
336
|
+
}
|
|
337
|
+
.footer button {
|
|
338
|
+
border-radius: 8px;
|
|
339
|
+
padding: 9px 14px;
|
|
340
|
+
font-size: 14px;
|
|
341
|
+
font-weight: 600;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
display: inline-flex;
|
|
344
|
+
align-items: center;
|
|
345
|
+
gap: 6px;
|
|
346
|
+
}
|
|
347
|
+
.footer .secondary {
|
|
348
|
+
border: 1px solid #cbd5e1;
|
|
349
|
+
background: #fff;
|
|
350
|
+
color: #334155;
|
|
351
|
+
}
|
|
352
|
+
.footer .primary {
|
|
353
|
+
border: none;
|
|
354
|
+
background: var(--sp-accent);
|
|
355
|
+
color: #fff;
|
|
356
|
+
}
|
|
357
|
+
.footer .primary:hover:not(:disabled) {
|
|
358
|
+
background: var(--sp-accent-hover);
|
|
359
|
+
}
|
|
360
|
+
.footer .primary::before {
|
|
361
|
+
content: "";
|
|
362
|
+
width: 14px;
|
|
363
|
+
height: 14px;
|
|
364
|
+
background: currentColor;
|
|
365
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M11.667 3.5 5.25 9.917 2.333 7' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
|
|
366
|
+
center / contain no-repeat;
|
|
367
|
+
}
|
|
368
|
+
.status {
|
|
369
|
+
padding: 24px 16px;
|
|
370
|
+
color: var(--sp-muted);
|
|
371
|
+
font-size: 14px;
|
|
372
|
+
}
|
|
373
|
+
.create-panel {
|
|
374
|
+
padding: 8px 0;
|
|
375
|
+
}
|
|
376
|
+
.create-panel p {
|
|
377
|
+
margin: 0 0 12px;
|
|
378
|
+
font-size: 14px;
|
|
379
|
+
color: #334155;
|
|
380
|
+
line-height: 1.5;
|
|
381
|
+
}
|
|
382
|
+
.create-panel label {
|
|
383
|
+
display: block;
|
|
384
|
+
font-size: 12px;
|
|
385
|
+
font-weight: 600;
|
|
386
|
+
color: #334155;
|
|
387
|
+
margin-bottom: 6px;
|
|
388
|
+
}
|
|
389
|
+
.create-panel input {
|
|
390
|
+
width: 100%;
|
|
391
|
+
box-sizing: border-box;
|
|
392
|
+
border: 1px solid #cbd5e1;
|
|
393
|
+
border-radius: 8px;
|
|
394
|
+
padding: 8px 10px;
|
|
395
|
+
font: 14px/1.4 inherit;
|
|
396
|
+
margin-bottom: 12px;
|
|
397
|
+
}
|
|
398
|
+
.create-panel .create-btn {
|
|
399
|
+
border: none;
|
|
400
|
+
border-radius: 8px;
|
|
401
|
+
padding: 9px 14px;
|
|
402
|
+
font-size: 14px;
|
|
403
|
+
font-weight: 600;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
background: var(--sp-accent);
|
|
406
|
+
color: #fff;
|
|
407
|
+
}
|
|
408
|
+
.create-panel .error {
|
|
409
|
+
margin: 0 0 12px;
|
|
410
|
+
color: #b91c1c;
|
|
411
|
+
font-size: 13px;
|
|
412
|
+
}
|
|
413
|
+
`;
|
|
414
|
+
function openEditPanel(keyPath, context) {
|
|
415
|
+
if (typeof document === "undefined") {
|
|
416
|
+
return { destroy: () => {
|
|
417
|
+
} };
|
|
418
|
+
}
|
|
419
|
+
closeEditPanel();
|
|
420
|
+
const host = document.createElement("div");
|
|
421
|
+
host.id = EDIT_PANEL_HOST_ID;
|
|
422
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
423
|
+
const style = document.createElement("style");
|
|
424
|
+
style.textContent = PANEL_STYLES;
|
|
425
|
+
const panel = document.createElement("div");
|
|
426
|
+
panel.className = "panel";
|
|
427
|
+
panel.setAttribute("role", "dialog");
|
|
428
|
+
panel.setAttribute("aria-label", "Edit translation");
|
|
429
|
+
const header = document.createElement("header");
|
|
430
|
+
header.className = "header";
|
|
431
|
+
const headerMain = document.createElement("div");
|
|
432
|
+
headerMain.className = "header-main";
|
|
433
|
+
const brand = document.createElement("p");
|
|
434
|
+
brand.className = "brand";
|
|
435
|
+
brand.textContent = "StringPush";
|
|
436
|
+
const title = document.createElement("h2");
|
|
437
|
+
title.className = "title";
|
|
438
|
+
title.textContent = "Edit translation";
|
|
439
|
+
const keyEl = document.createElement("p");
|
|
440
|
+
keyEl.className = "key-path";
|
|
441
|
+
keyEl.textContent = keyPath;
|
|
442
|
+
headerMain.append(brand, title, keyEl);
|
|
443
|
+
const closeBtn = document.createElement("button");
|
|
444
|
+
closeBtn.className = "close-btn";
|
|
445
|
+
closeBtn.type = "button";
|
|
446
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
447
|
+
closeBtn.textContent = "\xD7";
|
|
448
|
+
header.append(headerMain, closeBtn);
|
|
449
|
+
const bodyEl = document.createElement("div");
|
|
450
|
+
bodyEl.className = "body";
|
|
451
|
+
const status = document.createElement("div");
|
|
452
|
+
status.className = "status";
|
|
453
|
+
status.textContent = "Loading\u2026";
|
|
454
|
+
bodyEl.append(status);
|
|
455
|
+
const banner = document.createElement("div");
|
|
456
|
+
banner.className = "banner";
|
|
457
|
+
banner.hidden = true;
|
|
458
|
+
const footer = document.createElement("footer");
|
|
459
|
+
footer.className = "footer";
|
|
460
|
+
const cancelBtn = document.createElement("button");
|
|
461
|
+
cancelBtn.type = "button";
|
|
462
|
+
cancelBtn.className = "secondary";
|
|
463
|
+
cancelBtn.textContent = "Cancel";
|
|
464
|
+
const saveBtn = document.createElement("button");
|
|
465
|
+
saveBtn.type = "button";
|
|
466
|
+
saveBtn.className = "primary";
|
|
467
|
+
saveBtn.textContent = "Save draft";
|
|
468
|
+
saveBtn.disabled = true;
|
|
469
|
+
footer.append(cancelBtn, saveBtn);
|
|
470
|
+
panel.append(header, banner, bodyEl, footer);
|
|
471
|
+
shadow.append(style, panel);
|
|
472
|
+
document.body.append(host);
|
|
473
|
+
let destroyed = false;
|
|
474
|
+
let rows = [];
|
|
475
|
+
let keyId = "";
|
|
476
|
+
const destroy = () => {
|
|
477
|
+
if (destroyed) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
destroyed = true;
|
|
481
|
+
registerEditPanelRemoteHandler(null);
|
|
482
|
+
host.remove();
|
|
483
|
+
};
|
|
484
|
+
function applyRemoteUpdate(update) {
|
|
485
|
+
if (!_runtimecore.shouldApplyTranslationUpdate.call(void 0, update.keyPath, update.localeCode, update.version)) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const row = rows.find((entry) => entry.localeCode === update.localeCode);
|
|
489
|
+
if (!row) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if ((_nullishCoalesce(row.version, () => ( 0))) > update.version) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
if (row.draftValue !== row.value) {
|
|
496
|
+
showConflict(
|
|
497
|
+
`${update.localeCode} was updated in another tab. Reload to merge your changes.`
|
|
498
|
+
);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
row.value = update.value;
|
|
502
|
+
row.draftValue = update.value;
|
|
503
|
+
row.version = update.version;
|
|
504
|
+
_runtimecore.recordTranslationVersion.call(void 0, update.keyPath, update.localeCode, update.version);
|
|
505
|
+
renderRows();
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
closeBtn.addEventListener("click", destroy);
|
|
509
|
+
cancelBtn.addEventListener("click", destroy);
|
|
510
|
+
function renderRows() {
|
|
511
|
+
bodyEl.replaceChildren();
|
|
512
|
+
for (const row of rows) {
|
|
513
|
+
const wrap = document.createElement("div");
|
|
514
|
+
wrap.className = "locale-row";
|
|
515
|
+
const localeHeader = document.createElement("div");
|
|
516
|
+
localeHeader.className = "locale-header";
|
|
517
|
+
const label = document.createElement("span");
|
|
518
|
+
label.className = "locale-label";
|
|
519
|
+
const flag = document.createElement("span");
|
|
520
|
+
flag.className = "locale-flag";
|
|
521
|
+
flag.textContent = overlayLocaleFlag(row.localeCode);
|
|
522
|
+
flag.setAttribute("aria-hidden", "true");
|
|
523
|
+
label.append(flag, document.createTextNode(overlayLocaleLabel(row.localeCode)));
|
|
524
|
+
const badges = document.createElement("span");
|
|
525
|
+
badges.className = "locale-badges";
|
|
526
|
+
if (row.localeCode === context.activeLocaleCode) {
|
|
527
|
+
const defaultBadge = document.createElement("span");
|
|
528
|
+
defaultBadge.className = "badge badge-default";
|
|
529
|
+
defaultBadge.textContent = "Default";
|
|
530
|
+
badges.append(defaultBadge);
|
|
531
|
+
}
|
|
532
|
+
const draftBadge = document.createElement("span");
|
|
533
|
+
draftBadge.className = "badge badge-draft";
|
|
534
|
+
draftBadge.textContent = row.draftValue !== row.value ? "Unsaved" : "Draft";
|
|
535
|
+
badges.append(draftBadge);
|
|
536
|
+
localeHeader.append(label, badges);
|
|
537
|
+
const textarea = document.createElement("textarea");
|
|
538
|
+
textarea.id = `locale-${row.localeId}`;
|
|
539
|
+
textarea.setAttribute("aria-label", `${row.localeCode} translation`);
|
|
540
|
+
textarea.value = row.draftValue;
|
|
541
|
+
textarea.addEventListener("input", () => {
|
|
542
|
+
row.draftValue = textarea.value;
|
|
543
|
+
draftBadge.textContent = row.draftValue !== row.value ? "Unsaved" : "Draft";
|
|
544
|
+
});
|
|
545
|
+
wrap.append(localeHeader, textarea);
|
|
546
|
+
bodyEl.append(wrap);
|
|
547
|
+
}
|
|
548
|
+
saveBtn.disabled = false;
|
|
549
|
+
}
|
|
550
|
+
async function load() {
|
|
551
|
+
try {
|
|
552
|
+
const [locales, keyValues] = await Promise.all([
|
|
553
|
+
fetchOverlayLocales(context),
|
|
554
|
+
fetchOverlayKeyValues(context, keyPath)
|
|
555
|
+
]);
|
|
556
|
+
if (destroyed) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
keyId = keyValues.keyId;
|
|
560
|
+
const localeCodes = new Set(locales.map((l) => l.code));
|
|
561
|
+
rows = keyValues.entries.filter((entry) => localeCodes.has(entry.localeCode)).map((entry) => ({
|
|
562
|
+
...entry,
|
|
563
|
+
draftValue: entry.value
|
|
564
|
+
}));
|
|
565
|
+
for (const row of rows) {
|
|
566
|
+
if (row.version !== null) {
|
|
567
|
+
_runtimecore.recordTranslationVersion.call(void 0, keyPath, row.localeCode, row.version);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
banner.hidden = true;
|
|
571
|
+
footer.hidden = false;
|
|
572
|
+
renderRows();
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (error instanceof OverlayApiError && error.isKeyNotFound) {
|
|
575
|
+
showCreateKeyPrompt();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
status.textContent = error instanceof Error ? error.message : "Failed to load translations";
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function showCreateKeyPrompt() {
|
|
582
|
+
footer.hidden = true;
|
|
583
|
+
bodyEl.replaceChildren();
|
|
584
|
+
const wrap = document.createElement("div");
|
|
585
|
+
wrap.className = "create-panel";
|
|
586
|
+
const intro = document.createElement("p");
|
|
587
|
+
intro.textContent = "This key is not in your project yet. Create it to start editing translations for all locales.";
|
|
588
|
+
const label = document.createElement("label");
|
|
589
|
+
label.textContent = "Default message (optional)";
|
|
590
|
+
label.htmlFor = "create-default-message";
|
|
591
|
+
const input = document.createElement("input");
|
|
592
|
+
input.id = "create-default-message";
|
|
593
|
+
input.type = "text";
|
|
594
|
+
input.placeholder = "Text shown when no translation is saved";
|
|
595
|
+
if (context.suggestedDefaultMessage) {
|
|
596
|
+
input.value = context.suggestedDefaultMessage;
|
|
597
|
+
}
|
|
598
|
+
const errorEl = document.createElement("p");
|
|
599
|
+
errorEl.className = "error";
|
|
600
|
+
errorEl.hidden = true;
|
|
601
|
+
const createBtn = document.createElement("button");
|
|
602
|
+
createBtn.type = "button";
|
|
603
|
+
createBtn.className = "create-btn";
|
|
604
|
+
createBtn.textContent = "Create key";
|
|
605
|
+
createBtn.addEventListener("click", () => {
|
|
606
|
+
void (async () => {
|
|
607
|
+
createBtn.disabled = true;
|
|
608
|
+
errorEl.hidden = true;
|
|
609
|
+
try {
|
|
610
|
+
await createOverlayKey(context, {
|
|
611
|
+
keyPath,
|
|
612
|
+
defaultMessage: input.value.trim() || null
|
|
613
|
+
});
|
|
614
|
+
if (destroyed) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
status.textContent = "Loading\u2026";
|
|
618
|
+
bodyEl.replaceChildren(status);
|
|
619
|
+
footer.hidden = false;
|
|
620
|
+
saveBtn.disabled = true;
|
|
621
|
+
await load();
|
|
622
|
+
} catch (createError) {
|
|
623
|
+
createBtn.disabled = false;
|
|
624
|
+
errorEl.hidden = false;
|
|
625
|
+
errorEl.textContent = createError instanceof Error ? createError.message : "Failed to create key";
|
|
626
|
+
}
|
|
627
|
+
})();
|
|
628
|
+
});
|
|
629
|
+
wrap.append(intro, label, input, errorEl, createBtn);
|
|
630
|
+
bodyEl.append(wrap);
|
|
631
|
+
}
|
|
632
|
+
function showConflict(message) {
|
|
633
|
+
banner.hidden = false;
|
|
634
|
+
banner.replaceChildren();
|
|
635
|
+
const text = document.createElement("p");
|
|
636
|
+
text.textContent = message;
|
|
637
|
+
banner.append(text);
|
|
638
|
+
const reloadBtn = document.createElement("button");
|
|
639
|
+
reloadBtn.type = "button";
|
|
640
|
+
reloadBtn.textContent = "Reload";
|
|
641
|
+
reloadBtn.addEventListener("click", () => {
|
|
642
|
+
status.textContent = "Loading\u2026";
|
|
643
|
+
bodyEl.replaceChildren(status);
|
|
644
|
+
saveBtn.disabled = true;
|
|
645
|
+
void load();
|
|
646
|
+
});
|
|
647
|
+
banner.append(reloadBtn);
|
|
648
|
+
}
|
|
649
|
+
saveBtn.addEventListener("click", () => {
|
|
650
|
+
void (async () => {
|
|
651
|
+
saveBtn.disabled = true;
|
|
652
|
+
let hadConflict = false;
|
|
653
|
+
for (const row of rows) {
|
|
654
|
+
if (row.draftValue === row.value) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
const saved = await patchOverlayValue(context, {
|
|
659
|
+
keyId,
|
|
660
|
+
localeId: row.localeId,
|
|
661
|
+
value: row.draftValue,
|
|
662
|
+
version: row.version
|
|
663
|
+
});
|
|
664
|
+
row.value = saved.value;
|
|
665
|
+
row.version = saved.version;
|
|
666
|
+
row.draftValue = saved.value;
|
|
667
|
+
context.onSaved(keyPath, row.localeCode, saved.value, saved.version);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (error instanceof OverlayApiError && error.isVersionConflict) {
|
|
670
|
+
hadConflict = true;
|
|
671
|
+
showConflict(
|
|
672
|
+
"Someone else updated this key. Reload to fetch the latest versions, then try again."
|
|
673
|
+
);
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
showConflict(error instanceof Error ? error.message : "Save failed");
|
|
677
|
+
hadConflict = true;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
saveBtn.disabled = hadConflict;
|
|
682
|
+
if (!hadConflict) {
|
|
683
|
+
destroy();
|
|
684
|
+
}
|
|
685
|
+
})();
|
|
686
|
+
});
|
|
687
|
+
registerEditPanelRemoteHandler({ keyPath, applyRemoteUpdate });
|
|
688
|
+
void load();
|
|
689
|
+
return { destroy };
|
|
690
|
+
}
|
|
691
|
+
function closeEditPanel() {
|
|
692
|
+
registerEditPanelRemoteHandler(null);
|
|
693
|
+
_optionalChain([document, 'access', _7 => _7.getElementById, 'call', _8 => _8(EDIT_PANEL_HOST_ID), 'optionalAccess', _9 => _9.remove, 'call', _10 => _10()]);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/overlay/highlights.ts
|
|
697
|
+
var HIGHLIGHT_CLASS = "translation-sdk-target-highlight";
|
|
698
|
+
var STYLES_ID = "translation-sdk-highlight-styles";
|
|
699
|
+
var TOOLTIP_ID = "translation-sdk-key-tooltip";
|
|
700
|
+
var HIGHLIGHT_CSS = `
|
|
701
|
+
.${HIGHLIGHT_CLASS} {
|
|
702
|
+
outline: 2px solid #2563eb !important;
|
|
703
|
+
outline-offset: 2px !important;
|
|
704
|
+
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.28) !important;
|
|
705
|
+
}
|
|
706
|
+
.${HIGHLIGHT_CLASS}:focus-visible {
|
|
707
|
+
outline: 3px solid #1d4ed8 !important;
|
|
708
|
+
outline-offset: 2px !important;
|
|
709
|
+
}
|
|
710
|
+
#${TOOLTIP_ID} {
|
|
711
|
+
position: fixed;
|
|
712
|
+
z-index: 2147483645;
|
|
713
|
+
max-width: min(360px, 90vw);
|
|
714
|
+
padding: 4px 8px;
|
|
715
|
+
border-radius: 6px;
|
|
716
|
+
font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
717
|
+
color: #f8fafc;
|
|
718
|
+
background: #0f172a;
|
|
719
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
|
|
720
|
+
pointer-events: none;
|
|
721
|
+
display: none;
|
|
722
|
+
}
|
|
723
|
+
#${TOOLTIP_ID}[data-visible="true"] {
|
|
724
|
+
display: block;
|
|
725
|
+
}
|
|
726
|
+
`;
|
|
727
|
+
function createHighlightController(options) {
|
|
728
|
+
if (typeof document === "undefined") {
|
|
729
|
+
return { destroy: () => {
|
|
730
|
+
} };
|
|
731
|
+
}
|
|
732
|
+
const abort = new AbortController();
|
|
733
|
+
const { signal } = abort;
|
|
734
|
+
let highlighted = null;
|
|
735
|
+
const styleEl = document.createElement("style");
|
|
736
|
+
styleEl.id = STYLES_ID;
|
|
737
|
+
styleEl.textContent = HIGHLIGHT_CSS;
|
|
738
|
+
document.head.append(styleEl);
|
|
739
|
+
const tip = document.createElement("div");
|
|
740
|
+
tip.id = TOOLTIP_ID;
|
|
741
|
+
document.body.append(tip);
|
|
742
|
+
function isInsideOverlay(node) {
|
|
743
|
+
if (!node || !(node instanceof Element)) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
return Boolean(
|
|
747
|
+
node.closest(`#${OVERLAY_ROOT_ID}, #${EDIT_PANEL_HOST_ID}`)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
function clearHighlight() {
|
|
751
|
+
if (highlighted) {
|
|
752
|
+
highlighted.classList.remove(HIGHLIGHT_CLASS);
|
|
753
|
+
highlighted = null;
|
|
754
|
+
}
|
|
755
|
+
tip.dataset.visible = "false";
|
|
756
|
+
tip.textContent = "";
|
|
757
|
+
}
|
|
758
|
+
function positionTooltip(key, target, clientX) {
|
|
759
|
+
tip.textContent = key;
|
|
760
|
+
tip.dataset.visible = "true";
|
|
761
|
+
const rect = target.getBoundingClientRect();
|
|
762
|
+
const top = Math.min(rect.bottom + 8, window.innerHeight - 32);
|
|
763
|
+
const left = Math.min(Math.max(clientX, 8), window.innerWidth - 200);
|
|
764
|
+
tip.style.top = `${top}px`;
|
|
765
|
+
tip.style.left = `${left}px`;
|
|
766
|
+
}
|
|
767
|
+
function applyHighlight(target, key, clientX) {
|
|
768
|
+
if (highlighted !== target) {
|
|
769
|
+
clearHighlight();
|
|
770
|
+
highlighted = target;
|
|
771
|
+
target.classList.add(HIGHLIGHT_CLASS);
|
|
772
|
+
}
|
|
773
|
+
positionTooltip(key, target, clientX);
|
|
774
|
+
}
|
|
775
|
+
function inspectTarget(target, clientX, _clientY) {
|
|
776
|
+
if (!target || isInsideOverlay(target)) {
|
|
777
|
+
clearHighlight();
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const key = options.resolveKey(target);
|
|
781
|
+
if (!key) {
|
|
782
|
+
clearHighlight();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
applyHighlight(target, key, clientX);
|
|
786
|
+
}
|
|
787
|
+
document.addEventListener(
|
|
788
|
+
"pointermove",
|
|
789
|
+
(event) => {
|
|
790
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
791
|
+
inspectTarget(target, event.clientX, event.clientY);
|
|
792
|
+
},
|
|
793
|
+
{ signal }
|
|
794
|
+
);
|
|
795
|
+
document.addEventListener(
|
|
796
|
+
"focusin",
|
|
797
|
+
(event) => {
|
|
798
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
799
|
+
if (!target) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const rect = target.getBoundingClientRect();
|
|
803
|
+
inspectTarget(target, rect.left + rect.width / 2, rect.top);
|
|
804
|
+
},
|
|
805
|
+
{ capture: true, signal }
|
|
806
|
+
);
|
|
807
|
+
document.addEventListener(
|
|
808
|
+
"click",
|
|
809
|
+
(event) => {
|
|
810
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
811
|
+
if (!target || isInsideOverlay(target)) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const key = options.resolveKey(target);
|
|
815
|
+
if (!key) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
_optionalChain([options, 'access', _11 => _11.onActivateKey, 'optionalCall', _12 => _12(key, target)]);
|
|
819
|
+
},
|
|
820
|
+
{ capture: true, signal }
|
|
821
|
+
);
|
|
822
|
+
return {
|
|
823
|
+
destroy: () => {
|
|
824
|
+
abort.abort();
|
|
825
|
+
clearHighlight();
|
|
826
|
+
styleEl.remove();
|
|
827
|
+
tip.remove();
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/overlay/realtime-sync.ts
|
|
833
|
+
|
|
834
|
+
var _realtimeprotocol = require('@stringpush/realtime-protocol');
|
|
835
|
+
function handleRealtimeWireMessage(raw, context) {
|
|
836
|
+
let event;
|
|
837
|
+
try {
|
|
838
|
+
event = _realtimeprotocol.parseRealtimeEvent.call(void 0, raw);
|
|
839
|
+
} catch (e) {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
void applyRealtimeEvent(event, context).catch(() => {
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
async function applyRealtimeEvent(event, context) {
|
|
846
|
+
if (event.type === "translation.updated") {
|
|
847
|
+
const { payload } = event;
|
|
848
|
+
if (payload.projectId !== context.projectId || payload.environmentId !== context.environmentId) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
notifyEditPanelRemoteUpdate({
|
|
852
|
+
keyPath: payload.keyPath,
|
|
853
|
+
localeCode: payload.localeCode,
|
|
854
|
+
value: payload.value,
|
|
855
|
+
version: payload.version
|
|
856
|
+
});
|
|
857
|
+
if (_runtimecore.patchCatalogEntry.call(void 0,
|
|
858
|
+
payload.keyPath,
|
|
859
|
+
payload.localeCode,
|
|
860
|
+
payload.value,
|
|
861
|
+
payload.version
|
|
862
|
+
)) {
|
|
863
|
+
context.notifyTranslationsUpdated();
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (event.type === "bundle.published") {
|
|
868
|
+
const { payload } = event;
|
|
869
|
+
if (payload.projectId !== context.projectId || payload.applicationId !== context.applicationId || payload.environmentId !== context.environmentId) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const reloaded = await _runtimecore.reloadIfBundlePublishedNewer.call(void 0, payload.releases);
|
|
873
|
+
if (reloaded) {
|
|
874
|
+
context.notifyTranslationsUpdated();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/overlay/realtime-reconnect.ts
|
|
880
|
+
var REALTIME_RECONNECT_INITIAL_MS = 1e3;
|
|
881
|
+
var REALTIME_RECONNECT_MAX_MS = 3e4;
|
|
882
|
+
function computeRealtimeReconnectDelayMs(attempt, initialMs = REALTIME_RECONNECT_INITIAL_MS, maxMs = REALTIME_RECONNECT_MAX_MS) {
|
|
883
|
+
if (attempt <= 0) {
|
|
884
|
+
return initialMs;
|
|
885
|
+
}
|
|
886
|
+
return Math.min(initialMs * 2 ** (attempt - 1), maxMs);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/overlay/realtime-client.ts
|
|
890
|
+
function connectRealtime(options) {
|
|
891
|
+
if (typeof globalThis === "undefined" || !("WebSocket" in globalThis)) {
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
895
|
+
let intentionalClose = false;
|
|
896
|
+
let reconnectAttempt = 0;
|
|
897
|
+
let reconnectTimer = null;
|
|
898
|
+
let socket = null;
|
|
899
|
+
const clearReconnectTimer = () => {
|
|
900
|
+
if (reconnectTimer !== null) {
|
|
901
|
+
clearTimeout(reconnectTimer);
|
|
902
|
+
reconnectTimer = null;
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
const sendAuth = (ws) => {
|
|
906
|
+
ws.send(
|
|
907
|
+
JSON.stringify({
|
|
908
|
+
type: "auth",
|
|
909
|
+
token: options.editToken,
|
|
910
|
+
projectId: options.projectId
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
};
|
|
914
|
+
const attachSocket = (ws) => {
|
|
915
|
+
ws.addEventListener("open", () => {
|
|
916
|
+
reconnectAttempt = 0;
|
|
917
|
+
sendAuth(ws);
|
|
918
|
+
});
|
|
919
|
+
ws.addEventListener("message", (event) => {
|
|
920
|
+
const raw = typeof event.data === "string" ? event.data : null;
|
|
921
|
+
if (!raw) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
handleRealtimeWireMessage(raw, options.sync);
|
|
925
|
+
});
|
|
926
|
+
ws.addEventListener("close", () => {
|
|
927
|
+
if (socket === ws) {
|
|
928
|
+
socket = null;
|
|
929
|
+
}
|
|
930
|
+
if (!intentionalClose) {
|
|
931
|
+
scheduleReconnect();
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
};
|
|
935
|
+
const scheduleReconnect = () => {
|
|
936
|
+
if (intentionalClose) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
clearReconnectTimer();
|
|
940
|
+
reconnectAttempt += 1;
|
|
941
|
+
const delayMs = computeRealtimeReconnectDelayMs(reconnectAttempt);
|
|
942
|
+
reconnectTimer = setTimeout(() => {
|
|
943
|
+
reconnectTimer = null;
|
|
944
|
+
openSocket();
|
|
945
|
+
}, delayMs);
|
|
946
|
+
};
|
|
947
|
+
const openSocket = () => {
|
|
948
|
+
if (intentionalClose) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const ws = new WebSocketCtor(options.realtimeUrl);
|
|
952
|
+
socket = ws;
|
|
953
|
+
attachSocket(ws);
|
|
954
|
+
};
|
|
955
|
+
openSocket();
|
|
956
|
+
return {
|
|
957
|
+
close: () => {
|
|
958
|
+
intentionalClose = true;
|
|
959
|
+
clearReconnectTimer();
|
|
960
|
+
if (socket && (socket.readyState === WebSocketCtor.OPEN || socket.readyState === WebSocketCtor.CONNECTING)) {
|
|
961
|
+
socket.close();
|
|
962
|
+
}
|
|
963
|
+
socket = null;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/overlay/index.ts
|
|
969
|
+
function mountOverlay(context) {
|
|
970
|
+
if (typeof document === "undefined") {
|
|
971
|
+
return { destroy: () => {
|
|
972
|
+
} };
|
|
973
|
+
}
|
|
974
|
+
_optionalChain([document, 'access', _13 => _13.getElementById, 'call', _14 => _14(OVERLAY_ROOT_ID), 'optionalAccess', _15 => _15.remove, 'call', _16 => _16()]);
|
|
975
|
+
closeEditPanel();
|
|
976
|
+
const mountHost = document.createElement("div");
|
|
977
|
+
mountHost.id = OVERLAY_ROOT_ID;
|
|
978
|
+
mountHost.setAttribute("data-translation-overlay", "true");
|
|
979
|
+
const shadow = mountHost.attachShadow({ mode: "open" });
|
|
980
|
+
const label = document.createElement("div");
|
|
981
|
+
label.setAttribute("data-overlay-marker", OVERLAY_CHUNK_MARKER);
|
|
982
|
+
label.textContent = "Translation edit mode \u2014 click text to edit";
|
|
983
|
+
label.style.cssText = [
|
|
984
|
+
"position:fixed",
|
|
985
|
+
"bottom:16px",
|
|
986
|
+
"right:16px",
|
|
987
|
+
"z-index:2147483646",
|
|
988
|
+
"padding:8px 12px",
|
|
989
|
+
"border-radius:8px",
|
|
990
|
+
"font:600 13px/1.4 system-ui,sans-serif",
|
|
991
|
+
"color:#fff",
|
|
992
|
+
"background:#1e3a5f",
|
|
993
|
+
"box-shadow:0 4px 12px rgba(0,0,0,.25)",
|
|
994
|
+
"pointer-events:none"
|
|
995
|
+
].join(";");
|
|
996
|
+
shadow.append(label);
|
|
997
|
+
document.body.append(mountHost);
|
|
998
|
+
let panelHandle = null;
|
|
999
|
+
const highlights = createHighlightController({
|
|
1000
|
+
resolveKey: context.resolveKey,
|
|
1001
|
+
onActivateKey: (keyPath, element) => {
|
|
1002
|
+
_optionalChain([panelHandle, 'optionalAccess', _17 => _17.destroy, 'call', _18 => _18()]);
|
|
1003
|
+
const suggestedDefaultMessage = _optionalChain([element, 'access', _19 => _19.textContent, 'optionalAccess', _20 => _20.trim, 'call', _21 => _21()]) || void 0;
|
|
1004
|
+
panelHandle = openEditPanel(keyPath, {
|
|
1005
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
1006
|
+
editToken: context.editToken,
|
|
1007
|
+
origin: context.origin,
|
|
1008
|
+
activeLocaleCode: context.activeLocaleCode,
|
|
1009
|
+
suggestedDefaultMessage,
|
|
1010
|
+
onSaved: context.onCatalogPatched
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
const realtime = connectRealtime({
|
|
1015
|
+
realtimeUrl: context.realtimeUrl,
|
|
1016
|
+
editToken: context.editToken,
|
|
1017
|
+
projectId: context.projectId,
|
|
1018
|
+
sync: {
|
|
1019
|
+
projectId: context.projectId,
|
|
1020
|
+
applicationId: context.applicationId,
|
|
1021
|
+
environmentId: context.environmentId,
|
|
1022
|
+
notifyTranslationsUpdated: context.onTranslationsUpdated
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
return {
|
|
1026
|
+
destroy: () => {
|
|
1027
|
+
_optionalChain([panelHandle, 'optionalAccess', _22 => _22.destroy, 'call', _23 => _23()]);
|
|
1028
|
+
highlights.destroy();
|
|
1029
|
+
_optionalChain([realtime, 'optionalAccess', _24 => _24.close, 'call', _25 => _25()]);
|
|
1030
|
+
mountHost.remove();
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
exports.OVERLAY_CHUNK_MARKER = OVERLAY_CHUNK_MARKER; exports.OVERLAY_ROOT_ID = OVERLAY_ROOT_ID; exports.mountOverlay = mountOverlay;
|
|
1039
|
+
//# sourceMappingURL=overlay-MLOXYRPA.umd.cjs.map
|