clay-server 5.1.0-beta.2 → 5.1.0-beta.3
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/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +8 -37
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-sync.js +1 -1
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +12 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +0 -7
- package/lib/project-debate.js +3 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +57 -184
- package/lib/project.js +3 -3
- package/lib/public/app.js +6 -37
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/overlays.css +7 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/title-bar.css +5 -1
- package/lib/public/index.html +173 -447
- package/lib/public/modules/account-island.js +9 -7
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +13 -52
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/avatar.js +6 -1
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/default-ai.js +14 -7
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-mate-avatar-editor.js +2 -2
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +15 -6
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/style.css +4 -0
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +2 -2
- package/lib/server-mates.js +2 -1
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-settings.js +4 -0
- package/lib/server-storage.js +57 -0
- package/lib/server.js +10 -2
- package/lib/sessions.js +3 -4
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +2 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +3 -3
|
@@ -1,32 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { trapSettingsFocus, isolateSettingsDialog, confirmSettingsAction } from './server-settings-state.js';
|
|
4
|
+
import { initProjectEnvironment, openProjectEnvironment, projectEnvironmentDirty, discardProjectEnvironment } from './project-settings-environment.js';
|
|
5
|
+
import { enhanceSettingsSelects } from './settings-select.js';
|
|
6
|
+
// Project identity and access in a focused settings dialog.
|
|
2
7
|
import { refreshIcons } from './icons.js';
|
|
3
8
|
import { showToast } from './utils.js';
|
|
4
9
|
import { parseEmojis } from './markdown.js';
|
|
5
|
-
import { closeFileViewer } from './filebrowser.js';
|
|
6
10
|
import { holdNotesForSettings, releaseNotesForSettings } from './sticky-notes.js';
|
|
7
11
|
import { openNotesBrowser, closeNotesBrowser, isNotesBrowserOpen } from './sticky-notes-browser.js';
|
|
8
|
-
import { renderModelList, renderModeList, renderEffortBar, renderThinkingBar, renderBetaCard } from './settings-defaults.js';
|
|
9
12
|
import { initProjectSessionVisibilitySettings, populateProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
|
|
10
13
|
|
|
11
|
-
var ctx = null;
|
|
12
14
|
var panelEl = null;
|
|
13
15
|
var navItems = null;
|
|
14
16
|
var sections = null;
|
|
15
|
-
var currentSlug = null;
|
|
16
|
-
var currentProject = null; // { slug, name, icon }
|
|
17
17
|
var notesBrowserReopen = false;
|
|
18
|
+
var restoreBackground = null;
|
|
19
|
+
var returnFocus = null;
|
|
20
|
+
function settingsSlug() { return store.get("projectSettingsSlug"); }
|
|
21
|
+
function settingsProject() { return store.get("projectSettingsProject"); }
|
|
18
22
|
|
|
19
23
|
// Emoji categories (reuse from sidebar)
|
|
20
24
|
var EMOJI_CATEGORIES = null;
|
|
21
25
|
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
export function initProjectSettings(appCtx, emojiCategories) {
|
|
25
|
-
ctx = appCtx;
|
|
27
|
+
export function initProjectSettings(emojiCategories) {
|
|
26
28
|
EMOJI_CATEGORIES = emojiCategories;
|
|
27
29
|
panelEl = document.getElementById("project-settings");
|
|
28
30
|
if (!panelEl) return;
|
|
29
31
|
|
|
32
|
+
document.body.appendChild(panelEl);
|
|
33
|
+
var destinations = [{id: 'profile', label: 'General'}, {id: 'access', label: 'Access'}, {id: 'environment', label: 'Environment'}];
|
|
34
|
+
var nav = panelEl.querySelector('.server-settings-nav-items');
|
|
35
|
+
var dropdown = document.getElementById('ps-nav-dropdown');
|
|
36
|
+
destinations.forEach(function (page) {
|
|
37
|
+
var button = document.createElement('button');
|
|
38
|
+
button.className = 'settings-nav-item'; button.dataset.section = page.id; button.textContent = page.label;
|
|
39
|
+
nav.appendChild(button);
|
|
40
|
+
var option = document.createElement('option'); option.value = page.id; option.textContent = page.label; dropdown.appendChild(option);
|
|
41
|
+
});
|
|
42
|
+
initProjectEnvironment();
|
|
43
|
+
panelEl.addEventListener('click', function (event) { if (event.target === panelEl) closeProjectSettings(); });
|
|
44
|
+
panelEl.addEventListener('keydown', function (event) {
|
|
45
|
+
if (event.key === 'Escape' && !event.defaultPrevented) { event.preventDefault(); event.stopPropagation(); closeProjectSettings(); }
|
|
46
|
+
trapSettingsFocus(event, panelEl);
|
|
47
|
+
});
|
|
30
48
|
navItems = panelEl.querySelectorAll(".settings-nav-item");
|
|
31
49
|
sections = panelEl.querySelectorAll(".ps-section");
|
|
32
50
|
|
|
@@ -53,13 +71,6 @@ export function initProjectSettings(appCtx, emojiCategories) {
|
|
|
53
71
|
});
|
|
54
72
|
}
|
|
55
73
|
|
|
56
|
-
// ESC key
|
|
57
|
-
document.addEventListener("keydown", function (e) {
|
|
58
|
-
if (e.key === "Escape" && panelEl && !panelEl.classList.contains("hidden")) {
|
|
59
|
-
closeProjectSettings();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
74
|
// Profile: rename
|
|
64
75
|
var renameBtn = document.getElementById("ps-rename-btn");
|
|
65
76
|
var renameForm = document.getElementById("ps-rename-form");
|
|
@@ -70,7 +81,7 @@ export function initProjectSettings(appCtx, emojiCategories) {
|
|
|
70
81
|
if (renameBtn) {
|
|
71
82
|
renameBtn.addEventListener("click", function () {
|
|
72
83
|
renameForm.classList.remove("hidden");
|
|
73
|
-
renameInput.value =
|
|
84
|
+
renameInput.value = settingsProject() ? settingsProject().name || "" : "";
|
|
74
85
|
renameBtn.classList.add("hidden");
|
|
75
86
|
renameInput.focus();
|
|
76
87
|
renameInput.select();
|
|
@@ -100,81 +111,24 @@ export function initProjectSettings(appCtx, emojiCategories) {
|
|
|
100
111
|
}
|
|
101
112
|
if (iconRemoveBtn) {
|
|
102
113
|
iconRemoveBtn.addEventListener("click", function () {
|
|
103
|
-
if (
|
|
104
|
-
|
|
114
|
+
if (getWs() && store.get('connected')) {
|
|
115
|
+
getWs().send(JSON.stringify({ type: "set_project_icon", slug: settingsSlug(), icon: null }));
|
|
105
116
|
}
|
|
106
117
|
updateIconPreview(null);
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
// Instructions: save
|
|
111
|
-
var instrSave = document.getElementById("ps-instructions-save");
|
|
112
|
-
if (instrSave) {
|
|
113
|
-
instrSave.addEventListener("click", function () { saveInstructions(); });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Environment: add button
|
|
117
|
-
var envAddBtn = document.getElementById("ps-env-add-btn");
|
|
118
|
-
if (envAddBtn) {
|
|
119
|
-
envAddBtn.addEventListener("click", function () {
|
|
120
|
-
addEnvRow("", "", true);
|
|
121
|
-
autoSaveEnv();
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Environment: tab switching
|
|
126
|
-
var envTabs = panelEl.querySelectorAll(".ps-env-tab");
|
|
127
|
-
var envTabContents = panelEl.querySelectorAll(".ps-env-tab-content");
|
|
128
|
-
for (var ti = 0; ti < envTabs.length; ti++) {
|
|
129
|
-
envTabs[ti].addEventListener("click", function () {
|
|
130
|
-
var tab = this.dataset.tab;
|
|
131
|
-
for (var a = 0; a < envTabs.length; a++) {
|
|
132
|
-
envTabs[a].classList.toggle("active", envTabs[a].dataset.tab === tab);
|
|
133
|
-
}
|
|
134
|
-
for (var b = 0; b < envTabContents.length; b++) {
|
|
135
|
-
envTabContents[b].classList.toggle("active", envTabContents[b].dataset.tab === tab);
|
|
136
|
-
}
|
|
137
|
-
if (tab === "shared") loadSharedEnv();
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Environment: shared env add button
|
|
142
|
-
var sharedEnvAddBtn = document.getElementById("ps-shared-env-add-btn");
|
|
143
|
-
if (sharedEnvAddBtn) {
|
|
144
|
-
sharedEnvAddBtn.addEventListener("click", function () {
|
|
145
|
-
addSharedEnvRow("", "", true);
|
|
146
|
-
autoSaveSharedEnv();
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Owner: transfer
|
|
151
|
-
var transferBtn = document.getElementById("ps-transfer-btn");
|
|
152
|
-
if (transferBtn) {
|
|
153
|
-
transferBtn.addEventListener("click", function () {
|
|
154
|
-
showTransferForm();
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
var transferSave = document.getElementById("ps-transfer-save");
|
|
158
|
-
if (transferSave) {
|
|
159
|
-
transferSave.addEventListener("click", function () {
|
|
160
|
-
commitTransfer();
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
var transferCancel = document.getElementById("ps-transfer-cancel");
|
|
164
|
-
if (transferCancel) {
|
|
165
|
-
transferCancel.addEventListener("click", function () {
|
|
166
|
-
hideTransferForm();
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
121
|
initProjectSessionVisibilitySettings();
|
|
122
|
+
store.subscribe(function (next, previous) { if (next.myUserId !== previous.myUserId) closeProjectSettings({ cleanup: true }); });
|
|
171
123
|
}
|
|
172
124
|
|
|
173
|
-
// ===== Open / Close =====
|
|
174
125
|
export function openProjectSettings(slug, project) {
|
|
175
126
|
if (!panelEl) return;
|
|
176
|
-
|
|
177
|
-
|
|
127
|
+
returnFocus = document.activeElement;
|
|
128
|
+
if (!returnFocus || returnFocus === document.body) returnFocus = document.getElementById("title-bar-project-dropdown");
|
|
129
|
+
store.set({ projectSettingsSlug: slug, projectSettingsProject: project });
|
|
130
|
+
var env = store.get("projectEnvironmentEditor");
|
|
131
|
+
if (env && !env.dirty && !env.saving) store.set({ projectEnvironmentEditor: Object.assign({}, env, { loaded: false, loading: false }) });
|
|
178
132
|
|
|
179
133
|
// Set nav title
|
|
180
134
|
var navTitle = document.getElementById("ps-nav-title");
|
|
@@ -186,8 +140,6 @@ export function openProjectSettings(slug, project) {
|
|
|
186
140
|
// Populate profile
|
|
187
141
|
populateProfile();
|
|
188
142
|
|
|
189
|
-
// Close file viewer if open (prevent split-screen)
|
|
190
|
-
closeFileViewer();
|
|
191
143
|
|
|
192
144
|
// Sticky notes must not sit over settings; remember what to restore.
|
|
193
145
|
if (panelEl.classList.contains("hidden")) {
|
|
@@ -198,6 +150,9 @@ export function openProjectSettings(slug, project) {
|
|
|
198
150
|
|
|
199
151
|
// Show panel
|
|
200
152
|
panelEl.classList.remove("hidden");
|
|
153
|
+
if (!restoreBackground) restoreBackground = isolateSettingsDialog(panelEl);
|
|
154
|
+
document.getElementById("project-settings-close").focus();
|
|
155
|
+
enhanceSettingsSelects(panelEl);
|
|
201
156
|
refreshIcons();
|
|
202
157
|
}
|
|
203
158
|
|
|
@@ -205,9 +160,20 @@ export function openProjectSettings(slug, project) {
|
|
|
205
160
|
// not reopened over whatever screen comes next.
|
|
206
161
|
export function closeProjectSettings(opts) {
|
|
207
162
|
if (!panelEl) return;
|
|
163
|
+
if (!(opts && opts.cleanup) && (store.get("projectEnvironmentEditor") || {}).saving) { showToast("Wait for the environment save to finish."); return; }
|
|
164
|
+
var rename = document.getElementById("ps-rename-form");
|
|
165
|
+
var renameDirty = rename && !rename.classList.contains("hidden") && document.getElementById("ps-rename-input").value.trim() !== (settingsProject().name || "");
|
|
166
|
+
if (!(opts && (opts.cleanup || opts.confirmed)) && (projectEnvironmentDirty() || renameDirty)) {
|
|
167
|
+
confirmSettingsAction('Your changes have not been saved. Discard them and close?', 'Discard changes', function () {
|
|
168
|
+
discardProjectEnvironment(); closeProjectSettings({ confirmed: true });
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
208
172
|
var wasOpen = !panelEl.classList.contains("hidden");
|
|
209
173
|
panelEl.classList.add("hidden");
|
|
210
174
|
closePsEmojiPicker();
|
|
175
|
+
if (restoreBackground) { restoreBackground(); restoreBackground = null; }
|
|
176
|
+
if (returnFocus && returnFocus.isConnected && !(opts && opts.cleanup)) returnFocus.focus();
|
|
211
177
|
if (wasOpen) {
|
|
212
178
|
releaseNotesForSettings();
|
|
213
179
|
if (notesBrowserReopen && !(opts && opts.cleanup)) openNotesBrowser();
|
|
@@ -219,11 +185,11 @@ export function isProjectSettingsOpen() {
|
|
|
219
185
|
return panelEl && !panelEl.classList.contains("hidden");
|
|
220
186
|
}
|
|
221
187
|
|
|
222
|
-
// ===== Section switching =====
|
|
223
188
|
function switchSection(name) {
|
|
224
189
|
for (var i = 0; i < navItems.length; i++) {
|
|
225
190
|
var active = navItems[i].dataset.section === name;
|
|
226
191
|
navItems[i].classList.toggle("active", active);
|
|
192
|
+
navItems[i].setAttribute("aria-current", active ? "page" : "false");
|
|
227
193
|
}
|
|
228
194
|
for (var j = 0; j < sections.length; j++) {
|
|
229
195
|
var active2 = sections[j].dataset.section === name;
|
|
@@ -236,27 +202,12 @@ function switchSection(name) {
|
|
|
236
202
|
psNavDropdown.value = name;
|
|
237
203
|
}
|
|
238
204
|
|
|
239
|
-
|
|
240
|
-
if (name === "defaults") populateDefaults();
|
|
241
|
-
if (name === "instructions") loadInstructions();
|
|
242
|
-
if (name === "environment") {
|
|
243
|
-
// Reset tabs to "project" tab
|
|
244
|
-
var envTabs = panelEl.querySelectorAll(".ps-env-tab");
|
|
245
|
-
var envTabContents = panelEl.querySelectorAll(".ps-env-tab-content");
|
|
246
|
-
for (var t = 0; t < envTabs.length; t++) {
|
|
247
|
-
envTabs[t].classList.toggle("active", envTabs[t].dataset.tab === "project");
|
|
248
|
-
}
|
|
249
|
-
for (var u = 0; u < envTabContents.length; u++) {
|
|
250
|
-
envTabContents[u].classList.toggle("active", envTabContents[u].dataset.tab === "project");
|
|
251
|
-
}
|
|
252
|
-
loadEnvironment();
|
|
253
|
-
}
|
|
205
|
+
if (name === "environment") openProjectEnvironment(settingsSlug());
|
|
254
206
|
}
|
|
255
207
|
|
|
256
|
-
// ===== Profile =====
|
|
257
208
|
function populateProfile() {
|
|
258
209
|
var nameEl = document.getElementById("ps-project-name");
|
|
259
|
-
if (nameEl) nameEl.textContent =
|
|
210
|
+
if (nameEl) nameEl.textContent = settingsProject() ? settingsProject().name || "-" : "-";
|
|
260
211
|
|
|
261
212
|
// Reset rename form
|
|
262
213
|
var renameForm = document.getElementById("ps-rename-form");
|
|
@@ -265,74 +216,28 @@ function populateProfile() {
|
|
|
265
216
|
if (renameBtn) renameBtn.classList.remove("hidden");
|
|
266
217
|
|
|
267
218
|
// Icon
|
|
268
|
-
updateIconPreview(
|
|
269
|
-
|
|
270
|
-
populateProjectSessionVisibilitySettings(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
var ownerId = currentProject ? currentProject.projectOwnerId : null;
|
|
276
|
-
var isOwnerLocked = currentProject ? currentProject.ownerLocked : false;
|
|
277
|
-
var isMultiUser = ctx.multiUser;
|
|
278
|
-
if (isMultiUser) {
|
|
279
|
-
ownerField.style.display = "";
|
|
280
|
-
var ownerNameEl = document.getElementById("ps-owner-name");
|
|
281
|
-
var transferBtn = document.getElementById("ps-transfer-btn");
|
|
282
|
-
var ownerLockedHint = document.getElementById("ps-owner-locked-hint");
|
|
283
|
-
if (transferBtn) transferBtn.style.display = "none";
|
|
284
|
-
if (ownerLockedHint) {
|
|
285
|
-
if (isOwnerLocked) { ownerLockedHint.classList.remove("hidden"); } else { ownerLockedHint.classList.add("hidden"); }
|
|
286
|
-
}
|
|
287
|
-
// Fetch user list (only succeeds for admin)
|
|
288
|
-
fetch("/api/admin/users").then(function (r) {
|
|
289
|
-
if (!r.ok) throw new Error("not admin");
|
|
290
|
-
return r.json();
|
|
291
|
-
}).then(function (data) {
|
|
292
|
-
var users = data.users || [];
|
|
293
|
-
// Show owner name
|
|
294
|
-
if (ownerId) {
|
|
295
|
-
var owner = null;
|
|
296
|
-
for (var i = 0; i < users.length; i++) {
|
|
297
|
-
if (users[i].id === ownerId) { owner = users[i]; break; }
|
|
298
|
-
}
|
|
299
|
-
if (ownerNameEl) ownerNameEl.textContent = owner ? (owner.displayName || owner.username) : ownerId;
|
|
300
|
-
} else {
|
|
301
|
-
if (ownerNameEl) ownerNameEl.textContent = "Not set";
|
|
302
|
-
}
|
|
303
|
-
// Admin can transfer unless ownership is locked (home directory projects)
|
|
304
|
-
if (transferBtn && !isOwnerLocked) transferBtn.style.display = "";
|
|
305
|
-
}).catch(function () {
|
|
306
|
-
// Not admin, show owner name from limited info
|
|
307
|
-
if (ownerId) {
|
|
308
|
-
if (ownerNameEl) ownerNameEl.textContent = ownerId;
|
|
309
|
-
// Project owner can also transfer unless locked
|
|
310
|
-
if (!isOwnerLocked && ctx.myUserId && ctx.myUserId === ownerId && transferBtn) {
|
|
311
|
-
transferBtn.style.display = "";
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
if (ownerNameEl) ownerNameEl.textContent = "Not set";
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
hideTransferForm();
|
|
318
|
-
} else {
|
|
319
|
-
ownerField.style.display = "none";
|
|
320
|
-
}
|
|
321
|
-
}
|
|
219
|
+
updateIconPreview(settingsProject() ? settingsProject().icon : null);
|
|
220
|
+
|
|
221
|
+
populateProjectSessionVisibilitySettings(settingsSlug(), settingsProject());
|
|
222
|
+
document.getElementById("ps-access-note").textContent = store.get("isMultiUserMode") ? "Choose who can see new conversations in this project. Existing conversations keep their visibility." : "This server is in single-user mode. Project membership and shared session defaults are available in multi-user mode.";
|
|
223
|
+
|
|
224
|
+
var project = settingsProject() || {};
|
|
225
|
+
document.getElementById('ps-project-path').textContent = project.path || (settingsSlug() === store.get('currentSlug') ? store.get('cwd') : '') || 'Folder information unavailable';
|
|
322
226
|
}
|
|
323
227
|
|
|
324
228
|
function commitRename() {
|
|
325
229
|
var renameInput = document.getElementById("ps-rename-input");
|
|
326
230
|
var nameEl = document.getElementById("ps-project-name");
|
|
327
231
|
var newName = renameInput ? renameInput.value.trim() : "";
|
|
328
|
-
if (newName &&
|
|
329
|
-
|
|
232
|
+
if (newName && getWs() && store.get('connected')) {
|
|
233
|
+
getWs().send(JSON.stringify({ type: "set_project_title", slug: settingsSlug(), title: newName }));
|
|
330
234
|
if (nameEl) nameEl.textContent = newName;
|
|
331
|
-
if (
|
|
235
|
+
if (settingsProject()) settingsProject().name = newName;
|
|
332
236
|
var navTitle = document.getElementById("ps-nav-title");
|
|
333
237
|
if (navTitle) navTitle.textContent = newName;
|
|
334
238
|
}
|
|
335
|
-
cancelRename();
|
|
239
|
+
if (newName && getWs() && store.get("connected")) cancelRename();
|
|
240
|
+
else showToast("Enter a name and connect to the server before saving.");
|
|
336
241
|
}
|
|
337
242
|
|
|
338
243
|
function cancelRename() {
|
|
@@ -342,62 +247,19 @@ function cancelRename() {
|
|
|
342
247
|
if (renameBtn) renameBtn.classList.remove("hidden");
|
|
343
248
|
}
|
|
344
249
|
|
|
345
|
-
// ===== Owner transfer =====
|
|
346
|
-
function showTransferForm() {
|
|
347
|
-
var form = document.getElementById("ps-transfer-form");
|
|
348
|
-
var btn = document.getElementById("ps-transfer-btn");
|
|
349
|
-
var select = document.getElementById("ps-transfer-select");
|
|
350
|
-
if (!form || !select) return;
|
|
351
|
-
|
|
352
|
-
// Fetch user list and populate select
|
|
353
|
-
select.innerHTML = '<option value="">Loading...</option>';
|
|
354
|
-
fetch("/api/admin/users").then(function (r) { return r.json(); }).then(function (data) {
|
|
355
|
-
var users = data.users || [];
|
|
356
|
-
select.innerHTML = "";
|
|
357
|
-
for (var i = 0; i < users.length; i++) {
|
|
358
|
-
var u = users[i];
|
|
359
|
-
var opt = document.createElement("option");
|
|
360
|
-
opt.value = u.id;
|
|
361
|
-
opt.textContent = (u.displayName || u.username) + (u.linuxUser ? " (" + u.linuxUser + ")" : "");
|
|
362
|
-
if (currentProject && u.id === currentProject.projectOwnerId) opt.selected = true;
|
|
363
|
-
select.appendChild(opt);
|
|
364
|
-
}
|
|
365
|
-
}).catch(function () {
|
|
366
|
-
select.innerHTML = '<option value="">Failed to load users</option>';
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
form.classList.remove("hidden");
|
|
370
|
-
if (btn) btn.classList.add("hidden");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function hideTransferForm() {
|
|
374
|
-
var form = document.getElementById("ps-transfer-form");
|
|
375
|
-
var btn = document.getElementById("ps-transfer-btn");
|
|
376
|
-
if (form) form.classList.add("hidden");
|
|
377
|
-
if (btn) btn.classList.remove("hidden");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function commitTransfer() {
|
|
381
|
-
var select = document.getElementById("ps-transfer-select");
|
|
382
|
-
var userId = select ? select.value : "";
|
|
383
|
-
if (!userId || !ctx.ws || !ctx.connected) return;
|
|
384
|
-
ctx.ws.send(JSON.stringify({ type: "transfer_project_owner", slug: currentSlug, userId: userId }));
|
|
385
|
-
hideTransferForm();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
250
|
function updateIconPreview(icon) {
|
|
389
251
|
var preview = document.getElementById("ps-icon-preview");
|
|
390
252
|
var removeBtn = document.getElementById("ps-icon-remove-btn");
|
|
391
253
|
if (preview) {
|
|
392
254
|
preview.textContent = icon || "";
|
|
393
255
|
if (icon) parseEmojis(preview);
|
|
256
|
+
else { preview.innerHTML = '<i data-lucide="folder"></i>'; refreshIcons(); }
|
|
394
257
|
}
|
|
395
258
|
if (removeBtn) {
|
|
396
259
|
removeBtn.classList.toggle("hidden", !icon);
|
|
397
260
|
}
|
|
398
261
|
}
|
|
399
262
|
|
|
400
|
-
// ===== Emoji picker (inline in settings) =====
|
|
401
263
|
var psEmojiPickerEl = null;
|
|
402
264
|
|
|
403
265
|
function closePsEmojiPicker() {
|
|
@@ -468,8 +330,8 @@ function showPsEmojiPicker() {
|
|
|
468
330
|
btn.addEventListener("click", function (e) {
|
|
469
331
|
e.stopPropagation();
|
|
470
332
|
closePsEmojiPicker();
|
|
471
|
-
if (
|
|
472
|
-
|
|
333
|
+
if (getWs() && store.get('connected')) {
|
|
334
|
+
getWs().send(JSON.stringify({ type: "set_project_icon", slug: settingsSlug(), icon: emoji }));
|
|
473
335
|
}
|
|
474
336
|
updateIconPreview(emoji);
|
|
475
337
|
});
|
|
@@ -494,431 +356,13 @@ function showPsEmojiPicker() {
|
|
|
494
356
|
psEmojiPickerEl = picker;
|
|
495
357
|
}
|
|
496
358
|
|
|
497
|
-
// ===== Defaults =====
|
|
498
|
-
function psSendMsg(type, data) {
|
|
499
|
-
var ws = ctx.ws;
|
|
500
|
-
if (ws && ws.readyState === 1) {
|
|
501
|
-
var msg = Object.assign({ type: type }, data);
|
|
502
|
-
ws.send(JSON.stringify(msg));
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function psDefaultsOpts() {
|
|
507
|
-
return {
|
|
508
|
-
models: ctx.currentModels || [],
|
|
509
|
-
currentModel: ctx.currentModel || "",
|
|
510
|
-
currentMode: ctx.currentMode || "default",
|
|
511
|
-
currentEffort: ctx.currentEffort || "medium",
|
|
512
|
-
currentThinking: ctx.currentThinking || "adaptive",
|
|
513
|
-
currentThinkingBudget: ctx.currentThinkingBudget || 10000,
|
|
514
|
-
currentBetas: ctx.currentBetas || [],
|
|
515
|
-
sendMsg: psSendMsg,
|
|
516
|
-
modelMsgType: "set_project_default_model",
|
|
517
|
-
modeMsgType: "set_project_default_mode",
|
|
518
|
-
effortMsgType: "set_project_default_effort",
|
|
519
|
-
onModelSelect: function (model) {
|
|
520
|
-
renderBetaCard("ps", Object.assign({}, psDefaultsOpts(), { overrideModel: model }));
|
|
521
|
-
},
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function populateDefaults() {
|
|
526
|
-
var opts = psDefaultsOpts();
|
|
527
|
-
renderModelList("ps", opts);
|
|
528
|
-
renderBetaCard("ps", opts);
|
|
529
|
-
renderModeList("ps", opts);
|
|
530
|
-
renderEffortBar("ps", opts);
|
|
531
|
-
renderThinkingBar("ps", opts);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// ===== Instructions (CLAUDE.md) =====
|
|
535
|
-
function loadInstructions() {
|
|
536
|
-
var editor = document.getElementById("ps-instructions-editor");
|
|
537
|
-
var status = document.getElementById("ps-instructions-status");
|
|
538
|
-
var saveStatus = document.getElementById("ps-instructions-save-status");
|
|
539
|
-
if (saveStatus) saveStatus.textContent = "";
|
|
540
|
-
|
|
541
|
-
if (status) status.textContent = "Loading...";
|
|
542
|
-
|
|
543
|
-
if (ctx.ws && ctx.connected) {
|
|
544
|
-
ctx.ws.send(JSON.stringify({ type: "fs_read", path: "CLAUDE.md" }));
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
export function handleInstructionsRead(msg) {
|
|
549
|
-
var editor = document.getElementById("ps-instructions-editor");
|
|
550
|
-
var status = document.getElementById("ps-instructions-status");
|
|
551
|
-
if (!editor) return;
|
|
552
|
-
|
|
553
|
-
if (msg.error) {
|
|
554
|
-
editor.value = "";
|
|
555
|
-
if (status) status.textContent = "No CLAUDE.md file found. Save to create one.";
|
|
556
|
-
} else {
|
|
557
|
-
editor.value = msg.content || "";
|
|
558
|
-
if (status) status.textContent = "";
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function saveInstructions() {
|
|
563
|
-
var editor = document.getElementById("ps-instructions-editor");
|
|
564
|
-
var saveStatus = document.getElementById("ps-instructions-save-status");
|
|
565
|
-
if (!editor) return;
|
|
566
|
-
|
|
567
|
-
if (ctx.ws && ctx.connected) {
|
|
568
|
-
ctx.ws.send(JSON.stringify({ type: "fs_write", path: "CLAUDE.md", content: editor.value }));
|
|
569
|
-
if (saveStatus) saveStatus.textContent = "Saving...";
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
export function handleInstructionsWrite(msg) {
|
|
574
|
-
var saveStatus = document.getElementById("ps-instructions-save-status");
|
|
575
|
-
if (!saveStatus) return;
|
|
576
|
-
if (msg.ok) {
|
|
577
|
-
saveStatus.textContent = "Saved";
|
|
578
|
-
setTimeout(function () { saveStatus.textContent = ""; }, 2000);
|
|
579
|
-
} else {
|
|
580
|
-
saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// ===== Environment (key-value list) =====
|
|
585
|
-
var envSaveTimer = null;
|
|
586
|
-
var envStatusTimer = null;
|
|
587
|
-
|
|
588
|
-
function showEnvSaveStatus(elementId, text, duration) {
|
|
589
|
-
var saveStatus = document.getElementById(elementId);
|
|
590
|
-
if (!saveStatus) return;
|
|
591
|
-
if (envStatusTimer) clearTimeout(envStatusTimer);
|
|
592
|
-
saveStatus.textContent = text;
|
|
593
|
-
envStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function loadEnvironment() {
|
|
597
|
-
var saveStatus = document.getElementById("ps-env-save-status");
|
|
598
|
-
if (saveStatus) saveStatus.textContent = "";
|
|
599
|
-
|
|
600
|
-
if (ctx.ws && ctx.connected) {
|
|
601
|
-
ctx.ws.send(JSON.stringify({ type: "get_project_env", slug: currentSlug }));
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
export function handleProjectEnv(msg) {
|
|
606
|
-
// Parse envrc string into key-value pairs
|
|
607
|
-
var list = document.getElementById("ps-env-list");
|
|
608
|
-
if (!list) return;
|
|
609
|
-
list.innerHTML = "";
|
|
610
|
-
|
|
611
|
-
var pairs = parseEnvString(msg.envrc || "");
|
|
612
|
-
for (var i = 0; i < pairs.length; i++) {
|
|
613
|
-
addEnvRow(pairs[i].key, pairs[i].value, false);
|
|
614
|
-
}
|
|
615
|
-
refreshIcons();
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Check if text looks like env format: first line starts with a valid VAR_NAME=
|
|
619
|
-
export function looksLikeEnv(text) {
|
|
620
|
-
var first = text.split("\n")[0].trim();
|
|
621
|
-
if (first.indexOf("export ") === 0) first = first.substring(7);
|
|
622
|
-
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(first);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
export function parseEnvString(str) {
|
|
626
|
-
var pairs = [];
|
|
627
|
-
if (!str) return pairs;
|
|
628
|
-
var lines = str.split("\n");
|
|
629
|
-
for (var i = 0; i < lines.length; i++) {
|
|
630
|
-
var line = lines[i].trim();
|
|
631
|
-
if (!line || line.charAt(0) === "#") continue;
|
|
632
|
-
// Strip leading "export "
|
|
633
|
-
if (line.indexOf("export ") === 0) line = line.substring(7);
|
|
634
|
-
var eq = line.indexOf("=");
|
|
635
|
-
if (eq === -1) continue;
|
|
636
|
-
var key = line.substring(0, eq).trim();
|
|
637
|
-
var val = line.substring(eq + 1).trim();
|
|
638
|
-
// Saved values are JSON-quoted so shell metacharacters remain data.
|
|
639
|
-
if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') {
|
|
640
|
-
try { val = JSON.parse(val); } catch (e) { val = val.substring(1, val.length - 1); }
|
|
641
|
-
} else if (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'") {
|
|
642
|
-
val = val.substring(1, val.length - 1);
|
|
643
|
-
}
|
|
644
|
-
if (key) pairs.push({ key: key, value: val });
|
|
645
|
-
}
|
|
646
|
-
return pairs;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
export function serializeEnvValue(value) {
|
|
650
|
-
return JSON.stringify(value == null ? "" : String(value));
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function buildEnvString() {
|
|
654
|
-
var list = document.getElementById("ps-env-list");
|
|
655
|
-
if (!list) return "";
|
|
656
|
-
var rows = list.querySelectorAll(".ps-env-row");
|
|
657
|
-
var lines = [];
|
|
658
|
-
for (var i = 0; i < rows.length; i++) {
|
|
659
|
-
var keyInput = rows[i].querySelector(".ps-env-key");
|
|
660
|
-
var valInput = rows[i].querySelector(".ps-env-val");
|
|
661
|
-
var key = keyInput ? keyInput.value.trim() : "";
|
|
662
|
-
var val = valInput ? valInput.value : "";
|
|
663
|
-
if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
|
|
664
|
-
}
|
|
665
|
-
return lines.join("\n");
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function addEnvRow(key, value, focus) {
|
|
669
|
-
var list = document.getElementById("ps-env-list");
|
|
670
|
-
if (!list) return;
|
|
671
|
-
|
|
672
|
-
var row = document.createElement("div");
|
|
673
|
-
row.className = "ps-env-row";
|
|
674
|
-
|
|
675
|
-
var keyInput = document.createElement("input");
|
|
676
|
-
keyInput.type = "text";
|
|
677
|
-
keyInput.className = "ps-env-key";
|
|
678
|
-
keyInput.placeholder = "KEY";
|
|
679
|
-
keyInput.value = key;
|
|
680
|
-
keyInput.spellcheck = false;
|
|
681
|
-
keyInput.autocomplete = "off";
|
|
682
|
-
|
|
683
|
-
var valInput = document.createElement("input");
|
|
684
|
-
valInput.type = "text";
|
|
685
|
-
valInput.className = "ps-env-val";
|
|
686
|
-
valInput.placeholder = "value";
|
|
687
|
-
valInput.value = value;
|
|
688
|
-
valInput.spellcheck = false;
|
|
689
|
-
valInput.autocomplete = "off";
|
|
690
|
-
|
|
691
|
-
var delBtn = document.createElement("button");
|
|
692
|
-
delBtn.className = "ps-env-del";
|
|
693
|
-
delBtn.title = "Remove";
|
|
694
|
-
delBtn.innerHTML = '<i data-lucide="x"></i>';
|
|
695
|
-
|
|
696
|
-
delBtn.addEventListener("click", function () {
|
|
697
|
-
row.remove();
|
|
698
|
-
autoSaveEnv();
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// Auto-save on change
|
|
702
|
-
keyInput.addEventListener("input", function () { autoSaveEnv(); });
|
|
703
|
-
valInput.addEventListener("input", function () { autoSaveEnv(); });
|
|
704
|
-
|
|
705
|
-
// Paste detection: if pasting KEY=VALUE content into key field, parse it
|
|
706
|
-
keyInput.addEventListener("paste", function (e) {
|
|
707
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
708
|
-
if (text && looksLikeEnv(text)) {
|
|
709
|
-
e.preventDefault();
|
|
710
|
-
var pairs = parseEnvString(text);
|
|
711
|
-
if (pairs.length > 0) {
|
|
712
|
-
// Fill current row with first pair
|
|
713
|
-
keyInput.value = pairs[0].key;
|
|
714
|
-
valInput.value = pairs[0].value;
|
|
715
|
-
// Add remaining as new rows
|
|
716
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
717
|
-
addEnvRow(pairs[p].key, pairs[p].value, false);
|
|
718
|
-
}
|
|
719
|
-
autoSaveEnv();
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
// Also handle paste into value field
|
|
725
|
-
valInput.addEventListener("paste", function (e) {
|
|
726
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
727
|
-
if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
|
|
728
|
-
e.preventDefault();
|
|
729
|
-
var pairs = parseEnvString(text);
|
|
730
|
-
if (pairs.length > 0) {
|
|
731
|
-
keyInput.value = pairs[0].key;
|
|
732
|
-
valInput.value = pairs[0].value;
|
|
733
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
734
|
-
addEnvRow(pairs[p].key, pairs[p].value, false);
|
|
735
|
-
}
|
|
736
|
-
autoSaveEnv();
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
row.appendChild(keyInput);
|
|
742
|
-
row.appendChild(valInput);
|
|
743
|
-
row.appendChild(delBtn);
|
|
744
|
-
list.appendChild(row);
|
|
745
|
-
refreshIcons();
|
|
746
|
-
|
|
747
|
-
if (focus) keyInput.focus();
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function autoSaveEnv() {
|
|
751
|
-
if (envSaveTimer) clearTimeout(envSaveTimer);
|
|
752
|
-
envSaveTimer = setTimeout(function () {
|
|
753
|
-
var envrc = buildEnvString();
|
|
754
|
-
if (ctx.ws && ctx.connected) {
|
|
755
|
-
ctx.ws.send(JSON.stringify({ type: "set_project_env", slug: currentSlug, envrc: envrc }));
|
|
756
|
-
showEnvSaveStatus("ps-env-save-status", "Saving...", 2000);
|
|
757
|
-
}
|
|
758
|
-
}, 800);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
export function handleProjectEnvSaved(msg) {
|
|
762
|
-
if (msg.ok) {
|
|
763
|
-
showEnvSaveStatus("ps-env-save-status", msg.timing || "Saved", 5000);
|
|
764
|
-
} else {
|
|
765
|
-
showEnvSaveStatus("ps-env-save-status", "Error: " + (msg.error || "Failed to save"), 5000);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// ===== Shared Environment (via tabs) =====
|
|
770
|
-
var sharedEnvSaveTimer = null;
|
|
771
|
-
var sharedEnvStatusTimer = null;
|
|
772
|
-
|
|
773
|
-
function showSharedEnvSaveStatus(text, duration) {
|
|
774
|
-
var saveStatus = document.getElementById("ps-shared-env-save-status");
|
|
775
|
-
if (!saveStatus) return;
|
|
776
|
-
if (sharedEnvStatusTimer) clearTimeout(sharedEnvStatusTimer);
|
|
777
|
-
saveStatus.textContent = text;
|
|
778
|
-
sharedEnvStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
function loadSharedEnv() {
|
|
782
|
-
var saveStatus = document.getElementById("ps-shared-env-save-status");
|
|
783
|
-
if (saveStatus) saveStatus.textContent = "";
|
|
784
|
-
|
|
785
|
-
if (ctx.ws && ctx.connected) {
|
|
786
|
-
ctx.ws.send(JSON.stringify({ type: "get_shared_env" }));
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
export function handleProjectSharedEnv(msg) {
|
|
791
|
-
var list = document.getElementById("ps-shared-env-list");
|
|
792
|
-
if (!list) return;
|
|
793
|
-
list.innerHTML = "";
|
|
794
|
-
|
|
795
|
-
var pairs = parseEnvString(msg.envrc || "");
|
|
796
|
-
for (var i = 0; i < pairs.length; i++) {
|
|
797
|
-
addSharedEnvRow(pairs[i].key, pairs[i].value, false);
|
|
798
|
-
}
|
|
799
|
-
refreshIcons();
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
export function handleProjectSharedEnvSaved(msg) {
|
|
803
|
-
if (msg.ok) {
|
|
804
|
-
showSharedEnvSaveStatus(msg.timing || "Saved", 5000);
|
|
805
|
-
} else {
|
|
806
|
-
showSharedEnvSaveStatus("Error: " + (msg.error || "Failed to save"), 5000);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function buildSharedEnvString() {
|
|
811
|
-
var list = document.getElementById("ps-shared-env-list");
|
|
812
|
-
if (!list) return "";
|
|
813
|
-
var rows = list.querySelectorAll(".ps-env-row");
|
|
814
|
-
var lines = [];
|
|
815
|
-
for (var i = 0; i < rows.length; i++) {
|
|
816
|
-
var keyInput = rows[i].querySelector(".ps-env-key");
|
|
817
|
-
var valInput = rows[i].querySelector(".ps-env-val");
|
|
818
|
-
var key = keyInput ? keyInput.value.trim() : "";
|
|
819
|
-
var val = valInput ? valInput.value : "";
|
|
820
|
-
if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
|
|
821
|
-
}
|
|
822
|
-
return lines.join("\n");
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
function addSharedEnvRow(key, value, focus) {
|
|
826
|
-
var list = document.getElementById("ps-shared-env-list");
|
|
827
|
-
if (!list) return;
|
|
828
|
-
|
|
829
|
-
var row = document.createElement("div");
|
|
830
|
-
row.className = "ps-env-row";
|
|
831
|
-
|
|
832
|
-
var keyInput = document.createElement("input");
|
|
833
|
-
keyInput.type = "text";
|
|
834
|
-
keyInput.className = "ps-env-key";
|
|
835
|
-
keyInput.placeholder = "KEY";
|
|
836
|
-
keyInput.value = key;
|
|
837
|
-
keyInput.spellcheck = false;
|
|
838
|
-
keyInput.autocomplete = "off";
|
|
839
|
-
|
|
840
|
-
var valInput = document.createElement("input");
|
|
841
|
-
valInput.type = "text";
|
|
842
|
-
valInput.className = "ps-env-val";
|
|
843
|
-
valInput.placeholder = "value";
|
|
844
|
-
valInput.value = value;
|
|
845
|
-
valInput.spellcheck = false;
|
|
846
|
-
valInput.autocomplete = "off";
|
|
847
|
-
|
|
848
|
-
var delBtn = document.createElement("button");
|
|
849
|
-
delBtn.className = "ps-env-del";
|
|
850
|
-
delBtn.title = "Remove";
|
|
851
|
-
delBtn.innerHTML = '<i data-lucide="x"></i>';
|
|
852
|
-
|
|
853
|
-
delBtn.addEventListener("click", function () {
|
|
854
|
-
row.remove();
|
|
855
|
-
autoSaveSharedEnv();
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
keyInput.addEventListener("input", function () { autoSaveSharedEnv(); });
|
|
859
|
-
valInput.addEventListener("input", function () { autoSaveSharedEnv(); });
|
|
860
|
-
|
|
861
|
-
// Paste detection
|
|
862
|
-
keyInput.addEventListener("paste", function (e) {
|
|
863
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
864
|
-
if (text && looksLikeEnv(text)) {
|
|
865
|
-
e.preventDefault();
|
|
866
|
-
var pairs = parseEnvString(text);
|
|
867
|
-
if (pairs.length > 0) {
|
|
868
|
-
keyInput.value = pairs[0].key;
|
|
869
|
-
valInput.value = pairs[0].value;
|
|
870
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
871
|
-
addSharedEnvRow(pairs[p].key, pairs[p].value, false);
|
|
872
|
-
}
|
|
873
|
-
autoSaveSharedEnv();
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
valInput.addEventListener("paste", function (e) {
|
|
879
|
-
var text = (e.clipboardData || window.clipboardData).getData("text");
|
|
880
|
-
if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
|
|
881
|
-
e.preventDefault();
|
|
882
|
-
var pairs = parseEnvString(text);
|
|
883
|
-
if (pairs.length > 0) {
|
|
884
|
-
keyInput.value = pairs[0].key;
|
|
885
|
-
valInput.value = pairs[0].value;
|
|
886
|
-
for (var p = 1; p < pairs.length; p++) {
|
|
887
|
-
addSharedEnvRow(pairs[p].key, pairs[p].value, false);
|
|
888
|
-
}
|
|
889
|
-
autoSaveSharedEnv();
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
row.appendChild(keyInput);
|
|
895
|
-
row.appendChild(valInput);
|
|
896
|
-
row.appendChild(delBtn);
|
|
897
|
-
list.appendChild(row);
|
|
898
|
-
refreshIcons();
|
|
899
|
-
|
|
900
|
-
if (focus) keyInput.focus();
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
function autoSaveSharedEnv() {
|
|
904
|
-
if (sharedEnvSaveTimer) clearTimeout(sharedEnvSaveTimer);
|
|
905
|
-
sharedEnvSaveTimer = setTimeout(function () {
|
|
906
|
-
var envrc = buildSharedEnvString();
|
|
907
|
-
if (ctx.ws && ctx.connected) {
|
|
908
|
-
ctx.ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
|
|
909
|
-
showSharedEnvSaveStatus("Saving...", 2000);
|
|
910
|
-
}
|
|
911
|
-
}, 800);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// ===== Update from external events =====
|
|
915
359
|
export function updateProjectSettingsIcon(icon) {
|
|
916
|
-
if (
|
|
360
|
+
if (settingsProject()) settingsProject().icon = icon;
|
|
917
361
|
updateIconPreview(icon);
|
|
918
362
|
}
|
|
919
363
|
|
|
920
364
|
export function updateProjectSettingsName(name) {
|
|
921
|
-
if (
|
|
365
|
+
if (settingsProject()) settingsProject().name = name;
|
|
922
366
|
var nameEl = document.getElementById("ps-project-name");
|
|
923
367
|
if (nameEl) nameEl.textContent = name || "-";
|
|
924
368
|
var navTitle = document.getElementById("ps-nav-title");
|
|
@@ -926,8 +370,8 @@ export function updateProjectSettingsName(name) {
|
|
|
926
370
|
}
|
|
927
371
|
|
|
928
372
|
export function handleProjectOwnerChanged(msg) {
|
|
929
|
-
if (
|
|
930
|
-
|
|
373
|
+
if (settingsProject()) {
|
|
374
|
+
settingsProject().projectOwnerId = msg.ownerId;
|
|
931
375
|
}
|
|
932
376
|
var ownerNameEl = document.getElementById("ps-owner-name");
|
|
933
377
|
if (ownerNameEl) ownerNameEl.textContent = msg.ownerName || msg.ownerId || "Not set";
|