@8ux-co/eelzap 0.10.0 → 0.10.1
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/CHANGELOG.md +63 -1
- package/README.md +68 -8
- package/dist/{chunk-QN53G5P7.js → chunk-6XHCA4U4.js} +25 -9
- package/dist/chunk-6XHCA4U4.js.map +1 -0
- package/dist/{chunk-VW4EH7NY.js → chunk-7CXQK2PR.js} +148 -80
- package/dist/chunk-7CXQK2PR.js.map +1 -0
- package/dist/{chunk-2IUU53QA.js → chunk-7ZPXZZUT.js} +171 -2
- package/dist/chunk-7ZPXZZUT.js.map +1 -0
- package/dist/index.cjs +94 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -12
- package/dist/index.d.ts +119 -12
- package/dist/index.js +94 -1
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +6 -0
- package/dist/next.js.map +1 -1
- package/dist/preview.d.ts +55 -7
- package/dist/preview.js +95 -39
- package/dist/preview.js.map +1 -1
- package/dist/react.js +3 -3
- package/dist/react.js.map +1 -1
- package/dist/{signin-2QOSWZFK.js → signin-QT7YANBN.js} +14 -111
- package/dist/signin-QT7YANBN.js.map +1 -0
- package/dist/{suggest-5RHURHED.js → suggest-JZ63L3WP.js} +1001 -66
- package/dist/suggest-JZ63L3WP.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2IUU53QA.js.map +0 -1
- package/dist/chunk-QN53G5P7.js.map +0 -1
- package/dist/chunk-VW4EH7NY.js.map +0 -1
- package/dist/signin-2QOSWZFK.js.map +0 -1
- package/dist/suggest-5RHURHED.js.map +0 -1
|
@@ -3,24 +3,34 @@ import {
|
|
|
3
3
|
Overlay,
|
|
4
4
|
TagIndex,
|
|
5
5
|
buildAnchor,
|
|
6
|
+
cleanStega,
|
|
6
7
|
findAnchorElement,
|
|
8
|
+
isDraftRoutePath,
|
|
7
9
|
isRecordRef,
|
|
10
|
+
isShortcut,
|
|
8
11
|
normalizeText,
|
|
9
12
|
observeTags,
|
|
10
|
-
parseRecordRef
|
|
11
|
-
|
|
13
|
+
parseRecordRef,
|
|
14
|
+
reveal,
|
|
15
|
+
tagGuard
|
|
16
|
+
} from "./chunk-7CXQK2PR.js";
|
|
12
17
|
import {
|
|
13
18
|
SUGGEST_MESSAGES,
|
|
14
19
|
TOKENS,
|
|
15
20
|
el,
|
|
16
21
|
icon,
|
|
17
22
|
initials,
|
|
18
|
-
mountHost
|
|
19
|
-
|
|
23
|
+
mountHost,
|
|
24
|
+
readTokenAnswer
|
|
25
|
+
} from "./chunk-7ZPXZZUT.js";
|
|
20
26
|
import {
|
|
27
|
+
EXPIRY_MARGIN_MS,
|
|
28
|
+
accessLive,
|
|
21
29
|
clearToken,
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
readSession,
|
|
31
|
+
refreshLive,
|
|
32
|
+
writeToken
|
|
33
|
+
} from "./chunk-6XHCA4U4.js";
|
|
24
34
|
|
|
25
35
|
// src/preview/site/api.ts
|
|
26
36
|
var BASE = "/api/public/v1/comments";
|
|
@@ -73,6 +83,387 @@ function newIdempotencyKey(win) {
|
|
|
73
83
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
// src/preview/site/drag.ts
|
|
87
|
+
var BAR_STORAGE_KEY = "eelzap:bar";
|
|
88
|
+
var GUTTER = 16;
|
|
89
|
+
var STEP = 16;
|
|
90
|
+
var BIG_STEP = 64;
|
|
91
|
+
var GAP = 10;
|
|
92
|
+
var SLOP = 3;
|
|
93
|
+
var DOUBLE_MS = 400;
|
|
94
|
+
var clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
|
|
95
|
+
function storage(win) {
|
|
96
|
+
try {
|
|
97
|
+
return win.localStorage;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function read(win) {
|
|
103
|
+
try {
|
|
104
|
+
const value = JSON.parse(storage(win)?.getItem(BAR_STORAGE_KEY) ?? "null");
|
|
105
|
+
return {
|
|
106
|
+
pos: value && Number.isFinite(value.x) && Number.isFinite(value.y) ? { x: value.x, y: value.y } : null,
|
|
107
|
+
hidden: value?.hidden === true
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return { pos: null, hidden: false };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function write(win, point, hidden) {
|
|
114
|
+
try {
|
|
115
|
+
const store = storage(win);
|
|
116
|
+
if (point || hidden)
|
|
117
|
+
store?.setItem(BAR_STORAGE_KEY, JSON.stringify({ ...point, hidden: hidden || void 0 }));
|
|
118
|
+
else store?.removeItem(BAR_STORAGE_KEY);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function barDrag(win, doc, stack, label, tip) {
|
|
123
|
+
const saved = read(win);
|
|
124
|
+
let pos = saved.pos;
|
|
125
|
+
let hidden = saved.hidden;
|
|
126
|
+
let drag = null;
|
|
127
|
+
let dragged = false;
|
|
128
|
+
let lastTap = 0;
|
|
129
|
+
const viewport = () => {
|
|
130
|
+
const root = doc.documentElement;
|
|
131
|
+
return [root.clientWidth || win.innerWidth, root.clientHeight || win.innerHeight];
|
|
132
|
+
};
|
|
133
|
+
const bar = () => stack.querySelector(".toolbar, .pill");
|
|
134
|
+
const rectOf = (node) => {
|
|
135
|
+
const rect = node.getBoundingClientRect();
|
|
136
|
+
return { x: rect.left, y: rect.top };
|
|
137
|
+
};
|
|
138
|
+
function place() {
|
|
139
|
+
const toolbar = bar();
|
|
140
|
+
const cards = Array.from(stack.children);
|
|
141
|
+
const [vw, vh] = viewport();
|
|
142
|
+
if (!pos || !toolbar) {
|
|
143
|
+
stack.removeAttribute("style");
|
|
144
|
+
stack.removeAttribute("data-below");
|
|
145
|
+
for (const card of cards) {
|
|
146
|
+
card.style.marginLeft = "";
|
|
147
|
+
card.style.maxHeight = "";
|
|
148
|
+
}
|
|
149
|
+
placeMenu(toolbar, vw);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const w = toolbar.offsetWidth;
|
|
153
|
+
const h = toolbar.offsetHeight;
|
|
154
|
+
const x = clamp(pos.x, GUTTER, vw - w - GUTTER);
|
|
155
|
+
const y = clamp(pos.y, GUTTER, vh - h - GUTTER);
|
|
156
|
+
const below = vh - y - h > y;
|
|
157
|
+
const room = (below ? vh - y - h : y) - GAP - GUTTER;
|
|
158
|
+
Object.assign(stack.style, {
|
|
159
|
+
padding: "0",
|
|
160
|
+
alignItems: "flex-start",
|
|
161
|
+
flexDirection: below ? "column-reverse" : "column",
|
|
162
|
+
top: below ? `${y}px` : "auto",
|
|
163
|
+
bottom: below ? "auto" : `${vh - y - h}px`
|
|
164
|
+
});
|
|
165
|
+
stack.toggleAttribute("data-below", below);
|
|
166
|
+
for (const card of cards) {
|
|
167
|
+
if (card.classList.contains("toast") || card.classList.contains("menu")) continue;
|
|
168
|
+
if (card.classList.contains("list")) card.style.maxHeight = `min(50vh, ${room}px)`;
|
|
169
|
+
const cw = card.offsetWidth;
|
|
170
|
+
card.style.marginLeft = `${card === toolbar ? x : clamp(x + (w - cw) / 2, GUTTER / 2, vw - cw - GUTTER / 2)}px`;
|
|
171
|
+
}
|
|
172
|
+
placeMenu(toolbar, vw);
|
|
173
|
+
}
|
|
174
|
+
function placeMenu(toolbar, vw) {
|
|
175
|
+
const menu = stack.querySelector(".menu");
|
|
176
|
+
if (!menu || !toolbar) return;
|
|
177
|
+
const left = stack.getBoundingClientRect().left + (parseFloat(win.getComputedStyle(stack).paddingLeft) || 0);
|
|
178
|
+
const mw = menu.offsetWidth;
|
|
179
|
+
menu.style.alignSelf = "flex-start";
|
|
180
|
+
menu.style.marginLeft = `${clamp(toolbar.getBoundingClientRect().right - 35 - mw, GUTTER / 2, vw - mw - GUTTER / 2) - left}px`;
|
|
181
|
+
}
|
|
182
|
+
function moveTo(point, save) {
|
|
183
|
+
pos = point;
|
|
184
|
+
place();
|
|
185
|
+
const toolbar = bar();
|
|
186
|
+
if (toolbar) pos = rectOf(toolbar);
|
|
187
|
+
if (save) write(win, pos, hidden);
|
|
188
|
+
}
|
|
189
|
+
function reset() {
|
|
190
|
+
pos = null;
|
|
191
|
+
write(win, null, hidden);
|
|
192
|
+
place();
|
|
193
|
+
}
|
|
194
|
+
const onMove = (event) => {
|
|
195
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
196
|
+
const dx = event.clientX - drag.px;
|
|
197
|
+
const dy = event.clientY - drag.py;
|
|
198
|
+
if (!drag.moved && Math.hypot(dx, dy) < SLOP) return;
|
|
199
|
+
drag.moved = true;
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
pos = { x: drag.from.x + dx, y: drag.from.y + dy };
|
|
202
|
+
place();
|
|
203
|
+
};
|
|
204
|
+
const onUp = (event) => {
|
|
205
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
206
|
+
const { moved, node } = drag;
|
|
207
|
+
drag = null;
|
|
208
|
+
node.removeAttribute("data-dragging");
|
|
209
|
+
if (moved && pos) {
|
|
210
|
+
moveTo(pos, true);
|
|
211
|
+
lastTap = 0;
|
|
212
|
+
dragged = true;
|
|
213
|
+
win.setTimeout(() => dragged = false);
|
|
214
|
+
} else if (node === handle && event.type === "pointerup") {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
if (now - lastTap < DOUBLE_MS) {
|
|
217
|
+
lastTap = 0;
|
|
218
|
+
reset();
|
|
219
|
+
} else lastTap = now;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const stop = (event) => event.stopPropagation();
|
|
223
|
+
function begin(e, node) {
|
|
224
|
+
const toolbar = bar();
|
|
225
|
+
if (e.button !== 0 || !toolbar) return;
|
|
226
|
+
drag = {
|
|
227
|
+
id: e.pointerId,
|
|
228
|
+
px: e.clientX,
|
|
229
|
+
py: e.clientY,
|
|
230
|
+
from: rectOf(toolbar),
|
|
231
|
+
moved: false,
|
|
232
|
+
node
|
|
233
|
+
};
|
|
234
|
+
node.setAttribute("data-dragging", "");
|
|
235
|
+
try {
|
|
236
|
+
node.setPointerCapture(e.pointerId);
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const handle = el(doc, "button", {
|
|
241
|
+
class: "grip",
|
|
242
|
+
attrs: { type: "button", "aria-label": label, title: tip },
|
|
243
|
+
on: {
|
|
244
|
+
pointerdown: (event) => {
|
|
245
|
+
event.stopPropagation();
|
|
246
|
+
begin(event, handle);
|
|
247
|
+
},
|
|
248
|
+
keydown: (event) => {
|
|
249
|
+
const e = event;
|
|
250
|
+
const step = e.shiftKey ? BIG_STEP : STEP;
|
|
251
|
+
const delta = {
|
|
252
|
+
ArrowLeft: [-step, 0],
|
|
253
|
+
ArrowRight: [step, 0],
|
|
254
|
+
ArrowUp: [0, -step],
|
|
255
|
+
ArrowDown: [0, step]
|
|
256
|
+
};
|
|
257
|
+
const move = delta[e.key];
|
|
258
|
+
const toolbar = bar();
|
|
259
|
+
if (!move || !toolbar) return;
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
e.stopPropagation();
|
|
262
|
+
const from = rectOf(toolbar);
|
|
263
|
+
moveTo({ x: from.x + move[0], y: from.y + move[1] }, true);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
for (const type of [
|
|
268
|
+
"pointerup",
|
|
269
|
+
"dblclick",
|
|
270
|
+
"mousedown",
|
|
271
|
+
"mouseup",
|
|
272
|
+
"click",
|
|
273
|
+
"touchstart",
|
|
274
|
+
"touchend"
|
|
275
|
+
]) {
|
|
276
|
+
handle.addEventListener(type, stop);
|
|
277
|
+
}
|
|
278
|
+
handle.append(icon(doc, "grip"));
|
|
279
|
+
const onResize = () => place();
|
|
280
|
+
win.addEventListener("pointermove", onMove, true);
|
|
281
|
+
win.addEventListener("pointerup", onUp, true);
|
|
282
|
+
win.addEventListener("pointercancel", onUp, true);
|
|
283
|
+
win.addEventListener("resize", onResize);
|
|
284
|
+
win.addEventListener("orientationchange", onResize);
|
|
285
|
+
return {
|
|
286
|
+
handle,
|
|
287
|
+
grab(node) {
|
|
288
|
+
node.addEventListener("pointerdown", (event) => begin(event, node));
|
|
289
|
+
node.addEventListener(
|
|
290
|
+
"click",
|
|
291
|
+
(event) => {
|
|
292
|
+
if (dragged) event.stopImmediatePropagation();
|
|
293
|
+
},
|
|
294
|
+
true
|
|
295
|
+
);
|
|
296
|
+
return node;
|
|
297
|
+
},
|
|
298
|
+
place,
|
|
299
|
+
hidden: () => hidden,
|
|
300
|
+
setHidden(value) {
|
|
301
|
+
hidden = value;
|
|
302
|
+
write(win, pos, hidden);
|
|
303
|
+
},
|
|
304
|
+
destroy() {
|
|
305
|
+
win.removeEventListener("pointermove", onMove, true);
|
|
306
|
+
win.removeEventListener("pointerup", onUp, true);
|
|
307
|
+
win.removeEventListener("pointercancel", onUp, true);
|
|
308
|
+
win.removeEventListener("resize", onResize);
|
|
309
|
+
win.removeEventListener("orientationchange", onResize);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
var DRAG_CSS = `
|
|
314
|
+
.grip { width: 20px; height: 30px; justify-content: center; border-radius: 7px; color: ${TOKENS.muted};
|
|
315
|
+
cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
|
|
316
|
+
.grip[data-dragging], .pill[data-dragging] { cursor: grabbing; }
|
|
317
|
+
.pill { touch-action: none; user-select: none; -webkit-user-select: none; }
|
|
318
|
+
.bottom[data-below] .seg[data-tip]:hover::after { bottom: auto; top: 34px; }
|
|
319
|
+
`;
|
|
320
|
+
|
|
321
|
+
// src/preview/site/draft-session.ts
|
|
322
|
+
var DRAFT_SESSION_PATH = "/api/public/v1/preview/session";
|
|
323
|
+
var EDIT_INTENT_KEY = "eelzap:edit-intent";
|
|
324
|
+
var DRAFT_SESSION_KEY = "eelzap:draft-session";
|
|
325
|
+
var INTENT_MAX_AGE_MS = 2 * 6e4;
|
|
326
|
+
var DRAFT_SESSION_MESSAGES = {
|
|
327
|
+
es: {
|
|
328
|
+
preparing: "Preparando la edici\xF3n\u2026",
|
|
329
|
+
noRoute: "Este sitio no tiene la ruta de vista previa que Editar necesita. Usa Comentar o \xE1brelo desde Zap.",
|
|
330
|
+
failed: "No pudimos preparar la p\xE1gina para editar. Int\xE9ntalo de nuevo o usa Comentar.",
|
|
331
|
+
rate: (seconds) => `Demasiados intentos. Vuelve a intentarlo en ${seconds} ${seconds === 1 ? "segundo" : "segundos"}.`
|
|
332
|
+
},
|
|
333
|
+
en: {
|
|
334
|
+
preparing: "Getting the page ready to edit\u2026",
|
|
335
|
+
noRoute: "This site does not have the preview route Edit needs. Use Comment or open it from Zap.",
|
|
336
|
+
failed: "We could not get the page ready to edit. Try again or use Comment.",
|
|
337
|
+
rate: (seconds) => `Too many tries. Try again in ${seconds} ${seconds === 1 ? "second" : "seconds"}.`
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
function storage2(win) {
|
|
341
|
+
try {
|
|
342
|
+
return win.sessionStorage;
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function readJson(win, key) {
|
|
348
|
+
try {
|
|
349
|
+
const raw = storage2(win)?.getItem(key);
|
|
350
|
+
if (!raw) return null;
|
|
351
|
+
const value = JSON.parse(raw);
|
|
352
|
+
return value && typeof value === "object" ? value : null;
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function write2(win, key, value) {
|
|
358
|
+
try {
|
|
359
|
+
storage2(win)?.setItem(key, JSON.stringify(value));
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function remove(win, key) {
|
|
364
|
+
try {
|
|
365
|
+
storage2(win)?.removeItem(key);
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function currentPath(win) {
|
|
370
|
+
const { pathname, search, hash } = win.location;
|
|
371
|
+
return `${pathname}${search}${hash}`;
|
|
372
|
+
}
|
|
373
|
+
function inDraftSession(win, now = Date.now()) {
|
|
374
|
+
const value = readJson(win, DRAFT_SESSION_KEY);
|
|
375
|
+
return !!value && typeof value.exp === "number" && value.exp > now && value.origin === win.location.origin;
|
|
376
|
+
}
|
|
377
|
+
function takeEditIntent(win, now = Date.now()) {
|
|
378
|
+
const value = readJson(win, EDIT_INTENT_KEY);
|
|
379
|
+
remove(win, EDIT_INTENT_KEY);
|
|
380
|
+
return !!value && value.path === win.location.pathname && typeof value.at === "number" && now - value.at >= 0 && now - value.at < INTENT_MAX_AGE_MS;
|
|
381
|
+
}
|
|
382
|
+
function acceptSessionUrl(raw, win, draftRoute) {
|
|
383
|
+
if (typeof raw !== "string") return null;
|
|
384
|
+
let url;
|
|
385
|
+
try {
|
|
386
|
+
url = new URL(raw);
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
if (url.origin !== win.location.origin || url.pathname !== draftRoute) return null;
|
|
391
|
+
if (!/^zpt_[A-Za-z0-9_-]{43}$/.test(url.searchParams.get("token") ?? "")) return null;
|
|
392
|
+
const path = url.searchParams.get("path") ?? "";
|
|
393
|
+
if (!path.startsWith("/") || path.startsWith("//")) return null;
|
|
394
|
+
return url.toString();
|
|
395
|
+
}
|
|
396
|
+
async function requestDraftSession(ctx, token, now = Date.now) {
|
|
397
|
+
const { win } = ctx;
|
|
398
|
+
const draftRoute = ctx.draftRoute;
|
|
399
|
+
if (!isDraftRoutePath(draftRoute)) return { ok: false, reason: "no-route" };
|
|
400
|
+
let response;
|
|
401
|
+
try {
|
|
402
|
+
response = await win.fetch(`${ctx.zapOrigin}${DRAFT_SESSION_PATH}`, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
405
|
+
credentials: "omit",
|
|
406
|
+
body: JSON.stringify({ draftRoute, path: currentPath(win) })
|
|
407
|
+
});
|
|
408
|
+
} catch {
|
|
409
|
+
return { ok: false, reason: "refused" };
|
|
410
|
+
}
|
|
411
|
+
if (response.status === 401) return { ok: false, reason: "expired" };
|
|
412
|
+
if (response.status === 429) {
|
|
413
|
+
const seconds = Number.parseInt(response.headers.get("Retry-After") ?? "", 10);
|
|
414
|
+
return { ok: false, reason: "rate", retryAfter: seconds > 0 ? Math.min(seconds, 3600) : 30 };
|
|
415
|
+
}
|
|
416
|
+
if (!response.ok) return { ok: false, reason: "failed" };
|
|
417
|
+
let body;
|
|
418
|
+
try {
|
|
419
|
+
body = await response.json();
|
|
420
|
+
} catch {
|
|
421
|
+
return { ok: false, reason: "failed" };
|
|
422
|
+
}
|
|
423
|
+
const url = acceptSessionUrl(body?.url, win, draftRoute);
|
|
424
|
+
if (!url) return { ok: false, reason: "failed" };
|
|
425
|
+
const at = now();
|
|
426
|
+
const parsed = typeof body?.expiresAt === "string" ? Date.parse(body.expiresAt) : NaN;
|
|
427
|
+
const expiresAt = Number.isFinite(parsed) ? parsed : at + 10 * 6e4;
|
|
428
|
+
write2(win, EDIT_INTENT_KEY, { path: win.location.pathname, at });
|
|
429
|
+
write2(win, DRAFT_SESSION_KEY, { exp: expiresAt, origin: win.location.origin });
|
|
430
|
+
return { ok: true, url, expiresAt };
|
|
431
|
+
}
|
|
432
|
+
function draftExitUrl(win, draftRoute) {
|
|
433
|
+
const url = new URL(`${draftRoute.replace(/\/+$/, "")}/exit`, win.location.origin);
|
|
434
|
+
url.searchParams.set("path", currentPath(win));
|
|
435
|
+
return url.toString();
|
|
436
|
+
}
|
|
437
|
+
async function leaveDraftSession(ctx, token, tagged) {
|
|
438
|
+
const { win } = ctx;
|
|
439
|
+
const entered = inDraftSession(win);
|
|
440
|
+
remove(win, EDIT_INTENT_KEY);
|
|
441
|
+
remove(win, DRAFT_SESSION_KEY);
|
|
442
|
+
if (!entered && !tagged) return false;
|
|
443
|
+
try {
|
|
444
|
+
await win.fetch(`${ctx.zapOrigin}${DRAFT_SESSION_PATH}`, {
|
|
445
|
+
method: "DELETE",
|
|
446
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
447
|
+
credentials: "omit"
|
|
448
|
+
});
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
if (!isDraftRoutePath(ctx.draftRoute)) return false;
|
|
452
|
+
try {
|
|
453
|
+
const response = await win.fetch(draftExitUrl(win, ctx.draftRoute), {
|
|
454
|
+
method: "GET",
|
|
455
|
+
credentials: "same-origin",
|
|
456
|
+
redirect: "manual",
|
|
457
|
+
cache: "no-store"
|
|
458
|
+
});
|
|
459
|
+
if (response.type !== "opaqueredirect" && !response.ok) return false;
|
|
460
|
+
} catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
win.location.reload();
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
|
|
76
467
|
// src/preview/site/suggestion.ts
|
|
77
468
|
function editKindFor(tagged, field) {
|
|
78
469
|
if (!tagged || !field || tagged.html) return null;
|
|
@@ -171,7 +562,33 @@ function proposedPart(proposal) {
|
|
|
171
562
|
]
|
|
172
563
|
};
|
|
173
564
|
}
|
|
565
|
+
function sameValue(a, b) {
|
|
566
|
+
if (typeof a === "number" || typeof b === "number") return a === b;
|
|
567
|
+
return normalizeText(a) === normalizeText(b);
|
|
568
|
+
}
|
|
569
|
+
var LINE_BLOCKS = /* @__PURE__ */ new Set(["div", "p", "li"]);
|
|
570
|
+
function editableText(element) {
|
|
571
|
+
let out = "";
|
|
572
|
+
const walk = (node) => {
|
|
573
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
574
|
+
if (child.nodeType === 3) {
|
|
575
|
+
out += child.data;
|
|
576
|
+
} else if (child.nodeType === 1) {
|
|
577
|
+
const name = child.localName;
|
|
578
|
+
if (name === "br") {
|
|
579
|
+
out += "\n";
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (LINE_BLOCKS.has(name) && out !== "" && !out.endsWith("\n")) out += "\n";
|
|
583
|
+
walk(child);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
walk(element);
|
|
588
|
+
return out;
|
|
589
|
+
}
|
|
174
590
|
function parseProposed(type, raw) {
|
|
591
|
+
raw = cleanStega(raw);
|
|
175
592
|
if (type === "NUMBER") {
|
|
176
593
|
const text = raw.trim().replace(",", ".");
|
|
177
594
|
const value = Number(text);
|
|
@@ -224,6 +641,137 @@ function asDomAnchor(anchor) {
|
|
|
224
641
|
return { tag, selector, textQuote: null, rect: NOWHERE, viewport: { w: 1, h: 1 } };
|
|
225
642
|
}
|
|
226
643
|
|
|
644
|
+
// src/preview/site/session.ts
|
|
645
|
+
var REFRESH_LEAD_MS = 2 * 6e4;
|
|
646
|
+
function createTokenSession(ctx, stored, hooks, deps = {}) {
|
|
647
|
+
const { win, doc } = ctx;
|
|
648
|
+
const now = deps.now ?? (() => Date.now());
|
|
649
|
+
const setT = deps.setTimeout ?? ((fn, ms) => win.setTimeout(fn, ms));
|
|
650
|
+
const clearT = deps.clearTimeout ?? ((handle) => win.clearTimeout(handle));
|
|
651
|
+
let current = stored;
|
|
652
|
+
let expired = false;
|
|
653
|
+
let destroyed = false;
|
|
654
|
+
let inflight = null;
|
|
655
|
+
let timer = null;
|
|
656
|
+
function markExpired() {
|
|
657
|
+
if (expired || destroyed) return;
|
|
658
|
+
expired = true;
|
|
659
|
+
stop();
|
|
660
|
+
clearToken(win);
|
|
661
|
+
hooks.onExpired();
|
|
662
|
+
}
|
|
663
|
+
async function renew() {
|
|
664
|
+
if (!refreshLive(current, now()) || !current.refresh) return false;
|
|
665
|
+
let response;
|
|
666
|
+
try {
|
|
667
|
+
response = await win.fetch(`${ctx.authOrigin}/api/oauth/token`, {
|
|
668
|
+
method: "POST",
|
|
669
|
+
credentials: "omit",
|
|
670
|
+
body: new URLSearchParams({
|
|
671
|
+
grant_type: "refresh_token",
|
|
672
|
+
refresh_token: current.refresh,
|
|
673
|
+
client_id: ctx.clientId
|
|
674
|
+
})
|
|
675
|
+
});
|
|
676
|
+
} catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
if (!response.ok) return false;
|
|
680
|
+
try {
|
|
681
|
+
const next = await readTokenAnswer(response, ctx.siteId, now());
|
|
682
|
+
current = next;
|
|
683
|
+
writeToken(win, next);
|
|
684
|
+
return true;
|
|
685
|
+
} catch {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function refresh() {
|
|
690
|
+
if (expired || destroyed) return Promise.resolve(false);
|
|
691
|
+
inflight ?? (inflight = renew().finally(() => {
|
|
692
|
+
inflight = null;
|
|
693
|
+
if (!destroyed && !expired) schedule();
|
|
694
|
+
}));
|
|
695
|
+
return inflight;
|
|
696
|
+
}
|
|
697
|
+
async function check() {
|
|
698
|
+
if (expired || destroyed) return;
|
|
699
|
+
if (current.exp - REFRESH_LEAD_MS > now()) {
|
|
700
|
+
schedule();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const ok = await refresh();
|
|
704
|
+
if (!ok && !accessLive(current, now())) markExpired();
|
|
705
|
+
else if (!ok) {
|
|
706
|
+
stop();
|
|
707
|
+
timer = setT(() => void lastTry(), Math.max(0, current.exp - EXPIRY_MARGIN_MS - now()));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function lastTry() {
|
|
711
|
+
if (!await refresh()) markExpired();
|
|
712
|
+
}
|
|
713
|
+
function stop() {
|
|
714
|
+
if (timer !== null) clearT(timer);
|
|
715
|
+
timer = null;
|
|
716
|
+
}
|
|
717
|
+
function schedule() {
|
|
718
|
+
stop();
|
|
719
|
+
if (expired || destroyed) return;
|
|
720
|
+
timer = setT(() => void check(), Math.max(0, current.exp - REFRESH_LEAD_MS - now()));
|
|
721
|
+
}
|
|
722
|
+
const onVisible = () => {
|
|
723
|
+
if (doc.visibilityState === "visible") void check();
|
|
724
|
+
};
|
|
725
|
+
doc.addEventListener("visibilitychange", onVisible);
|
|
726
|
+
async function ensure() {
|
|
727
|
+
if (expired) return null;
|
|
728
|
+
if (accessLive(current, now())) return current.token;
|
|
729
|
+
if (await refresh()) return current.token;
|
|
730
|
+
markExpired();
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
async function call2(request) {
|
|
734
|
+
const token = await ensure();
|
|
735
|
+
if (token === null) return { ok: false, kind: "expired" };
|
|
736
|
+
const first = await request(token);
|
|
737
|
+
if (first.ok || first.kind !== "expired") return first;
|
|
738
|
+
if (await refresh()) {
|
|
739
|
+
const second = await request(current.token);
|
|
740
|
+
if (!second.ok && second.kind === "expired") markExpired();
|
|
741
|
+
return second;
|
|
742
|
+
}
|
|
743
|
+
markExpired();
|
|
744
|
+
return first;
|
|
745
|
+
}
|
|
746
|
+
void check();
|
|
747
|
+
return {
|
|
748
|
+
token: () => current.token,
|
|
749
|
+
get expired() {
|
|
750
|
+
return expired;
|
|
751
|
+
},
|
|
752
|
+
ensure,
|
|
753
|
+
refresh,
|
|
754
|
+
call: call2,
|
|
755
|
+
destroy() {
|
|
756
|
+
destroyed = true;
|
|
757
|
+
stop();
|
|
758
|
+
doc.removeEventListener("visibilitychange", onVisible);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
var SESSION_MESSAGES = {
|
|
763
|
+
es: {
|
|
764
|
+
expiredTitle: "Tu sesi\xF3n de Zap expir\xF3",
|
|
765
|
+
expiredBody: "Vuelve a entrar para seguir editando y comentando.",
|
|
766
|
+
signInAgain: "Volver a entrar"
|
|
767
|
+
},
|
|
768
|
+
en: {
|
|
769
|
+
expiredTitle: "Your Zap session expired",
|
|
770
|
+
expiredBody: "Sign in again to keep editing and commenting.",
|
|
771
|
+
signInAgain: "Sign in again"
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
227
775
|
// src/preview/site/suggest.ts
|
|
228
776
|
var OVERLAY_MODE = {
|
|
229
777
|
navigate: "off",
|
|
@@ -236,24 +784,31 @@ var TOAST_MS = 8e3;
|
|
|
236
784
|
var SAVED_MS = 4e3;
|
|
237
785
|
function start(ctx, options = {}) {
|
|
238
786
|
const { win, doc } = ctx;
|
|
239
|
-
const stored =
|
|
787
|
+
const stored = readSession(win, ctx.siteId);
|
|
240
788
|
if (!stored) {
|
|
241
789
|
void ctx.open("signin", { reason: "expired" });
|
|
242
790
|
return { destroy() {
|
|
243
791
|
} };
|
|
244
792
|
}
|
|
245
|
-
const
|
|
793
|
+
const auth = createTokenSession(ctx, stored, { onExpired: () => expire() });
|
|
794
|
+
const sm = SESSION_MESSAGES[ctx.locale];
|
|
246
795
|
const m = SUGGEST_MESSAGES[ctx.locale];
|
|
247
|
-
const
|
|
796
|
+
const dm = DRAFT_SESSION_MESSAGES[ctx.locale];
|
|
797
|
+
let reopenEdit = takeEditIntent(win);
|
|
798
|
+
const host = mountHost(doc, CSS + DRAG_CSS);
|
|
248
799
|
const layer = el(doc, "div", { class: "layer" });
|
|
249
800
|
const marks = el(doc, "div", { class: "marks" });
|
|
250
801
|
const bottom = el(doc, "div", { class: "bottom" });
|
|
251
|
-
|
|
802
|
+
const tip = el(doc, "div", { class: "tip", attrs: { role: "tooltip" } });
|
|
803
|
+
layer.append(marks, bottom, tip);
|
|
804
|
+
const drag = barDrag(win, doc, bottom, m.move, m.moveTip);
|
|
805
|
+
const key = ctx.shortcut ? ctx.shortcut.toUpperCase() : null;
|
|
252
806
|
host.root.append(layer);
|
|
253
807
|
let mode = "navigate";
|
|
254
808
|
let selection = [];
|
|
255
809
|
let data = null;
|
|
256
810
|
let refused = false;
|
|
811
|
+
let expired = false;
|
|
257
812
|
let composer = null;
|
|
258
813
|
let editing = null;
|
|
259
814
|
let list = null;
|
|
@@ -266,6 +821,10 @@ function start(ctx, options = {}) {
|
|
|
266
821
|
let pins = [];
|
|
267
822
|
let numbers = /* @__PURE__ */ new Map();
|
|
268
823
|
let lastClick = { x: 0, y: 0, additive: false };
|
|
824
|
+
let menuOpen = false;
|
|
825
|
+
let focusNext = null;
|
|
826
|
+
let entering = false;
|
|
827
|
+
let paused = null;
|
|
269
828
|
const onClickFirst = (event) => {
|
|
270
829
|
lastClick = {
|
|
271
830
|
x: event.clientX,
|
|
@@ -274,6 +833,15 @@ function start(ctx, options = {}) {
|
|
|
274
833
|
};
|
|
275
834
|
};
|
|
276
835
|
win.addEventListener("click", onClickFirst, true);
|
|
836
|
+
const onPressFirst = (event) => {
|
|
837
|
+
if (menuOpen && event.target !== host.host) setMenu(false);
|
|
838
|
+
};
|
|
839
|
+
win.addEventListener("pointerdown", onPressFirst, true);
|
|
840
|
+
layer.addEventListener("pointerdown", (event) => {
|
|
841
|
+
if (!menuOpen) return;
|
|
842
|
+
const keep = Array.from(bottom.querySelectorAll('.menu, [data-action="account"]'));
|
|
843
|
+
if (!keep.some((node) => node.contains(event.target))) setMenu(false);
|
|
844
|
+
});
|
|
277
845
|
const index = new TagIndex(doc);
|
|
278
846
|
index.scan();
|
|
279
847
|
const overlay = new Overlay({
|
|
@@ -289,26 +857,47 @@ function start(ctx, options = {}) {
|
|
|
289
857
|
const found = data?.comments.find((comment) => comment.id === id);
|
|
290
858
|
if (!found) return;
|
|
291
859
|
list = [found];
|
|
292
|
-
|
|
860
|
+
if (drag.hidden()) setHidden(false, false);
|
|
861
|
+
else renderBottom();
|
|
293
862
|
}
|
|
294
863
|
});
|
|
295
864
|
overlay.mount();
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
865
|
+
doc.documentElement.appendChild(host.host);
|
|
866
|
+
const check = tagGuard();
|
|
867
|
+
let stopObserving = () => {
|
|
868
|
+
};
|
|
869
|
+
const atBoot = check(index.elements.length, true);
|
|
870
|
+
if (atBoot) {
|
|
871
|
+
paused = atBoot;
|
|
872
|
+
console.warn(`eelzap: preview paused (${atBoot})`);
|
|
873
|
+
} else
|
|
874
|
+
stopObserving = observeTags(
|
|
875
|
+
doc.documentElement,
|
|
876
|
+
(page) => {
|
|
877
|
+
index.scan();
|
|
878
|
+
const reason = check(index.elements.length, page);
|
|
879
|
+
if (reason) pause(reason);
|
|
880
|
+
else placePins();
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
ignore: (node) => {
|
|
884
|
+
const own = overlay.hostElement;
|
|
885
|
+
return node === host.host || !!own && (node === own || own.contains(node));
|
|
886
|
+
}
|
|
306
887
|
}
|
|
307
|
-
|
|
308
|
-
)
|
|
888
|
+
);
|
|
889
|
+
function pause(reason) {
|
|
890
|
+
if (paused) return;
|
|
891
|
+
paused = reason;
|
|
892
|
+
stopObserving();
|
|
893
|
+
console.warn(`eelzap: preview paused (${reason})`);
|
|
894
|
+
if (destroyed) return;
|
|
895
|
+
overlay.setPins([]);
|
|
896
|
+
setMode("navigate");
|
|
897
|
+
}
|
|
309
898
|
async function load() {
|
|
310
899
|
const refs = Array.from(new Set(index.elements.map((tagged) => tagged.recordRef)));
|
|
311
|
-
const result = await fetchOnPage(ctx, token, currentPageUrl(win), refs);
|
|
900
|
+
const result = await auth.call((token) => fetchOnPage(ctx, token, currentPageUrl(win), refs));
|
|
312
901
|
if (destroyed) return;
|
|
313
902
|
if (!result.ok) {
|
|
314
903
|
onFailure(result);
|
|
@@ -317,13 +906,21 @@ function start(ctx, options = {}) {
|
|
|
317
906
|
const first = data === null;
|
|
318
907
|
data = result.data;
|
|
319
908
|
const labels = {};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
909
|
+
const foreign = {};
|
|
910
|
+
for (const [ref, fields] of Object.entries(data.fields ?? {})) {
|
|
911
|
+
const name = data.records?.[ref];
|
|
912
|
+
for (const [key2, field] of Object.entries(fields)) {
|
|
913
|
+
if (typeof field?.label !== "string") continue;
|
|
914
|
+
if (name) foreign[`${ref}#${key2}`] = [field.label, m.inRecord(name)];
|
|
915
|
+
else labels[key2] = field.label;
|
|
323
916
|
}
|
|
324
917
|
}
|
|
325
|
-
overlay.setLabels(labels);
|
|
918
|
+
overlay.setLabels(labels, foreign);
|
|
326
919
|
if (!data.liveEditing && mode === "edit") setMode("navigate");
|
|
920
|
+
if (reopenEdit) {
|
|
921
|
+
reopenEdit = false;
|
|
922
|
+
if (canEdit()) setMode("edit");
|
|
923
|
+
}
|
|
327
924
|
if (first && options.reason === "renewed") {
|
|
328
925
|
const name = data.viewer?.name;
|
|
329
926
|
setToast({ title: m.renewed, body: name ? m.renewedAs(name) : m.renewedBody });
|
|
@@ -357,51 +954,253 @@ function start(ctx, options = {}) {
|
|
|
357
954
|
}
|
|
358
955
|
function expire() {
|
|
359
956
|
clearToken(win);
|
|
360
|
-
|
|
957
|
+
if (expired || destroyed) return;
|
|
958
|
+
expired = true;
|
|
959
|
+
closeComposer();
|
|
960
|
+
overlay.setMode("off");
|
|
961
|
+
renderBottom();
|
|
962
|
+
}
|
|
963
|
+
function signInAgain() {
|
|
964
|
+
void ctx.open("signin", { reason: "expired", signIn: true });
|
|
361
965
|
}
|
|
362
966
|
function canEdit() {
|
|
363
967
|
return !!data?.liveEditing;
|
|
364
968
|
}
|
|
365
969
|
function setMode(next) {
|
|
366
|
-
if (next === "edit" && !canEdit()) return;
|
|
970
|
+
if (next === "edit" && !canEdit() || paused && next !== "navigate") return;
|
|
971
|
+
if (status === "preparing") return;
|
|
972
|
+
if (next === "edit" && !index.elements.length && !inDraftSession(win)) {
|
|
973
|
+
void prepareEdit();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
367
976
|
endEdit();
|
|
368
977
|
closeComposer();
|
|
369
978
|
mode = next;
|
|
370
979
|
selection = [];
|
|
371
980
|
overlay.clearSelection();
|
|
372
981
|
overlay.setMode(OVERLAY_MODE[next]);
|
|
982
|
+
if (next === "edit" && !index.elements.length)
|
|
983
|
+
setToast({ title: m.noFields, body: m.noFieldsBody });
|
|
373
984
|
renderBottom();
|
|
374
985
|
}
|
|
986
|
+
async function prepareEdit() {
|
|
987
|
+
setStatus("preparing");
|
|
988
|
+
const token = await auth.ensure();
|
|
989
|
+
if (token === null || destroyed) return;
|
|
990
|
+
const result = await requestDraftSession(ctx, token);
|
|
991
|
+
if (destroyed) return;
|
|
992
|
+
if (result.ok) {
|
|
993
|
+
win.location.assign(result.url);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
setStatus(null);
|
|
997
|
+
if (result.reason === "expired") {
|
|
998
|
+
expire();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const body = result.reason === "no-route" ? dm.noRoute : result.reason === "rate" ? dm.rate(result.retryAfter) : dm.failed;
|
|
1002
|
+
setToast({ title: m.noFields, body, error: result.reason !== "no-route" });
|
|
1003
|
+
}
|
|
375
1004
|
function renderBottom() {
|
|
376
1005
|
if (destroyed) return;
|
|
1006
|
+
hideTip();
|
|
1007
|
+
const active = host.root.activeElement?.getAttribute("data-action");
|
|
1008
|
+
const hidden = drag.hidden();
|
|
377
1009
|
const children = [];
|
|
378
1010
|
if (toast) children.push(toastCard(toast));
|
|
1011
|
+
if (expired) {
|
|
1012
|
+
children.push(expiredCard());
|
|
1013
|
+
bottom.replaceChildren(...children);
|
|
1014
|
+
drag.place();
|
|
1015
|
+
marks.replaceChildren();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
379
1018
|
if (refused) {
|
|
380
1019
|
children.push(refusedCard());
|
|
381
1020
|
bottom.replaceChildren(...children);
|
|
1021
|
+
drag.place();
|
|
382
1022
|
marks.replaceChildren();
|
|
383
1023
|
return;
|
|
384
1024
|
}
|
|
385
|
-
if (
|
|
386
|
-
if (
|
|
1025
|
+
if (paused && !hidden) children.push(pausedCard(paused));
|
|
1026
|
+
if (list && !hidden) children.push(listCard(list));
|
|
1027
|
+
if (selection.length > 0 && !composer && !hidden) children.push(trayCard());
|
|
387
1028
|
if (status) {
|
|
388
1029
|
children.push(
|
|
389
1030
|
el(doc, "div", { class: "card status", attrs: { role: "status" } }, [
|
|
390
1031
|
status === "saved" ? icon(doc, "check", 14) : null,
|
|
391
|
-
status === "saved" ? m.saved : m.saving
|
|
1032
|
+
status === "saved" ? m.saved : status === "preparing" ? dm.preparing : m.saving
|
|
392
1033
|
])
|
|
393
1034
|
);
|
|
394
1035
|
}
|
|
395
|
-
children.push(
|
|
1036
|
+
if (menuOpen && !hidden) children.push(menuCard());
|
|
1037
|
+
const bar = hidden ? pill() : toolbar();
|
|
1038
|
+
if (entering) bar.setAttribute("data-enter", "");
|
|
1039
|
+
entering = false;
|
|
1040
|
+
children.push(bar);
|
|
396
1041
|
bottom.replaceChildren(...children);
|
|
1042
|
+
drag.place();
|
|
397
1043
|
drawMarks();
|
|
1044
|
+
const want = focusNext ?? active;
|
|
1045
|
+
focusNext = null;
|
|
1046
|
+
if (want)
|
|
1047
|
+
bottom.querySelector(`[data-action="${want}"]`)?.focus({ preventScroll: true });
|
|
398
1048
|
}
|
|
399
|
-
function
|
|
1049
|
+
function openCount() {
|
|
400
1050
|
const open = data?.comments.length ?? 0;
|
|
1051
|
+
return [open, data?.truncated ? `${open}+` : String(open)];
|
|
1052
|
+
}
|
|
1053
|
+
function setMenu(open, focus = null) {
|
|
1054
|
+
if (menuOpen === open && !focus) return;
|
|
1055
|
+
menuOpen = open;
|
|
1056
|
+
focusNext = focus;
|
|
1057
|
+
renderBottom();
|
|
1058
|
+
}
|
|
1059
|
+
function setHidden(value, fromUi) {
|
|
1060
|
+
if (refused || drag.hidden() === value) return;
|
|
1061
|
+
if (value) {
|
|
1062
|
+
endEdit();
|
|
1063
|
+
closeComposer();
|
|
1064
|
+
selection = [];
|
|
1065
|
+
overlay.clearSelection();
|
|
1066
|
+
list = null;
|
|
1067
|
+
menuOpen = false;
|
|
1068
|
+
}
|
|
1069
|
+
drag.setHidden(value);
|
|
1070
|
+
overlay.setMode(value ? "off" : OVERLAY_MODE[mode]);
|
|
1071
|
+
focusNext = fromUi ? value ? "show" : "hide" : null;
|
|
1072
|
+
entering = true;
|
|
1073
|
+
renderBottom();
|
|
1074
|
+
}
|
|
1075
|
+
function withTip(target, text, end) {
|
|
1076
|
+
const show = () => {
|
|
1077
|
+
tip.replaceChildren(text);
|
|
1078
|
+
if (key) tip.append(el(doc, "span", { class: "kbd", text: `Shift ${key}` }));
|
|
1079
|
+
tip.classList.add("on");
|
|
1080
|
+
const rect = target.getBoundingClientRect();
|
|
1081
|
+
const vw = doc.documentElement.clientWidth || win.innerWidth;
|
|
1082
|
+
const w = tip.offsetWidth;
|
|
1083
|
+
const gap = end ? 15 : 8;
|
|
1084
|
+
const x = end ? rect.right + 7 - w : rect.left + (rect.width - w) / 2;
|
|
1085
|
+
tip.style.left = `${Math.min(Math.max(x, 8), vw - w - 8)}px`;
|
|
1086
|
+
tip.style.top = bottom.hasAttribute("data-below") ? `${rect.bottom + gap}px` : `${rect.top - gap - tip.offsetHeight}px`;
|
|
1087
|
+
};
|
|
1088
|
+
const focus = () => {
|
|
1089
|
+
try {
|
|
1090
|
+
if (target.matches(":focus-visible")) show();
|
|
1091
|
+
} catch {
|
|
1092
|
+
show();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
for (const [type, handler] of [
|
|
1096
|
+
["mouseenter", show],
|
|
1097
|
+
["focus", focus],
|
|
1098
|
+
["mouseleave", hideTip],
|
|
1099
|
+
["blur", hideTip],
|
|
1100
|
+
["pointerdown", hideTip]
|
|
1101
|
+
]) {
|
|
1102
|
+
target.addEventListener(type, handler);
|
|
1103
|
+
}
|
|
1104
|
+
if (key) target.setAttribute("aria-keyshortcuts", `Shift+${key}`);
|
|
1105
|
+
return target;
|
|
1106
|
+
}
|
|
1107
|
+
function hideTip() {
|
|
1108
|
+
tip.classList.remove("on");
|
|
1109
|
+
}
|
|
1110
|
+
function avatar(name, large = false) {
|
|
1111
|
+
return el(doc, "span", {
|
|
1112
|
+
class: large ? "avatar lg" : "avatar",
|
|
1113
|
+
attrs: { "aria-hidden": "true" },
|
|
1114
|
+
text: initials(name)
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function menuCard() {
|
|
1118
|
+
const name = data?.viewer?.name ?? null;
|
|
1119
|
+
const email = data?.viewer?.email ?? null;
|
|
1120
|
+
const item = (action, label, glyph, run) => el(
|
|
1121
|
+
doc,
|
|
1122
|
+
"button",
|
|
1123
|
+
{
|
|
1124
|
+
class: "mi",
|
|
1125
|
+
attrs: { type: "button", role: "menuitem", tabindex: "-1", "data-action": action },
|
|
1126
|
+
on: {
|
|
1127
|
+
click: run,
|
|
1128
|
+
pointerenter: (event) => event.currentTarget.focus()
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
[icon(doc, glyph), label]
|
|
1132
|
+
);
|
|
1133
|
+
return el(
|
|
1134
|
+
doc,
|
|
1135
|
+
"div",
|
|
1136
|
+
{
|
|
1137
|
+
class: "card menu",
|
|
1138
|
+
attrs: { role: "menu", "aria-label": m.accountMenu, tabindex: "-1", "data-action": "menu" },
|
|
1139
|
+
on: { keydown: onMenuKey }
|
|
1140
|
+
},
|
|
1141
|
+
[
|
|
1142
|
+
el(doc, "div", { class: "menu-head" }, [
|
|
1143
|
+
avatar(name, true),
|
|
1144
|
+
el(doc, "div", { class: "menu-who" }, [
|
|
1145
|
+
el(doc, "span", { class: "menu-name", text: name ?? m.someone }),
|
|
1146
|
+
email ? el(doc, "span", { class: "menu-mail", text: email }) : null
|
|
1147
|
+
])
|
|
1148
|
+
]),
|
|
1149
|
+
el(doc, "div", { class: "sep", attrs: { role: "separator" } }),
|
|
1150
|
+
item("open-zap", m.openInZap, "external", () => {
|
|
1151
|
+
setMenu(false, "account");
|
|
1152
|
+
win.open(zapUrl(data?.viewer?.editorUrl, ctx.zapOrigin), "_blank", "noopener");
|
|
1153
|
+
}),
|
|
1154
|
+
el(doc, "div", { class: "sep", attrs: { role: "separator" } }),
|
|
1155
|
+
item("sign-out", m.signOut, "exit", exit)
|
|
1156
|
+
]
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
function onMenuKey(event) {
|
|
1160
|
+
const e = event;
|
|
1161
|
+
if (e.key === "Tab") {
|
|
1162
|
+
setMenu(false, "account");
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const items = Array.from(bottom.querySelectorAll('.menu [role="menuitem"]'));
|
|
1166
|
+
const at = items.indexOf(host.root.activeElement);
|
|
1167
|
+
const to = { ArrowDown: at + 1, ArrowUp: at < 0 ? -1 : at - 1, Home: 0, End: -1 }[e.key];
|
|
1168
|
+
if (to === void 0) return;
|
|
1169
|
+
e.preventDefault();
|
|
1170
|
+
items[(to + items.length) % items.length]?.focus();
|
|
1171
|
+
}
|
|
1172
|
+
function pill() {
|
|
1173
|
+
const [open, shown] = openCount();
|
|
1174
|
+
return withTip(
|
|
1175
|
+
drag.grab(
|
|
1176
|
+
el(
|
|
1177
|
+
doc,
|
|
1178
|
+
"button",
|
|
1179
|
+
{
|
|
1180
|
+
class: "card pill",
|
|
1181
|
+
attrs: {
|
|
1182
|
+
type: "button",
|
|
1183
|
+
"aria-label": m.showBar(open).replace(String(open), shown) + (paused ? `. ${m.paused}` : ""),
|
|
1184
|
+
"data-action": "show"
|
|
1185
|
+
},
|
|
1186
|
+
on: { click: () => setHidden(false, true) }
|
|
1187
|
+
},
|
|
1188
|
+
[
|
|
1189
|
+
icon(doc, "zap", 20),
|
|
1190
|
+
el(doc, "span", { class: "pill-count" }, [icon(doc, "thread", 14), shown])
|
|
1191
|
+
]
|
|
1192
|
+
)
|
|
1193
|
+
),
|
|
1194
|
+
m.show,
|
|
1195
|
+
false
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
function toolbar() {
|
|
1199
|
+
const [open, shown] = openCount();
|
|
401
1200
|
const name = data?.viewer?.name ?? null;
|
|
402
1201
|
const tool = (value, label, glyph) => {
|
|
403
|
-
const off = value === "edit" && !canEdit();
|
|
404
|
-
const why = off && data ? m.editOff : null;
|
|
1202
|
+
const off = value === "edit" && !canEdit() || !!paused && value !== "navigate";
|
|
1203
|
+
const why = paused && off ? m.paused : off && data ? m.editOff : null;
|
|
405
1204
|
return el(
|
|
406
1205
|
doc,
|
|
407
1206
|
"button",
|
|
@@ -425,6 +1224,8 @@ function start(ctx, options = {}) {
|
|
|
425
1224
|
"div",
|
|
426
1225
|
{ class: "card toolbar", attrs: { role: "toolbar", "aria-label": m.toolbar } },
|
|
427
1226
|
[
|
|
1227
|
+
drag.handle,
|
|
1228
|
+
el(doc, "span", { class: "divider", attrs: { "aria-hidden": "true" } }),
|
|
428
1229
|
icon(doc, "zap", 20),
|
|
429
1230
|
el(doc, "div", { class: "segments", attrs: { role: "group", "aria-label": m.tools } }, [
|
|
430
1231
|
tool("navigate", m.navigate, "pointer"),
|
|
@@ -445,18 +1246,50 @@ function start(ctx, options = {}) {
|
|
|
445
1246
|
}
|
|
446
1247
|
}
|
|
447
1248
|
},
|
|
448
|
-
[
|
|
449
|
-
icon(doc, "thread"),
|
|
450
|
-
data?.truncated ? m.open(open).replace(String(open), `${open}+`) : m.open(open)
|
|
451
|
-
]
|
|
1249
|
+
[icon(doc, "thread"), m.open(open).replace(String(open), shown)]
|
|
452
1250
|
),
|
|
453
1251
|
el(doc, "span", { class: "divider", attrs: { "aria-hidden": "true" } }),
|
|
454
|
-
name ? el(doc, "span", { class: "avatar", attrs: { title: name }, text: initials(name) }) : null,
|
|
455
1252
|
el(
|
|
456
1253
|
doc,
|
|
457
1254
|
"button",
|
|
458
|
-
{
|
|
459
|
-
|
|
1255
|
+
{
|
|
1256
|
+
class: "tool account",
|
|
1257
|
+
attrs: {
|
|
1258
|
+
type: "button",
|
|
1259
|
+
"aria-haspopup": "menu",
|
|
1260
|
+
"aria-expanded": String(menuOpen),
|
|
1261
|
+
"aria-label": name ? m.account(name) : m.accountMenu,
|
|
1262
|
+
"data-action": "account"
|
|
1263
|
+
},
|
|
1264
|
+
on: {
|
|
1265
|
+
// A pointer opens onto the menu itself; Enter or Space onto its first item.
|
|
1266
|
+
click: (event) => setMenu(
|
|
1267
|
+
!menuOpen,
|
|
1268
|
+
menuOpen ? "account" : event.detail ? "menu" : "open-zap"
|
|
1269
|
+
),
|
|
1270
|
+
keydown: (event) => {
|
|
1271
|
+
const k = event.key;
|
|
1272
|
+
if (k !== "ArrowDown" && k !== "ArrowUp") return;
|
|
1273
|
+
event.preventDefault();
|
|
1274
|
+
setMenu(true, k === "ArrowUp" ? "sign-out" : "open-zap");
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
[avatar(name), icon(doc, "chevron", 13)]
|
|
1279
|
+
),
|
|
1280
|
+
withTip(
|
|
1281
|
+
el(
|
|
1282
|
+
doc,
|
|
1283
|
+
"button",
|
|
1284
|
+
{
|
|
1285
|
+
class: "tool hide",
|
|
1286
|
+
attrs: { type: "button", "aria-label": m.hide, "data-action": "hide" },
|
|
1287
|
+
on: { click: () => setHidden(true, true) }
|
|
1288
|
+
},
|
|
1289
|
+
[icon(doc, "eyeOff", 16)]
|
|
1290
|
+
),
|
|
1291
|
+
m.hide,
|
|
1292
|
+
true
|
|
460
1293
|
)
|
|
461
1294
|
]
|
|
462
1295
|
);
|
|
@@ -517,7 +1350,7 @@ function start(ctx, options = {}) {
|
|
|
517
1350
|
{
|
|
518
1351
|
class: "item",
|
|
519
1352
|
attrs: { type: "button", "data-comment": comment.id },
|
|
520
|
-
on: { click: () =>
|
|
1353
|
+
on: { click: () => reveal2(comment.id) }
|
|
521
1354
|
},
|
|
522
1355
|
[
|
|
523
1356
|
n ? el(doc, "span", { class: "num", text: String(n) }) : null,
|
|
@@ -534,9 +1367,9 @@ function start(ctx, options = {}) {
|
|
|
534
1367
|
]
|
|
535
1368
|
);
|
|
536
1369
|
}
|
|
537
|
-
function
|
|
1370
|
+
function reveal2(id) {
|
|
538
1371
|
const pin = pins.find((entry) => entry.id === id);
|
|
539
|
-
if (pin?.element) pin.element
|
|
1372
|
+
if (pin?.element) reveal(win, pin.element);
|
|
540
1373
|
else if (pin?.spot) {
|
|
541
1374
|
const root = doc.documentElement;
|
|
542
1375
|
win.scrollTo?.({
|
|
@@ -563,6 +1396,43 @@ function start(ctx, options = {}) {
|
|
|
563
1396
|
]
|
|
564
1397
|
);
|
|
565
1398
|
}
|
|
1399
|
+
function pausedCard(reason) {
|
|
1400
|
+
return el(doc, "div", { class: "card refused paused", attrs: { role: "status" } }, [
|
|
1401
|
+
icon(doc, "alert", 18),
|
|
1402
|
+
el(doc, "div", { class: "toast-text" }, [
|
|
1403
|
+
el(doc, "span", { class: "title", text: m.paused }),
|
|
1404
|
+
el(doc, "span", {
|
|
1405
|
+
class: "muted",
|
|
1406
|
+
text: reason === "cap" ? m.pausedCap : m.pausedRunaway
|
|
1407
|
+
})
|
|
1408
|
+
]),
|
|
1409
|
+
el(doc, "button", {
|
|
1410
|
+
class: "tool",
|
|
1411
|
+
text: m.reload,
|
|
1412
|
+
attrs: { type: "button", "data-action": "reload" },
|
|
1413
|
+
on: { click: () => win.location.reload() }
|
|
1414
|
+
})
|
|
1415
|
+
]);
|
|
1416
|
+
}
|
|
1417
|
+
function expiredCard() {
|
|
1418
|
+
return el(doc, "div", { class: "card refused", attrs: { role: "alert" } }, [
|
|
1419
|
+
icon(doc, "alert", 18),
|
|
1420
|
+
el(doc, "div", { class: "toast-text" }, [
|
|
1421
|
+
el(doc, "span", { class: "title", text: sm.expiredTitle }),
|
|
1422
|
+
el(doc, "span", { class: "muted", text: sm.expiredBody })
|
|
1423
|
+
]),
|
|
1424
|
+
el(
|
|
1425
|
+
doc,
|
|
1426
|
+
"button",
|
|
1427
|
+
{
|
|
1428
|
+
class: "tool",
|
|
1429
|
+
attrs: { type: "button", "data-action": "sign-in-again" },
|
|
1430
|
+
on: { click: signInAgain }
|
|
1431
|
+
},
|
|
1432
|
+
[icon(doc, "eel"), sm.signInAgain]
|
|
1433
|
+
)
|
|
1434
|
+
]);
|
|
1435
|
+
}
|
|
566
1436
|
function refusedCard() {
|
|
567
1437
|
return el(doc, "div", { class: "card refused", attrs: { role: "alert" } }, [
|
|
568
1438
|
icon(doc, "alert", 18),
|
|
@@ -603,11 +1473,12 @@ function start(ctx, options = {}) {
|
|
|
603
1473
|
renderBottom();
|
|
604
1474
|
}
|
|
605
1475
|
function exit() {
|
|
1476
|
+
void leaveDraftSession(ctx, auth.token(), index.elements.length > 0);
|
|
606
1477
|
clearToken(win);
|
|
607
1478
|
ctx.close();
|
|
608
1479
|
}
|
|
609
1480
|
function placePins() {
|
|
610
|
-
if (destroyed || refused || !data) return;
|
|
1481
|
+
if (destroyed || refused || paused || !data) return;
|
|
611
1482
|
const placed = pinsFor(data.comments ?? [], index, doc, win);
|
|
612
1483
|
pins = placed.pins;
|
|
613
1484
|
numbers = placed.numbers;
|
|
@@ -693,7 +1564,7 @@ function start(ctx, options = {}) {
|
|
|
693
1564
|
const field = fieldOf(data, tagged);
|
|
694
1565
|
const proposable = editKindFor(tagged, field) !== null;
|
|
695
1566
|
const target = single;
|
|
696
|
-
const before = field?.type === "URL" && target?.localName === "a" ? target.getAttribute("href") ?? "" : (target?.textContent ?? "")
|
|
1567
|
+
const before = field?.type === "URL" && target?.localName === "a" ? target.getAttribute("href") ?? "" : normalizeText(target?.textContent ?? "");
|
|
697
1568
|
const spotPin = point ? anchors[0] : null;
|
|
698
1569
|
const textarea = el(doc, "textarea", {
|
|
699
1570
|
class: "composer",
|
|
@@ -879,7 +1750,7 @@ function start(ctx, options = {}) {
|
|
|
879
1750
|
const parsed = parseProposed(session.field.type, session.proposal.value);
|
|
880
1751
|
if (!parsed.ok) return "invalid";
|
|
881
1752
|
const before = parseProposed(session.field.type, session.before);
|
|
882
|
-
if (before.ok && before.value
|
|
1753
|
+
if (before.ok && sameValue(before.value, parsed.value)) return null;
|
|
883
1754
|
return { fieldKey: session.tagged.fieldKey, locale: session.tagged.locale, value: parsed.value };
|
|
884
1755
|
}
|
|
885
1756
|
function refreshSend() {
|
|
@@ -921,7 +1792,8 @@ function start(ctx, options = {}) {
|
|
|
921
1792
|
session.busy = true;
|
|
922
1793
|
delete session.error.dataset.sticky;
|
|
923
1794
|
refreshSend();
|
|
924
|
-
const
|
|
1795
|
+
const key2 = session.idempotency.key;
|
|
1796
|
+
const result = await auth.call((token) => createComment(ctx, token, body, key2));
|
|
925
1797
|
if (destroyed || composer !== session) return;
|
|
926
1798
|
session.busy = false;
|
|
927
1799
|
if (!result.ok) {
|
|
@@ -968,7 +1840,7 @@ function start(ctx, options = {}) {
|
|
|
968
1840
|
const element = tagged.element;
|
|
969
1841
|
const pageUrl = currentPageUrl(win);
|
|
970
1842
|
const anchors = [toApiAnchor(buildAnchor(element, index, pageUrl, doc, win), tagged.recordRef)];
|
|
971
|
-
const before = field.type === "URL" && element.localName === "a" ? element.getAttribute("href") ?? "" : (element.textContent ?? "")
|
|
1843
|
+
const before = field.type === "URL" && element.localName === "a" ? element.getAttribute("href") ?? "" : normalizeText(element.textContent ?? "");
|
|
972
1844
|
const inline = kind === "text" ? beginInlineEdit(element) : null;
|
|
973
1845
|
const session = {
|
|
974
1846
|
element,
|
|
@@ -1075,8 +1947,7 @@ function start(ctx, options = {}) {
|
|
|
1075
1947
|
}
|
|
1076
1948
|
function typedValue(session) {
|
|
1077
1949
|
if (session.input) return session.input.value;
|
|
1078
|
-
|
|
1079
|
-
return element.innerText ?? element.textContent ?? "";
|
|
1950
|
+
return editableText(session.element);
|
|
1080
1951
|
}
|
|
1081
1952
|
function finishEdit(session, keepTyped) {
|
|
1082
1953
|
session.done = true;
|
|
@@ -1099,7 +1970,7 @@ function start(ctx, options = {}) {
|
|
|
1099
1970
|
return;
|
|
1100
1971
|
}
|
|
1101
1972
|
const before = parseProposed(session.field.type, session.before);
|
|
1102
|
-
if (before.ok && before.value
|
|
1973
|
+
if (before.ok && sameValue(before.value, parsed.value)) {
|
|
1103
1974
|
finishEdit(session, false);
|
|
1104
1975
|
return;
|
|
1105
1976
|
}
|
|
@@ -1118,7 +1989,8 @@ function start(ctx, options = {}) {
|
|
|
1118
1989
|
anchors: session.anchors,
|
|
1119
1990
|
proposal
|
|
1120
1991
|
});
|
|
1121
|
-
const
|
|
1992
|
+
const draftKey = newIdempotencyKey(win);
|
|
1993
|
+
const result = await auth.call((token) => saveToDraft(ctx, token, body, draftKey));
|
|
1122
1994
|
if (destroyed) return;
|
|
1123
1995
|
if (!result.ok) {
|
|
1124
1996
|
session.revert();
|
|
@@ -1132,6 +2004,16 @@ function start(ctx, options = {}) {
|
|
|
1132
2004
|
if (editing && !editing.done) commitEdit(editing);
|
|
1133
2005
|
}
|
|
1134
2006
|
const onKey = (event) => {
|
|
2007
|
+
if (event.key === "Escape") hideTip();
|
|
2008
|
+
if (menuOpen && event.key === "Escape") {
|
|
2009
|
+
event.preventDefault();
|
|
2010
|
+
setMenu(false, "account");
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (ctx.shortcut && !editing && !composer && isShortcut(event, ctx.shortcut)) {
|
|
2014
|
+
setHidden(!drag.hidden(), !!host.root.activeElement);
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1135
2017
|
if (composer && event.key === "Escape") {
|
|
1136
2018
|
event.preventDefault();
|
|
1137
2019
|
closeComposer();
|
|
@@ -1147,17 +2029,21 @@ function start(ctx, options = {}) {
|
|
|
1147
2029
|
commitEdit(session);
|
|
1148
2030
|
}
|
|
1149
2031
|
};
|
|
2032
|
+
const blocks = () => mode !== "navigate" && !drag.hidden();
|
|
1150
2033
|
const onClick = (event) => {
|
|
1151
|
-
if (editing?.kind === "text" && !editing.done && editing.element.contains(event.target)) {
|
|
2034
|
+
if (editing?.kind === "text" && !editing.done && editing.element.contains(event.target) || blocks() && event.target.closest?.("a[href],area[href]")) {
|
|
1152
2035
|
event.preventDefault();
|
|
1153
2036
|
}
|
|
1154
2037
|
};
|
|
2038
|
+
const onSubmit = (event) => {
|
|
2039
|
+
if (blocks()) event.preventDefault();
|
|
2040
|
+
};
|
|
1155
2041
|
win.addEventListener("keydown", onKey, true);
|
|
1156
2042
|
win.addEventListener("click", onClick, true);
|
|
2043
|
+
win.addEventListener("submit", onSubmit, true);
|
|
1157
2044
|
win.addEventListener("scroll", schedule, true);
|
|
1158
2045
|
win.addEventListener("resize", schedule);
|
|
1159
|
-
|
|
1160
|
-
overlay.setMode(OVERLAY_MODE[mode]);
|
|
2046
|
+
overlay.setMode(drag.hidden() ? "off" : OVERLAY_MODE[mode]);
|
|
1161
2047
|
renderBottom();
|
|
1162
2048
|
void load();
|
|
1163
2049
|
return {
|
|
@@ -1165,37 +2051,84 @@ function start(ctx, options = {}) {
|
|
|
1165
2051
|
endEdit();
|
|
1166
2052
|
destroyed = true;
|
|
1167
2053
|
closeComposer();
|
|
1168
|
-
|
|
2054
|
+
auth.destroy();
|
|
1169
2055
|
if (toastTimer !== null) win.clearTimeout(toastTimer);
|
|
1170
2056
|
if (statusTimer !== null) win.clearTimeout(statusTimer);
|
|
1171
2057
|
win.removeEventListener("click", onClickFirst, true);
|
|
2058
|
+
win.removeEventListener("pointerdown", onPressFirst, true);
|
|
1172
2059
|
win.removeEventListener("keydown", onKey, true);
|
|
1173
2060
|
win.removeEventListener("click", onClick, true);
|
|
2061
|
+
win.removeEventListener("submit", onSubmit, true);
|
|
1174
2062
|
win.removeEventListener("scroll", schedule, true);
|
|
1175
2063
|
win.removeEventListener("resize", schedule);
|
|
1176
2064
|
stopObserving();
|
|
1177
2065
|
overlay.destroy();
|
|
2066
|
+
drag.destroy();
|
|
1178
2067
|
host.destroy();
|
|
1179
2068
|
}
|
|
1180
2069
|
};
|
|
1181
2070
|
}
|
|
2071
|
+
function zapUrl(editorUrl, zapOrigin) {
|
|
2072
|
+
const home = new URL("/", zapOrigin);
|
|
2073
|
+
try {
|
|
2074
|
+
const url = new URL(editorUrl ?? "");
|
|
2075
|
+
if (url.protocol === "https:" || url.protocol === "http:") return url.href;
|
|
2076
|
+
} catch {
|
|
2077
|
+
}
|
|
2078
|
+
return home.href;
|
|
2079
|
+
}
|
|
1182
2080
|
var CSS = `
|
|
1183
2081
|
.marks { position: fixed; inset: 0; pointer-events: none; }
|
|
1184
2082
|
.mark { position: fixed; border-radius: 4px; outline: 2px solid rgba(207,135,0,.55); }
|
|
1185
2083
|
.bottom { position: fixed; left: 0; right: 0; bottom: 22px; display: flex; flex-direction: column;
|
|
1186
2084
|
align-items: center; gap: 10px; pointer-events: none; padding: 0 8px; }
|
|
1187
2085
|
.bottom > .card { position: relative; }
|
|
1188
|
-
.toolbar { display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px
|
|
2086
|
+
.toolbar { display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 4px; white-space: nowrap;
|
|
1189
2087
|
max-width: 100%; overflow-x: auto; }
|
|
1190
|
-
.segments { display: inline-flex; gap: 2px; padding: 2px; border-radius: 7px; background: ${TOKENS.subtle};
|
|
1191
|
-
|
|
2088
|
+
.segments { display: inline-flex; gap: 2px; padding: 2px; border-radius: 7px; background: ${TOKENS.subtle};
|
|
2089
|
+
background: oklch(0.94 0.008 250); }
|
|
2090
|
+
.seg { position: relative; height: 28px; padding: 0 10px; border-radius: 5px; font-size: 12px; color: ${TOKENS.muted}; }
|
|
1192
2091
|
.seg[aria-pressed="true"] { background: ${TOKENS.bg}; color: ${TOKENS.fg}; box-shadow: 0 1px 2px rgba(15,23,42,.05); }
|
|
1193
2092
|
.seg[aria-disabled="true"] { opacity: .5; cursor: default; }
|
|
1194
2093
|
.seg[data-tip]:hover::after { content: attr(data-tip); position: absolute; bottom: 34px; left: 50%;
|
|
1195
2094
|
transform: translateX(-50%); padding: 6px 10px; border-radius: 9px; background: ${TOKENS.dark}; color: #FFFFFF;
|
|
1196
2095
|
font-size: 12px; white-space: nowrap; }
|
|
1197
2096
|
.tool { height: 30px; padding: 0 9px; border-radius: 9px; color: ${TOKENS.muted}; }
|
|
1198
|
-
|
|
2097
|
+
/* Hover as the suite's SegmentedControl and ghost button (SitioBarraEstados): a tool on the track
|
|
2098
|
+
gets foreground text over white at 60%, no shadow; the bar's own buttons, on the white card, the
|
|
2099
|
+
ghost hover: foreground text over slate-100. Both keep the focus ring. */
|
|
2100
|
+
.seg:not([aria-pressed="true"]):not([aria-disabled="true"]):hover { background: rgba(255,255,255,.6); color: ${TOKENS.fg}; }
|
|
2101
|
+
.tool:hover, .grip:hover, .account[aria-expanded="true"] { background: #F1F5F9; color: ${TOKENS.fg}; }
|
|
2102
|
+
.seg, .tool, .grip, .pill { transition: color .15s cubic-bezier(.4,0,.2,1), background-color .15s cubic-bezier(.4,0,.2,1); }
|
|
2103
|
+
.seg:focus-visible, .tool:focus-visible, .grip:focus-visible { box-shadow: 0 0 0 3px ${TOKENS.focus}; }
|
|
2104
|
+
/* The account chip and the hide arrow (SitioBarraEstados): ghost buttons, chevrons muted until hover. */
|
|
2105
|
+
.account { padding: 0 4px 0 2px; gap: 3px; }
|
|
2106
|
+
.hide { width: 30px; padding: 0; justify-content: center; }
|
|
2107
|
+
.avatar.lg { width: 32px; height: 32px; }
|
|
2108
|
+
.menu { width: 260px; padding: 4px; border-radius: 11px; outline: none;
|
|
2109
|
+
box-shadow: 0 10px 15px -3px rgba(15,23,42,.1), 0 4px 6px -4px rgba(15,23,42,.08); }
|
|
2110
|
+
.menu-head { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }
|
|
2111
|
+
.menu-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
2112
|
+
.menu-name { font-weight: 500; }
|
|
2113
|
+
.menu-mail { font-size: 12px; color: ${TOKENS.muted}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2114
|
+
.sep { height: 1px; background: ${TOKENS.border}; margin: 4px -4px; }
|
|
2115
|
+
.menu-head + .sep { margin-top: 0; }
|
|
2116
|
+
.mi { width: 100%; height: 32px; gap: 9px; padding: 0 8px; border-radius: 7px; font-weight: 400; color: ${TOKENS.fg}; }
|
|
2117
|
+
.mi > .icon { color: ${TOKENS.muted}; }
|
|
2118
|
+
.mi:focus, .mi:focus-visible { outline: none; background: #F1F5F9; box-shadow: none; }
|
|
2119
|
+
.pill { height: 44px; gap: 8px; padding: 0 12px 0 11px; border-radius: 999px; }
|
|
2120
|
+
.pill:hover { background: #F8FAFC; }
|
|
2121
|
+
.pill:focus-visible { box-shadow: 0 0 0 3px ${TOKENS.focus}, 0 20px 25px -5px rgba(15,23,42,.12); }
|
|
2122
|
+
.pill-count { display: inline-flex; align-items: center; gap: 4px; }
|
|
2123
|
+
.tip { position: fixed; display: inline-flex; align-items: center; gap: 8px; padding: 5px 6px 5px 9px;
|
|
2124
|
+
border-radius: 7px; background: #101828; color: #FFFFFF; font-weight: 500; font-size: 12px; line-height: 18px;
|
|
2125
|
+
white-space: nowrap; pointer-events: none; }
|
|
2126
|
+
.tip:not(.on) { display: none; }
|
|
2127
|
+
.kbd { font-size: 11px; line-height: 16px; color: #CBD5E1; padding: 1px 5px; border-radius: 4px;
|
|
2128
|
+
background: rgba(255,255,255,.12); }
|
|
2129
|
+
[data-enter] { animation: eelzap-in .16s cubic-bezier(.4,0,.2,1); }
|
|
2130
|
+
@keyframes eelzap-in { from { opacity: 0; transform: scale(.96); } }
|
|
2131
|
+
@media (prefers-reduced-motion: reduce) { .seg, .tool, .grip, .pill { transition: none; } [data-enter] { animation: none; } }
|
|
1199
2132
|
.tool.strong { color: ${TOKENS.fg}; }
|
|
1200
2133
|
.divider { width: 1px; height: 22px; background: ${TOKENS.border}; }
|
|
1201
2134
|
.avatar { width: 26px; height: 26px; border-radius: 7px; background: #DBEAFE; color: #1D4ED8;
|
|
@@ -1219,6 +2152,7 @@ var CSS = `
|
|
|
1219
2152
|
.refused { align-self: center; margin-right: 0; width: min(420px, calc(100vw - 16px)); align-items: flex-start; }
|
|
1220
2153
|
.toast > .icon { color: #16A34A; }
|
|
1221
2154
|
.refused > .icon, .toast-error > .icon { color: ${TOKENS.danger}; }
|
|
2155
|
+
.paused > .icon { color: ${TOKENS.muted}; }
|
|
1222
2156
|
.toast-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
|
|
1223
2157
|
.title { font-weight: 600; }
|
|
1224
2158
|
.head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
@@ -1251,6 +2185,7 @@ input.value { height: 36px; border-radius: 9px; }
|
|
|
1251
2185
|
`;
|
|
1252
2186
|
export {
|
|
1253
2187
|
OVERLAY_HOST_TAG,
|
|
1254
|
-
start
|
|
2188
|
+
start,
|
|
2189
|
+
zapUrl
|
|
1255
2190
|
};
|
|
1256
|
-
//# sourceMappingURL=suggest-
|
|
2191
|
+
//# sourceMappingURL=suggest-JZ63L3WP.js.map
|