clay-server 5.2.1-beta.1 → 5.3.0-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/clay-control-presentation-binding.js +5 -3
- package/lib/clay-product-control-tools.js +3 -3
- package/lib/clay-product-control.js +4 -2
- package/lib/clay-screen-controls.js +18 -3
- package/lib/clay-split-view-awareness.js +35 -0
- package/lib/clay-split-view-controls.js +122 -0
- package/lib/clay-split-view-input.js +20 -0
- package/lib/clay-split-view-render-proof.js +22 -0
- package/lib/pane-asset-manifest.js +63 -0
- package/lib/project-connection.js +19 -10
- package/lib/project-session-actions.js +1 -0
- package/lib/project-session-delete.js +2 -2
- package/lib/project-session-folders.js +1 -0
- package/lib/project-sessions.js +6 -1
- package/lib/project-split-view-document.js +33 -0
- package/lib/project-split-view-names.js +67 -0
- package/lib/project-split-view-organization.js +23 -0
- package/lib/project-split-views.js +479 -0
- package/lib/project-worker-conversation.js +158 -0
- package/lib/project-worker-creation-proposal.js +3 -4
- package/lib/project-worker-proposal.js +1 -1
- package/lib/project-worker-removal.js +101 -0
- package/lib/project.js +28 -1
- package/lib/public/app.js +32 -13
- package/lib/public/css/base.css +9 -0
- package/lib/public/css/filebrowser.css +0 -1
- package/lib/public/css/pane-header-controls.css +100 -0
- package/lib/public/css/pane.css +24 -5
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/split-view-layout.css +460 -0
- package/lib/public/css/split-view-worker-activity.css +97 -0
- package/lib/public/css/sticky-notes.css +13 -0
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +4 -1
- package/lib/public/modules/app-messages.js +25 -8
- package/lib/public/modules/chat-render-runtime.js +45 -0
- package/lib/public/modules/clay-control-copy.js +3 -0
- package/lib/public/modules/clay-navigation-render.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +15 -5
- package/lib/public/modules/clay-split-view-screen.js +74 -0
- package/lib/public/modules/clay-tool-activity.js +5 -0
- package/lib/public/modules/command-palette.js +30 -28
- package/lib/public/modules/contextual-clay-session.js +26 -2
- package/lib/public/modules/contextual-clay.js +5 -2
- package/lib/public/modules/contextual-mate-sessions.js +2 -0
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/mate-knowledge-workbench.js +2 -7
- package/lib/public/modules/mate-sidebar.js +1 -1
- package/lib/public/modules/palette-clay-conversation.js +87 -0
- package/lib/public/modules/palette-render-helpers.js +25 -0
- package/lib/public/modules/pane-activation.js +46 -7
- package/lib/public/modules/pane-binding.js +12 -2
- package/lib/public/modules/pane-bridge.js +12 -3
- package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
- package/lib/public/modules/pane-feature-assets.js +35 -0
- package/lib/public/modules/pane-search-receipt.js +12 -0
- package/lib/public/modules/permission-control.js +6 -6
- package/lib/public/modules/popup-chat-rendering.js +244 -0
- package/lib/public/modules/project-activation.js +2 -2
- package/lib/public/modules/qrcode.js +12 -1
- package/lib/public/modules/right-workbench.js +68 -0
- package/lib/public/modules/search-hints.js +79 -0
- package/lib/public/modules/session-context-menu.js +35 -2
- package/lib/public/modules/session-folder-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -1
- package/lib/public/modules/session-folder-layout.js +7 -6
- package/lib/public/modules/session-search-pane-state.js +30 -0
- package/lib/public/modules/session-search.js +53 -13
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
- package/lib/public/modules/sidebar-sessions.js +57 -24
- package/lib/public/modules/sidebar.js +20 -3
- package/lib/public/modules/split-group-helpers.js +1 -1
- package/lib/public/modules/split-group-restore.js +8 -11
- package/lib/public/modules/split-pair-ui.js +18 -0
- package/lib/public/modules/split-pane-header-state.js +45 -0
- package/lib/public/modules/split-pane-reconciler.js +23 -4
- package/lib/public/modules/split-pane-renderer.js +330 -23
- package/lib/public/modules/split-pane-retention.js +157 -0
- package/lib/public/modules/split-session-boundary.js +21 -0
- package/lib/public/modules/split-view-create-action.js +15 -0
- package/lib/public/modules/split-view-create-menu.js +83 -0
- package/lib/public/modules/split-view-create-request.js +23 -0
- package/lib/public/modules/split-view-layout-client.js +198 -0
- package/lib/public/modules/split-view-layout-icons.js +37 -0
- package/lib/public/modules/split-view-layout-scope.js +38 -0
- package/lib/public/modules/split-view-organization.js +73 -0
- package/lib/public/modules/split-view-presentation-close.js +35 -0
- package/lib/public/modules/split-view-presentation-drag.js +254 -0
- package/lib/public/modules/split-view-presentation-drop.js +57 -0
- package/lib/public/modules/split-view-presentation-lookup.js +44 -0
- package/lib/public/modules/split-view-presentation-renderer.js +494 -0
- package/lib/public/modules/split-view-presentations.js +495 -0
- package/lib/public/modules/split-view-shell-controls.js +28 -0
- package/lib/public/modules/split-view-sidebar-menu.js +42 -0
- package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
- package/lib/public/modules/split-view-singleton.js +21 -0
- package/lib/public/modules/split-view-worker-activity.js +94 -0
- package/lib/public/modules/split-view-worker-controls.js +30 -0
- package/lib/public/modules/split-view-worker-removal.js +137 -0
- package/lib/public/modules/split-view-worker-scroll.js +57 -0
- package/lib/public/modules/split-view-worker-transcript.js +46 -0
- package/lib/public/modules/split-view-workers.js +352 -0
- package/lib/public/modules/split-view.js +61 -22
- package/lib/public/modules/sticky-notes.js +9 -2
- package/lib/public/modules/tools.js +25 -5
- package/lib/public/modules/workbench-layout-preference.js +76 -0
- package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
- package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
- package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
- package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
- package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
- package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
- package/lib/public/pane-assets/manifest.json +551 -0
- package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/sdk-bridge.js +16 -3
- package/lib/server-owner-ui.js +6 -2
- package/lib/server-owner-view-observation.js +34 -0
- package/lib/server-palette.js +1 -0
- package/lib/server-pane-bootstrap.js +24 -0
- package/lib/server-settings.js +2 -0
- package/lib/server-static-assets.js +52 -0
- package/lib/server-workbench-layout-settings.js +56 -0
- package/lib/server.js +6 -45
- package/lib/session-activity-state.js +28 -0
- package/lib/session-list-delivery.js +17 -0
- package/lib/session-pair-turn-control.js +1 -6
- package/lib/session-search.js +3 -0
- package/lib/session-split-group-anchors.js +2 -2
- package/lib/session-split-group-roles.js +5 -8
- package/lib/session-split-group-v2-store.js +0 -3
- package/lib/sessions.js +16 -6
- package/lib/split-view-layout.js +132 -0
- package/lib/users-workbench-layout-preferences.js +33 -0
- package/lib/users.js +3 -0
- package/lib/workspace-query-service.js +1 -5
- package/lib/ws-schema.js +15 -0
- package/lib/yoke/adapters/codex.js +1 -0
- package/lib/yoke/codex-app-server.js +3 -1
- package/lib/yoke/codex-idle-recovery.js +73 -20
- package/lib/yoke/codex-recovery-admission.js +65 -4
- package/package.json +6 -3
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Popup transcript presentation hooks. Tool identity, lifecycle and evidence
|
|
2
|
+
// stay owned by tools.js; this module only controls the compact activity view.
|
|
3
|
+
import { iconHtml } from './icons.js';
|
|
4
|
+
import { syncToolGroupDisclosure } from './clay-tool-activity.js';
|
|
5
|
+
|
|
6
|
+
// Mounted transcript nodes own completed records without retaining removed history.
|
|
7
|
+
var mountedToolGroups = new WeakMap();
|
|
8
|
+
export function rememberMountedToolGroup(group) { mountedToolGroups.set(group.el, group); }
|
|
9
|
+
export function mountedToolGroup(element) { return mountedToolGroups.get(element) || null; }
|
|
10
|
+
export function transcriptToolGroups(root) {
|
|
11
|
+
return root ? Array.from(root.querySelectorAll('.tool-group')).map(mountedToolGroup).filter(function (group) { return !!group; }) : [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setPopupChatRendering(enabled) {
|
|
15
|
+
if (typeof document === 'undefined' || !document.body) return false;
|
|
16
|
+
document.body.classList.toggle('popup-chat-rendering', enabled === true);
|
|
17
|
+
if (!enabled) {
|
|
18
|
+
var rows = document.querySelectorAll('.popup-chat-activity-row[hidden]');
|
|
19
|
+
for (var i = 0; i < rows.length; i++) rows[i].hidden = false;
|
|
20
|
+
var groups = document.querySelectorAll('.popup-chat-activity');
|
|
21
|
+
for (var j = 0; j < groups.length; j++) {
|
|
22
|
+
var items = groups[j].querySelector('.tool-group-items');
|
|
23
|
+
if (items) {
|
|
24
|
+
var expanded = !groups[j].classList.contains('collapsed');
|
|
25
|
+
items.inert = !expanded;
|
|
26
|
+
items.setAttribute('aria-hidden', String(!expanded));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return enabled === true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function shouldEnablePopupChatRendering(body, state, frame, identity) {
|
|
34
|
+
return !!(body && body.classList && body.classList.contains('contextual-chat-pane') && state && state.paneMode && frame &&
|
|
35
|
+
identity && identity.kind === 'mate' && identity.mateId && identity.mateId === frame.dataset.mateId &&
|
|
36
|
+
state.myUserId && state.sessionOriginId && state.paneSessionId && state.activeSessionId === state.paneSessionId &&
|
|
37
|
+
(!frame.dataset.expectedSessionOrigin || frame.dataset.expectedSessionOrigin === state.sessionOriginId));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function popupChatContextIsReady(body, state, frame, identity, surface) {
|
|
41
|
+
if (!body || !body.classList || !body.classList.contains('contextual-chat-pane')) return false;
|
|
42
|
+
if (!state || !state.paneMode || !state.connected || !state.myUserId || !state.contextualSessionReady) return false;
|
|
43
|
+
if (!state.sessionOriginId || state.activeSessionId !== state.paneSessionId) return false;
|
|
44
|
+
if (!frame || !frame.dataset || !frame.dataset.mateId || frame.dataset.ownerId !== state.myUserId) return false;
|
|
45
|
+
if (frame.hidden || frame.inert || !surface || surface.dataset.mode !== 'popover' || !surface.contains(frame)) return false;
|
|
46
|
+
if (String(frame.dataset.sessionId || '') !== String(state.paneSessionId || '')) return false;
|
|
47
|
+
if (frame.dataset.expectedSessionOrigin && frame.dataset.expectedSessionOrigin !== state.sessionOriginId) return false;
|
|
48
|
+
if (state.activeProjectMateId !== frame.dataset.mateId) return false;
|
|
49
|
+
if (!identity || identity.kind !== 'mate' || identity.mateId !== frame.dataset.mateId) return false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function popupChatRenderingEnabled(identity) {
|
|
54
|
+
return !!(identity && identity.kind === 'mate' && typeof document !== 'undefined' &&
|
|
55
|
+
document.body && document.body.classList.contains('popup-chat-rendering'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function popupChatRenderingActive() {
|
|
59
|
+
return typeof document !== 'undefined' && document.body && document.body.classList.contains('popup-chat-rendering');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function reconcilePopupToolGroups(groups, active, activate, deactivate) {
|
|
63
|
+
for (var i = 0; i < groups.length; i++) {
|
|
64
|
+
var group = groups[i];
|
|
65
|
+
if (!group) continue;
|
|
66
|
+
if (active) {
|
|
67
|
+
group.popupChat = true;
|
|
68
|
+
activate(group);
|
|
69
|
+
} else if (group.popupChat) {
|
|
70
|
+
deactivate(group);
|
|
71
|
+
group.popupChat = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function decorateTool(tool) {
|
|
77
|
+
if (!tool || !tool.el || tool.popupOriginalName !== undefined) return;
|
|
78
|
+
var name = tool.el.querySelector('.tool-name');
|
|
79
|
+
tool.popupOriginalName = name.textContent;
|
|
80
|
+
tool.popupOriginalHidden = tool.el.hidden;
|
|
81
|
+
tool.popupOriginalInert = tool.el.inert;
|
|
82
|
+
tool.el.classList.add('popup-chat-activity-row');
|
|
83
|
+
// Native descriptions own reference nodes and their handlers throughout mode changes.
|
|
84
|
+
if (!tool.clayActivity) {
|
|
85
|
+
tool.popupPresentedName = popupToolActionLabel(tool.name);
|
|
86
|
+
name.textContent = tool.popupPresentedName;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decorateGroup(group) {
|
|
91
|
+
if (!group || !group.el || group.popupOriginalState) return;
|
|
92
|
+
var header = group.el.querySelector('.tool-group-header');
|
|
93
|
+
var label = group.el.querySelector('.tool-group-label');
|
|
94
|
+
group.popupOriginalState = {
|
|
95
|
+
label: label.textContent,
|
|
96
|
+
collapsed: group.el.classList.contains('collapsed'),
|
|
97
|
+
userToggled: !!group.userToggled,
|
|
98
|
+
groupDisplay: group.el.style.display,
|
|
99
|
+
headerDisplay: header.style.display,
|
|
100
|
+
headerAriaExpanded: header.getAttribute('aria-expanded'),
|
|
101
|
+
headerAriaLabel: header.getAttribute('aria-label'),
|
|
102
|
+
headerTitle: header.title
|
|
103
|
+
};
|
|
104
|
+
group.el.classList.add('popup-chat-activity');
|
|
105
|
+
var icon = document.createElement('span');
|
|
106
|
+
icon.className = 'popup-chat-activity-icon';
|
|
107
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
108
|
+
icon.innerHTML = iconHtml('activity');
|
|
109
|
+
var heading = document.createElement('span');
|
|
110
|
+
heading.className = 'popup-chat-activity-heading';
|
|
111
|
+
heading.textContent = 'Activity';
|
|
112
|
+
label.textContent = '';
|
|
113
|
+
label.appendChild(heading);
|
|
114
|
+
var count = document.createElement('span');
|
|
115
|
+
count.className = 'popup-chat-activity-count';
|
|
116
|
+
count.setAttribute('aria-label', '0 recorded events');
|
|
117
|
+
header.insertBefore(icon, label);
|
|
118
|
+
header.insertBefore(count, header.querySelector('.tool-group-status-icon'));
|
|
119
|
+
for (var i = 0; i < group.toolIds.length; i++) decorateTool(group.toolRecords[group.toolIds[i]]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function restoreGroup(group) {
|
|
123
|
+
if (!group || !group.el) return;
|
|
124
|
+
for (var i = 0; i < group.toolIds.length; i++) {
|
|
125
|
+
var tool = group.toolRecords[group.toolIds[i]];
|
|
126
|
+
if (!tool || !tool.el || tool.popupOriginalName === undefined) continue;
|
|
127
|
+
var name = tool.el.querySelector('.tool-name');
|
|
128
|
+
if (!tool.clayActivity && name.textContent === tool.popupPresentedName) name.textContent = tool.popupOriginalName;
|
|
129
|
+
tool.el.hidden = tool.popupOriginalHidden;
|
|
130
|
+
tool.el.inert = tool.popupOriginalInert;
|
|
131
|
+
tool.el.classList.remove('popup-chat-activity-row');
|
|
132
|
+
delete tool.popupOriginalName;
|
|
133
|
+
delete tool.popupPresentedName;
|
|
134
|
+
delete tool.popupOriginalHidden;
|
|
135
|
+
delete tool.popupOriginalInert;
|
|
136
|
+
}
|
|
137
|
+
var original = group.popupOriginalState;
|
|
138
|
+
group.popupOriginalState = null;
|
|
139
|
+
if (!original) return;
|
|
140
|
+
var label = group.el.querySelector('.tool-group-label');
|
|
141
|
+
var header = group.el.querySelector('.tool-group-header');
|
|
142
|
+
label.textContent = original.label;
|
|
143
|
+
group.el.style.display = original.groupDisplay;
|
|
144
|
+
header.style.display = original.headerDisplay;
|
|
145
|
+
group.userToggled = original.userToggled;
|
|
146
|
+
group.el.classList.toggle('collapsed', original.collapsed);
|
|
147
|
+
if (original.headerAriaLabel === null) header.removeAttribute('aria-label');
|
|
148
|
+
else header.setAttribute('aria-label', original.headerAriaLabel);
|
|
149
|
+
header.title = original.headerTitle;
|
|
150
|
+
var icon = header.querySelector('.popup-chat-activity-icon');
|
|
151
|
+
var count = header.querySelector('.popup-chat-activity-count');
|
|
152
|
+
if (icon) icon.remove();
|
|
153
|
+
if (count) count.remove();
|
|
154
|
+
group.el.classList.remove('popup-chat-activity');
|
|
155
|
+
syncToolGroupDisclosure(group.el);
|
|
156
|
+
if (original.headerAriaExpanded !== null) header.setAttribute('aria-expanded', original.headerAriaExpanded);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function syncPopupChatToolGroups(enabled, groups, identity) {
|
|
160
|
+
var active = !!(enabled && popupChatRenderingActive() && identity && identity.kind === 'mate');
|
|
161
|
+
reconcilePopupToolGroups(groups, active, function (group) {
|
|
162
|
+
decorateGroup(group);
|
|
163
|
+
for (var i = 0; i < group.toolIds.length; i++) decorateTool(group.toolRecords[group.toolIds[i]]);
|
|
164
|
+
group.el.style.display = '';
|
|
165
|
+
group.el.querySelector('.tool-group-header').style.display = '';
|
|
166
|
+
if (!group.userToggled) group.el.classList.add('collapsed');
|
|
167
|
+
syncToolGroupDisclosure(group.el);
|
|
168
|
+
syncPopupActivityGroup(group);
|
|
169
|
+
}, restoreGroup);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasIndependentControl(row) {
|
|
173
|
+
if (!row || !row.querySelectorAll) return false;
|
|
174
|
+
var controls = row.querySelectorAll('button, a[href], input, select, textarea, [role="button"], [role="menuitem"], [role="option"]');
|
|
175
|
+
var disclosure = row.querySelector('.tool-header');
|
|
176
|
+
if (row.querySelector('.tool-desc-link')) return true;
|
|
177
|
+
for (var i = 0; i < controls.length; i++) {
|
|
178
|
+
if (controls[i] !== disclosure) return true;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function syncPopupActivityGroup(group) {
|
|
184
|
+
if (!group || !group.popupChat || !group.el) return false;
|
|
185
|
+
var header = group.el.querySelector('.tool-group-header');
|
|
186
|
+
var items = group.el.querySelector('.tool-group-items');
|
|
187
|
+
var count = group.el.querySelector('.popup-chat-activity-count');
|
|
188
|
+
if (!header || !items) return false;
|
|
189
|
+
|
|
190
|
+
var expanded = !group.el.classList.contains('collapsed');
|
|
191
|
+
var rows = items.children;
|
|
192
|
+
var firstVisible = Math.max(0, rows.length - 2);
|
|
193
|
+
var pinned = 0;
|
|
194
|
+
for (var i = 0; i < rows.length; i++) {
|
|
195
|
+
var row = rows[i];
|
|
196
|
+
var keepVisible = hasIndependentControl(row);
|
|
197
|
+
if (keepVisible) pinned++;
|
|
198
|
+
row.classList.add('popup-chat-activity-row');
|
|
199
|
+
row.hidden = !expanded && i < firstVisible && !keepVisible;
|
|
200
|
+
row.inert = row.hidden;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// The native group disclosure remains the one control for revealing older
|
|
204
|
+
// activity. In popup mode the visible recent rows stay reachable even while
|
|
205
|
+
// the rest of the group is collapsed.
|
|
206
|
+
items.inert = false;
|
|
207
|
+
items.setAttribute('aria-hidden', 'false');
|
|
208
|
+
header.setAttribute('aria-expanded', String(expanded));
|
|
209
|
+
header.setAttribute('aria-label', 'Activity, ' + group.toolCount + ' recorded events. ' + (expanded ? 'Hide older activity' : 'Show older activity'));
|
|
210
|
+
header.title = expanded ? 'Hide older activity' : 'Show older activity';
|
|
211
|
+
if (count) {
|
|
212
|
+
count.textContent = String(group.toolCount);
|
|
213
|
+
count.setAttribute('aria-label', group.toolCount + ' recorded events');
|
|
214
|
+
count.dataset.pinnedCount = String(pinned);
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function popupToolActionLabel(name) {
|
|
220
|
+
var labels = {
|
|
221
|
+
Read: 'Read', Edit: 'Edit', Write: 'Write', Bash: 'Command',
|
|
222
|
+
Grep: 'Search files', Glob: 'Find files', WebSearch: 'Web search',
|
|
223
|
+
WebFetch: 'Fetch page', Task: 'Task'
|
|
224
|
+
};
|
|
225
|
+
return labels[name] || name || 'Tool activity';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function popupToolDescription(name, input) {
|
|
229
|
+
input = input && typeof input === 'object' ? input : {};
|
|
230
|
+
if (name === 'Read' || name === 'Edit' || name === 'Write') {
|
|
231
|
+
var filePath = typeof input.file_path === 'string' ? input.file_path : '';
|
|
232
|
+
var parts = filePath.split('/');
|
|
233
|
+
return parts.length > 3 ? '…/' + parts.slice(-3).join('/') : filePath;
|
|
234
|
+
}
|
|
235
|
+
if (name === 'Bash') return typeof input.description === 'string' ? input.description.slice(0, 100) : '';
|
|
236
|
+
if (name === 'Grep') return typeof input.pattern === 'string' ? input.pattern.slice(0, 100) : '';
|
|
237
|
+
if (name === 'Glob') return typeof input.pattern === 'string' ? input.pattern.slice(0, 100) : '';
|
|
238
|
+
if (name === 'WebSearch') return typeof input.query === 'string' ? input.query.slice(0, 100) : '';
|
|
239
|
+
if (name === 'WebFetch') {
|
|
240
|
+
try { return new URL(input.url).hostname; } catch (error) { return ''; }
|
|
241
|
+
}
|
|
242
|
+
if (name === 'Task') return typeof input.description === 'string' ? input.description.slice(0, 100) : '';
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
@@ -70,7 +70,7 @@ export function isProjectSessionReady(state, socket) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function splitPanesMatchProject(split, slug) {
|
|
73
|
-
if (!split || !Array.isArray(split.panes) || split.panes.length < 2 ||
|
|
73
|
+
if (!split || !Array.isArray(split.panes) || split.panes.length < 2 || !slug) return false;
|
|
74
74
|
for (var i = 0; i < split.panes.length; i++) if (split.panes[i].slug !== slug) return false;
|
|
75
75
|
return true;
|
|
76
76
|
}
|
|
@@ -78,7 +78,7 @@ export function splitPanesMatchProject(split, slug) {
|
|
|
78
78
|
export function hasCurrentSplitGroup(split, groups) {
|
|
79
79
|
if (!split || !split.groupId) return true;
|
|
80
80
|
var members = split.panes && split.panes.map(function (pane) { return pane.sessionId; });
|
|
81
|
-
if (!members || members.length < 2
|
|
81
|
+
if (!members || members.length < 2) return false;
|
|
82
82
|
var available = Array.isArray(groups) ? groups : [];
|
|
83
83
|
for (var i = 0; i < available.length; i++) {
|
|
84
84
|
var group = available[i];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { showToast, copyToClipboard } from './utils.js';
|
|
2
4
|
|
|
3
5
|
var activeShareUrl = null;
|
|
4
6
|
var activeShareTitle = null;
|
|
@@ -32,6 +34,15 @@ export function triggerShare(options) {
|
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
if (typeof qrcode === 'undefined') {
|
|
38
|
+
var owner = store.get('myUserId'), slug = store.get('currentSlug'), generation = store.get('projectSplitViewSocketGeneration');
|
|
39
|
+
ensurePaneFeatureAssets('qr').then(function () {
|
|
40
|
+
if (owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) triggerShare(options);
|
|
41
|
+
}).catch(function (error) {
|
|
42
|
+
if (owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) showToast(error.message, 'error');
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
35
46
|
// Show QR overlay
|
|
36
47
|
var qrOverlay = document.getElementById("qr-overlay");
|
|
37
48
|
var qrCanvas = document.getElementById("qr-canvas");
|
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
// Single arbitration point for the mutually exclusive right-side workbench.
|
|
2
2
|
|
|
3
3
|
import { store } from './store.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { showToast } from './utils.js';
|
|
4
6
|
|
|
5
7
|
var closers = {};
|
|
8
|
+
var panelIds = { terminal: 'terminal-container', browser: 'shared-browser-panel', 'file-viewer': 'file-viewer',
|
|
9
|
+
'project-logs': 'project-logs-panel', you: 'you-panel', issues: 'issues-panel', linear: 'linear-panel',
|
|
10
|
+
'notes-browser': 'notes-browser', 'scheduled-tasks': 'scheduled-tasks-panel', debates: 'debates-workbench',
|
|
11
|
+
'mcp-skills': 'mcp-skills-workbench', 'mate-knowledge': 'mate-knowledge-workbench', wireframe: 'wireframe-panel' };
|
|
12
|
+
|
|
13
|
+
// Pane startup does not need account settings for a closed workbench.
|
|
14
|
+
function ensureLayoutPreference() {
|
|
15
|
+
var account = store.get('myUserId'), project = store.get('currentSlug');
|
|
16
|
+
return import('./workbench-layout-preference.js').then(function (api) {
|
|
17
|
+
if (account !== store.get('myUserId') || project !== store.get('currentSlug')) return false;
|
|
18
|
+
return api.ensureWorkbenchLayoutPreference();
|
|
19
|
+
}).catch(function () {
|
|
20
|
+
if (account !== store.get('myUserId') || project !== store.get('currentSlug')) return false;
|
|
21
|
+
store.set({ workbenchLayoutPreference: Object.assign({}, store.get('workbenchLayoutPreference') || {}, {
|
|
22
|
+
loading: false, saving: false, error: 'Workbench layout could not be loaded. Try again.'
|
|
23
|
+
}) });
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderLayoutControls() {
|
|
29
|
+
if (typeof document === 'undefined') return;
|
|
30
|
+
var main = document.getElementById('main-panels');
|
|
31
|
+
if (!main) return;
|
|
32
|
+
var state = store.get('workbenchLayoutPreference') || {}, docked = state.mode === 'docked';
|
|
33
|
+
main.dataset.workbenchMode = docked ? 'docked' : 'overlay';
|
|
34
|
+
var panel = document.getElementById(panelIds[store.get('rightWorkbenchOwner')]);
|
|
35
|
+
if (!panel) return;
|
|
36
|
+
var header = panel.querySelector(':scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
|
|
37
|
+
if (!header) return;
|
|
38
|
+
var button = panel.querySelector('.workbench-layout-toggle');
|
|
39
|
+
if (!button) {
|
|
40
|
+
var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, :scope.mcp-skills-header > div') || header;
|
|
41
|
+
button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
|
|
42
|
+
button.addEventListener('click', function () {
|
|
43
|
+
var current = store.get('workbenchLayoutPreference') || {};
|
|
44
|
+
if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
|
|
45
|
+
var owner = store.get('myUserId'), project = store.get('currentSlug');
|
|
46
|
+
function currentTarget() { return button.isConnected && owner === store.get('myUserId') && project === store.get('currentSlug'); }
|
|
47
|
+
import('./workbench-layout-preference.js').then(function (api) {
|
|
48
|
+
var fresh = store.get('workbenchLayoutPreference') || {};
|
|
49
|
+
if (!currentTarget() || fresh.mode !== current.mode || fresh.revision !== current.revision || fresh.loading || fresh.saving || !fresh.ready) return false;
|
|
50
|
+
return api.saveWorkbenchLayoutMode(current.mode === 'docked' ? 'overlay' : 'docked');
|
|
51
|
+
}).then(function (saved) {
|
|
52
|
+
if (saved === false && currentTarget()) showToast((store.get('workbenchLayoutPreference') || {}).error || 'Workbench layout could not be saved. Try again.', 'error');
|
|
53
|
+
}).catch(function (error) {
|
|
54
|
+
if (currentTarget()) showToast(error.message || 'Workbench layout could not be saved. Try again.', 'error');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
|
|
58
|
+
actions.insertBefore(button, anchor || null);
|
|
59
|
+
}
|
|
60
|
+
if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
|
|
61
|
+
button.dataset.mode = docked ? 'docked' : 'overlay';
|
|
62
|
+
button.innerHTML = iconHtml(docked ? 'layers-2' : 'panel-right');
|
|
63
|
+
refreshIcons();
|
|
64
|
+
}
|
|
65
|
+
button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
|
|
66
|
+
button.title = state.error || button.getAttribute('aria-label');
|
|
67
|
+
button.setAttribute('aria-pressed', docked ? 'true' : 'false');
|
|
68
|
+
button.disabled = !!state.loading || !!state.saving;
|
|
69
|
+
button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
|
|
70
|
+
}
|
|
6
71
|
|
|
7
72
|
export function registerRightWorkbench(name, close) {
|
|
8
73
|
if (!name || typeof close !== "function") return;
|
|
@@ -10,6 +75,7 @@ export function registerRightWorkbench(name, close) {
|
|
|
10
75
|
}
|
|
11
76
|
|
|
12
77
|
export function claimRightWorkbench(name) {
|
|
78
|
+
if (typeof document !== 'undefined') ensureLayoutPreference();
|
|
13
79
|
var previousRevision = store.get("rightWorkbenchRevision") || 0;
|
|
14
80
|
var nextRevision = previousRevision + 1;
|
|
15
81
|
storeSet(name, nextRevision);
|
|
@@ -18,6 +84,7 @@ export function claimRightWorkbench(name) {
|
|
|
18
84
|
if (keys[i] === name) continue;
|
|
19
85
|
try { closers[keys[i]](); } catch (error) { /* one panel must not block another */ }
|
|
20
86
|
}
|
|
87
|
+
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(renderLayoutControls);
|
|
21
88
|
return nextRevision;
|
|
22
89
|
}
|
|
23
90
|
|
|
@@ -39,6 +106,7 @@ export function resetRightWorkbench() {
|
|
|
39
106
|
}
|
|
40
107
|
|
|
41
108
|
store.subscribe(function (state, previous) {
|
|
109
|
+
if (state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner) renderLayoutControls();
|
|
42
110
|
if (state.currentSlug === previous.currentSlug && state.myUserId === previous.myUserId) return;
|
|
43
111
|
resetRightWorkbench();
|
|
44
112
|
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
export var SEARCH_HINT_INTERVAL = 180000;
|
|
4
|
+
export var SEARCH_HINT_QUESTIONS = Object.freeze([
|
|
5
|
+
'What was I working on recently?',
|
|
6
|
+
'Where did I leave off yesterday?',
|
|
7
|
+
'What did I review recently?',
|
|
8
|
+
"Find the session I said I'd return to.",
|
|
9
|
+
'What work did I leave unfinished?',
|
|
10
|
+
'What did we decide about this project?',
|
|
11
|
+
'Why did we choose this approach?',
|
|
12
|
+
'Find the conversation about this bug.',
|
|
13
|
+
'Which sessions need my attention?',
|
|
14
|
+
"What's happening across this view?",
|
|
15
|
+
'What are my Workers doing?',
|
|
16
|
+
'Which sessions are waiting for approval?'
|
|
17
|
+
]);
|
|
18
|
+
export function searchHintText(index) {
|
|
19
|
+
return 'Search or ask Clay, like “' + SEARCH_HINT_QUESTIONS[index % SEARCH_HINT_QUESTIONS.length] + '”';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var trigger = null, input = null, timer = null, unsubscribe = null;
|
|
23
|
+
function relevantFocus(target) { return !!target && (target === input || trigger && trigger.contains(target)); }
|
|
24
|
+
function available() {
|
|
25
|
+
return trigger && input && !document.hidden && !store.get('paneMode') && window === window.top &&
|
|
26
|
+
!store.get('searchHintsPaletteOpen') && !store.get('searchHintsHovered') && !store.get('searchHintsFocused');
|
|
27
|
+
}
|
|
28
|
+
function cancelTimer() { if (timer !== null) { clearTimeout(timer); timer = null; } }
|
|
29
|
+
function paint() {
|
|
30
|
+
var label = trigger.querySelector('.cmd-palette-searchbar-text');
|
|
31
|
+
var text = searchHintText(store.get('searchHintIndex') || 0);
|
|
32
|
+
if (label) label.textContent = text;
|
|
33
|
+
input.placeholder = text;
|
|
34
|
+
}
|
|
35
|
+
function schedule() {
|
|
36
|
+
if (!available()) { cancelTimer(); return; }
|
|
37
|
+
if (timer !== null) return;
|
|
38
|
+
timer = setTimeout(function () {
|
|
39
|
+
timer = null;
|
|
40
|
+
if (!available()) return;
|
|
41
|
+
store.set({ searchHintIndex: ((store.get('searchHintIndex') || 0) + 1) % SEARCH_HINT_QUESTIONS.length });
|
|
42
|
+
paint(); schedule();
|
|
43
|
+
}, SEARCH_HINT_INTERVAL);
|
|
44
|
+
}
|
|
45
|
+
function visibilityChanged() { cancelTimer(); schedule(); }
|
|
46
|
+
function enter() { store.set({ searchHintsHovered: true }); }
|
|
47
|
+
function leave() { store.set({ searchHintsHovered: false }); }
|
|
48
|
+
function focusIn(event) { store.set({ searchHintsFocused: relevantFocus(event.target) }); }
|
|
49
|
+
function focusOut(event) { store.set({ searchHintsFocused: relevantFocus(event.relatedTarget) }); }
|
|
50
|
+
export function setSearchHintsPaletteOpen(open) { store.set({ searchHintsPaletteOpen: open === true }); }
|
|
51
|
+
export function disposeSearchHints() {
|
|
52
|
+
cancelTimer();
|
|
53
|
+
if (!trigger) return;
|
|
54
|
+
trigger.removeEventListener('mouseenter', enter); trigger.removeEventListener('mouseleave', leave);
|
|
55
|
+
document.removeEventListener('focusin', focusIn); document.removeEventListener('focusout', focusOut);
|
|
56
|
+
document.removeEventListener('visibilitychange', visibilityChanged);
|
|
57
|
+
if (unsubscribe) unsubscribe();
|
|
58
|
+
trigger = null; input = null; unsubscribe = null;
|
|
59
|
+
}
|
|
60
|
+
export function initSearchHints(button, field) {
|
|
61
|
+
if (button) button.removeAttribute('title');
|
|
62
|
+
if (trigger === button && input === field) return;
|
|
63
|
+
disposeSearchHints();
|
|
64
|
+
if (!button || !field) return;
|
|
65
|
+
trigger = button; input = field;
|
|
66
|
+
store.set({ searchHintIndex: store.get('searchHintIndex') || 0, searchHintsPaletteOpen: false,
|
|
67
|
+
searchHintsHovered: trigger.matches(':hover'), searchHintsFocused: relevantFocus(document.activeElement) });
|
|
68
|
+
if (!store.get('searchHintsFocused')) paint();
|
|
69
|
+
trigger.addEventListener('mouseenter', enter); trigger.addEventListener('mouseleave', leave);
|
|
70
|
+
document.addEventListener('focusin', focusIn); document.addEventListener('focusout', focusOut);
|
|
71
|
+
document.addEventListener('visibilitychange', visibilityChanged);
|
|
72
|
+
unsubscribe = store.subscribe(function (state, previous) {
|
|
73
|
+
if (state.paneMode !== previous.paneMode || state.searchHintsPaletteOpen !== previous.searchHintsPaletteOpen ||
|
|
74
|
+
state.searchHintsHovered !== previous.searchHintsHovered || state.searchHintsFocused !== previous.searchHintsFocused) {
|
|
75
|
+
cancelTimer(); schedule();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
schedule();
|
|
79
|
+
}
|
|
@@ -22,6 +22,7 @@ export function closeSessionContextMenu(restoreFocus) {
|
|
|
22
22
|
document.removeEventListener("keydown", onMenuKey, true);
|
|
23
23
|
window.removeEventListener("blur", onWindowBlur);
|
|
24
24
|
if (state.anchor) state.anchor.setAttribute("aria-expanded", "false");
|
|
25
|
+
state.el.hidden = true;
|
|
25
26
|
if (state.el && state.el.isConnected) state.el.remove();
|
|
26
27
|
if (restoreFocus !== false && state.anchor && state.anchor.isConnected) {
|
|
27
28
|
state.anchor.focus({ preventScroll: true });
|
|
@@ -54,10 +55,40 @@ function onMenuKey(event) {
|
|
|
54
55
|
event.preventDefault();
|
|
55
56
|
event.stopPropagation();
|
|
56
57
|
closeSessionContextMenu(true);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
var items = Array.from(state.el.querySelectorAll('button:not([disabled])')), index = items.indexOf(document.activeElement), next = null;
|
|
61
|
+
if (!items.length) return;
|
|
62
|
+
if (event.key === 'ArrowDown' || event.key === 'Tab') next = (index + (event.shiftKey ? -1 : 1) + items.length) % items.length;
|
|
63
|
+
else if (event.key === 'ArrowUp') next = (index - 1 + items.length) % items.length;
|
|
64
|
+
else if (event.key === 'Home') next = 0;
|
|
65
|
+
else if (event.key === 'End') next = items.length - 1;
|
|
66
|
+
if (next !== null) { event.preventDefault(); event.stopPropagation(); items[next].focus({ preventScroll: true }); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function positionSessionContextMenu(menu, anchor, point) {
|
|
70
|
+
var rect = anchor.getBoundingClientRect(), box = menu.getBoundingClientRect();
|
|
71
|
+
var x = point ? point.x : rect.left, y = point ? point.y : rect.bottom + 2;
|
|
72
|
+
menu.style.left = Math.max(8, Math.min(x, window.innerWidth - box.width - 8)) + 'px';
|
|
73
|
+
menu.style.top = Math.max(8, Math.min(y, window.innerHeight - box.height - 8)) + 'px';
|
|
74
|
+
menu.style.right = 'auto';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function reconcileSessionContextMenu() {
|
|
78
|
+
var state = menuState(); if (!state) return;
|
|
79
|
+
var anchor = state.resolveAnchor ? state.resolveAnchor() : state.anchor;
|
|
80
|
+
if (!anchor || !anchor.isConnected || state.slug !== store.get('currentSlug') || state.viewerId !== store.get('myUserId') || state.ws !== getWs()) {
|
|
81
|
+
closeSessionContextMenu(false); return;
|
|
57
82
|
}
|
|
83
|
+
if (anchor === state.anchor) return;
|
|
84
|
+
state.anchor.setAttribute('aria-expanded', 'false');
|
|
85
|
+
var hadTabIndex = anchor.hasAttribute('tabindex'), tabIndex = anchor.getAttribute('tabindex');
|
|
86
|
+
anchor.setAttribute('aria-haspopup', 'menu'); anchor.setAttribute('aria-expanded', 'true');
|
|
87
|
+
if (!hadTabIndex) anchor.setAttribute('tabindex', '-1');
|
|
88
|
+
store.set({ sessionContextMenu: Object.assign({}, state, { anchor: anchor, anchorHadTabIndex: hadTabIndex, anchorTabIndex: tabIndex }) });
|
|
58
89
|
}
|
|
59
90
|
|
|
60
|
-
export function mountSessionContextMenu(menu, anchor, sessionId) {
|
|
91
|
+
export function mountSessionContextMenu(menu, anchor, sessionId, options) {
|
|
61
92
|
closeSessionContextMenu(false);
|
|
62
93
|
menu.setAttribute("role", "menu");
|
|
63
94
|
var items = menu.querySelectorAll("button");
|
|
@@ -76,6 +107,8 @@ export function mountSessionContextMenu(menu, anchor, sessionId) {
|
|
|
76
107
|
anchorHadTabIndex: anchorHadTabIndex,
|
|
77
108
|
anchorTabIndex: anchorTabIndex,
|
|
78
109
|
sessionId: sessionId || null,
|
|
110
|
+
resolveAnchor: options && options.resolveAnchor || null,
|
|
111
|
+
slug: store.get('currentSlug'), viewerId: store.get('myUserId'), ws: getWs(),
|
|
79
112
|
} });
|
|
80
113
|
document.addEventListener("pointerdown", onOutsidePress, true);
|
|
81
114
|
document.addEventListener("mousedown", onOutsidePress, true);
|
|
@@ -205,7 +238,7 @@ export function startHeaderSessionInlineRename(title, control, session) {
|
|
|
205
238
|
}
|
|
206
239
|
|
|
207
240
|
store.subscribe(function (state, previous) {
|
|
208
|
-
if (state.currentSlug !== previous.currentSlug || state.dmMode !== previous.dmMode ||
|
|
241
|
+
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId || state.dmMode !== previous.dmMode ||
|
|
209
242
|
state.sessionActionEpoch !== previous.sessionActionEpoch || state.panePendingBinding !== previous.panePendingBinding ||
|
|
210
243
|
(previous.connected && !state.connected)) {
|
|
211
244
|
closeSessionContextMenu(false);
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
import { store } from './store.js';
|
|
8
8
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
9
9
|
import { escapeHtml } from './utils.js';
|
|
10
|
+
import { closeSessionContextMenu, positionSessionContextMenu } from './session-context-menu.js';
|
|
10
11
|
|
|
11
12
|
var LONG_PRESS_MS = 500;
|
|
12
13
|
var MOVE_TOLERANCE = 10;
|
|
13
14
|
var POST_RELEASE_MS = 400;
|
|
14
15
|
var HOLD_MAX_MS = 15000;
|
|
15
|
-
var EDGE = 8;
|
|
16
16
|
|
|
17
17
|
function contextState() {
|
|
18
18
|
return store.get('sessionFolderContext') || { press: null, held: null, suppressUntil: 0 };
|
|
@@ -72,7 +72,7 @@ function onMenuKey(event) {
|
|
|
72
72
|
|
|
73
73
|
// Opens the menu with its top-left corner at the point, kept inside the viewport.
|
|
74
74
|
export function openFolderMenuAt(anchor, entries, point) {
|
|
75
|
-
closeFolderMenu();
|
|
75
|
+
closeFolderMenu(false); closeSessionContextMenu(false);
|
|
76
76
|
var menu = document.createElement("div");
|
|
77
77
|
menu.className = "session-ctx-menu session-folder-menu";
|
|
78
78
|
menu.setAttribute("role", "menu");
|
|
@@ -94,16 +94,12 @@ export function openFolderMenuAt(anchor, entries, point) {
|
|
|
94
94
|
});
|
|
95
95
|
menu.style.visibility = "hidden";
|
|
96
96
|
document.body.appendChild(menu);
|
|
97
|
-
|
|
98
|
-
var left = Math.min(Math.max(EDGE, point.x), Math.max(EDGE, window.innerWidth - box.width - EDGE));
|
|
99
|
-
var top = point.y;
|
|
100
|
-
if (top + box.height > window.innerHeight - EDGE) top = Math.min(point.y - box.height, window.innerHeight - box.height - EDGE);
|
|
101
|
-
top = Math.max(EDGE, top);
|
|
102
|
-
menu.style.left = left + "px";
|
|
103
|
-
menu.style.top = top + "px";
|
|
104
|
-
menu.style.right = "auto";
|
|
97
|
+
positionSessionContextMenu(menu, anchor, point);
|
|
105
98
|
menu.style.visibility = "";
|
|
106
|
-
|
|
99
|
+
var root = anchor.closest('.session-folder-folder');
|
|
100
|
+
store.set({ sessionFolderMenu: { el: menu, anchor: anchor, folderId: root && root.dataset.folderId,
|
|
101
|
+
mobile: !!(root && root.classList.contains('is-mobile')), slug: store.get('currentSlug'), viewerId: store.get('myUserId'),
|
|
102
|
+
folders: JSON.stringify((store.get('sessionFolders') || {}).folders || []) } });
|
|
107
103
|
document.addEventListener("pointerdown", onMenuOutside, true);
|
|
108
104
|
document.addEventListener("mousedown", onMenuOutside, true);
|
|
109
105
|
document.addEventListener("touchstart", onMenuOutside, true);
|
|
@@ -114,6 +110,17 @@ export function openFolderMenuAt(anchor, entries, point) {
|
|
|
114
110
|
if (first) first.focus({ preventScroll: true });
|
|
115
111
|
}
|
|
116
112
|
|
|
113
|
+
export function reconcileFolderMenu() {
|
|
114
|
+
var menu = store.get('sessionFolderMenu'); if (!menu) return;
|
|
115
|
+
if (!menu.folderId || menu.slug !== store.get('currentSlug') || menu.viewerId !== store.get('myUserId') ||
|
|
116
|
+
menu.folders !== JSON.stringify((store.get('sessionFolders') || {}).folders || [])) { closeFolderMenu(false); return; }
|
|
117
|
+
var roots = document.querySelectorAll('.session-folder-folder');
|
|
118
|
+
var root = Array.from(roots).find(function (item) { return item.dataset.folderId === menu.folderId && item.classList.contains('is-mobile') === menu.mobile; });
|
|
119
|
+
var anchor = root && root.querySelector('.session-folder-toggle');
|
|
120
|
+
if (!anchor) { closeFolderMenu(false); return; }
|
|
121
|
+
if (anchor !== menu.anchor) store.set({ sessionFolderMenu: Object.assign({}, menu, { anchor: anchor }) });
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
// --- Long press (touch and pen) ---
|
|
118
125
|
|
|
119
126
|
function onScrollCancel() {
|
|
@@ -164,14 +171,14 @@ export function consumeFolderClickSuppress() {
|
|
|
164
171
|
// entries when it opens, so they always reflect the current folder order.
|
|
165
172
|
export function wireFolderContext(header, anchor, entriesFor) {
|
|
166
173
|
function open(x, y) {
|
|
167
|
-
if (store.get('sessionFolderMenu')) return;
|
|
168
174
|
openFolderMenuAt(anchor, entriesFor(), { x: x, y: y });
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
header.addEventListener("contextmenu", function (event) {
|
|
172
178
|
event.preventDefault();
|
|
173
179
|
event.stopPropagation();
|
|
174
|
-
|
|
180
|
+
var rect = anchor.getBoundingClientRect();
|
|
181
|
+
open(event.clientX || event.clientY ? event.clientX : rect.left + 16, event.clientX || event.clientY ? event.clientY : rect.bottom);
|
|
175
182
|
});
|
|
176
183
|
|
|
177
184
|
header.addEventListener("keydown", function (event) {
|
|
@@ -213,3 +220,7 @@ export function wireFolderContext(header, anchor, entriesFor) {
|
|
|
213
220
|
header.addEventListener("pointerleave", cancelFolderPress);
|
|
214
221
|
header.addEventListener("dragstart", cancelFolderPress);
|
|
215
222
|
}
|
|
223
|
+
|
|
224
|
+
store.subscribe(function (state, previous) {
|
|
225
|
+
if (state.myUserId !== previous.myUserId || previous.connected && !state.connected) closeFolderMenu(false);
|
|
226
|
+
});
|
|
@@ -178,13 +178,28 @@ function radioRow(mode, labelText, hint, current, extraClass) {
|
|
|
178
178
|
return row;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// Display context only. Session counts and deletion authority still come from the preview.
|
|
182
|
+
function folderHasSavedViews(folderId) {
|
|
183
|
+
var snapshot = store.snap();
|
|
184
|
+
if (!snapshot.connected || !snapshot.authIdentityLoaded || snapshot.dmMode || snapshot.paneMode ||
|
|
185
|
+
!snapshot.currentSlug || snapshot.activeProjectSlug !== snapshot.currentSlug || !snapshot.projectSplitViewHydrated || snapshot.projectSplitViewError ||
|
|
186
|
+
snapshot.projectSplitViewRequestSlug !== snapshot.currentSlug || snapshot.projectSplitViewRequestSocket !== getWs() ||
|
|
187
|
+
snapshot.projectSplitViewRequestOwnerId !== (snapshot.myUserId || null) ||
|
|
188
|
+
snapshot.projectSplitViewRequestGeneration !== snapshot.projectSplitViewSocketGeneration) return false;
|
|
189
|
+
return (snapshot.projectSplitViews || []).some(function (view) { return view.folderId === folderId; });
|
|
190
|
+
}
|
|
191
|
+
|
|
181
192
|
function describe(current) {
|
|
182
193
|
var preview = current.preview;
|
|
194
|
+
var hasViews = folderHasSavedViews(current.folderId);
|
|
195
|
+
var viewCopy = "Saved Split views will move to Unfiled and keep their Favorites status.";
|
|
196
|
+
if (preview.sessionCount === 0 && hasViews) return 'The folder "' + current.label + '" contains saved Split views. ' + viewCopy + " No sessions will be deleted.";
|
|
183
197
|
if (preview.sessionCount === 0) return 'The folder "' + current.label + '" is empty. Deleting it does not affect any sessions.';
|
|
184
198
|
var text = 'The folder "' + current.label + '" contains ' + phrase(preview.sessionCount, "session") + ".";
|
|
185
199
|
if (preview.workerCount > 0) {
|
|
186
200
|
text += " " + phrase(preview.workerCount, "Split Worker session") + " owned by those Drivers would be deleted with them, including hidden sessions and previous generations.";
|
|
187
201
|
}
|
|
202
|
+
if (hasViews) text += " " + viewCopy + " The choices below apply to individually filed sessions; deleting member sessions also removes them from their Split views.";
|
|
188
203
|
return text;
|
|
189
204
|
}
|
|
190
205
|
|
|
@@ -327,7 +342,9 @@ export function handleFolderDeleteState(msg) {
|
|
|
327
342
|
store.subscribe(function (next, previous) {
|
|
328
343
|
if (next.sessionFolderDelete !== previous.sessionFolderDelete) paint();
|
|
329
344
|
// Retry depends on the connection, so a change in it repaints the dialog.
|
|
330
|
-
else if (next.connected !== previous.connected
|
|
345
|
+
else if (next.sessionFolderDelete && (next.connected !== previous.connected || next.projectSplitViews !== previous.projectSplitViews ||
|
|
346
|
+
next.projectSplitViewHydrated !== previous.projectSplitViewHydrated || next.projectSplitViewError !== previous.projectSplitViewError ||
|
|
347
|
+
next.currentSlug !== previous.currentSlug || next.myUserId !== previous.myUserId)) paint();
|
|
331
348
|
var current = next.sessionFolderDelete;
|
|
332
349
|
if (next.currentSlug !== previous.currentSlug || next.dmMode !== previous.dmMode) clearDustParticles("folder-delete-dust");
|
|
333
350
|
if (!current || !previous.connected || next.connected) return;
|