clay-server 2.27.0-beta.11 → 2.27.0-beta.13
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/lib/project.js +5 -1
- package/lib/public/app.js +1485 -8511
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +399 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +865 -0
- package/lib/public/modules/app-messages.js +1478 -0
- package/lib/public/modules/app-misc.js +301 -0
- package/lib/public/modules/app-panels.js +907 -0
- package/lib/public/modules/app-projects.js +782 -0
- package/lib/public/modules/app-rate-limit.js +448 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +240 -0
- package/lib/public/modules/sidebar-mates.js +801 -0
- package/lib/public/modules/sidebar-mobile.js +1259 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/sdk-bridge.js +54 -0
- package/package.json +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// app-misc.js - Modals (image, paste, confirm), force PIN, PWA install, extension bridge
|
|
2
|
+
// Extracted from app.js (PR-34)
|
|
3
|
+
|
|
4
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
5
|
+
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
6
|
+
|
|
7
|
+
var _ctx = null;
|
|
8
|
+
|
|
9
|
+
// --- Module-owned state ---
|
|
10
|
+
var confirmCallback = null;
|
|
11
|
+
var _extRequestCallbacks = {};
|
|
12
|
+
|
|
13
|
+
export function initMisc(ctx) {
|
|
14
|
+
_ctx = ctx;
|
|
15
|
+
|
|
16
|
+
// --- Confirm modal listeners ---
|
|
17
|
+
var confirmModal = _ctx.$("confirm-modal");
|
|
18
|
+
var confirmOk = _ctx.$("confirm-ok");
|
|
19
|
+
var confirmCancel = _ctx.$("confirm-cancel");
|
|
20
|
+
|
|
21
|
+
confirmOk.addEventListener("click", function () {
|
|
22
|
+
if (confirmCallback) confirmCallback();
|
|
23
|
+
hideConfirm();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
confirmCancel.addEventListener("click", hideConfirm);
|
|
27
|
+
confirmModal.querySelector(".confirm-backdrop").addEventListener("click", hideConfirm);
|
|
28
|
+
|
|
29
|
+
// --- PWA install prompt ---
|
|
30
|
+
(function () {
|
|
31
|
+
var installPill = document.getElementById("pwa-install-pill");
|
|
32
|
+
var modal = document.getElementById("pwa-install-modal");
|
|
33
|
+
var confirmBtn = document.getElementById("pwa-modal-confirm");
|
|
34
|
+
var cancelBtn = document.getElementById("pwa-modal-cancel");
|
|
35
|
+
if (!installPill || !modal) return;
|
|
36
|
+
|
|
37
|
+
// Already standalone -- never show
|
|
38
|
+
if (document.documentElement.classList.contains("pwa-standalone")) return;
|
|
39
|
+
|
|
40
|
+
// Show pill on mobile browsers (the primary target for PWA install)
|
|
41
|
+
var isMobile = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
42
|
+
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
43
|
+
if (isMobile) {
|
|
44
|
+
installPill.classList.remove("hidden");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Also show on desktop if beforeinstallprompt fires
|
|
48
|
+
window.addEventListener("beforeinstallprompt", function (e) {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
installPill.classList.remove("hidden");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function openModal() {
|
|
54
|
+
modal.classList.remove("hidden");
|
|
55
|
+
lucide.createIcons({ nodes: [modal] });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function closeModal() {
|
|
59
|
+
modal.classList.add("hidden");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
installPill.addEventListener("click", openModal);
|
|
63
|
+
cancelBtn.addEventListener("click", closeModal);
|
|
64
|
+
modal.querySelector(".pwa-modal-backdrop").addEventListener("click", closeModal);
|
|
65
|
+
|
|
66
|
+
confirmBtn.addEventListener("click", function () {
|
|
67
|
+
// Builtin cert (*.d.clay.studio): open PWA setup guide
|
|
68
|
+
if (location.hostname.endsWith(".d.clay.studio")) {
|
|
69
|
+
closeModal();
|
|
70
|
+
location.href = "/pwa";
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// mkcert / other: redirect to onboarding setup page
|
|
74
|
+
var port = parseInt(location.port, 10);
|
|
75
|
+
var setupUrl;
|
|
76
|
+
if (!port) {
|
|
77
|
+
// Standard port (443/80), behind a reverse proxy with real cert
|
|
78
|
+
setupUrl = location.protocol + "//" + location.hostname + "/setup";
|
|
79
|
+
} else {
|
|
80
|
+
// Non-standard port, Clay serving directly with onboarding server on port+1
|
|
81
|
+
setupUrl = "http://" + location.hostname + ":" + (port + 1) + "/setup";
|
|
82
|
+
}
|
|
83
|
+
location.href = setupUrl;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Hide after install
|
|
87
|
+
window.addEventListener("appinstalled", function () {
|
|
88
|
+
installPill.classList.add("hidden");
|
|
89
|
+
closeModal();
|
|
90
|
+
});
|
|
91
|
+
})();
|
|
92
|
+
|
|
93
|
+
// --- Extension bridge window message listener ---
|
|
94
|
+
window.addEventListener("message", function(event) {
|
|
95
|
+
if (event.source !== window) return;
|
|
96
|
+
if (!event.data || event.data.source !== "clay-chrome-extension") return;
|
|
97
|
+
var msg = event.data.payload;
|
|
98
|
+
|
|
99
|
+
if (msg.type === "clay_ext_tab_list") {
|
|
100
|
+
_ctx.updateBrowserTabList(msg.tabs);
|
|
101
|
+
// Also inform server about tab list
|
|
102
|
+
var ws = _ctx.getWs();
|
|
103
|
+
if (ws && ws.readyState === 1) {
|
|
104
|
+
ws.send(JSON.stringify({
|
|
105
|
+
type: "browser_tab_list",
|
|
106
|
+
tabs: msg.tabs
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (msg.type === "clay_ext_result") {
|
|
111
|
+
handleExtensionResult(msg.requestId, msg.result);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function showImageModal(src) {
|
|
117
|
+
var modal = _ctx.$("image-modal");
|
|
118
|
+
var img = _ctx.$("image-modal-img");
|
|
119
|
+
if (!modal || !img) return;
|
|
120
|
+
img.src = src;
|
|
121
|
+
modal.classList.remove("hidden");
|
|
122
|
+
refreshIcons(modal);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function closeImageModal() {
|
|
126
|
+
var modal = _ctx.$("image-modal");
|
|
127
|
+
if (modal) modal.classList.add("hidden");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function showPasteModal(text) {
|
|
131
|
+
var modal = _ctx.$("paste-modal");
|
|
132
|
+
var body = _ctx.$("paste-modal-body");
|
|
133
|
+
if (!modal || !body) return;
|
|
134
|
+
body.textContent = text;
|
|
135
|
+
modal.classList.remove("hidden");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function closePasteModal() {
|
|
139
|
+
var modal = _ctx.$("paste-modal");
|
|
140
|
+
if (modal) modal.classList.add("hidden");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function showConfirm(text, onConfirm, okLabel, destructive) {
|
|
144
|
+
var confirmText = _ctx.$("confirm-text");
|
|
145
|
+
var confirmOk = _ctx.$("confirm-ok");
|
|
146
|
+
var confirmModal = _ctx.$("confirm-modal");
|
|
147
|
+
confirmText.textContent = text;
|
|
148
|
+
confirmCallback = onConfirm;
|
|
149
|
+
confirmOk.textContent = okLabel || "Delete";
|
|
150
|
+
confirmOk.className = "confirm-btn " + (destructive === false ? "confirm-ok" : "confirm-delete");
|
|
151
|
+
confirmModal.classList.remove("hidden");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function hideConfirm() {
|
|
155
|
+
var confirmModal = _ctx.$("confirm-modal");
|
|
156
|
+
confirmModal.classList.add("hidden");
|
|
157
|
+
confirmCallback = null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function showForceChangePinOverlay() {
|
|
161
|
+
var ov = document.createElement("div");
|
|
162
|
+
ov.id = "force-change-pin-overlay";
|
|
163
|
+
ov.style.cssText = "position:fixed;inset:0;background:var(--bg,#0e0e10);z-index:99999;display:flex;align-items:center;justify-content:center;flex-direction:column";
|
|
164
|
+
ov.innerHTML = '<div style="width:100%;max-width:380px;padding:24px;text-align:center">' +
|
|
165
|
+
'<h2 style="margin:0 0 8px;color:var(--text,#fff);font-size:22px">Set your new PIN</h2>' +
|
|
166
|
+
'<p style="margin:0 0 24px;color:var(--text-secondary,#aaa);font-size:14px">Your temporary PIN has expired. Please set a new 6-digit PIN to continue.</p>' +
|
|
167
|
+
'<div style="display:flex;gap:8px;justify-content:center;margin-bottom:16px" id="fcp-boxes">' +
|
|
168
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
169
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
170
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
171
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
172
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
173
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none">' +
|
|
174
|
+
'</div>' +
|
|
175
|
+
'<button id="fcp-save" disabled style="width:100%;padding:12px;border:none;border-radius:10px;background:var(--accent,#7c3aed);color:#fff;font-size:15px;font-weight:600;cursor:pointer;opacity:0.5">Save PIN</button>' +
|
|
176
|
+
'<div id="fcp-err" style="margin-top:12px;color:#ef4444;font-size:13px"></div>' +
|
|
177
|
+
'</div>';
|
|
178
|
+
document.body.appendChild(ov);
|
|
179
|
+
|
|
180
|
+
var boxes = ov.querySelectorAll(".fcp-digit");
|
|
181
|
+
var saveBtn = ov.querySelector("#fcp-save");
|
|
182
|
+
var errEl = ov.querySelector("#fcp-err");
|
|
183
|
+
var pinValues = ["", "", "", "", "", ""];
|
|
184
|
+
|
|
185
|
+
function setDigit(idx, v) {
|
|
186
|
+
pinValues[idx] = v;
|
|
187
|
+
boxes[idx].value = v ? "\u2022" : "";
|
|
188
|
+
boxes[idx].classList.toggle("filled", v.length > 0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getPin() {
|
|
192
|
+
return pinValues.join("");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function updateBtn() {
|
|
196
|
+
var ready = getPin().length === 6;
|
|
197
|
+
saveBtn.disabled = !ready;
|
|
198
|
+
saveBtn.style.opacity = ready ? "1" : "0.5";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
202
|
+
(function (idx) {
|
|
203
|
+
boxes[idx].addEventListener("input", function () {
|
|
204
|
+
var raw = this.value.replace(/[^0-9]/g, "");
|
|
205
|
+
if (!raw) { setDigit(idx, ""); updateBtn(); return; }
|
|
206
|
+
var v = raw.charAt(raw.length - 1);
|
|
207
|
+
setDigit(idx, v);
|
|
208
|
+
if (v && idx < 5) boxes[idx + 1].focus();
|
|
209
|
+
updateBtn();
|
|
210
|
+
});
|
|
211
|
+
boxes[idx].addEventListener("keydown", function (e) {
|
|
212
|
+
if (e.key === "Backspace") {
|
|
213
|
+
if (!pinValues[idx] && idx > 0) {
|
|
214
|
+
setDigit(idx - 1, "");
|
|
215
|
+
boxes[idx - 1].focus();
|
|
216
|
+
} else {
|
|
217
|
+
setDigit(idx, "");
|
|
218
|
+
}
|
|
219
|
+
updateBtn();
|
|
220
|
+
}
|
|
221
|
+
if (e.key === "ArrowLeft" && idx > 0) boxes[idx - 1].focus();
|
|
222
|
+
if (e.key === "ArrowRight" && idx < 5) boxes[idx + 1].focus();
|
|
223
|
+
if (e.key === "Enter" && !saveBtn.disabled) doSave();
|
|
224
|
+
e.stopPropagation();
|
|
225
|
+
});
|
|
226
|
+
boxes[idx].addEventListener("keyup", function (e) { e.stopPropagation(); });
|
|
227
|
+
boxes[idx].addEventListener("keypress", function (e) { e.stopPropagation(); });
|
|
228
|
+
boxes[idx].addEventListener("paste", function (e) {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
var text = (e.clipboardData || window.clipboardData).getData("text").replace(/[^0-9]/g, "").substring(0, 6);
|
|
231
|
+
for (var j = 0; j < text.length && (idx + j) < 6; j++) {
|
|
232
|
+
setDigit(idx + j, text.charAt(j));
|
|
233
|
+
}
|
|
234
|
+
if (text.length > 0) {
|
|
235
|
+
var focusIdx = Math.min(idx + text.length, 5);
|
|
236
|
+
boxes[focusIdx].focus();
|
|
237
|
+
}
|
|
238
|
+
updateBtn();
|
|
239
|
+
});
|
|
240
|
+
boxes[idx].addEventListener("focus", function () { this.select(); });
|
|
241
|
+
})(i);
|
|
242
|
+
}
|
|
243
|
+
boxes[0].focus();
|
|
244
|
+
|
|
245
|
+
function doSave() {
|
|
246
|
+
var pin = getPin();
|
|
247
|
+
if (pin.length !== 6) return;
|
|
248
|
+
saveBtn.disabled = true;
|
|
249
|
+
saveBtn.style.opacity = "0.5";
|
|
250
|
+
errEl.textContent = "";
|
|
251
|
+
fetch("/api/user/pin", {
|
|
252
|
+
method: "PUT",
|
|
253
|
+
headers: { "Content-Type": "application/json" },
|
|
254
|
+
body: JSON.stringify({ newPin: pin }),
|
|
255
|
+
}).then(function (r) { return r.json(); }).then(function (d) {
|
|
256
|
+
if (d.ok) {
|
|
257
|
+
ov.remove();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
errEl.textContent = d.error || "Failed to save PIN";
|
|
261
|
+
saveBtn.disabled = false;
|
|
262
|
+
saveBtn.style.opacity = "1";
|
|
263
|
+
}).catch(function () {
|
|
264
|
+
errEl.textContent = "Connection error";
|
|
265
|
+
saveBtn.disabled = false;
|
|
266
|
+
saveBtn.style.opacity = "1";
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
saveBtn.addEventListener("click", doSave);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function sendExtensionCommand(command, args, requestId) {
|
|
273
|
+
window.postMessage({
|
|
274
|
+
source: "clay-page",
|
|
275
|
+
payload: {
|
|
276
|
+
type: "clay_ext_command",
|
|
277
|
+
command: command,
|
|
278
|
+
args: args,
|
|
279
|
+
requestId: requestId
|
|
280
|
+
}
|
|
281
|
+
}, "*");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function handleExtensionResult(requestId, result) {
|
|
285
|
+
// Check local callback first (for server-initiated requests)
|
|
286
|
+
var cb = _extRequestCallbacks[requestId];
|
|
287
|
+
if (cb) {
|
|
288
|
+
delete _extRequestCallbacks[requestId];
|
|
289
|
+
cb(result);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Forward to server
|
|
293
|
+
var ws = _ctx.getWs();
|
|
294
|
+
if (ws && ws.readyState === 1) {
|
|
295
|
+
ws.send(JSON.stringify({
|
|
296
|
+
type: "extension_result",
|
|
297
|
+
requestId: requestId,
|
|
298
|
+
result: result
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
}
|