@chrysb/alphaclaw 0.3.5-beta.0 → 0.4.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 +66 -32
- package/lib/public/assets/icons/google_icon.svg +8 -0
- package/lib/public/css/explorer.css +254 -6
- package/lib/public/js/app.js +165 -100
- package/lib/public/js/components/channels.js +1 -0
- package/lib/public/js/components/credentials-modal.js +36 -8
- package/lib/public/js/components/file-tree.js +267 -88
- package/lib/public/js/components/file-viewer/constants.js +6 -0
- package/lib/public/js/components/file-viewer/diff-viewer.js +46 -0
- package/lib/public/js/components/file-viewer/editor-surface.js +120 -0
- package/lib/public/js/components/file-viewer/frontmatter-panel.js +56 -0
- package/lib/public/js/components/file-viewer/index.js +202 -0
- package/lib/public/js/components/file-viewer/markdown-split-view.js +51 -0
- package/lib/public/js/components/file-viewer/media-preview.js +44 -0
- package/lib/public/js/components/file-viewer/scroll-sync.js +95 -0
- package/lib/public/js/components/file-viewer/sqlite-viewer.js +167 -0
- package/lib/public/js/components/file-viewer/status-banners.js +64 -0
- package/lib/public/js/components/file-viewer/storage.js +58 -0
- package/lib/public/js/components/file-viewer/toolbar.js +119 -0
- package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +93 -0
- package/lib/public/js/components/file-viewer/use-file-diff.js +60 -0
- package/lib/public/js/components/file-viewer/use-file-loader.js +312 -0
- package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +32 -0
- package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +25 -0
- package/lib/public/js/components/file-viewer/use-file-viewer.js +471 -0
- package/lib/public/js/components/file-viewer/utils.js +11 -0
- package/lib/public/js/components/gateway.js +83 -30
- package/lib/public/js/components/google/account-row.js +98 -0
- package/lib/public/js/components/google/add-account-modal.js +93 -0
- package/lib/public/js/components/google/index.js +439 -0
- package/lib/public/js/components/google/use-google-accounts.js +41 -0
- package/lib/public/js/components/icons.js +39 -0
- package/lib/public/js/components/sidebar-git-panel.js +115 -25
- package/lib/public/js/components/sidebar.js +91 -75
- package/lib/public/js/components/usage-tab.js +4 -1
- package/lib/public/js/components/watchdog-tab.js +6 -0
- package/lib/public/js/lib/api.js +88 -8
- package/lib/public/js/lib/browse-file-policies.js +52 -0
- package/lib/public/js/lib/syntax-highlighters/index.js +6 -5
- package/lib/public/shared/browse-file-policies.json +13 -0
- package/lib/scripts/git +40 -0
- package/lib/scripts/git-askpass +6 -0
- package/lib/server/constants.js +20 -0
- package/lib/server/google-state.js +187 -0
- package/lib/server/helpers.js +12 -4
- package/lib/server/onboarding/github.js +21 -2
- package/lib/server/onboarding/index.js +1 -3
- package/lib/server/onboarding/openclaw.js +3 -0
- package/lib/server/onboarding/workspace.js +40 -0
- package/lib/server/routes/browse/constants.js +51 -0
- package/lib/server/routes/browse/file-helpers.js +43 -0
- package/lib/server/routes/browse/git.js +131 -0
- package/lib/server/routes/browse/index.js +660 -0
- package/lib/server/routes/browse/path-utils.js +53 -0
- package/lib/server/routes/browse/sqlite.js +140 -0
- package/lib/server/routes/google.js +414 -213
- package/lib/server/routes/proxy.js +11 -5
- package/lib/setup/core-prompts/TOOLS.md +0 -4
- package/lib/setup/gitignore +3 -0
- package/lib/setup/hourly-git-sync.sh +28 -1
- package/package.json +1 -1
- package/lib/public/js/components/file-viewer.js +0 -1095
- package/lib/public/js/components/google.js +0 -228
- package/lib/server/routes/browse.js +0 -500
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
const EditorTextarea = ({
|
|
7
|
+
overlay = false,
|
|
8
|
+
editorTextareaRef,
|
|
9
|
+
renderContent,
|
|
10
|
+
handleContentInput,
|
|
11
|
+
handleEditorScroll,
|
|
12
|
+
handleEditorSelectionChange,
|
|
13
|
+
isEditBlocked,
|
|
14
|
+
isPreviewOnly,
|
|
15
|
+
}) => html`
|
|
16
|
+
<textarea
|
|
17
|
+
class=${overlay ? "file-viewer-editor file-viewer-editor-overlay" : "file-viewer-editor"}
|
|
18
|
+
ref=${editorTextareaRef}
|
|
19
|
+
value=${renderContent}
|
|
20
|
+
onInput=${handleContentInput}
|
|
21
|
+
onScroll=${handleEditorScroll}
|
|
22
|
+
onSelect=${handleEditorSelectionChange}
|
|
23
|
+
onKeyUp=${handleEditorSelectionChange}
|
|
24
|
+
onClick=${handleEditorSelectionChange}
|
|
25
|
+
disabled=${isEditBlocked || isPreviewOnly}
|
|
26
|
+
readonly=${isEditBlocked || isPreviewOnly}
|
|
27
|
+
spellcheck=${false}
|
|
28
|
+
autocorrect="off"
|
|
29
|
+
autocapitalize="off"
|
|
30
|
+
autocomplete="off"
|
|
31
|
+
data-gramm="false"
|
|
32
|
+
data-gramm_editor="false"
|
|
33
|
+
data-enable-grammarly="false"
|
|
34
|
+
wrap="soft"
|
|
35
|
+
></textarea>
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export const EditorSurface = ({
|
|
39
|
+
editorShellClassName = "file-viewer-editor-shell",
|
|
40
|
+
editorShellAriaHidden,
|
|
41
|
+
editorLineNumbers,
|
|
42
|
+
editorLineNumbersRef,
|
|
43
|
+
editorLineNumberRowRefs,
|
|
44
|
+
shouldUseHighlightedEditor,
|
|
45
|
+
highlightedEditorLines,
|
|
46
|
+
editorHighlightRef,
|
|
47
|
+
editorHighlightLineRefs,
|
|
48
|
+
editorTextareaRef,
|
|
49
|
+
renderContent,
|
|
50
|
+
handleContentInput,
|
|
51
|
+
handleEditorScroll,
|
|
52
|
+
handleEditorSelectionChange,
|
|
53
|
+
isEditBlocked,
|
|
54
|
+
isPreviewOnly,
|
|
55
|
+
}) => html`
|
|
56
|
+
<div class=${editorShellClassName} aria-hidden=${editorShellAriaHidden}>
|
|
57
|
+
<div class="file-viewer-editor-line-num-col" ref=${editorLineNumbersRef}>
|
|
58
|
+
${editorLineNumbers.map(
|
|
59
|
+
(lineNumber) => html`
|
|
60
|
+
<div
|
|
61
|
+
class="file-viewer-editor-line-num"
|
|
62
|
+
key=${lineNumber}
|
|
63
|
+
ref=${(element) => {
|
|
64
|
+
editorLineNumberRowRefs.current[lineNumber - 1] = element;
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
${lineNumber}
|
|
68
|
+
</div>
|
|
69
|
+
`,
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
${shouldUseHighlightedEditor
|
|
73
|
+
? html`
|
|
74
|
+
<div class="file-viewer-editor-stack">
|
|
75
|
+
<div class="file-viewer-editor-highlight" ref=${editorHighlightRef}>
|
|
76
|
+
${highlightedEditorLines.map(
|
|
77
|
+
(line) => html`
|
|
78
|
+
<div
|
|
79
|
+
class="file-viewer-editor-highlight-line"
|
|
80
|
+
key=${line.lineNumber}
|
|
81
|
+
ref=${(element) => {
|
|
82
|
+
editorHighlightLineRefs.current[line.lineNumber - 1] = element;
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<span
|
|
86
|
+
class="file-viewer-editor-highlight-line-content"
|
|
87
|
+
dangerouslySetInnerHTML=${{
|
|
88
|
+
__html: line.html,
|
|
89
|
+
}}
|
|
90
|
+
></span>
|
|
91
|
+
</div>
|
|
92
|
+
`,
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
<${EditorTextarea}
|
|
96
|
+
overlay=${true}
|
|
97
|
+
editorTextareaRef=${editorTextareaRef}
|
|
98
|
+
renderContent=${renderContent}
|
|
99
|
+
handleContentInput=${handleContentInput}
|
|
100
|
+
handleEditorScroll=${handleEditorScroll}
|
|
101
|
+
handleEditorSelectionChange=${handleEditorSelectionChange}
|
|
102
|
+
isEditBlocked=${isEditBlocked}
|
|
103
|
+
isPreviewOnly=${isPreviewOnly}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
: html`
|
|
108
|
+
<${EditorTextarea}
|
|
109
|
+
overlay=${false}
|
|
110
|
+
editorTextareaRef=${editorTextareaRef}
|
|
111
|
+
renderContent=${renderContent}
|
|
112
|
+
handleContentInput=${handleContentInput}
|
|
113
|
+
handleEditorScroll=${handleEditorScroll}
|
|
114
|
+
handleEditorSelectionChange=${handleEditorSelectionChange}
|
|
115
|
+
isEditBlocked=${isEditBlocked}
|
|
116
|
+
isPreviewOnly=${isPreviewOnly}
|
|
117
|
+
/>
|
|
118
|
+
`}
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { formatFrontmatterValue } from "../../lib/syntax-highlighters/index.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
export const FrontmatterPanel = ({
|
|
8
|
+
isMarkdownFile,
|
|
9
|
+
parsedFrontmatter,
|
|
10
|
+
frontmatterCollapsed,
|
|
11
|
+
setFrontmatterCollapsed,
|
|
12
|
+
}) => {
|
|
13
|
+
if (!isMarkdownFile || parsedFrontmatter.entries.length <= 0) return null;
|
|
14
|
+
|
|
15
|
+
return html`
|
|
16
|
+
<div class="frontmatter-box">
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
class="frontmatter-title"
|
|
20
|
+
onclick=${() => setFrontmatterCollapsed((collapsed) => !collapsed)}
|
|
21
|
+
>
|
|
22
|
+
<span
|
|
23
|
+
class=${`frontmatter-chevron ${frontmatterCollapsed ? "" : "open"}`}
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
>
|
|
26
|
+
<svg viewBox="0 0 20 20" focusable="false">
|
|
27
|
+
<path d="M7 4l6 6-6 6" />
|
|
28
|
+
</svg>
|
|
29
|
+
</span>
|
|
30
|
+
<span>frontmatter</span>
|
|
31
|
+
</button>
|
|
32
|
+
${!frontmatterCollapsed
|
|
33
|
+
? html`
|
|
34
|
+
<div class="frontmatter-grid">
|
|
35
|
+
${parsedFrontmatter.entries.map((entry) => {
|
|
36
|
+
const formattedValue = formatFrontmatterValue(entry.rawValue);
|
|
37
|
+
const isMultilineValue = formattedValue.includes("\n");
|
|
38
|
+
return html`
|
|
39
|
+
<div class="frontmatter-row" key=${entry.key}>
|
|
40
|
+
<div class="frontmatter-key">${entry.key}</div>
|
|
41
|
+
${isMultilineValue
|
|
42
|
+
? html`
|
|
43
|
+
<pre class="frontmatter-value frontmatter-value-pre">
|
|
44
|
+
${formattedValue}</pre
|
|
45
|
+
>
|
|
46
|
+
`
|
|
47
|
+
: html`<div class="frontmatter-value">${formattedValue}</div>`}
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
})}
|
|
51
|
+
</div>
|
|
52
|
+
`
|
|
53
|
+
: null}
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
56
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useState } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
import { LoadingSpinner } from "../loading-spinner.js";
|
|
5
|
+
import { ConfirmDialog } from "../confirm-dialog.js";
|
|
6
|
+
import { SqliteViewer } from "./sqlite-viewer.js";
|
|
7
|
+
import { FileViewerToolbar } from "./toolbar.js";
|
|
8
|
+
import { FileViewerStatusBanners } from "./status-banners.js";
|
|
9
|
+
import { FrontmatterPanel } from "./frontmatter-panel.js";
|
|
10
|
+
import { DiffViewer } from "./diff-viewer.js";
|
|
11
|
+
import { MediaPreview } from "./media-preview.js";
|
|
12
|
+
import { EditorSurface } from "./editor-surface.js";
|
|
13
|
+
import { MarkdownSplitView } from "./markdown-split-view.js";
|
|
14
|
+
import { kSqlitePageSize } from "./constants.js";
|
|
15
|
+
import { useFileViewer } from "./use-file-viewer.js";
|
|
16
|
+
|
|
17
|
+
const html = htm.bind(h);
|
|
18
|
+
|
|
19
|
+
export const FileViewer = ({
|
|
20
|
+
filePath = "",
|
|
21
|
+
isPreviewOnly = false,
|
|
22
|
+
browseView = "edit",
|
|
23
|
+
onRequestEdit = () => {},
|
|
24
|
+
onRequestClearSelection = () => {},
|
|
25
|
+
}) => {
|
|
26
|
+
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
|
27
|
+
const { state, derived, refs, actions, context } = useFileViewer({
|
|
28
|
+
filePath,
|
|
29
|
+
isPreviewOnly,
|
|
30
|
+
browseView,
|
|
31
|
+
onRequestClearSelection,
|
|
32
|
+
onRequestEdit,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!state.hasSelectedPath) {
|
|
36
|
+
return html`
|
|
37
|
+
<div class="file-viewer-empty">
|
|
38
|
+
<div class="file-viewer-empty-mark">[ ]</div>
|
|
39
|
+
<div class="file-viewer-empty-title">Browse and edit files<br />Syncs to git</div>
|
|
40
|
+
</div>
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return html`
|
|
45
|
+
<div class="file-viewer">
|
|
46
|
+
<${FileViewerToolbar}
|
|
47
|
+
pathSegments=${derived.pathSegments}
|
|
48
|
+
isDirty=${derived.isDirty}
|
|
49
|
+
isPreviewOnly=${state.isPreviewOnly}
|
|
50
|
+
isDiffView=${state.isDiffView}
|
|
51
|
+
isMarkdownFile=${state.isMarkdownFile}
|
|
52
|
+
viewMode=${state.viewMode}
|
|
53
|
+
handleChangeViewMode=${actions.handleChangeViewMode}
|
|
54
|
+
handleSave=${actions.handleSave}
|
|
55
|
+
loading=${state.loading}
|
|
56
|
+
canEditFile=${derived.canEditFile}
|
|
57
|
+
isEditBlocked=${derived.isEditBlocked}
|
|
58
|
+
isImageFile=${state.isImageFile}
|
|
59
|
+
isAudioFile=${state.isAudioFile}
|
|
60
|
+
isSqliteFile=${state.isSqliteFile}
|
|
61
|
+
saving=${state.saving}
|
|
62
|
+
deleting=${state.deleting}
|
|
63
|
+
restoring=${state.restoring}
|
|
64
|
+
canDeleteFile=${derived.canDeleteFile}
|
|
65
|
+
isDeleteBlocked=${derived.isDeleteBlocked}
|
|
66
|
+
isProtectedFile=${derived.isProtectedFile}
|
|
67
|
+
canRestoreDeletedDiff=${state.isDiffView && !!state.diffStatus?.isDeleted}
|
|
68
|
+
onRequestDelete=${() => setDeleteConfirmOpen(true)}
|
|
69
|
+
onRequestRestore=${actions.handleRestore}
|
|
70
|
+
/>
|
|
71
|
+
<${FileViewerStatusBanners}
|
|
72
|
+
isDiffView=${state.isDiffView}
|
|
73
|
+
onRequestEdit=${onRequestEdit}
|
|
74
|
+
normalizedPath=${context.normalizedPath}
|
|
75
|
+
isDeletedDiff=${!!state.diffStatus?.isDeleted}
|
|
76
|
+
isLockedFile=${derived.isLockedFile}
|
|
77
|
+
isProtectedFile=${derived.isProtectedFile}
|
|
78
|
+
isProtectedLocked=${derived.isProtectedLocked}
|
|
79
|
+
handleEditProtectedFile=${actions.handleEditProtectedFile}
|
|
80
|
+
/>
|
|
81
|
+
${!state.isDiffView
|
|
82
|
+
? html`
|
|
83
|
+
<${FrontmatterPanel}
|
|
84
|
+
isMarkdownFile=${state.isMarkdownFile}
|
|
85
|
+
parsedFrontmatter=${derived.parsedFrontmatter}
|
|
86
|
+
frontmatterCollapsed=${state.frontmatterCollapsed}
|
|
87
|
+
setFrontmatterCollapsed=${actions.setFrontmatterCollapsed}
|
|
88
|
+
/>
|
|
89
|
+
`
|
|
90
|
+
: null}
|
|
91
|
+
${state.loading
|
|
92
|
+
? html`
|
|
93
|
+
<div class="file-viewer-loading-shell">
|
|
94
|
+
${state.showDelayedLoadingSpinner
|
|
95
|
+
? html`<${LoadingSpinner} className="h-4 w-4" />`
|
|
96
|
+
: null}
|
|
97
|
+
</div>
|
|
98
|
+
`
|
|
99
|
+
: state.error
|
|
100
|
+
? html`<div class="file-viewer-state file-viewer-state-error">${state.error}</div>`
|
|
101
|
+
: state.isFolderPath
|
|
102
|
+
? html`
|
|
103
|
+
<div class="file-viewer-state">
|
|
104
|
+
Folder selected. Choose a file from this folder in the tree.
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
: state.isImageFile || state.isAudioFile
|
|
108
|
+
? html`
|
|
109
|
+
<${MediaPreview}
|
|
110
|
+
isImageFile=${state.isImageFile}
|
|
111
|
+
imageDataUrl=${state.imageDataUrl}
|
|
112
|
+
pathSegments=${derived.pathSegments}
|
|
113
|
+
isAudioFile=${state.isAudioFile}
|
|
114
|
+
audioDataUrl=${state.audioDataUrl}
|
|
115
|
+
/>
|
|
116
|
+
`
|
|
117
|
+
: state.isSqliteFile
|
|
118
|
+
? html`
|
|
119
|
+
<${SqliteViewer}
|
|
120
|
+
sqliteSummary=${state.sqliteSummary}
|
|
121
|
+
sqliteSelectedTable=${state.sqliteSelectedTable}
|
|
122
|
+
setSqliteSelectedTable=${actions.setSqliteSelectedTable}
|
|
123
|
+
sqliteTableOffset=${state.sqliteTableOffset}
|
|
124
|
+
setSqliteTableOffset=${actions.setSqliteTableOffset}
|
|
125
|
+
sqliteTableLoading=${state.sqliteTableLoading}
|
|
126
|
+
sqliteTableError=${state.sqliteTableError}
|
|
127
|
+
sqliteTableData=${state.sqliteTableData}
|
|
128
|
+
kSqlitePageSize=${kSqlitePageSize}
|
|
129
|
+
/>
|
|
130
|
+
`
|
|
131
|
+
: state.isDiffView
|
|
132
|
+
? html`
|
|
133
|
+
<${DiffViewer}
|
|
134
|
+
diffLoading=${state.diffLoading}
|
|
135
|
+
diffError=${state.diffError}
|
|
136
|
+
diffContent=${state.diffContent}
|
|
137
|
+
/>
|
|
138
|
+
`
|
|
139
|
+
: html`
|
|
140
|
+
${state.isMarkdownFile
|
|
141
|
+
? html`
|
|
142
|
+
<${MarkdownSplitView}
|
|
143
|
+
viewMode=${state.viewMode}
|
|
144
|
+
previewRef=${refs.previewRef}
|
|
145
|
+
handlePreviewScroll=${actions.handlePreviewScroll}
|
|
146
|
+
previewHtml=${state.previewHtml}
|
|
147
|
+
editorLineNumbers=${derived.editorLineNumbers}
|
|
148
|
+
editorLineNumbersRef=${refs.editorLineNumbersRef}
|
|
149
|
+
editorLineNumberRowRefs=${refs.editorLineNumberRowRefs}
|
|
150
|
+
highlightedEditorLines=${derived.highlightedEditorLines}
|
|
151
|
+
editorHighlightRef=${refs.editorHighlightRef}
|
|
152
|
+
editorHighlightLineRefs=${refs.editorHighlightLineRefs}
|
|
153
|
+
editorTextareaRef=${refs.editorTextareaRef}
|
|
154
|
+
renderContent=${state.renderContent}
|
|
155
|
+
handleContentInput=${actions.handleContentInput}
|
|
156
|
+
handleEditorScroll=${actions.handleEditorScroll}
|
|
157
|
+
handleEditorSelectionChange=${actions.handleEditorSelectionChange}
|
|
158
|
+
isEditBlocked=${derived.isEditBlocked}
|
|
159
|
+
isPreviewOnly=${state.isPreviewOnly}
|
|
160
|
+
/>
|
|
161
|
+
`
|
|
162
|
+
: html`
|
|
163
|
+
<${EditorSurface}
|
|
164
|
+
editorLineNumbers=${derived.editorLineNumbers}
|
|
165
|
+
editorLineNumbersRef=${refs.editorLineNumbersRef}
|
|
166
|
+
editorLineNumberRowRefs=${refs.editorLineNumberRowRefs}
|
|
167
|
+
shouldUseHighlightedEditor=${derived.shouldUseHighlightedEditor}
|
|
168
|
+
highlightedEditorLines=${derived.highlightedEditorLines}
|
|
169
|
+
editorHighlightRef=${refs.editorHighlightRef}
|
|
170
|
+
editorHighlightLineRefs=${refs.editorHighlightLineRefs}
|
|
171
|
+
editorTextareaRef=${refs.editorTextareaRef}
|
|
172
|
+
renderContent=${state.renderContent}
|
|
173
|
+
handleContentInput=${actions.handleContentInput}
|
|
174
|
+
handleEditorScroll=${actions.handleEditorScroll}
|
|
175
|
+
handleEditorSelectionChange=${actions.handleEditorSelectionChange}
|
|
176
|
+
isEditBlocked=${derived.isEditBlocked}
|
|
177
|
+
isPreviewOnly=${state.isPreviewOnly}
|
|
178
|
+
/>
|
|
179
|
+
`}
|
|
180
|
+
`}
|
|
181
|
+
<${ConfirmDialog}
|
|
182
|
+
visible=${deleteConfirmOpen}
|
|
183
|
+
title="Delete file?"
|
|
184
|
+
message=${`Delete ${context.normalizedPath || "this file"}? This can be restored from diff view before sync.`}
|
|
185
|
+
confirmLabel="Delete"
|
|
186
|
+
confirmLoadingLabel="Deleting..."
|
|
187
|
+
cancelLabel="Cancel"
|
|
188
|
+
confirmTone="warning"
|
|
189
|
+
confirmLoading=${state.deleting}
|
|
190
|
+
confirmDisabled=${!derived.canDeleteFile || state.deleting}
|
|
191
|
+
onCancel=${() => {
|
|
192
|
+
if (state.deleting) return;
|
|
193
|
+
setDeleteConfirmOpen(false);
|
|
194
|
+
}}
|
|
195
|
+
onConfirm=${async () => {
|
|
196
|
+
await actions.handleDelete();
|
|
197
|
+
setDeleteConfirmOpen(false);
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
`;
|
|
202
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { EditorSurface } from "./editor-surface.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
export const MarkdownSplitView = ({
|
|
8
|
+
viewMode,
|
|
9
|
+
previewRef,
|
|
10
|
+
handlePreviewScroll,
|
|
11
|
+
previewHtml,
|
|
12
|
+
editorLineNumbers,
|
|
13
|
+
editorLineNumbersRef,
|
|
14
|
+
editorLineNumberRowRefs,
|
|
15
|
+
highlightedEditorLines,
|
|
16
|
+
editorHighlightRef,
|
|
17
|
+
editorHighlightLineRefs,
|
|
18
|
+
editorTextareaRef,
|
|
19
|
+
renderContent,
|
|
20
|
+
handleContentInput,
|
|
21
|
+
handleEditorScroll,
|
|
22
|
+
handleEditorSelectionChange,
|
|
23
|
+
isEditBlocked,
|
|
24
|
+
isPreviewOnly,
|
|
25
|
+
}) => html`
|
|
26
|
+
<div
|
|
27
|
+
class=${`file-viewer-preview ${viewMode === "preview" ? "" : "file-viewer-pane-hidden"}`}
|
|
28
|
+
ref=${previewRef}
|
|
29
|
+
onscroll=${handlePreviewScroll}
|
|
30
|
+
aria-hidden=${viewMode === "preview" ? "false" : "true"}
|
|
31
|
+
dangerouslySetInnerHTML=${{ __html: previewHtml }}
|
|
32
|
+
></div>
|
|
33
|
+
<${EditorSurface}
|
|
34
|
+
editorShellClassName=${`file-viewer-editor-shell ${viewMode === "edit" ? "" : "file-viewer-pane-hidden"}`}
|
|
35
|
+
editorShellAriaHidden=${viewMode === "edit" ? "false" : "true"}
|
|
36
|
+
editorLineNumbers=${editorLineNumbers}
|
|
37
|
+
editorLineNumbersRef=${editorLineNumbersRef}
|
|
38
|
+
editorLineNumberRowRefs=${editorLineNumberRowRefs}
|
|
39
|
+
shouldUseHighlightedEditor=${true}
|
|
40
|
+
highlightedEditorLines=${highlightedEditorLines}
|
|
41
|
+
editorHighlightRef=${editorHighlightRef}
|
|
42
|
+
editorHighlightLineRefs=${editorHighlightLineRefs}
|
|
43
|
+
editorTextareaRef=${editorTextareaRef}
|
|
44
|
+
renderContent=${renderContent}
|
|
45
|
+
handleContentInput=${handleContentInput}
|
|
46
|
+
handleEditorScroll=${handleEditorScroll}
|
|
47
|
+
handleEditorSelectionChange=${handleEditorSelectionChange}
|
|
48
|
+
isEditBlocked=${isEditBlocked}
|
|
49
|
+
isPreviewOnly=${isPreviewOnly}
|
|
50
|
+
/>
|
|
51
|
+
`;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
export const MediaPreview = ({
|
|
7
|
+
isImageFile,
|
|
8
|
+
imageDataUrl,
|
|
9
|
+
pathSegments,
|
|
10
|
+
isAudioFile,
|
|
11
|
+
audioDataUrl,
|
|
12
|
+
}) => {
|
|
13
|
+
if (isImageFile) {
|
|
14
|
+
return html`
|
|
15
|
+
<div class="file-viewer-image-shell">
|
|
16
|
+
${imageDataUrl
|
|
17
|
+
? html`
|
|
18
|
+
<img
|
|
19
|
+
src=${imageDataUrl}
|
|
20
|
+
alt=${pathSegments[pathSegments.length - 1] || "Selected image"}
|
|
21
|
+
class="file-viewer-image"
|
|
22
|
+
/>
|
|
23
|
+
`
|
|
24
|
+
: html`<div class="file-viewer-state">Could not render image preview.</div>`}
|
|
25
|
+
</div>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isAudioFile) {
|
|
30
|
+
return html`
|
|
31
|
+
<div class="file-viewer-audio-shell">
|
|
32
|
+
${audioDataUrl
|
|
33
|
+
? html`
|
|
34
|
+
<audio class="file-viewer-audio-player" controls preload="metadata" src=${audioDataUrl}>
|
|
35
|
+
Your browser does not support audio playback.
|
|
36
|
+
</audio>
|
|
37
|
+
`
|
|
38
|
+
: html`<div class="file-viewer-state">Could not render audio preview.</div>`}
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useRef } from "https://esm.sh/preact/hooks";
|
|
2
|
+
|
|
3
|
+
export const getScrollRatio = (element) => {
|
|
4
|
+
if (!element) return 0;
|
|
5
|
+
const maxScrollTop = element.scrollHeight - element.clientHeight;
|
|
6
|
+
if (maxScrollTop <= 0) return 0;
|
|
7
|
+
return element.scrollTop / maxScrollTop;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const setScrollByRatio = (element, ratio) => {
|
|
11
|
+
if (!element) return;
|
|
12
|
+
const maxScrollTop = element.scrollHeight - element.clientHeight;
|
|
13
|
+
if (maxScrollTop <= 0) {
|
|
14
|
+
element.scrollTop = 0;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const clampedRatio = Math.max(0, Math.min(1, ratio));
|
|
18
|
+
element.scrollTop = maxScrollTop * clampedRatio;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const useScrollSync = ({
|
|
22
|
+
viewMode,
|
|
23
|
+
setViewMode,
|
|
24
|
+
previewRef,
|
|
25
|
+
editorTextareaRef,
|
|
26
|
+
editorLineNumbersRef,
|
|
27
|
+
editorHighlightRef,
|
|
28
|
+
}) => {
|
|
29
|
+
const viewScrollRatioRef = useRef(0);
|
|
30
|
+
const isSyncingScrollRef = useRef(false);
|
|
31
|
+
|
|
32
|
+
const handleEditorScroll = (event) => {
|
|
33
|
+
if (isSyncingScrollRef.current) return;
|
|
34
|
+
const nextScrollTop = event.currentTarget.scrollTop;
|
|
35
|
+
const nextRatio = getScrollRatio(event.currentTarget);
|
|
36
|
+
viewScrollRatioRef.current = nextRatio;
|
|
37
|
+
if (!editorLineNumbersRef.current) return;
|
|
38
|
+
editorLineNumbersRef.current.scrollTop = nextScrollTop;
|
|
39
|
+
if (editorHighlightRef.current) {
|
|
40
|
+
editorHighlightRef.current.scrollTop = nextScrollTop;
|
|
41
|
+
editorHighlightRef.current.scrollLeft = event.currentTarget.scrollLeft;
|
|
42
|
+
}
|
|
43
|
+
if (previewRef.current) {
|
|
44
|
+
isSyncingScrollRef.current = true;
|
|
45
|
+
setScrollByRatio(previewRef.current, nextRatio);
|
|
46
|
+
window.requestAnimationFrame(() => {
|
|
47
|
+
isSyncingScrollRef.current = false;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handlePreviewScroll = (event) => {
|
|
53
|
+
if (isSyncingScrollRef.current) return;
|
|
54
|
+
const nextRatio = getScrollRatio(event.currentTarget);
|
|
55
|
+
viewScrollRatioRef.current = nextRatio;
|
|
56
|
+
isSyncingScrollRef.current = true;
|
|
57
|
+
setScrollByRatio(editorTextareaRef.current, nextRatio);
|
|
58
|
+
setScrollByRatio(editorLineNumbersRef.current, nextRatio);
|
|
59
|
+
setScrollByRatio(editorHighlightRef.current, nextRatio);
|
|
60
|
+
window.requestAnimationFrame(() => {
|
|
61
|
+
isSyncingScrollRef.current = false;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleChangeViewMode = (nextMode) => {
|
|
66
|
+
if (nextMode === viewMode) return;
|
|
67
|
+
const nextRatio =
|
|
68
|
+
viewMode === "preview"
|
|
69
|
+
? getScrollRatio(previewRef.current)
|
|
70
|
+
: getScrollRatio(editorTextareaRef.current);
|
|
71
|
+
viewScrollRatioRef.current = nextRatio;
|
|
72
|
+
setViewMode(nextMode);
|
|
73
|
+
window.requestAnimationFrame(() => {
|
|
74
|
+
isSyncingScrollRef.current = true;
|
|
75
|
+
if (nextMode === "preview") {
|
|
76
|
+
setScrollByRatio(previewRef.current, nextRatio);
|
|
77
|
+
} else {
|
|
78
|
+
setScrollByRatio(editorTextareaRef.current, nextRatio);
|
|
79
|
+
setScrollByRatio(editorLineNumbersRef.current, nextRatio);
|
|
80
|
+
setScrollByRatio(editorHighlightRef.current, nextRatio);
|
|
81
|
+
}
|
|
82
|
+
window.requestAnimationFrame(() => {
|
|
83
|
+
isSyncingScrollRef.current = false;
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
viewScrollRatioRef,
|
|
90
|
+
isSyncingScrollRef,
|
|
91
|
+
handleEditorScroll,
|
|
92
|
+
handlePreviewScroll,
|
|
93
|
+
handleChangeViewMode,
|
|
94
|
+
};
|
|
95
|
+
};
|