clay-server 5.0.0-beta.4 → 5.0.1-beta.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/lib/public/app.js +2 -0
- package/lib/public/css/sidebar.css +13 -0
- package/lib/public/css/title-bar.css +7 -1
- package/lib/public/modules/app-header.js +14 -4
- package/lib/public/modules/session-context-menu.js +209 -0
- package/lib/public/modules/session-github.js +46 -10
- package/lib/public/modules/sidebar-mobile.js +4 -0
- package/lib/public/modules/sidebar-sessions.js +25 -72
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -295,6 +295,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
295
295
|
sessionFolderMenu: null,
|
|
296
296
|
sessionFolderContext: null,
|
|
297
297
|
sessionFolderModal: null,
|
|
298
|
+
sessionContextMenu: null,
|
|
299
|
+
sessionInlineRename: null,
|
|
298
300
|
splitDelegations: {},
|
|
299
301
|
pairResultStatus: [],
|
|
300
302
|
notesTemporarilyHidden: false,
|
|
@@ -1033,6 +1033,8 @@ body:not(.home-active) #icon-strip-hint-mate { display: none; }
|
|
|
1033
1033
|
/* --- Session inline rename --- */
|
|
1034
1034
|
.session-rename-input {
|
|
1035
1035
|
width: 100%;
|
|
1036
|
+
min-width: 0;
|
|
1037
|
+
box-sizing: border-box;
|
|
1036
1038
|
background: var(--input-bg);
|
|
1037
1039
|
border: 1px solid var(--text-dimmer);
|
|
1038
1040
|
border-radius: 6px;
|
|
@@ -1043,6 +1045,17 @@ body:not(.home-active) #icon-strip-hint-mate { display: none; }
|
|
|
1043
1045
|
outline: none;
|
|
1044
1046
|
}
|
|
1045
1047
|
|
|
1048
|
+
.session-rename-input:focus {
|
|
1049
|
+
border-color: var(--link);
|
|
1050
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--link) 18%, transparent);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.session-renaming .session-item-title,
|
|
1054
|
+
.session-renaming .mobile-session-title {
|
|
1055
|
+
overflow: visible;
|
|
1056
|
+
text-overflow: clip;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1046
1059
|
/* --- Session loop groups --- */
|
|
1047
1060
|
.session-loop-wrapper {
|
|
1048
1061
|
margin-bottom: 0;
|
|
@@ -386,10 +386,16 @@
|
|
|
386
386
|
/* The inline title editor must own the header while the keyboard is open.
|
|
387
387
|
Leaving the other controls visible made its usable width nearly zero. */
|
|
388
388
|
.title-bar-content.session-renaming .status,
|
|
389
|
-
.title-bar-content.session-renaming #header-left > :not(.header-rename-input) {
|
|
389
|
+
.title-bar-content.session-renaming #header-left > :not(.header-title):not(.header-rename-input) {
|
|
390
390
|
display: none !important;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
.title-bar-content.session-renaming .header-title {
|
|
394
|
+
display: flex;
|
|
395
|
+
flex: 1 1 100%;
|
|
396
|
+
overflow: visible;
|
|
397
|
+
}
|
|
398
|
+
|
|
393
399
|
.title-bar-content.session-renaming .header-rename-input {
|
|
394
400
|
flex: 1 1 100%;
|
|
395
401
|
width: 100%;
|
|
@@ -13,6 +13,7 @@ import { getSessionUsage, setSessionUsage, getContextData, setContextData, updat
|
|
|
13
13
|
import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
|
|
14
14
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
15
15
|
import { bindPermissionControl, renderPermissionControl } from './permission-control.js';
|
|
16
|
+
import { startHeaderSessionInlineRename } from './session-context-menu.js';
|
|
16
17
|
|
|
17
18
|
// --- Module-owned state ---
|
|
18
19
|
var sessionInfoPopover = null;
|
|
@@ -59,7 +60,20 @@ export function initHeader() {
|
|
|
59
60
|
if (headerRenameBtn) {
|
|
60
61
|
headerRenameBtn.addEventListener("click", function () {
|
|
61
62
|
if (!store.get('activeSessionId')) return;
|
|
63
|
+
var split = store.get('splitPanes');
|
|
62
64
|
var currentText = headerTitleEl.textContent;
|
|
65
|
+
if (!split) {
|
|
66
|
+
var sessions = getCachedSessions() || [];
|
|
67
|
+
var active = null;
|
|
68
|
+
for (var si = 0; si < sessions.length; si++) {
|
|
69
|
+
if (String(sessions[si].id) === String(store.get('activeSessionId'))) { active = sessions[si]; break; }
|
|
70
|
+
}
|
|
71
|
+
startHeaderSessionInlineRename(headerTitleEl, headerRenameBtn, active || {
|
|
72
|
+
id: store.get('activeSessionId'),
|
|
73
|
+
title: currentText,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
63
77
|
var input = document.createElement("input");
|
|
64
78
|
input.type = "text";
|
|
65
79
|
input.className = "header-rename-input";
|
|
@@ -78,15 +92,11 @@ export function initHeader() {
|
|
|
78
92
|
if (newTitle && newTitle !== currentText && ws && ws.readyState === 1) {
|
|
79
93
|
// With a split group open the header shows the GROUP name, so the
|
|
80
94
|
// pencil must rename the group, not the parent's last active session.
|
|
81
|
-
var split = store.get('splitPanes');
|
|
82
95
|
if (split) {
|
|
83
96
|
if (split.groupId) {
|
|
84
97
|
ws.send(JSON.stringify({ type: "split_group_rename", id: split.groupId, name: newTitle }));
|
|
85
98
|
headerTitleEl.textContent = newTitle;
|
|
86
99
|
}
|
|
87
|
-
} else {
|
|
88
|
-
ws.send(JSON.stringify({ type: "rename_session", id: store.get('activeSessionId'), title: newTitle }));
|
|
89
|
-
headerTitleEl.textContent = newTitle;
|
|
90
100
|
}
|
|
91
101
|
}
|
|
92
102
|
input.remove();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Session context-menu lifecycle and exact-row title editing.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
5
|
+
import { showToast } from './utils.js';
|
|
6
|
+
|
|
7
|
+
function menuState() {
|
|
8
|
+
return store.get('sessionContextMenu');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function renameState() {
|
|
12
|
+
return store.get('sessionInlineRename');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function closeSessionContextMenu(restoreFocus) {
|
|
16
|
+
var state = menuState();
|
|
17
|
+
if (!state) return;
|
|
18
|
+
store.set({ sessionContextMenu: null });
|
|
19
|
+
document.removeEventListener("pointerdown", onOutsidePress, true);
|
|
20
|
+
document.removeEventListener("mousedown", onOutsidePress, true);
|
|
21
|
+
document.removeEventListener("touchstart", onOutsidePress, true);
|
|
22
|
+
document.removeEventListener("keydown", onMenuKey, true);
|
|
23
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
24
|
+
if (state.anchor) state.anchor.setAttribute("aria-expanded", "false");
|
|
25
|
+
if (state.el && state.el.isConnected) state.el.remove();
|
|
26
|
+
if (restoreFocus !== false && state.anchor && state.anchor.isConnected) {
|
|
27
|
+
state.anchor.focus({ preventScroll: true });
|
|
28
|
+
if (!state.anchorHadTabIndex) {
|
|
29
|
+
state.anchor.addEventListener("blur", function cleanupAnchorTabIndex() {
|
|
30
|
+
state.anchor.removeAttribute("tabindex");
|
|
31
|
+
}, { once: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (state.anchor && restoreFocus === false && !state.anchorHadTabIndex) state.anchor.removeAttribute("tabindex");
|
|
35
|
+
else if (state.anchor && state.anchorHadTabIndex) state.anchor.setAttribute("tabindex", state.anchorTabIndex);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onOutsidePress(event) {
|
|
39
|
+
var state = menuState();
|
|
40
|
+
if (!state) return;
|
|
41
|
+
if (event.target && state.el.contains(event.target)) return;
|
|
42
|
+
// Do not cancel the press: the outside control still receives its click.
|
|
43
|
+
closeSessionContextMenu(false);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function onWindowBlur() {
|
|
47
|
+
closeSessionContextMenu(false);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onMenuKey(event) {
|
|
51
|
+
var state = menuState();
|
|
52
|
+
if (!state) return;
|
|
53
|
+
if (event.key === "Escape") {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
closeSessionContextMenu(true);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function mountSessionContextMenu(menu, anchor, sessionId) {
|
|
61
|
+
closeSessionContextMenu(false);
|
|
62
|
+
menu.setAttribute("role", "menu");
|
|
63
|
+
var items = menu.querySelectorAll("button");
|
|
64
|
+
for (var i = 0; i < items.length; i++) {
|
|
65
|
+
items[i].type = "button";
|
|
66
|
+
items[i].setAttribute("role", "menuitem");
|
|
67
|
+
}
|
|
68
|
+
anchor.setAttribute("aria-haspopup", "menu");
|
|
69
|
+
anchor.setAttribute("aria-expanded", "true");
|
|
70
|
+
var anchorHadTabIndex = anchor.hasAttribute("tabindex");
|
|
71
|
+
var anchorTabIndex = anchor.getAttribute("tabindex");
|
|
72
|
+
if (!anchorHadTabIndex) anchor.setAttribute("tabindex", "-1");
|
|
73
|
+
store.set({ sessionContextMenu: {
|
|
74
|
+
el: menu,
|
|
75
|
+
anchor: anchor,
|
|
76
|
+
anchorHadTabIndex: anchorHadTabIndex,
|
|
77
|
+
anchorTabIndex: anchorTabIndex,
|
|
78
|
+
sessionId: sessionId || null,
|
|
79
|
+
} });
|
|
80
|
+
document.addEventListener("pointerdown", onOutsidePress, true);
|
|
81
|
+
document.addEventListener("mousedown", onOutsidePress, true);
|
|
82
|
+
document.addEventListener("touchstart", onOutsidePress, true);
|
|
83
|
+
document.addEventListener("keydown", onMenuKey, true);
|
|
84
|
+
window.addEventListener("blur", onWindowBlur);
|
|
85
|
+
var first = menu.querySelector("button:not([disabled])");
|
|
86
|
+
if (first) first.focus({ preventScroll: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function restoreTitle(state) {
|
|
90
|
+
if (!state || !state.title || !state.title.isConnected) return;
|
|
91
|
+
state.title.replaceChildren();
|
|
92
|
+
for (var i = 0; i < state.originalNodes.length; i++) state.title.appendChild(state.originalNodes[i]);
|
|
93
|
+
if (state.row) state.row.classList.remove("session-renaming");
|
|
94
|
+
if (state.control) state.control.style.display = state.controlDisplay;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function cancelSessionInlineRename(restoreFocus) {
|
|
98
|
+
var state = renameState();
|
|
99
|
+
if (!state) return;
|
|
100
|
+
store.set({ sessionInlineRename: null });
|
|
101
|
+
restoreTitle(state);
|
|
102
|
+
if (restoreFocus && state.row && state.row.isConnected) state.row.focus({ preventScroll: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function currentRenameCanSend(state) {
|
|
106
|
+
var ws = getWs();
|
|
107
|
+
var permissions = store.get('permissions');
|
|
108
|
+
var sameSession = state && state.source === "header" ?
|
|
109
|
+
String(store.get('activeSessionId')) === String(state.sessionId) :
|
|
110
|
+
state && state.row && String(state.row.dataset.sessionId) === String(state.sessionId);
|
|
111
|
+
return !!(state && state.row && state.row.isConnected && sameSession &&
|
|
112
|
+
store.get('connected') && state.ws === ws && ws && ws.readyState === 1 &&
|
|
113
|
+
state.slug === store.get('currentSlug') &&
|
|
114
|
+
(!permissions || permissions.sessionRename !== false));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function finishRename(state, commit) {
|
|
118
|
+
if (!state || state !== renameState()) return;
|
|
119
|
+
store.set({ sessionInlineRename: null });
|
|
120
|
+
var nextTitle = state.input.value.trim();
|
|
121
|
+
restoreTitle(state);
|
|
122
|
+
if (!commit || !nextTitle || nextTitle === state.currentTitle) return;
|
|
123
|
+
if (!currentRenameCanSend(state)) {
|
|
124
|
+
showToast("Rename cancelled because the project, connection, or permission changed.", "warn");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
state.ws.send(JSON.stringify({ type: "rename_session", id: state.sessionId, title: nextTitle }));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
showToast("Session rename could not be sent.", "error");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function stopRowInteraction(event) {
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function beginSessionInlineRename(row, title, control, session, source, inputClass) {
|
|
139
|
+
cancelSessionInlineRename(false);
|
|
140
|
+
if (!row || !row.isConnected || !title || !session) return false;
|
|
141
|
+
|
|
142
|
+
var originalNodes = [];
|
|
143
|
+
while (title.firstChild) originalNodes.push(title.removeChild(title.firstChild));
|
|
144
|
+
var input = document.createElement("input");
|
|
145
|
+
input.type = "text";
|
|
146
|
+
input.className = inputClass || "session-rename-input";
|
|
147
|
+
input.value = session.title || "New Session";
|
|
148
|
+
input.maxLength = 100;
|
|
149
|
+
input.setAttribute("aria-label", "Session title");
|
|
150
|
+
title.appendChild(input);
|
|
151
|
+
row.classList.add("session-renaming");
|
|
152
|
+
var controlDisplay = control ? control.style.display : "";
|
|
153
|
+
if (control) control.style.display = "none";
|
|
154
|
+
|
|
155
|
+
var state = {
|
|
156
|
+
row: row,
|
|
157
|
+
title: title,
|
|
158
|
+
input: input,
|
|
159
|
+
originalNodes: originalNodes,
|
|
160
|
+
sessionId: session.id,
|
|
161
|
+
currentTitle: session.title || "New Session",
|
|
162
|
+
control: control || null,
|
|
163
|
+
controlDisplay: controlDisplay,
|
|
164
|
+
source: source,
|
|
165
|
+
slug: store.get('currentSlug'),
|
|
166
|
+
ws: getWs(),
|
|
167
|
+
composing: false,
|
|
168
|
+
};
|
|
169
|
+
store.set({ sessionInlineRename: state });
|
|
170
|
+
|
|
171
|
+
input.addEventListener("compositionstart", function () { state.composing = true; });
|
|
172
|
+
input.addEventListener("compositionend", function () { state.composing = false; });
|
|
173
|
+
input.addEventListener("keydown", function (event) {
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
if (event.key === "Enter" && !state.composing && !event.isComposing && event.keyCode !== 229) {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
finishRename(state, true);
|
|
178
|
+
} else if (event.key === "Escape") {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
finishRename(state, false);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
input.addEventListener("blur", function () { finishRename(state, true); });
|
|
184
|
+
["click", "dblclick", "pointerdown", "mousedown", "touchstart", "contextmenu", "dragstart"].forEach(function (type) {
|
|
185
|
+
input.addEventListener(type, stopRowInteraction);
|
|
186
|
+
});
|
|
187
|
+
input.focus({ preventScroll: true });
|
|
188
|
+
input.select();
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function startSessionInlineRename(row, session) {
|
|
193
|
+
if (!row) return false;
|
|
194
|
+
var title = row.querySelector(".session-item-title") || row.querySelector(".mobile-session-title");
|
|
195
|
+
return beginSessionInlineRename(row, title, null, session, "row", "session-rename-input");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function startHeaderSessionInlineRename(title, control, session) {
|
|
199
|
+
var row = title && title.closest(".title-bar-content");
|
|
200
|
+
return beginSessionInlineRename(row, title, control, session, "header", "header-rename-input");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
store.subscribe(function (state, previous) {
|
|
204
|
+
if (state.currentSlug !== previous.currentSlug || state.dmMode !== previous.dmMode ||
|
|
205
|
+
(previous.connected && !state.connected)) {
|
|
206
|
+
closeSessionContextMenu(false);
|
|
207
|
+
cancelSessionInlineRename(false);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
@@ -130,7 +130,48 @@ export function resetGithubWork() {
|
|
|
130
130
|
|
|
131
131
|
function closeGithubWorkPopover() {
|
|
132
132
|
var popup = document.getElementById('github-work-popover');
|
|
133
|
-
if (popup) popup.
|
|
133
|
+
if (popup && popup._closeGithubWorkPopover) popup._closeGithubWorkPopover(false);
|
|
134
|
+
else if (popup) popup.remove();
|
|
135
|
+
}
|
|
136
|
+
function mountGithubWorkPopover(popup, button) {
|
|
137
|
+
var closed = false;
|
|
138
|
+
var observer = new MutationObserver(function () {
|
|
139
|
+
if (!button.isConnected || !popup.isConnected) dismiss(false);
|
|
140
|
+
});
|
|
141
|
+
function includes(target) {
|
|
142
|
+
return target === popup || target === button || popup.contains(target) || button.contains(target);
|
|
143
|
+
}
|
|
144
|
+
function outsidePress(event) {
|
|
145
|
+
if (!includes(event.target)) dismiss(false);
|
|
146
|
+
}
|
|
147
|
+
function keydown(event) {
|
|
148
|
+
if (event.key === 'Escape') dismiss(true);
|
|
149
|
+
}
|
|
150
|
+
function dismiss(restoreFocus) {
|
|
151
|
+
if (closed) return;
|
|
152
|
+
closed = true;
|
|
153
|
+
window.removeEventListener('pointerdown', outsidePress, true);
|
|
154
|
+
window.removeEventListener('touchstart', outsidePress, true);
|
|
155
|
+
window.removeEventListener('keydown', keydown, true);
|
|
156
|
+
window.removeEventListener('blur', blur);
|
|
157
|
+
observer.disconnect();
|
|
158
|
+
popup.removeEventListener('toggle', toggled);
|
|
159
|
+
button.setAttribute('aria-expanded', 'false');
|
|
160
|
+
try { popup.hidePopover(); } catch (error) {}
|
|
161
|
+
popup.remove();
|
|
162
|
+
if (restoreFocus && button.isConnected) button.focus({ preventScroll: true });
|
|
163
|
+
}
|
|
164
|
+
function blur() { dismiss(false); }
|
|
165
|
+
function toggled(event) {
|
|
166
|
+
if (event.newState === 'closed') dismiss(false);
|
|
167
|
+
}
|
|
168
|
+
popup._closeGithubWorkPopover = dismiss;
|
|
169
|
+
popup.addEventListener('toggle', toggled);
|
|
170
|
+
window.addEventListener('pointerdown', outsidePress, true);
|
|
171
|
+
window.addEventListener('touchstart', outsidePress, true);
|
|
172
|
+
window.addEventListener('keydown', keydown, true);
|
|
173
|
+
window.addEventListener('blur', blur);
|
|
174
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
134
175
|
}
|
|
135
176
|
function bindGithubWorkPopover() {
|
|
136
177
|
if (store.get('githubWorkPopoverBound')) return;
|
|
@@ -144,7 +185,8 @@ function bindGithubWorkPopover() {
|
|
|
144
185
|
event.stopPropagation();
|
|
145
186
|
var existing = document.getElementById('github-work-popover');
|
|
146
187
|
var same = existing && button.getAttribute('aria-expanded') === 'true';
|
|
147
|
-
if (existing
|
|
188
|
+
if (existing && existing._closeGithubWorkPopover) existing._closeGithubWorkPopover(false);
|
|
189
|
+
else if (existing) existing.remove();
|
|
148
190
|
if (same) return;
|
|
149
191
|
var links;
|
|
150
192
|
try { links = JSON.parse(button.dataset.githubLinks); } catch (error) { return; }
|
|
@@ -157,15 +199,9 @@ function bindGithubWorkPopover() {
|
|
|
157
199
|
popup.setAttribute('aria-label', workTitle);
|
|
158
200
|
popup.innerHTML = '<strong>' + workTitle + '</strong>' + githubWorkMarkup({ githubLinks: links }, false, true);
|
|
159
201
|
document.body.appendChild(popup);
|
|
202
|
+
mountGithubWorkPopover(popup, button);
|
|
160
203
|
button.setAttribute('aria-expanded', 'true');
|
|
161
|
-
popup.
|
|
162
|
-
if (toggle.newState === 'closed') {
|
|
163
|
-
button.setAttribute('aria-expanded', 'false');
|
|
164
|
-
if (document.activeElement === document.body && button.isConnected) button.focus();
|
|
165
|
-
popup.remove();
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
popup.showPopover();
|
|
204
|
+
try { popup.showPopover(); } catch (error) { popup._closeGithubWorkPopover(false); return; }
|
|
169
205
|
var rect = button.getBoundingClientRect();
|
|
170
206
|
var box = popup.getBoundingClientRect();
|
|
171
207
|
popup.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - box.width - 8)) + 'px';
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
renderMobileDriverHierarchy,
|
|
46
46
|
renderMobileOrphanHierarchy
|
|
47
47
|
} from './sidebar-session-hierarchy.js';
|
|
48
|
+
import { cancelSessionInlineRename, closeSessionContextMenu } from './session-context-menu.js';
|
|
48
49
|
|
|
49
50
|
// --- Mobile state ---
|
|
50
51
|
var mobileChatSheetOpen = false;
|
|
@@ -768,6 +769,9 @@ export function refreshMobileChatSheet() {
|
|
|
768
769
|
var sessionListEl = sheet.querySelector(".mobile-chat-session-list");
|
|
769
770
|
if (!sessionListEl) return;
|
|
770
771
|
|
|
772
|
+
closeSessionContextMenu(false);
|
|
773
|
+
cancelSessionInlineRename(false);
|
|
774
|
+
|
|
771
775
|
// Update chips: active state and processing dots
|
|
772
776
|
var chips = sheet.querySelectorAll(".mobile-chat-chip");
|
|
773
777
|
for (var i = 0; i < chips.length; i++) {
|
|
@@ -31,6 +31,12 @@ import {
|
|
|
31
31
|
renderDesktopDriverHierarchy,
|
|
32
32
|
renderDesktopOrphanHierarchy
|
|
33
33
|
} from './sidebar-session-hierarchy.js';
|
|
34
|
+
import {
|
|
35
|
+
cancelSessionInlineRename,
|
|
36
|
+
closeSessionContextMenu,
|
|
37
|
+
mountSessionContextMenu,
|
|
38
|
+
startSessionInlineRename
|
|
39
|
+
} from './session-context-menu.js';
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
// --- Session state ---
|
|
@@ -43,9 +49,6 @@ var countdownTimer = null;
|
|
|
43
49
|
var countdownContainer = null;
|
|
44
50
|
|
|
45
51
|
// --- Session context menu ---
|
|
46
|
-
var sessionCtxMenu = null;
|
|
47
|
-
var sessionCtxMenuAnchor = null;
|
|
48
|
-
var sessionCtxSessionId = null;
|
|
49
52
|
var armedDeleteSessionId = null;
|
|
50
53
|
var armedDeleteTimer = null;
|
|
51
54
|
|
|
@@ -186,7 +189,6 @@ export function startNewSession(vendor, extra) {
|
|
|
186
189
|
export function initSidebarSessions() {
|
|
187
190
|
|
|
188
191
|
document.addEventListener("click", function () {
|
|
189
|
-
closeSessionCtxMenu();
|
|
190
192
|
clearArmedSessionDelete();
|
|
191
193
|
});
|
|
192
194
|
|
|
@@ -228,21 +230,8 @@ export function getExpandedLoopRuns() {
|
|
|
228
230
|
|
|
229
231
|
// --- Context menu ---
|
|
230
232
|
|
|
231
|
-
function closeSessionCtxMenu(restoreFocus) {
|
|
232
|
-
var anchor = sessionCtxMenuAnchor;
|
|
233
|
-
if (anchor) anchor.setAttribute("aria-expanded", "false");
|
|
234
|
-
sessionCtxMenuAnchor = null;
|
|
235
|
-
if (sessionCtxMenu) {
|
|
236
|
-
sessionCtxMenu.remove();
|
|
237
|
-
sessionCtxMenu = null;
|
|
238
|
-
sessionCtxSessionId = null;
|
|
239
|
-
}
|
|
240
|
-
if (restoreFocus && anchor && document.contains(anchor)) anchor.focus();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
233
|
function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
244
|
-
|
|
245
|
-
sessionCtxSessionId = sessionId;
|
|
234
|
+
closeSessionContextMenu(false);
|
|
246
235
|
|
|
247
236
|
var menu = document.createElement("div");
|
|
248
237
|
menu.className = "session-ctx-menu";
|
|
@@ -254,7 +243,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
254
243
|
bookmarkItem.innerHTML = iconHtml(isFavorite ? "star-off" : "star") + " <span>" + (isFavorite ? "Remove from Favorites" : "Add to Favorites") + "</span>";
|
|
255
244
|
bookmarkItem.addEventListener("click", function (e) {
|
|
256
245
|
e.stopPropagation();
|
|
257
|
-
|
|
246
|
+
closeSessionContextMenu(false);
|
|
258
247
|
toggleFavorite(sessionData);
|
|
259
248
|
});
|
|
260
249
|
menu.appendChild(bookmarkItem);
|
|
@@ -264,7 +253,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
264
253
|
moveItem.innerHTML = iconHtml("folder-input") + " <span>Move to folder</span>";
|
|
265
254
|
moveItem.addEventListener("click", function (e) {
|
|
266
255
|
e.stopPropagation();
|
|
267
|
-
|
|
256
|
+
closeSessionContextMenu(false);
|
|
268
257
|
openMoveToFolder(sessionData);
|
|
269
258
|
});
|
|
270
259
|
menu.appendChild(moveItem);
|
|
@@ -275,8 +264,8 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
275
264
|
renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
|
|
276
265
|
renameItem.addEventListener("click", function (e) {
|
|
277
266
|
e.stopPropagation();
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
closeSessionContextMenu(false);
|
|
268
|
+
startSessionInlineRename(anchorBtn, sessionData);
|
|
280
269
|
});
|
|
281
270
|
menu.appendChild(renameItem);
|
|
282
271
|
|
|
@@ -290,7 +279,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
290
279
|
visItem.innerHTML = iconHtml(isPrivate ? "eye" : "eye-off") + " <span>" + (isPrivate ? "Make Shared" : "Make Private") + "</span>";
|
|
291
280
|
visItem.addEventListener("click", function (e) {
|
|
292
281
|
e.stopPropagation();
|
|
293
|
-
|
|
282
|
+
closeSessionContextMenu(false);
|
|
294
283
|
var newVis = isPrivate ? "shared" : "private";
|
|
295
284
|
if (getWs() && store.get('connected')) {
|
|
296
285
|
getWs().send(JSON.stringify({ type: "set_session_visibility", sessionId: sessionId, visibility: newVis }));
|
|
@@ -305,14 +294,14 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
305
294
|
deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
|
|
306
295
|
deleteItem.addEventListener("click", function (e) {
|
|
307
296
|
e.stopPropagation();
|
|
308
|
-
|
|
297
|
+
closeSessionContextMenu(false);
|
|
309
298
|
confirmDeleteSession({ id: sessionId, title: title });
|
|
310
299
|
});
|
|
311
300
|
menu.appendChild(deleteItem);
|
|
312
301
|
}
|
|
313
302
|
|
|
314
303
|
document.body.appendChild(menu);
|
|
315
|
-
|
|
304
|
+
mountSessionContextMenu(menu, anchorBtn, sessionId);
|
|
316
305
|
refreshIcons();
|
|
317
306
|
|
|
318
307
|
// Position: fixed relative to the anchor button
|
|
@@ -335,7 +324,7 @@ export function openSessionContextMenu(anchor, session) {
|
|
|
335
324
|
}
|
|
336
325
|
|
|
337
326
|
function showSplitGroupCtxMenu(anchor, group) {
|
|
338
|
-
|
|
327
|
+
closeSessionContextMenu(false);
|
|
339
328
|
var menu = document.createElement("div");
|
|
340
329
|
menu.className = "session-ctx-menu";
|
|
341
330
|
|
|
@@ -344,7 +333,7 @@ function showSplitGroupCtxMenu(anchor, group) {
|
|
|
344
333
|
renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
|
|
345
334
|
renameItem.addEventListener("click", function (e) {
|
|
346
335
|
e.stopPropagation();
|
|
347
|
-
|
|
336
|
+
closeSessionContextMenu(false);
|
|
348
337
|
startSplitGroupInlineRename(group);
|
|
349
338
|
});
|
|
350
339
|
menu.appendChild(renameItem);
|
|
@@ -354,13 +343,13 @@ function showSplitGroupCtxMenu(anchor, group) {
|
|
|
354
343
|
separateItem.innerHTML = iconHtml("columns-2") + " <span>Separate</span>";
|
|
355
344
|
separateItem.addEventListener("click", function (e) {
|
|
356
345
|
e.stopPropagation();
|
|
357
|
-
|
|
346
|
+
closeSessionContextMenu(false);
|
|
358
347
|
separateGroup(group);
|
|
359
348
|
});
|
|
360
349
|
menu.appendChild(separateItem);
|
|
361
350
|
|
|
362
351
|
document.body.appendChild(menu);
|
|
363
|
-
|
|
352
|
+
mountSessionContextMenu(menu, anchor, null);
|
|
364
353
|
refreshIcons();
|
|
365
354
|
requestAnimationFrame(function () {
|
|
366
355
|
var rect = anchor.getBoundingClientRect();
|
|
@@ -374,7 +363,7 @@ function showSplitGroupCtxMenu(anchor, group) {
|
|
|
374
363
|
}
|
|
375
364
|
|
|
376
365
|
function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
377
|
-
|
|
366
|
+
closeSessionContextMenu(false);
|
|
378
367
|
|
|
379
368
|
var menu = document.createElement("div");
|
|
380
369
|
menu.className = "session-ctx-menu";
|
|
@@ -384,7 +373,7 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
|
384
373
|
renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
|
|
385
374
|
renameItem.addEventListener("click", function (e) {
|
|
386
375
|
e.stopPropagation();
|
|
387
|
-
|
|
376
|
+
closeSessionContextMenu(false);
|
|
388
377
|
startLoopInlineRename(loopId, loopName);
|
|
389
378
|
});
|
|
390
379
|
menu.appendChild(renameItem);
|
|
@@ -395,7 +384,7 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
|
395
384
|
deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
|
|
396
385
|
deleteItem.addEventListener("click", function (e) {
|
|
397
386
|
e.stopPropagation();
|
|
398
|
-
|
|
387
|
+
closeSessionContextMenu(false);
|
|
399
388
|
var msg = 'Delete "' + (loopName || "Loop") + '"';
|
|
400
389
|
if (childCount > 1) msg += " and its " + childCount + " sessions";
|
|
401
390
|
msg += "? This cannot be undone.";
|
|
@@ -409,7 +398,7 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
|
409
398
|
}
|
|
410
399
|
|
|
411
400
|
document.body.appendChild(menu);
|
|
412
|
-
|
|
401
|
+
mountSessionContextMenu(menu, anchorBtn, null);
|
|
413
402
|
refreshIcons();
|
|
414
403
|
|
|
415
404
|
requestAnimationFrame(function () {
|
|
@@ -427,51 +416,12 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
|
|
|
427
416
|
|
|
428
417
|
// --- Inline rename ---
|
|
429
418
|
|
|
430
|
-
// A session can be on screen twice (its folder and Favorites); work on the copy
|
|
431
|
-
// the user can actually see.
|
|
432
419
|
function visibleSessionRow(sessionId) {
|
|
433
420
|
var rows = getSessionListEl().querySelectorAll('.session-item[data-session-id="' + sessionId + '"]');
|
|
434
421
|
for (var i = 0; i < rows.length; i++) if (rows[i].getClientRects().length) return rows[i];
|
|
435
422
|
return rows[0] || null;
|
|
436
423
|
}
|
|
437
424
|
|
|
438
|
-
function startInlineRename(sessionId, currentTitle) {
|
|
439
|
-
var el = visibleSessionRow(sessionId);
|
|
440
|
-
if (!el) return;
|
|
441
|
-
var textSpan = el.querySelector(".session-item-text");
|
|
442
|
-
if (!textSpan) return;
|
|
443
|
-
|
|
444
|
-
var input = document.createElement("input");
|
|
445
|
-
input.type = "text";
|
|
446
|
-
input.className = "session-rename-input";
|
|
447
|
-
input.value = currentTitle || "New Session";
|
|
448
|
-
|
|
449
|
-
var originalHtml = textSpan.innerHTML;
|
|
450
|
-
textSpan.innerHTML = "";
|
|
451
|
-
textSpan.appendChild(input);
|
|
452
|
-
input.focus();
|
|
453
|
-
input.select();
|
|
454
|
-
|
|
455
|
-
function commitRename() {
|
|
456
|
-
var newTitle = input.value.trim();
|
|
457
|
-
if (newTitle && newTitle !== currentTitle && getWs() && store.get('connected')) {
|
|
458
|
-
getWs().send(JSON.stringify({ type: "rename_session", id: sessionId, title: newTitle }));
|
|
459
|
-
}
|
|
460
|
-
// Restore text (server will send updated session_list)
|
|
461
|
-
textSpan.innerHTML = originalHtml;
|
|
462
|
-
if (newTitle && newTitle !== currentTitle) {
|
|
463
|
-
textSpan.textContent = newTitle;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
input.addEventListener("keydown", function (e) {
|
|
468
|
-
if (e.key === "Enter") { e.preventDefault(); commitRename(); }
|
|
469
|
-
if (e.key === "Escape") { e.preventDefault(); textSpan.innerHTML = originalHtml; }
|
|
470
|
-
});
|
|
471
|
-
input.addEventListener("blur", commitRename);
|
|
472
|
-
input.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
473
|
-
}
|
|
474
|
-
|
|
475
425
|
function startSplitGroupInlineRename(group) {
|
|
476
426
|
var el = getSessionListEl().querySelector('.split-group-item[data-split-group-id="' + group.id + '"]');
|
|
477
427
|
if (!el) return;
|
|
@@ -1023,6 +973,9 @@ export function renderSessionList(sessions) {
|
|
|
1023
973
|
cachedSessions = sessions;
|
|
1024
974
|
}
|
|
1025
975
|
|
|
976
|
+
closeSessionContextMenu(false);
|
|
977
|
+
cancelSessionInlineRename(false);
|
|
978
|
+
|
|
1026
979
|
// Remember the inline folder draft before either surface is rebuilt: the mobile
|
|
1027
980
|
// refresh below builds a fresh row whose queued focus must not be re-captured.
|
|
1028
981
|
captureFolderInputs();
|
package/package.json
CHANGED