clay-server 5.3.0-beta.1 → 5.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project-worker-conversation.js +67 -3
- package/lib/public/css/pane.css +0 -36
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +4 -1
- package/lib/public/modules/app-favicon.js +1 -1
- package/lib/public/modules/app-messages.js +7 -2
- package/lib/public/modules/chat-render-runtime.js +9 -0
- package/lib/public/modules/filebrowser.js +6 -2
- package/lib/public/modules/markdown-view-mode.js +34 -0
- package/lib/public/modules/pane-file-bridge.js +8 -3
- package/lib/public/modules/session-folder-collapse.js +2 -1
- package/lib/public/modules/session-folder-dnd.js +4 -1
- package/lib/public/modules/split-view-presentation-drag.js +24 -0
- package/lib/public/modules/split-view-presentations.js +5 -2
- package/lib/public/modules/split-view-workers.js +78 -12
- package/lib/public/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/{app-OJEQXBBS.js → app-2DRBYY3L.js} +1 -1
- package/lib/public/pane-assets/{chunk-UVGOPFEX.js → chunk-4BM7UVNG.js} +1 -1
- package/lib/public/pane-assets/{chunk-4SMIVAB4.js → chunk-C4RVVYNT.js} +1 -1
- package/lib/public/pane-assets/{chunk-GML2R5JE.js → chunk-O6MQL6MR.js} +94 -94
- package/lib/public/pane-assets/{chunk-623V5CEO.js → chunk-PO2JOHHW.js} +1 -1
- package/lib/public/pane-assets/{chunk-I5P3MWQG.js → chunk-UMGHINOT.js} +1 -1
- package/lib/public/pane-assets/manifest.json +322 -320
- package/lib/public/pane-assets/style-63EZCREE.css +1 -0
- package/lib/ws-schema.js +1 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/style-GN7R6XKV.css +0 -1
|
@@ -5,6 +5,7 @@ var provenance = require("./session-provenance");
|
|
|
5
5
|
function attachProjectWorkerConversation(ctx) {
|
|
6
6
|
var sm = ctx.sm;
|
|
7
7
|
var users = ctx.usersModule;
|
|
8
|
+
var watches = new WeakMap();
|
|
8
9
|
var MAX_MESSAGES = 120;
|
|
9
10
|
var MAX_TEXT = 8000;
|
|
10
11
|
var MAX_PAGE_TEXT = 64000;
|
|
@@ -136,19 +137,82 @@ function attachProjectWorkerConversation(ctx) {
|
|
|
136
137
|
if (!nextBefore && index >= 0) nextBefore = { index: index, offset: offset };
|
|
137
138
|
return { messages: rows, nextBefore: nextBefore, hasMore: nextBefore !== null };
|
|
138
139
|
}
|
|
140
|
+
// Follow recorded events forward without dropping an intervening busy burst.
|
|
141
|
+
function snapshotAfter(history, after) {
|
|
142
|
+
var rows = [], index = after.index, offset = after.offset, budget = MAX_PAGE_TEXT;
|
|
143
|
+
while (index < history.length && rows.length < MAX_MESSAGES && budget > 0) {
|
|
144
|
+
var item = record(history, index);
|
|
145
|
+
if (!item || offset >= item.text.length) { index++; offset = 0; continue; }
|
|
146
|
+
var metadataSize = (item.toolName || '').length + (item.toolId || '').length;
|
|
147
|
+
if (budget <= metadataSize) break;
|
|
148
|
+
var end = Math.min(item.text.length, offset + Math.min(MAX_TEXT, budget - metadataSize));
|
|
149
|
+
rows.push({ role: item.role, text: item.text.slice(offset, end), recordIndex: index, recordType: history[index].type,
|
|
150
|
+
start: offset, end: end, toolName: item.toolName || null, toolId: item.toolId || null, isError: item.isError === true });
|
|
151
|
+
budget -= end - offset + metadataSize;
|
|
152
|
+
offset = end;
|
|
153
|
+
}
|
|
154
|
+
while (index < history.length) {
|
|
155
|
+
var remaining = record(history, index);
|
|
156
|
+
if (remaining && offset < remaining.text.length) break;
|
|
157
|
+
index++; offset = 0;
|
|
158
|
+
}
|
|
159
|
+
return { messages: rows, hasMore: false, nextBefore: null, moreRecent: index < history.length };
|
|
160
|
+
}
|
|
161
|
+
function stopWatching(ws) {
|
|
162
|
+
var watch = watches.get(ws);
|
|
163
|
+
if (!watch) return;
|
|
164
|
+
watches.delete(ws);
|
|
165
|
+
clearTimeout(watch.timer);
|
|
166
|
+
if (watch.release) watch.release();
|
|
167
|
+
if (ws.removeListener) ws.removeListener('close', watch.close);
|
|
168
|
+
}
|
|
169
|
+
function watchWorker(ws, msg, pair) {
|
|
170
|
+
stopWatching(ws);
|
|
171
|
+
msg = { requestId: msg.requestId.slice(0, 160), parentSessionOriginId: pair.driver.sessionOriginId,
|
|
172
|
+
workerSessionOriginId: pair.worker.sessionOriginId, generation: msg.generation };
|
|
173
|
+
var watch = { worker: pair.worker, requestId: msg.requestId, timer: null };
|
|
174
|
+
watch.close = function () { stopWatching(ws); };
|
|
175
|
+
watch.notify = function () {
|
|
176
|
+
if (watch.timer) return;
|
|
177
|
+
watch.timer = setTimeout(function () {
|
|
178
|
+
watch.timer = null;
|
|
179
|
+
var user = actor(ws), latest = user && exactPair(ws, msg, user);
|
|
180
|
+
if (ws.readyState !== 1 || !latest || latest.worker !== pair.worker || latest.driver !== pair.driver) { stopWatching(ws); return; }
|
|
181
|
+
send(ws, { type: 'split_worker_conversation_changed', subscriptionId: msg.requestId,
|
|
182
|
+
parentSessionOriginId: msg.parentSessionOriginId, workerSessionOriginId: msg.workerSessionOriginId,
|
|
183
|
+
generation: msg.generation, isProcessing: pair.worker.isProcessing === true });
|
|
184
|
+
}, 32);
|
|
185
|
+
};
|
|
186
|
+
watch.release = sm.subscribeSession(pair.worker.localId, watch.notify);
|
|
187
|
+
watches.set(ws, watch);
|
|
188
|
+
if (ws.once) ws.once('close', watch.close);
|
|
189
|
+
}
|
|
139
190
|
function handleMessage(ws, msg) {
|
|
140
191
|
if (!msg || msg.type !== "split_worker_conversation_request") return false;
|
|
141
192
|
var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 160) : "";
|
|
142
193
|
var response = { type: "split_worker_conversation", requestId: requestId, pageBefore: msg.before == null ? null : msg.before,
|
|
143
194
|
parentSessionOriginId: msg.parentSessionOriginId || null, workerSessionOriginId: msg.workerSessionOriginId || null,
|
|
144
195
|
generation: msg.generation || null, messages: [], hasMore: false, nextBefore: null };
|
|
196
|
+
if (msg.watch === false) {
|
|
197
|
+
var watch = watches.get(ws);
|
|
198
|
+
if (watch && watch.requestId === msg.requestId && watch.worker.sessionOriginId === msg.workerSessionOriginId) stopWatching(ws);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
145
201
|
var current = actor(ws), pair = current && exactPair(ws, msg, current);
|
|
146
202
|
if (!requestId || !pair) response.error = "This Worker conversation is no longer available.";
|
|
147
203
|
else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
204
|
+
if (msg.after != null) {
|
|
205
|
+
var after = msg.after;
|
|
206
|
+
if (msg.before != null || !after || !Number.isInteger(after.index) || after.index < 0 || after.index >= pair.worker.history.length ||
|
|
207
|
+
!Number.isInteger(after.offset) || after.offset < 0) response.error = 'Worker activity cursor is invalid.';
|
|
208
|
+
else Object.assign(response, snapshotAfter(pair.worker.history || [], after));
|
|
209
|
+
} else {
|
|
210
|
+
var before = msg.before == null ? pair.worker.history.length : msg.before;
|
|
211
|
+
if (!(Number.isInteger(before) && before >= 0) && !(before && typeof before === "object" && Number.isInteger(before.index) && Number.isInteger(before.offset) && before.index >= 0 && before.offset >= 0)) response.error = "Worker conversation page is invalid.";
|
|
212
|
+
else Object.assign(response, snapshot(pair.worker.history || [], before));
|
|
213
|
+
}
|
|
151
214
|
}
|
|
215
|
+
if (!response.error && msg.watch === true) watchWorker(ws, msg, pair);
|
|
152
216
|
send(ws, response);
|
|
153
217
|
return true;
|
|
154
218
|
}
|
package/lib/public/css/pane.css
CHANGED
|
@@ -229,31 +229,6 @@ body.pane-mode #input-area {
|
|
|
229
229
|
}
|
|
230
230
|
.split-pair-live { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); animation: pair-status-pulse 1.5s ease-in-out infinite; }
|
|
231
231
|
|
|
232
|
-
#split-host.split-delegating::after {
|
|
233
|
-
content: "→";
|
|
234
|
-
position: absolute;
|
|
235
|
-
z-index: 4;
|
|
236
|
-
top: calc(50% - 10px);
|
|
237
|
-
left: calc(50% - 17px);
|
|
238
|
-
width: 34px;
|
|
239
|
-
height: 20px;
|
|
240
|
-
display: grid;
|
|
241
|
-
place-items: center;
|
|
242
|
-
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
|
|
243
|
-
border-radius: 999px;
|
|
244
|
-
pointer-events: none;
|
|
245
|
-
background: var(--sidebar-bg);
|
|
246
|
-
color: var(--accent);
|
|
247
|
-
font-size: 14px;
|
|
248
|
-
font-weight: 800;
|
|
249
|
-
animation: pair-divider-flow 1.1s ease-in-out infinite;
|
|
250
|
-
filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 65%, transparent));
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
#split-host.split-direction-left::after { content: "←"; animation-name: pair-divider-flow-left; }
|
|
254
|
-
|
|
255
|
-
@keyframes pair-divider-flow { 0%, 100% { transform: translateX(-3px); opacity: 0.65; } 50% { transform: translateX(3px); opacity: 1; } }
|
|
256
|
-
@keyframes pair-divider-flow-left { 0%, 100% { transform: translateX(3px); opacity: 0.65; } 50% { transform: translateX(-3px); opacity: 1; } }
|
|
257
232
|
@keyframes pair-status-pulse { 50% { opacity: 0.55; } }
|
|
258
233
|
|
|
259
234
|
/* Worker activity notice shown while this pane processes a delegated task. */
|
|
@@ -596,18 +571,7 @@ body.pane-mode #input-area {
|
|
|
596
571
|
flex: 1 1 auto;
|
|
597
572
|
}
|
|
598
573
|
|
|
599
|
-
#split-host:has(.split-pane-role-worker).split-delegating::after {
|
|
600
|
-
content: "↑";
|
|
601
|
-
/* Center on worker bottom + half the gap, accounting for the shell
|
|
602
|
-
inset and the 30% basis being relative to the inset content box. */
|
|
603
|
-
top: calc(var(--split-shell-inset) + var(--split-worker-height) + var(--split-pane-gap-half) - 10px);
|
|
604
|
-
animation-name: pair-divider-flow-up;
|
|
605
|
-
}
|
|
606
574
|
|
|
607
|
-
@keyframes pair-divider-flow-up {
|
|
608
|
-
0%, 100% { transform: translateY(3px); opacity: 0.65; }
|
|
609
|
-
50% { transform: translateY(-3px); opacity: 1; }
|
|
610
|
-
}
|
|
611
575
|
}
|
|
612
576
|
|
|
613
577
|
/* Prevent an iframe from taking the pointer midway through a note gesture. */
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
.session-folder-chevron, .session-folder-icon { display: inline-flex; flex: none; }
|
|
23
23
|
.session-folder-chevron svg, .session-folder-icon svg { width: 12px; height: 12px; display: block; }
|
|
24
|
-
.session-folder-chevron { transition: transform 0.15s ease; }
|
|
24
|
+
.session-folder-chevron { align-items: center; justify-content: center; min-height: 20px; min-width: 16px; margin: -4px -2px; transition: transform 0.15s ease; }
|
|
25
25
|
.session-folder-toggle[aria-expanded="true"] .session-folder-chevron { transform: rotate(90deg); }
|
|
26
26
|
.session-folder-favorites .session-folder-icon { color: var(--accent); }
|
|
27
27
|
.session-folder-label { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -274,7 +274,7 @@ body.split-view-resizing .split-pane-frame { pointer-events: none; }
|
|
|
274
274
|
.split-view-worker-controls:hover { background: var(--sidebar-hover); color: var(--text); }
|
|
275
275
|
.split-view-worker-controls:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
276
276
|
.split-view-worker-dot { width: 5px; height: 5px; flex: 0 0 5px; border-radius: 50%; background: var(--text-dimmer); }
|
|
277
|
-
.split-view-worker-dot[data-state="working"] { background: var(--brand-green);
|
|
277
|
+
.split-view-worker-dot[data-state="working"] { background: var(--brand-green); }
|
|
278
278
|
.split-view-worker-dot[data-state="waiting"] { background: var(--brand-gold); }
|
|
279
279
|
@keyframes split-view-working { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
|
|
280
280
|
.split-view-group { margin: 2px 0 8px; }
|
|
@@ -458,3 +458,6 @@ body.split-view-resizing .split-pane-frame { pointer-events: none; }
|
|
|
458
458
|
#split-view-arrange-context-list button { display: block; width: 100%; min-height: 28px; padding: 5px 7px; text-align: left; font-size: 11px; }
|
|
459
459
|
#split-view-arrange-context-list [aria-selected="true"] { background: var(--accent-bg); color: var(--accent); }
|
|
460
460
|
.split-view-arrange [data-focus-layout] { width: 100%; min-height: 29px; margin-top: 5px; border-top: 1px solid var(--border-subtle); font-size: 11px; }
|
|
461
|
+
|
|
462
|
+
/* Light reflects received WebSocket output, independent of processing state. */
|
|
463
|
+
.split-view-worker-dot.io { background: var(--brand-green); box-shadow: 0 0 5px var(--brand-green); }
|
|
@@ -139,7 +139,7 @@ export function blinkIO() {
|
|
|
139
139
|
|
|
140
140
|
export function blinkSessionDot(sessionId) {
|
|
141
141
|
// A favorite is on screen twice; blink every copy.
|
|
142
|
-
var els = document.querySelectorAll('.session-item[data-session-id="' + sessionId + '"] .session-processing');
|
|
142
|
+
var els = document.querySelectorAll('.session-item[data-session-id="' + sessionId + '"] .session-processing, .split-view-worker-chip[data-worker-session-id="' + sessionId + '"] .split-view-worker-dot');
|
|
143
143
|
if (!els.length) return;
|
|
144
144
|
for (var i = 0; i < els.length; i++) els[i].classList.add("io");
|
|
145
145
|
clearTimeout(sessionIoTimers[sessionId]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { showCreatedWorkerPresentation } from './worker-created-presentation.js';
|
|
1
2
|
import { handleWorkerRemovalResult } from './split-view-worker-removal.js';
|
|
2
3
|
import { handleStudioDocumentMessage } from './clay-studio-document.js';
|
|
3
4
|
import { saveSessionRichDraft, restoreSessionRichDraft } from './session-rich-drafts.js';
|
|
@@ -55,7 +56,7 @@ import { handlePaletteSessionSwitch, handlePaletteHistoryDone, handlePaletteHist
|
|
|
55
56
|
import { handleFindInSessionResults } from './session-search.js';
|
|
56
57
|
import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
|
|
57
58
|
import { handleProjectSplitViews, handleProjectSplitViewResult, requestProjectSplitViews } from './split-view-presentations.js';
|
|
58
|
-
import { handleSplitWorkerConversation } from './split-view-workers.js';
|
|
59
|
+
import { handleSplitWorkerConversation, handleWorkerSessionActivity, handleWorkerConversationChanged } from './split-view-workers.js';
|
|
59
60
|
import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
|
|
60
61
|
import { handleSessionActionMessage } from './session-actions.js';
|
|
61
62
|
import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
|
|
@@ -808,6 +809,9 @@ export function processMessage(msg) {
|
|
|
808
809
|
case "project_split_view_result":
|
|
809
810
|
handleProjectSplitViewResult(msg);
|
|
810
811
|
break;
|
|
812
|
+
case "split_worker_conversation_changed":
|
|
813
|
+
handleWorkerConversationChanged(msg);
|
|
814
|
+
break;
|
|
811
815
|
case "split_worker_conversation":
|
|
812
816
|
handleSplitWorkerConversation(msg);
|
|
813
817
|
break;
|
|
@@ -840,7 +844,7 @@ export function processMessage(msg) {
|
|
|
840
844
|
|
|
841
845
|
case "pair_session_created":
|
|
842
846
|
var createdPairGroup = handlePairCreated(msg);
|
|
843
|
-
if (createdPairGroup)
|
|
847
|
+
if (createdPairGroup) showCreatedWorkerPresentation(createdPairGroup);
|
|
844
848
|
break;
|
|
845
849
|
|
|
846
850
|
case "session_handoff_result":
|
|
@@ -915,6 +919,7 @@ export function processMessage(msg) {
|
|
|
915
919
|
|
|
916
920
|
case "session_io":
|
|
917
921
|
blinkSessionDot(msg.id);
|
|
922
|
+
handleWorkerSessionActivity(msg);
|
|
918
923
|
break;
|
|
919
924
|
|
|
920
925
|
case "session_unread":
|
|
@@ -205,6 +205,7 @@ export function addToMessages(element) {
|
|
|
205
205
|
if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
|
|
206
206
|
else messagesEl.appendChild(element);
|
|
207
207
|
keepActiveThinkingAtTail();
|
|
208
|
+
if (stickyBottomResizeObs && stickyBottom) stickyBottomResizeObs.observe(element);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
export function addHistoryIndex(element, historyIndex) {
|
|
@@ -247,6 +248,11 @@ export function scrollToBottom() {
|
|
|
247
248
|
if (prependAnchor || store.get('replayingHistory')) return;
|
|
248
249
|
var messagesEl = getMessagesEl();
|
|
249
250
|
if (!messagesEl) return;
|
|
251
|
+
if (stickyBottom) {
|
|
252
|
+
pinToBottomNow();
|
|
253
|
+
requestAnimationFrame(function () { if (stickyBottom && !layoutScrollLock()) pinToBottomNow(); });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
250
256
|
var distFromBottom = messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight;
|
|
251
257
|
if (distFromBottom > 150 || isUserScrolledUp) {
|
|
252
258
|
if (distFromBottom > 150) isUserScrolledUp = true;
|
|
@@ -270,7 +276,10 @@ export function scrollToBottom() {
|
|
|
270
276
|
|
|
271
277
|
export function forceScrollToBottom() {
|
|
272
278
|
if (prependAnchor || store.get('replayingHistory')) return;
|
|
279
|
+
// An explicit new turn supersedes a layout-only scroll receipt.
|
|
280
|
+
if (layoutScrollLock()) store.set({ paneLayoutScrollLock: null });
|
|
273
281
|
armStickyBottom(750);
|
|
282
|
+
requestAnimationFrame(function () { if (stickyBottom && !layoutScrollLock()) pinToBottomNow(); });
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
export function getMsgTime() {
|
|
@@ -16,6 +16,7 @@ import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-c
|
|
|
16
16
|
import { registerClayFileLinkOpener } from './clay-file-links.js';
|
|
17
17
|
import { isPlantUmlFile, renderPlantUmlPreview, cancelPlantUmlPreview } from './plantuml-preview.js';
|
|
18
18
|
import { isHtmlFile, renderHtmlPreview } from './html-preview.js';
|
|
19
|
+
import { initialMarkdownRendered, rememberMarkdownViewMode, resetMarkdownViewModes } from './markdown-view-mode.js';
|
|
19
20
|
|
|
20
21
|
var ctx;
|
|
21
22
|
var showDropHint = function () {};
|
|
@@ -48,7 +49,7 @@ export function initFileBrowser(_ctx) {
|
|
|
48
49
|
ctx.fileViewerEl.classList.remove("hidden");
|
|
49
50
|
if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
|
|
50
51
|
},
|
|
51
|
-
onEmpty: function() { teardownFileViewer(); },
|
|
52
|
+
onEmpty: function() { resetMarkdownViewModes(); teardownFileViewer(); },
|
|
52
53
|
});
|
|
53
54
|
registerRightWorkbench("file-viewer", teardownFileViewer);
|
|
54
55
|
|
|
@@ -204,6 +205,7 @@ export function initFileBrowser(_ctx) {
|
|
|
204
205
|
document.getElementById("file-viewer-render").addEventListener("click", function () {
|
|
205
206
|
if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml") && !store.get("fileViewerIsHtml"))) return;
|
|
206
207
|
isRendered = !isRendered;
|
|
208
|
+
if (currentIsMarkdown) rememberMarkdownViewMode((store.get('clayControlFileView') || {}).projectSlug, currentFilePath, isRendered);
|
|
207
209
|
if (store.get("fileViewerIsHtml")) {
|
|
208
210
|
renderHtmlBody();
|
|
209
211
|
return;
|
|
@@ -534,6 +536,7 @@ export function setFileViewerFullscreen(enabled) {
|
|
|
534
536
|
export function resetFileBrowser() {
|
|
535
537
|
releaseRightWorkbench("file-viewer");
|
|
536
538
|
clearFileViewerTabs();
|
|
539
|
+
resetMarkdownViewModes();
|
|
537
540
|
teardownFileViewer();
|
|
538
541
|
// Clear all cached state
|
|
539
542
|
treeData = {};
|
|
@@ -1217,6 +1220,7 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1217
1220
|
store.set({ fileViewerIsHtml: !lightweightPreview && isHtmlFile(msg.path) });
|
|
1218
1221
|
store.set({ fileViewerIsPlantUml: !lightweightPreview && isPlantUmlFile(msg.path) });
|
|
1219
1222
|
if (lightweightPreview) isRendered = false;
|
|
1223
|
+
if (currentIsMarkdown && !keepRenderState) isRendered = initialMarkdownRendered((store.get('clayControlFileView') || {}).projectSlug, msg.path, store.get('pendingFileNavigation'), pendingOpenMode);
|
|
1220
1224
|
if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
|
|
1221
1225
|
|
|
1222
1226
|
if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml") || store.get("fileViewerIsHtml")) {
|
|
@@ -1227,7 +1231,7 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1227
1231
|
copyBtn.title = "Copy contents";
|
|
1228
1232
|
}
|
|
1229
1233
|
|
|
1230
|
-
// Markdown
|
|
1234
|
+
// Markdown, HTML, SVG and diagrams start as previews; a line target or diff starts as source.
|
|
1231
1235
|
if (lightweightPreview) {
|
|
1232
1236
|
renderLargeTextFile(bodyEl, msg.content, msg.size);
|
|
1233
1237
|
} else if (currentIsMarkdown) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Initial Markdown view for the file viewer. Rendered is the default; an
|
|
2
|
+
// explicit line target or diff needs source rows, and a toggle made by the
|
|
3
|
+
// user for a path in this tab session is respected on later focus/reopen.
|
|
4
|
+
// Choices are scoped to the source project that owns the file.
|
|
5
|
+
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
|
|
8
|
+
function modeKey(projectSlug, path) {
|
|
9
|
+
return JSON.stringify([projectSlug || null, path]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function chosenMode(projectSlug, path) {
|
|
13
|
+
var modes = store.get('fileViewerMarkdownModes') || {};
|
|
14
|
+
var key = modeKey(projectSlug, path);
|
|
15
|
+
return Object.prototype.hasOwnProperty.call(modes, key) ? modes[key] : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function initialMarkdownRendered(projectSlug, path, pendingNavigation, pendingMode) {
|
|
19
|
+
if (pendingNavigation && pendingNavigation.line && pendingNavigation.path === path) return false;
|
|
20
|
+
if (pendingMode && pendingMode.type === 'diff') return false;
|
|
21
|
+
var chosen = chosenMode(projectSlug, path);
|
|
22
|
+
return chosen ? chosen === 'rendered' : true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rememberMarkdownViewMode(projectSlug, path, rendered) {
|
|
26
|
+
if (!path) return;
|
|
27
|
+
var next = Object.assign({}, store.get('fileViewerMarkdownModes') || {});
|
|
28
|
+
next[modeKey(projectSlug, path)] = rendered ? 'rendered' : 'source';
|
|
29
|
+
store.set({ fileViewerMarkdownModes: next });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resetMarkdownViewModes() {
|
|
33
|
+
store.set({ fileViewerMarkdownModes: {} });
|
|
34
|
+
}
|
|
@@ -59,9 +59,14 @@ export function handlePaneFileMessage(event, host, openFile, openFiles) {
|
|
|
59
59
|
if (!msg || msg.type !== "clay-pane-open-file") return false;
|
|
60
60
|
var frame = paneFrame(host, event.source);
|
|
61
61
|
var state = store.snap();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// A live reusable pane is authorized by its document binding, which covers
|
|
63
|
+
// legacy splits, saved views and single-session views. Other frames keep the
|
|
64
|
+
// legacy split membership and URL checks.
|
|
65
|
+
var live = frame && frame.clayPane;
|
|
66
|
+
var pane = live ? { slug: frame.dataset.projectSlug, sessionId: frame.dataset.sessionId } : matchingPane(frame, state.splitPanes);
|
|
67
|
+
if (live && !livePaneBinding(frame, msg)) return true;
|
|
68
|
+
if (live && Number(msg.sessionId) !== live.identity().sessionId) return true;
|
|
69
|
+
if (!pane || (!live && !hasLivePaneSource(frame, pane)) || String(pane.slug) !== String(state.currentSlug) || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== String(frame.dataset.sessionId) || !validLocation(msg.line) || !validLocation(msg.column)) return true;
|
|
65
70
|
if (typeof msg.path !== "string" || !msg.path) return true;
|
|
66
71
|
if (!state.connected || typeof openFile !== "function") return true;
|
|
67
72
|
// The frame/session association was validated above. The host browser owns
|
|
@@ -89,7 +89,8 @@ function setAccessibleState(root, body, toggle, open) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function finish(key, token, root, body, toggle, open) {
|
|
92
|
-
if (currentRecord(key, token)
|
|
92
|
+
if (!currentRecord(key, token) || !root.isConnected) return;
|
|
93
|
+
putRecord(key, null);
|
|
93
94
|
cleanBody(body);
|
|
94
95
|
setAccessibleState(root, body, toggle, open);
|
|
95
96
|
body.hidden = !open;
|
|
@@ -186,8 +186,11 @@ function ghostFor(label) {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
export function wireFolderDrag(header, folderId, label) {
|
|
189
|
-
|
|
189
|
+
// Keep disclosure clicks separate from the native folder-reorder gesture.
|
|
190
|
+
var handle = header.querySelector('.session-folder-label') || header;
|
|
191
|
+
handle.setAttribute("draggable", "true");
|
|
190
192
|
header.addEventListener("dragstart", function (event) {
|
|
193
|
+
if (event.target.closest && event.target.closest('.session-folder-chevron, .session-folder-new-btn')) { event.preventDefault(); return; }
|
|
191
194
|
var root = header.closest(".session-folder-folder");
|
|
192
195
|
var parent = root && root.parentElement;
|
|
193
196
|
if (!parent) return;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { store } from './store.js';
|
|
4
4
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
5
5
|
import { showToast } from './utils.js';
|
|
6
|
+
import { currentFolderState, sendFolderOp } from './session-folders.js';
|
|
7
|
+
import { organizePresentationView } from './split-view-presentations.js';
|
|
6
8
|
|
|
7
9
|
var host = null;
|
|
8
10
|
var commitDrop = null;
|
|
@@ -48,6 +50,13 @@ function onPointerDown(event) {
|
|
|
48
50
|
function targetAt(x, y) {
|
|
49
51
|
var element = document.elementFromPoint(x, y);
|
|
50
52
|
if (!element || !element.closest || element.closest(".workbench, .split-view-menu, .split-view-arrange, [role='dialog'], .session-folder-view-menu, .session-context-menu, .session-folder-menu")) return null;
|
|
53
|
+
var folder = element.closest(".session-folder");
|
|
54
|
+
if (folder && !element.closest(".session-folder-unit, .session-item, .split-view-group")) {
|
|
55
|
+
var folderId = folder.dataset.folderId;
|
|
56
|
+
if (folderId === "favorites" || folderId === "unfiled" || currentFolderState().folders.some(function (item) { return item.id === folderId; })) {
|
|
57
|
+
return { folderId: folderId, element: folder, sidebar: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
51
60
|
var sidebar = sidebarTargetAt(element);
|
|
52
61
|
if (sidebar) return sidebar;
|
|
53
62
|
var pane = element.closest('.split-pane');
|
|
@@ -143,6 +152,11 @@ function renderPreview(drag, x, y) {
|
|
|
143
152
|
if (!preview || !target) { if (preview) preview.hidden = true; return; }
|
|
144
153
|
if (target.sidebar) {
|
|
145
154
|
preview.hidden = true;
|
|
155
|
+
if (target.folderId) {
|
|
156
|
+
target.element.classList.add("session-folder-drop");
|
|
157
|
+
ghost.textContent += target.folderId === "favorites" ? " · Add to Favorites" : " · Move to folder";
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
146
160
|
var sameView = target.viewId && target.viewId === drag.sourceViewId;
|
|
147
161
|
var view = viewForOrigin(target.origin), blocked = !sameView && view && view.sessions.length >= 8;
|
|
148
162
|
target.element.dataset.splitViewDrop = blocked ? 'blocked' : 'ready';
|
|
@@ -181,6 +195,7 @@ function renderPreview(drag, x, y) {
|
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
function clearSidebarPreview() {
|
|
198
|
+
document.querySelectorAll('.session-folder-drop').forEach(function (element) { element.classList.remove('session-folder-drop'); });
|
|
184
199
|
document.querySelectorAll('[data-split-view-drop]').forEach(function (element) { delete element.dataset.splitViewDrop; });
|
|
185
200
|
}
|
|
186
201
|
|
|
@@ -225,6 +240,15 @@ function onPointerUp(event) {
|
|
|
225
240
|
cleanup(true, true); showToast("Eight sessions are already open. Close a pane to add another.", "warn"); return;
|
|
226
241
|
}
|
|
227
242
|
cleanup(true, true);
|
|
243
|
+
if (target && target.folderId) {
|
|
244
|
+
var session = sessionById(drag.sessionId);
|
|
245
|
+
if (!validSession(session) || session.sessionOriginId !== drag.origin) return;
|
|
246
|
+
var view = viewForOrigin(drag.origin), favorite = target.folderId === 'favorites';
|
|
247
|
+
var folderId = target.folderId === 'unfiled' ? null : target.folderId;
|
|
248
|
+
if (view) organizePresentationView(view.id, view.revision, favorite ? view.folderId || null : folderId, favorite ? true : view.favorite === true);
|
|
249
|
+
else sendFolderOp(favorite ? { op: 'set_favorite', sessionId: session.id, favorite: true } : { op: 'place_session', sessionId: session.id, folderId: folderId });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
228
252
|
if (target && drag.origin !== target.origin && commitDrop) commitDrop(drag, target);
|
|
229
253
|
}
|
|
230
254
|
|
|
@@ -108,6 +108,9 @@ export function handleProjectSplitViews(msg) {
|
|
|
108
108
|
renderActive();
|
|
109
109
|
return true;
|
|
110
110
|
}
|
|
111
|
+
// A repeated read must not cancel a captured gesture in an unchanged view.
|
|
112
|
+
if (state.projectSplitViewHydrated && !state.projectSplitViewPending && !msg.error && !state.projectSplitViewError &&
|
|
113
|
+
msg.revision === state.projectSplitViewRevision && JSON.stringify(nextViews) === JSON.stringify(state.projectSplitViews)) return true;
|
|
111
114
|
var pending = state.projectSplitViewPending;
|
|
112
115
|
if (state.projectSplitViewDrag || state.projectSplitViewDividerDrag) cleanupTransientInteractions(state);
|
|
113
116
|
var activeId = state.projectSplitViewActiveId;
|
|
@@ -195,7 +198,7 @@ function mutation(type, body, meta) {
|
|
|
195
198
|
}
|
|
196
199
|
return true;
|
|
197
200
|
}
|
|
198
|
-
function setActiveView(id, preferredOrigin) {
|
|
201
|
+
export function setActiveView(id, preferredOrigin) {
|
|
199
202
|
var state = store.snap(), view = viewExists(state.projectSplitViews, id);
|
|
200
203
|
if (!view) return false;
|
|
201
204
|
try {
|
|
@@ -217,7 +220,7 @@ function checkMountedPaneDrafts() {
|
|
|
217
220
|
if (!host) return;
|
|
218
221
|
Array.from(host.querySelectorAll('.split-pane-frame')).forEach(function (frame) { if (frame.clayPane) frame.clayPane.checkDraft(); });
|
|
219
222
|
}
|
|
220
|
-
function switchToStandalone(sessionId) {
|
|
223
|
+
export function switchToStandalone(sessionId) {
|
|
221
224
|
var state = store.snap();
|
|
222
225
|
try {
|
|
223
226
|
if (state.projectSplitViewActiveId || state.splitPanes) checkMountedPaneDrafts();
|