feat: file edit added
This commit is contained in:
@@ -23,6 +23,11 @@ let state = {
|
||||
};
|
||||
const ROW_JUMP_STEP = 10;
|
||||
let wildcardDialogMode = "select";
|
||||
let editorState = {
|
||||
path: null,
|
||||
originalContent: "",
|
||||
modified: null,
|
||||
};
|
||||
|
||||
function paneState(pane) {
|
||||
return state.panes[pane];
|
||||
@@ -70,6 +75,20 @@ function viewerElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function editorElements() {
|
||||
return {
|
||||
overlay: document.getElementById("editor-modal"),
|
||||
title: document.getElementById("editor-title"),
|
||||
fileName: document.getElementById("editor-file-name"),
|
||||
filePath: document.getElementById("editor-file-path"),
|
||||
error: document.getElementById("editor-error"),
|
||||
content: document.getElementById("editor-content"),
|
||||
closeButton: document.getElementById("editor-close-btn"),
|
||||
saveButton: document.getElementById("editor-save-btn"),
|
||||
cancelButton: document.getElementById("editor-cancel-btn"),
|
||||
};
|
||||
}
|
||||
|
||||
async function apiRequest(method, url, body) {
|
||||
const options = { method, headers: {} };
|
||||
if (body !== undefined) {
|
||||
@@ -158,12 +177,25 @@ function updateActionButtons() {
|
||||
const exactlyOne = count === 1;
|
||||
const allFiles = hasSelection && selectedItems.every((item) => item.kind === "file");
|
||||
document.getElementById("view-btn").disabled = !exactlyOne || !allFiles;
|
||||
document.getElementById("edit-btn").disabled = !exactlyOne || !allFiles || !isEditableSelection(selectedItems[0] || null);
|
||||
document.getElementById("rename-btn").disabled = !exactlyOne;
|
||||
document.getElementById("delete-btn").disabled = !hasSelection;
|
||||
document.getElementById("copy-btn").disabled = !allFiles;
|
||||
document.getElementById("move-btn").disabled = !allFiles;
|
||||
}
|
||||
|
||||
function isEditableSelection(item) {
|
||||
if (!item || item.kind !== "file") {
|
||||
return false;
|
||||
}
|
||||
const name = item.name || "";
|
||||
const lower = name.toLowerCase();
|
||||
if (lower === "dockerfile" || lower === "containerfile") {
|
||||
return true;
|
||||
}
|
||||
return [".txt", ".log", ".md", ".yml", ".yaml", ".json", ".js", ".css", ".html"].some((suffix) => lower.endsWith(suffix));
|
||||
}
|
||||
|
||||
function currentParentPath(path) {
|
||||
if (!path.includes("/")) {
|
||||
return null;
|
||||
@@ -650,6 +682,10 @@ function isViewerOpen() {
|
||||
return !viewerElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function isEditorOpen() {
|
||||
return !editorElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
||||
}
|
||||
@@ -739,6 +775,33 @@ function closeViewer() {
|
||||
viewer.content.textContent = "";
|
||||
}
|
||||
|
||||
function editorIsDirty() {
|
||||
return editorElements().content.value !== editorState.originalContent;
|
||||
}
|
||||
|
||||
function resetEditorState() {
|
||||
editorState = {
|
||||
path: null,
|
||||
originalContent: "",
|
||||
modified: null,
|
||||
};
|
||||
}
|
||||
|
||||
function attemptCloseEditor() {
|
||||
if (editorIsDirty() && !window.confirm("Discard unsaved changes?")) {
|
||||
return;
|
||||
}
|
||||
closeEditor();
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
const editor = editorElements();
|
||||
editor.overlay.classList.add("hidden");
|
||||
editor.error.textContent = "";
|
||||
editor.content.value = "";
|
||||
resetEditorState();
|
||||
}
|
||||
|
||||
async function openViewer() {
|
||||
const selectedItems = activePaneState().selectedItems;
|
||||
if (selectedItems.length !== 1 || selectedItems[0].kind !== "file") {
|
||||
@@ -766,6 +829,59 @@ async function openViewer() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openEditor() {
|
||||
const selectedItems = activePaneState().selectedItems;
|
||||
if (selectedItems.length !== 1 || !isEditableSelection(selectedItems[0])) {
|
||||
return;
|
||||
}
|
||||
const selected = selectedItems[0];
|
||||
const editor = editorElements();
|
||||
editor.overlay.classList.remove("hidden");
|
||||
editor.title.textContent = "Edit";
|
||||
editor.fileName.textContent = selected.name;
|
||||
editor.filePath.textContent = selected.path;
|
||||
editor.error.textContent = "";
|
||||
editor.content.value = "";
|
||||
editor.content.disabled = true;
|
||||
editor.saveButton.disabled = true;
|
||||
try {
|
||||
const data = await apiRequest("GET", `/api/files/view?${new URLSearchParams({ path: selected.path, for_edit: "true" }).toString()}`);
|
||||
editor.fileName.textContent = data.name;
|
||||
editor.filePath.textContent = data.path;
|
||||
editor.content.value = data.content;
|
||||
editor.content.disabled = false;
|
||||
editor.saveButton.disabled = false;
|
||||
editorState.path = data.path;
|
||||
editorState.originalContent = data.content;
|
||||
editorState.modified = data.modified;
|
||||
editor.content.focus();
|
||||
} catch (err) {
|
||||
editor.error.textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveEditor() {
|
||||
if (!editorState.path) {
|
||||
return;
|
||||
}
|
||||
const editor = editorElements();
|
||||
editor.error.textContent = "";
|
||||
try {
|
||||
const response = await apiRequest("POST", "/api/files/save", {
|
||||
path: editorState.path,
|
||||
content: editor.content.value,
|
||||
expected_modified: editorState.modified,
|
||||
});
|
||||
editorState.originalContent = editor.content.value;
|
||||
editorState.modified = response.modified;
|
||||
setStatus(`Saved ${response.path}`);
|
||||
closeEditor();
|
||||
await loadBrowsePane(state.activePane);
|
||||
} catch (err) {
|
||||
editor.error.textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
function moveCurrentRow(delta) {
|
||||
const pane = state.activePane;
|
||||
const model = paneState(pane);
|
||||
@@ -821,6 +937,13 @@ function clearSelectionForActivePane() {
|
||||
}
|
||||
|
||||
function handleKeyboardShortcuts(event) {
|
||||
if (isEditorOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
attemptCloseEditor();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isViewerOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
@@ -910,6 +1033,7 @@ function setupEvents() {
|
||||
setupPaneEvents("right");
|
||||
document.addEventListener("keydown", handleKeyboardShortcuts);
|
||||
document.getElementById("view-btn").onclick = openViewer;
|
||||
document.getElementById("edit-btn").onclick = openEditor;
|
||||
document.getElementById("rename-btn").onclick = renameSelected;
|
||||
document.getElementById("delete-btn").onclick = deleteSelected;
|
||||
document.getElementById("copy-btn").onclick = startCopySelected;
|
||||
@@ -943,6 +1067,16 @@ function setupEvents() {
|
||||
closeViewer();
|
||||
}
|
||||
};
|
||||
|
||||
const editor = editorElements();
|
||||
editor.closeButton.onclick = attemptCloseEditor;
|
||||
editor.cancelButton.onclick = attemptCloseEditor;
|
||||
editor.saveButton.onclick = saveEditor;
|
||||
editor.overlay.onclick = (event) => {
|
||||
if (event.target === editor.overlay) {
|
||||
attemptCloseEditor();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function init() {
|
||||
|
||||
Reference in New Issue
Block a user