@chrysb/alphaclaw 0.3.4 → 0.3.5-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alphaclaw.js +17 -2
- package/lib/public/css/explorer.css +184 -3
- package/lib/public/css/theme.css +1 -1
- package/lib/public/js/app.js +57 -7
- package/lib/public/js/components/file-tree.js +49 -2
- package/lib/public/js/components/file-viewer.js +435 -204
- package/lib/public/js/components/gateway.js +12 -18
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/sidebar-git-panel.js +155 -28
- package/lib/public/js/components/sidebar.js +1 -1
- package/lib/public/js/components/watchdog-tab.js +0 -2
- package/lib/public/js/lib/api.js +15 -0
- package/lib/server/helpers.js +18 -5
- package/lib/server/internal-files-migration.js +93 -0
- package/lib/server/onboarding/cron.js +6 -4
- package/lib/server/onboarding/index.js +7 -0
- package/lib/server/onboarding/openclaw.js +6 -1
- package/lib/server/routes/browse.js +233 -28
- package/lib/server/routes/pairings.js +8 -2
- package/lib/server/routes/system.js +5 -1
- package/lib/server.js +7 -0
- package/package.json +1 -1
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from "https://esm.sh/preact/hooks";
|
|
9
9
|
import htm from "https://esm.sh/htm";
|
|
10
10
|
import { marked } from "https://esm.sh/marked";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
fetchBrowseFileDiff,
|
|
13
|
+
fetchFileContent,
|
|
14
|
+
saveFileContent,
|
|
15
|
+
} from "../lib/api.js";
|
|
12
16
|
import {
|
|
13
17
|
formatFrontmatterValue,
|
|
14
18
|
getFileSyntaxKind,
|
|
@@ -24,7 +28,7 @@ import {
|
|
|
24
28
|
import { ActionButton } from "./action-button.js";
|
|
25
29
|
import { LoadingSpinner } from "./loading-spinner.js";
|
|
26
30
|
import { SegmentedControl } from "./segmented-control.js";
|
|
27
|
-
import { SaveFillIcon } from "./icons.js";
|
|
31
|
+
import { LockLineIcon, SaveFillIcon } from "./icons.js";
|
|
28
32
|
import { showToast } from "./toast.js";
|
|
29
33
|
|
|
30
34
|
const html = htm.bind(h);
|
|
@@ -32,6 +36,12 @@ const kFileViewerModeStorageKey = "alphaclaw.browse.fileViewerMode";
|
|
|
32
36
|
const kLegacyFileViewerModeStorageKey = "alphaclawBrowseFileViewerMode";
|
|
33
37
|
const kEditorSelectionStorageKey = "alphaclaw.browse.editorSelectionByPath";
|
|
34
38
|
const kProtectedBrowsePaths = new Set(["openclaw.json", "devices/paired.json"]);
|
|
39
|
+
const kLockedBrowsePaths = new Set([
|
|
40
|
+
"hooks/bootstrap/agents.md",
|
|
41
|
+
"hooks/bootstrap/tools.md",
|
|
42
|
+
".alphaclaw/hourly-git-sync.sh",
|
|
43
|
+
".alphaclaw/.cli-device-auto-approved",
|
|
44
|
+
]);
|
|
35
45
|
const kLoadingIndicatorDelayMs = 1000;
|
|
36
46
|
const kFileRefreshIntervalMs = 5000;
|
|
37
47
|
|
|
@@ -49,6 +59,20 @@ const normalizePolicyPath = (inputPath) =>
|
|
|
49
59
|
.trim()
|
|
50
60
|
.toLowerCase();
|
|
51
61
|
|
|
62
|
+
const matchesPolicyPath = (policyPathSet, normalizedPath) => {
|
|
63
|
+
const safeNormalizedPath = String(normalizedPath || "").trim();
|
|
64
|
+
if (!safeNormalizedPath) return false;
|
|
65
|
+
for (const policyPath of policyPathSet) {
|
|
66
|
+
if (
|
|
67
|
+
safeNormalizedPath === policyPath ||
|
|
68
|
+
safeNormalizedPath.endsWith(`/${policyPath}`)
|
|
69
|
+
) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
|
|
52
76
|
const readStoredFileViewerMode = () => {
|
|
53
77
|
try {
|
|
54
78
|
const storedMode = String(
|
|
@@ -68,35 +92,38 @@ const clampSelectionIndex = (value, maxValue) => {
|
|
|
68
92
|
return Math.max(0, Math.min(maxValue, numericValue));
|
|
69
93
|
};
|
|
70
94
|
|
|
71
|
-
const
|
|
72
|
-
const safePath = String(filePath || "").trim();
|
|
73
|
-
if (!safePath) return null;
|
|
95
|
+
const readEditorSelectionStorageMap = () => {
|
|
74
96
|
try {
|
|
75
|
-
const rawStorageValue = window.localStorage.getItem(
|
|
76
|
-
|
|
97
|
+
const rawStorageValue = window.localStorage.getItem(
|
|
98
|
+
kEditorSelectionStorageKey,
|
|
99
|
+
);
|
|
100
|
+
if (!rawStorageValue) return {};
|
|
77
101
|
const parsedStorageValue = JSON.parse(rawStorageValue);
|
|
78
|
-
if (!parsedStorageValue || typeof parsedStorageValue !== "object")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
start: selection.start,
|
|
83
|
-
end: selection.end,
|
|
84
|
-
};
|
|
102
|
+
if (!parsedStorageValue || typeof parsedStorageValue !== "object")
|
|
103
|
+
return {};
|
|
104
|
+
return parsedStorageValue;
|
|
85
105
|
} catch {
|
|
86
|
-
return
|
|
106
|
+
return {};
|
|
87
107
|
}
|
|
88
108
|
};
|
|
89
109
|
|
|
110
|
+
const readStoredEditorSelection = (filePath) => {
|
|
111
|
+
const safePath = String(filePath || "").trim();
|
|
112
|
+
if (!safePath) return null;
|
|
113
|
+
const storageMap = readEditorSelectionStorageMap();
|
|
114
|
+
const selection = storageMap[safePath];
|
|
115
|
+
if (!selection || typeof selection !== "object") return null;
|
|
116
|
+
return {
|
|
117
|
+
start: selection.start,
|
|
118
|
+
end: selection.end,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
90
122
|
const writeStoredEditorSelection = (filePath, selection) => {
|
|
91
123
|
const safePath = String(filePath || "").trim();
|
|
92
124
|
if (!safePath || !selection || typeof selection !== "object") return;
|
|
93
125
|
try {
|
|
94
|
-
const
|
|
95
|
-
const parsedStorageValue = rawStorageValue ? JSON.parse(rawStorageValue) : {};
|
|
96
|
-
const nextStorageValue =
|
|
97
|
-
parsedStorageValue && typeof parsedStorageValue === "object"
|
|
98
|
-
? parsedStorageValue
|
|
99
|
-
: {};
|
|
126
|
+
const nextStorageValue = readEditorSelectionStorageMap();
|
|
100
127
|
nextStorageValue[safePath] = {
|
|
101
128
|
start: selection.start,
|
|
102
129
|
end: selection.end,
|
|
@@ -108,10 +135,11 @@ const writeStoredEditorSelection = (filePath, selection) => {
|
|
|
108
135
|
} catch {}
|
|
109
136
|
};
|
|
110
137
|
|
|
111
|
-
|
|
112
138
|
export const FileViewer = ({
|
|
113
139
|
filePath = "",
|
|
114
140
|
isPreviewOnly = false,
|
|
141
|
+
browseView = "edit",
|
|
142
|
+
onRequestEdit = () => {},
|
|
115
143
|
}) => {
|
|
116
144
|
const normalizedPath = String(filePath || "").trim();
|
|
117
145
|
const normalizedPolicyPath = normalizePolicyPath(normalizedPath);
|
|
@@ -121,11 +149,15 @@ export const FileViewer = ({
|
|
|
121
149
|
const [loading, setLoading] = useState(false);
|
|
122
150
|
const [showDelayedLoadingSpinner, setShowDelayedLoadingSpinner] =
|
|
123
151
|
useState(false);
|
|
152
|
+
const [diffLoading, setDiffLoading] = useState(false);
|
|
153
|
+
const [diffError, setDiffError] = useState("");
|
|
154
|
+
const [diffContent, setDiffContent] = useState("");
|
|
124
155
|
const [saving, setSaving] = useState(false);
|
|
125
156
|
const [error, setError] = useState("");
|
|
126
157
|
const [isFolderPath, setIsFolderPath] = useState(false);
|
|
127
158
|
const [frontmatterCollapsed, setFrontmatterCollapsed] = useState(false);
|
|
128
|
-
const [externalChangeNoticeShown, setExternalChangeNoticeShown] =
|
|
159
|
+
const [externalChangeNoticeShown, setExternalChangeNoticeShown] =
|
|
160
|
+
useState(false);
|
|
129
161
|
const [protectedEditBypassPaths, setProtectedEditBypassPaths] = useState(
|
|
130
162
|
() => new Set(),
|
|
131
163
|
);
|
|
@@ -147,11 +179,17 @@ export const FileViewer = ({
|
|
|
147
179
|
);
|
|
148
180
|
const hasSelectedPath = normalizedPath.length > 0;
|
|
149
181
|
const canEditFile = hasSelectedPath && !isFolderPath && !isPreviewOnly;
|
|
182
|
+
const isDiffView = String(browseView || "edit") === "diff";
|
|
150
183
|
const isDirty = canEditFile && content !== initialContent;
|
|
184
|
+
const isLockedFile =
|
|
185
|
+
canEditFile && matchesPolicyPath(kLockedBrowsePaths, normalizedPolicyPath);
|
|
151
186
|
const isProtectedFile =
|
|
152
|
-
canEditFile &&
|
|
187
|
+
canEditFile &&
|
|
188
|
+
!isLockedFile &&
|
|
189
|
+
matchesPolicyPath(kProtectedBrowsePaths, normalizedPolicyPath);
|
|
153
190
|
const isProtectedLocked =
|
|
154
191
|
isProtectedFile && !protectedEditBypassPaths.has(normalizedPolicyPath);
|
|
192
|
+
const isEditBlocked = isLockedFile || isProtectedLocked;
|
|
155
193
|
const syntaxKind = useMemo(
|
|
156
194
|
() => getFileSyntaxKind(normalizedPath),
|
|
157
195
|
[normalizedPath],
|
|
@@ -315,7 +353,9 @@ export const FileViewer = ({
|
|
|
315
353
|
dispatchEvent: (event) => window.dispatchEvent(event),
|
|
316
354
|
});
|
|
317
355
|
setExternalChangeNoticeShown(false);
|
|
318
|
-
window.dispatchEvent(
|
|
356
|
+
window.dispatchEvent(
|
|
357
|
+
new CustomEvent("alphaclaw:browse-tree-refresh"),
|
|
358
|
+
);
|
|
319
359
|
return;
|
|
320
360
|
}
|
|
321
361
|
if (!externalChangeNoticeShown) {
|
|
@@ -331,7 +371,10 @@ export const FileViewer = ({
|
|
|
331
371
|
fileRefreshInFlightRef.current = false;
|
|
332
372
|
}
|
|
333
373
|
};
|
|
334
|
-
const intervalId = window.setInterval(
|
|
374
|
+
const intervalId = window.setInterval(
|
|
375
|
+
refreshFromDisk,
|
|
376
|
+
kFileRefreshIntervalMs,
|
|
377
|
+
);
|
|
335
378
|
return () => {
|
|
336
379
|
window.clearInterval(intervalId);
|
|
337
380
|
};
|
|
@@ -347,6 +390,36 @@ export const FileViewer = ({
|
|
|
347
390
|
externalChangeNoticeShown,
|
|
348
391
|
]);
|
|
349
392
|
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
let active = true;
|
|
395
|
+
if (!hasSelectedPath || !isDiffView || isPreviewOnly) {
|
|
396
|
+
setDiffLoading(false);
|
|
397
|
+
setDiffError("");
|
|
398
|
+
setDiffContent("");
|
|
399
|
+
return () => {
|
|
400
|
+
active = false;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const loadDiff = async () => {
|
|
404
|
+
setDiffLoading(true);
|
|
405
|
+
setDiffError("");
|
|
406
|
+
try {
|
|
407
|
+
const data = await fetchBrowseFileDiff(normalizedPath);
|
|
408
|
+
if (!active) return;
|
|
409
|
+
setDiffContent(String(data?.content || ""));
|
|
410
|
+
} catch (nextError) {
|
|
411
|
+
if (!active) return;
|
|
412
|
+
setDiffError(nextError.message || "Could not load diff");
|
|
413
|
+
} finally {
|
|
414
|
+
if (active) setDiffLoading(false);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
loadDiff();
|
|
418
|
+
return () => {
|
|
419
|
+
active = false;
|
|
420
|
+
};
|
|
421
|
+
}, [hasSelectedPath, isDiffView, isPreviewOnly, normalizedPath]);
|
|
422
|
+
|
|
350
423
|
useEffect(() => {
|
|
351
424
|
if (loadedFilePathRef.current !== normalizedPath) return;
|
|
352
425
|
if (!canEditFile || !hasSelectedPath || loading) return;
|
|
@@ -371,26 +444,79 @@ export const FileViewer = ({
|
|
|
371
444
|
]);
|
|
372
445
|
|
|
373
446
|
useEffect(() => {
|
|
374
|
-
if (!canEditFile || loading || !hasSelectedPath) return;
|
|
375
|
-
if (loadedFilePathRef.current !== normalizedPath) return;
|
|
376
|
-
if (restoredSelectionPathRef.current === normalizedPath) return;
|
|
377
|
-
|
|
378
|
-
if (!textareaElement) return;
|
|
447
|
+
if (!canEditFile || loading || !hasSelectedPath) return () => {};
|
|
448
|
+
if (loadedFilePathRef.current !== normalizedPath) return () => {};
|
|
449
|
+
if (restoredSelectionPathRef.current === normalizedPath) return () => {};
|
|
450
|
+
if (viewMode !== "edit") return () => {};
|
|
379
451
|
const storedSelection = readStoredEditorSelection(normalizedPath);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
452
|
+
if (!storedSelection) {
|
|
453
|
+
restoredSelectionPathRef.current = normalizedPath;
|
|
454
|
+
return () => {};
|
|
455
|
+
}
|
|
456
|
+
let frameId = 0;
|
|
457
|
+
let attempts = 0;
|
|
458
|
+
const restoreSelection = () => {
|
|
459
|
+
const textareaElement = editorTextareaRef.current;
|
|
460
|
+
if (!textareaElement) {
|
|
461
|
+
attempts += 1;
|
|
462
|
+
if (attempts < 6)
|
|
463
|
+
frameId = window.requestAnimationFrame(restoreSelection);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const maxIndex = String(content || "").length;
|
|
467
|
+
const start = clampSelectionIndex(storedSelection.start, maxIndex);
|
|
468
|
+
const end = clampSelectionIndex(storedSelection.end, maxIndex);
|
|
469
|
+
textareaElement.focus();
|
|
470
|
+
textareaElement.setSelectionRange(start, Math.max(start, end));
|
|
471
|
+
window.requestAnimationFrame(() => {
|
|
472
|
+
const nextTextareaElement = editorTextareaRef.current;
|
|
473
|
+
if (!nextTextareaElement) return;
|
|
474
|
+
const safeContent = String(content || "");
|
|
475
|
+
const safeStart = clampSelectionIndex(start, safeContent.length);
|
|
476
|
+
const lineIndex =
|
|
477
|
+
safeContent.slice(0, safeStart).split("\n").length - 1;
|
|
478
|
+
const computedStyle = window.getComputedStyle(nextTextareaElement);
|
|
479
|
+
const parsedLineHeight = Number.parseFloat(
|
|
480
|
+
computedStyle.lineHeight || "",
|
|
481
|
+
);
|
|
482
|
+
const lineHeight =
|
|
483
|
+
Number.isFinite(parsedLineHeight) && parsedLineHeight > 0
|
|
484
|
+
? parsedLineHeight
|
|
485
|
+
: 20;
|
|
486
|
+
const nextScrollTop = Math.max(
|
|
487
|
+
0,
|
|
488
|
+
lineIndex * lineHeight - nextTextareaElement.clientHeight * 0.4,
|
|
489
|
+
);
|
|
490
|
+
nextTextareaElement.scrollTop = nextScrollTop;
|
|
491
|
+
if (editorLineNumbersRef.current) {
|
|
492
|
+
editorLineNumbersRef.current.scrollTop = nextScrollTop;
|
|
493
|
+
}
|
|
494
|
+
if (editorHighlightRef.current) {
|
|
495
|
+
editorHighlightRef.current.scrollTop = nextScrollTop;
|
|
496
|
+
}
|
|
497
|
+
viewScrollRatioRef.current = getScrollRatio(nextTextareaElement);
|
|
498
|
+
});
|
|
499
|
+
restoredSelectionPathRef.current = normalizedPath;
|
|
500
|
+
};
|
|
501
|
+
frameId = window.requestAnimationFrame(restoreSelection);
|
|
502
|
+
return () => {
|
|
503
|
+
if (frameId) window.cancelAnimationFrame(frameId);
|
|
504
|
+
};
|
|
505
|
+
}, [
|
|
506
|
+
canEditFile,
|
|
507
|
+
loading,
|
|
508
|
+
hasSelectedPath,
|
|
509
|
+
normalizedPath,
|
|
510
|
+
content,
|
|
511
|
+
viewMode,
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
const handleSave = useCallback(async () => {
|
|
515
|
+
if (!canEditFile || saving || !isDirty || isEditBlocked) return;
|
|
390
516
|
setSaving(true);
|
|
391
517
|
setError("");
|
|
392
518
|
try {
|
|
393
|
-
|
|
519
|
+
await saveFileContent(normalizedPath, content);
|
|
394
520
|
setInitialContent(content);
|
|
395
521
|
setExternalChangeNoticeShown(false);
|
|
396
522
|
clearStoredFileDraft(normalizedPath);
|
|
@@ -402,11 +528,7 @@ export const FileViewer = ({
|
|
|
402
528
|
detail: { path: normalizedPath },
|
|
403
529
|
}),
|
|
404
530
|
);
|
|
405
|
-
|
|
406
|
-
showToast("Saved, but git sync failed", "error");
|
|
407
|
-
} else {
|
|
408
|
-
showToast("Saved and synced", "success");
|
|
409
|
-
}
|
|
531
|
+
showToast("Saved", "success");
|
|
410
532
|
} catch (saveError) {
|
|
411
533
|
const message = saveError.message || "Could not save file";
|
|
412
534
|
setError(message);
|
|
@@ -414,7 +536,31 @@ export const FileViewer = ({
|
|
|
414
536
|
} finally {
|
|
415
537
|
setSaving(false);
|
|
416
538
|
}
|
|
417
|
-
}
|
|
539
|
+
}, [
|
|
540
|
+
canEditFile,
|
|
541
|
+
saving,
|
|
542
|
+
isDirty,
|
|
543
|
+
isEditBlocked,
|
|
544
|
+
normalizedPath,
|
|
545
|
+
content,
|
|
546
|
+
initialContent,
|
|
547
|
+
]);
|
|
548
|
+
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
const handleKeyDown = (event) => {
|
|
551
|
+
const isSaveShortcut =
|
|
552
|
+
(event.metaKey || event.ctrlKey) &&
|
|
553
|
+
!event.shiftKey &&
|
|
554
|
+
!event.altKey &&
|
|
555
|
+
String(event.key || "").toLowerCase() === "s";
|
|
556
|
+
if (!isSaveShortcut) return;
|
|
557
|
+
if (!canEditFile || isPreviewOnly || isDiffView || viewMode !== "edit") return;
|
|
558
|
+
event.preventDefault();
|
|
559
|
+
void handleSave();
|
|
560
|
+
};
|
|
561
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
562
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
563
|
+
}, [canEditFile, isPreviewOnly, isDiffView, viewMode, handleSave]);
|
|
418
564
|
|
|
419
565
|
const handleEditProtectedFile = () => {
|
|
420
566
|
if (!normalizedPolicyPath) return;
|
|
@@ -426,7 +572,7 @@ export const FileViewer = ({
|
|
|
426
572
|
};
|
|
427
573
|
|
|
428
574
|
const handleContentInput = (event) => {
|
|
429
|
-
if (
|
|
575
|
+
if (isEditBlocked || isPreviewOnly) return;
|
|
430
576
|
const nextContent = event.target.value;
|
|
431
577
|
setContent(nextContent);
|
|
432
578
|
if (hasSelectedPath && canEditFile) {
|
|
@@ -571,7 +717,8 @@ export const FileViewer = ({
|
|
|
571
717
|
${isPreviewOnly
|
|
572
718
|
? html`<div class="file-viewer-preview-pill">Preview</div>`
|
|
573
719
|
: null}
|
|
574
|
-
${
|
|
720
|
+
${!isDiffView &&
|
|
721
|
+
isMarkdownFile &&
|
|
575
722
|
html`
|
|
576
723
|
<${SegmentedControl}
|
|
577
724
|
className="mr-2.5"
|
|
@@ -583,20 +730,49 @@ export const FileViewer = ({
|
|
|
583
730
|
onChange=${handleChangeViewMode}
|
|
584
731
|
/>
|
|
585
732
|
`}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
733
|
+
${!isDiffView
|
|
734
|
+
? html`
|
|
735
|
+
<${ActionButton}
|
|
736
|
+
onClick=${handleSave}
|
|
737
|
+
disabled=${loading || !isDirty || !canEditFile || isEditBlocked}
|
|
738
|
+
loading=${saving}
|
|
739
|
+
tone=${isDirty ? "primary" : "secondary"}
|
|
740
|
+
size="sm"
|
|
741
|
+
idleLabel="Save"
|
|
742
|
+
loadingLabel="Saving..."
|
|
743
|
+
idleIcon=${SaveFillIcon}
|
|
744
|
+
idleIconClassName="file-viewer-save-icon"
|
|
745
|
+
className="file-viewer-save-action"
|
|
746
|
+
/>
|
|
747
|
+
`
|
|
748
|
+
: null}
|
|
598
749
|
</div>
|
|
599
|
-
${
|
|
750
|
+
${isDiffView
|
|
751
|
+
? html`
|
|
752
|
+
<div class="file-viewer-protected-banner file-viewer-diff-banner">
|
|
753
|
+
<div class="file-viewer-protected-banner-text">
|
|
754
|
+
Viewing unsynced changes
|
|
755
|
+
</div>
|
|
756
|
+
<${ActionButton}
|
|
757
|
+
onClick=${() => onRequestEdit(normalizedPath)}
|
|
758
|
+
tone="secondary"
|
|
759
|
+
size="sm"
|
|
760
|
+
idleLabel="View file"
|
|
761
|
+
/>
|
|
762
|
+
</div>
|
|
763
|
+
`
|
|
764
|
+
: null}
|
|
765
|
+
${!isDiffView && isLockedFile
|
|
766
|
+
? html`
|
|
767
|
+
<div class="file-viewer-protected-banner is-locked">
|
|
768
|
+
<${LockLineIcon} className="file-viewer-protected-banner-icon" />
|
|
769
|
+
<div class="file-viewer-protected-banner-text">
|
|
770
|
+
This file is managed by Alpha Claw and cannot be edited.
|
|
771
|
+
</div>
|
|
772
|
+
</div>
|
|
773
|
+
`
|
|
774
|
+
: null}
|
|
775
|
+
${!isDiffView && isProtectedFile
|
|
600
776
|
? html`
|
|
601
777
|
<div class="file-viewer-protected-banner">
|
|
602
778
|
<div class="file-viewer-protected-banner-text">
|
|
@@ -683,139 +859,215 @@ ${formattedValue}</pre
|
|
|
683
859
|
Folder selected. Choose a file from this folder in the tree.
|
|
684
860
|
</div>
|
|
685
861
|
`
|
|
686
|
-
:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
862
|
+
: isDiffView
|
|
863
|
+
? html`
|
|
864
|
+
<div class="file-viewer-diff-shell">
|
|
865
|
+
${diffLoading
|
|
866
|
+
? html`
|
|
867
|
+
<div class="file-viewer-loading-shell">
|
|
868
|
+
<${LoadingSpinner} className="h-4 w-4" />
|
|
869
|
+
</div>
|
|
870
|
+
`
|
|
871
|
+
: diffError
|
|
872
|
+
? html`
|
|
873
|
+
<div
|
|
874
|
+
class="file-viewer-state file-viewer-state-error"
|
|
875
|
+
>
|
|
876
|
+
${diffError}
|
|
877
|
+
</div>
|
|
878
|
+
`
|
|
879
|
+
: html`
|
|
880
|
+
<pre class="file-viewer-diff-pre">
|
|
881
|
+
${(diffContent || "").split("\n").map((line, lineIndex) => {
|
|
882
|
+
const lineClass =
|
|
883
|
+
line.startsWith("+") &&
|
|
884
|
+
!line.startsWith("+++")
|
|
885
|
+
? "is-added"
|
|
886
|
+
: line.startsWith("-") &&
|
|
887
|
+
!line.startsWith("---")
|
|
888
|
+
? "is-removed"
|
|
889
|
+
: line.startsWith("@@")
|
|
890
|
+
? "is-hunk"
|
|
891
|
+
: line.startsWith("diff ") ||
|
|
892
|
+
line.startsWith("index ") ||
|
|
893
|
+
line.startsWith("--- ") ||
|
|
894
|
+
line.startsWith("+++ ")
|
|
895
|
+
? "is-header"
|
|
896
|
+
: "";
|
|
897
|
+
return html`
|
|
898
|
+
<div
|
|
899
|
+
key=${`${lineIndex}:${line.slice(0, 20)}`}
|
|
900
|
+
class=${`file-viewer-diff-line ${lineClass}`.trim()}
|
|
901
|
+
>
|
|
902
|
+
${line || " "}
|
|
903
|
+
</div>
|
|
904
|
+
`;
|
|
905
|
+
})}
|
|
906
|
+
</pre
|
|
907
|
+
>
|
|
908
|
+
`}
|
|
909
|
+
</div>
|
|
910
|
+
`
|
|
911
|
+
: html`
|
|
912
|
+
${isMarkdownFile
|
|
913
|
+
? html`
|
|
700
914
|
<div
|
|
701
|
-
class
|
|
702
|
-
ref=${
|
|
915
|
+
class=${`file-viewer-preview ${viewMode === "preview" ? "" : "file-viewer-pane-hidden"}`}
|
|
916
|
+
ref=${previewRef}
|
|
917
|
+
onscroll=${handlePreviewScroll}
|
|
918
|
+
aria-hidden=${viewMode === "preview"
|
|
919
|
+
? "false"
|
|
920
|
+
: "true"}
|
|
921
|
+
dangerouslySetInnerHTML=${{ __html: previewHtml }}
|
|
922
|
+
></div>
|
|
923
|
+
<div
|
|
924
|
+
class=${`file-viewer-editor-shell ${viewMode === "edit" ? "" : "file-viewer-pane-hidden"}`}
|
|
925
|
+
aria-hidden=${viewMode === "edit" ? "false" : "true"}
|
|
703
926
|
>
|
|
704
|
-
${editorLineNumbers.map(
|
|
705
|
-
(lineNumber) => html`
|
|
706
|
-
<div
|
|
707
|
-
class="file-viewer-editor-line-num"
|
|
708
|
-
key=${lineNumber}
|
|
709
|
-
ref=${(element) => {
|
|
710
|
-
editorLineNumberRowRefs.current[
|
|
711
|
-
lineNumber - 1
|
|
712
|
-
] = element;
|
|
713
|
-
}}
|
|
714
|
-
>
|
|
715
|
-
${lineNumber}
|
|
716
|
-
</div>
|
|
717
|
-
`,
|
|
718
|
-
)}
|
|
719
|
-
</div>
|
|
720
|
-
<div class="file-viewer-editor-stack">
|
|
721
927
|
<div
|
|
722
|
-
class="file-viewer-editor-
|
|
723
|
-
ref=${
|
|
928
|
+
class="file-viewer-editor-line-num-col"
|
|
929
|
+
ref=${editorLineNumbersRef}
|
|
724
930
|
>
|
|
725
|
-
${
|
|
726
|
-
(
|
|
931
|
+
${editorLineNumbers.map(
|
|
932
|
+
(lineNumber) => html`
|
|
727
933
|
<div
|
|
728
|
-
class="file-viewer-editor-
|
|
729
|
-
key=${
|
|
934
|
+
class="file-viewer-editor-line-num"
|
|
935
|
+
key=${lineNumber}
|
|
730
936
|
ref=${(element) => {
|
|
731
|
-
|
|
732
|
-
|
|
937
|
+
editorLineNumberRowRefs.current[
|
|
938
|
+
lineNumber - 1
|
|
733
939
|
] = element;
|
|
734
940
|
}}
|
|
735
941
|
>
|
|
736
|
-
|
|
737
|
-
class="file-viewer-editor-highlight-line-content"
|
|
738
|
-
dangerouslySetInnerHTML=${{
|
|
739
|
-
__html: line.html,
|
|
740
|
-
}}
|
|
741
|
-
></span>
|
|
942
|
+
${lineNumber}
|
|
742
943
|
</div>
|
|
743
944
|
`,
|
|
744
945
|
)}
|
|
745
946
|
</div>
|
|
746
|
-
<
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
947
|
+
<div class="file-viewer-editor-stack">
|
|
948
|
+
<div
|
|
949
|
+
class="file-viewer-editor-highlight"
|
|
950
|
+
ref=${editorHighlightRef}
|
|
951
|
+
>
|
|
952
|
+
${highlightedEditorLines.map(
|
|
953
|
+
(line) => html`
|
|
954
|
+
<div
|
|
955
|
+
class="file-viewer-editor-highlight-line"
|
|
956
|
+
key=${line.lineNumber}
|
|
957
|
+
ref=${(element) => {
|
|
958
|
+
editorHighlightLineRefs.current[
|
|
959
|
+
line.lineNumber - 1
|
|
960
|
+
] = element;
|
|
961
|
+
}}
|
|
962
|
+
>
|
|
963
|
+
<span
|
|
964
|
+
class="file-viewer-editor-highlight-line-content"
|
|
965
|
+
dangerouslySetInnerHTML=${{
|
|
966
|
+
__html: line.html,
|
|
967
|
+
}}
|
|
968
|
+
></span>
|
|
969
|
+
</div>
|
|
970
|
+
`,
|
|
971
|
+
)}
|
|
972
|
+
</div>
|
|
973
|
+
<textarea
|
|
974
|
+
class="file-viewer-editor file-viewer-editor-overlay"
|
|
975
|
+
ref=${editorTextareaRef}
|
|
976
|
+
value=${content}
|
|
977
|
+
onInput=${handleContentInput}
|
|
978
|
+
onScroll=${handleEditorScroll}
|
|
979
|
+
onSelect=${handleEditorSelectionChange}
|
|
980
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
981
|
+
onClick=${handleEditorSelectionChange}
|
|
982
|
+
disabled=${isEditBlocked || isPreviewOnly}
|
|
983
|
+
readonly=${isEditBlocked || isPreviewOnly}
|
|
984
|
+
spellcheck=${false}
|
|
985
|
+
autocorrect="off"
|
|
986
|
+
autocapitalize="off"
|
|
987
|
+
autocomplete="off"
|
|
988
|
+
data-gramm="false"
|
|
989
|
+
data-gramm_editor="false"
|
|
990
|
+
data-enable-grammarly="false"
|
|
991
|
+
wrap="soft"
|
|
992
|
+
></textarea>
|
|
993
|
+
</div>
|
|
788
994
|
</div>
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
995
|
+
`
|
|
996
|
+
: html`
|
|
997
|
+
<div class="file-viewer-editor-shell">
|
|
998
|
+
<div
|
|
999
|
+
class="file-viewer-editor-line-num-col"
|
|
1000
|
+
ref=${editorLineNumbersRef}
|
|
1001
|
+
>
|
|
1002
|
+
${editorLineNumbers.map(
|
|
1003
|
+
(lineNumber) => html`
|
|
792
1004
|
<div
|
|
793
|
-
class="file-viewer-editor-
|
|
794
|
-
|
|
1005
|
+
class="file-viewer-editor-line-num"
|
|
1006
|
+
key=${lineNumber}
|
|
1007
|
+
ref=${(element) => {
|
|
1008
|
+
editorLineNumberRowRefs.current[
|
|
1009
|
+
lineNumber - 1
|
|
1010
|
+
] = element;
|
|
1011
|
+
}}
|
|
795
1012
|
>
|
|
796
|
-
${
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1013
|
+
${lineNumber}
|
|
1014
|
+
</div>
|
|
1015
|
+
`,
|
|
1016
|
+
)}
|
|
1017
|
+
</div>
|
|
1018
|
+
${shouldUseHighlightedEditor
|
|
1019
|
+
? html`
|
|
1020
|
+
<div class="file-viewer-editor-stack">
|
|
1021
|
+
<div
|
|
1022
|
+
class="file-viewer-editor-highlight"
|
|
1023
|
+
ref=${editorHighlightRef}
|
|
1024
|
+
>
|
|
1025
|
+
${highlightedEditorLines.map(
|
|
1026
|
+
(line) => html`
|
|
1027
|
+
<div
|
|
1028
|
+
class="file-viewer-editor-highlight-line"
|
|
1029
|
+
key=${line.lineNumber}
|
|
1030
|
+
ref=${(element) => {
|
|
1031
|
+
editorHighlightLineRefs.current[
|
|
1032
|
+
line.lineNumber - 1
|
|
1033
|
+
] = element;
|
|
811
1034
|
}}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1035
|
+
>
|
|
1036
|
+
<span
|
|
1037
|
+
class="file-viewer-editor-highlight-line-content"
|
|
1038
|
+
dangerouslySetInnerHTML=${{
|
|
1039
|
+
__html: line.html,
|
|
1040
|
+
}}
|
|
1041
|
+
></span>
|
|
1042
|
+
</div>
|
|
1043
|
+
`,
|
|
1044
|
+
)}
|
|
1045
|
+
</div>
|
|
1046
|
+
<textarea
|
|
1047
|
+
class="file-viewer-editor file-viewer-editor-overlay"
|
|
1048
|
+
ref=${editorTextareaRef}
|
|
1049
|
+
value=${content}
|
|
1050
|
+
onInput=${handleContentInput}
|
|
1051
|
+
onScroll=${handleEditorScroll}
|
|
1052
|
+
onSelect=${handleEditorSelectionChange}
|
|
1053
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
1054
|
+
onClick=${handleEditorSelectionChange}
|
|
1055
|
+
disabled=${isEditBlocked || isPreviewOnly}
|
|
1056
|
+
readonly=${isEditBlocked || isPreviewOnly}
|
|
1057
|
+
spellcheck=${false}
|
|
1058
|
+
autocorrect="off"
|
|
1059
|
+
autocapitalize="off"
|
|
1060
|
+
autocomplete="off"
|
|
1061
|
+
data-gramm="false"
|
|
1062
|
+
data-gramm_editor="false"
|
|
1063
|
+
data-enable-grammarly="false"
|
|
1064
|
+
wrap="soft"
|
|
1065
|
+
></textarea>
|
|
816
1066
|
</div>
|
|
1067
|
+
`
|
|
1068
|
+
: html`
|
|
817
1069
|
<textarea
|
|
818
|
-
class="file-viewer-editor
|
|
1070
|
+
class="file-viewer-editor"
|
|
819
1071
|
ref=${editorTextareaRef}
|
|
820
1072
|
value=${content}
|
|
821
1073
|
onInput=${handleContentInput}
|
|
@@ -823,7 +1075,8 @@ ${formattedValue}</pre
|
|
|
823
1075
|
onSelect=${handleEditorSelectionChange}
|
|
824
1076
|
onKeyUp=${handleEditorSelectionChange}
|
|
825
1077
|
onClick=${handleEditorSelectionChange}
|
|
826
|
-
|
|
1078
|
+
disabled=${isEditBlocked || isPreviewOnly}
|
|
1079
|
+
readonly=${isEditBlocked || isPreviewOnly}
|
|
827
1080
|
spellcheck=${false}
|
|
828
1081
|
autocorrect="off"
|
|
829
1082
|
autocapitalize="off"
|
|
@@ -833,32 +1086,10 @@ ${formattedValue}</pre
|
|
|
833
1086
|
data-enable-grammarly="false"
|
|
834
1087
|
wrap="soft"
|
|
835
1088
|
></textarea>
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
class="file-viewer-editor"
|
|
841
|
-
ref=${editorTextareaRef}
|
|
842
|
-
value=${content}
|
|
843
|
-
onInput=${handleContentInput}
|
|
844
|
-
onScroll=${handleEditorScroll}
|
|
845
|
-
onSelect=${handleEditorSelectionChange}
|
|
846
|
-
onKeyUp=${handleEditorSelectionChange}
|
|
847
|
-
onClick=${handleEditorSelectionChange}
|
|
848
|
-
readonly=${isProtectedLocked || isPreviewOnly}
|
|
849
|
-
spellcheck=${false}
|
|
850
|
-
autocorrect="off"
|
|
851
|
-
autocapitalize="off"
|
|
852
|
-
autocomplete="off"
|
|
853
|
-
data-gramm="false"
|
|
854
|
-
data-gramm_editor="false"
|
|
855
|
-
data-enable-grammarly="false"
|
|
856
|
-
wrap="soft"
|
|
857
|
-
></textarea>
|
|
858
|
-
`}
|
|
859
|
-
</div>
|
|
860
|
-
`}
|
|
861
|
-
`}
|
|
1089
|
+
`}
|
|
1090
|
+
</div>
|
|
1091
|
+
`}
|
|
1092
|
+
`}
|
|
862
1093
|
</div>
|
|
863
1094
|
`;
|
|
864
1095
|
};
|