From 67a542c93c97516cc1db03a56d0489ecd6533d1b Mon Sep 17 00:00:00 2001 From: Graham Steffaniak Date: Mon, 9 Jun 2025 16:41:37 -0500 Subject: [PATCH 1/4] updated with editor bugfix --- frontend/public/config.generated.yaml | 2 +- frontend/src/views/files/Editor.vue | 90 ++++++++++++++++----------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/frontend/public/config.generated.yaml b/frontend/public/config.generated.yaml index 82765aa5d..ed6294cf6 100644 --- a/frontend/public/config.generated.yaml +++ b/frontend/public/config.generated.yaml @@ -1,5 +1,5 @@ server: - numImageProcessors: 8 # number of concurrent image processing jobs used to create previews, default is number of cpu cores available. + numImageProcessors: 14 # number of concurrent image processing jobs used to create previews, default is number of cpu cores available. socket: "" # socket to listen on tlsKey: "" # path to TLS key tlsCert: "" # path to TLS cert diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index a491c7d0b..31a217273 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -8,6 +8,8 @@ import { eventBus } from "@/store/eventBus"; import { state, getters, mutations } from "@/store"; import { filesApi } from "@/api"; +// Assuming 'notify' is a utility you have for showing notifications +import { notify } from "@/notify"; import ace, { version as ace_version } from "ace-builds"; import modelist from "ace-builds/src-noconflict/ext-modelist"; import "ace-builds/src-min-noconflict/theme-chrome"; @@ -18,6 +20,8 @@ export default { data: function () { return { editor: null, // The editor instance + // Initialize filename from the route it's created with + filename: "", }; }, computed: { @@ -25,20 +29,26 @@ export default { return getters.isDarkMode(); }, }, - watch: { - $route() { - if (this.editor) { - this.editor.destroy(); - this.editor = null; - } - // Wait for the DOM to update after the route change - this.$nextTick(() => { - this.setupEditor(); - }); - }, + // Use beforeRouteUpdate to react to file changes + beforeRouteUpdate(to, from, next) { + + // Destroy the old editor instance to ensure a clean state + if (this.editor) { + this.editor.destroy(); + this.editor = null; + } + + // Call setupEditor on the next DOM update cycle + this.$nextTick(() => { + this.setupEditor(); + }); + + // Continue with the navigation + next(); }, created() { window.addEventListener("keydown", this.keyEvent); + eventBus.on("handleEditorValueRequest", this.handleEditorValueRequest); }, beforeUnmount() { window.removeEventListener("keydown", this.keyEvent); @@ -47,28 +57,33 @@ export default { } }, mounted: function () { - mutations.resetSelected(); - mutations.addSelected({ - name: state.req.name, - path: state.req.path, - size: state.req.size, - type: state.req.type, - source: state.req.source, - url: state.req.url, - }); - // Wait for the initial DOM render to complete - this.$nextTick(() => { - this.setupEditor(); - }); - eventBus.on("handleEditorValueRequest", this.handleEditorValueRequest); + // This will run only when the component is first added to the page + this.setupEditor(); }, methods: { - setupEditor() { + setupEditor(attempt = 1) { + this.filename = decodeURIComponent(this.$route.path.split("/").pop()) + // Safety Check 1: Use the component's 'filename' data property for comparison + if (state.req.name !== this.filename) { + if (attempt < 5) { + console.warn( + `[Attempt ${attempt}/5] State filename ("${state.req.name}") does not match route filename ("${this.filename}"). Retrying in 500ms...` + ); + setTimeout(() => this.setupEditor(attempt + 1), 500); + } else { + const errorMsg = `[FATAL] Failed to sync state with the route for "${this.filename}" after 5 attempts. Aborting editor setup to prevent data corruption.`; + console.error(errorMsg); + notify.showError(errorMsg); // Using the custom notifier + } + return; + } + + console.log( + "State and route are in sync. Proceeding with editor setup for", + this.filename + ); const editorEl = document.getElementById("editor"); if (!editorEl) { - console.warn( - "Editor component mounted, but #editor div was not found in the DOM. Aborting setup." - ); return; } @@ -76,24 +91,27 @@ export default { "basePath", `https://cdn.jsdelivr.net/npm/ace-builds@${ace_version}/src-min-noconflict/` ); - const fileContent = state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || ""; - this.editor = ace.edit(editorEl, { mode: modelist.getModeForPath(state.req.name).mode, value: fileContent, showPrintMargin: false, - theme: "ace/theme/chrome", + theme: this.isDarkMode ? "ace/theme/twilight" : "ace/theme/chrome", readOnly: state.req.type === "textImmutable", wrap: false, }); - - if (this.isDarkMode) { - this.editor.setTheme("ace/theme/twilight"); - } + this.filename = decodeURIComponent(this.$route.path.split("/").pop()) }, handleEditorValueRequest() { + // Safety Check 2: Final verification before saving + if (state.req.name !== this.filename) { + // Corrected the error message to be more accurate + const errorMsg = `CRITICAL: Save operation aborted. The application's active file ("${state.req.name}") does not match the file you are trying to save ("${this.filename}").`; + notify.showError(errorMsg); + return; + } + if (this.editor) { filesApi.put(state.req.path, state.req.source, this.editor.getValue()); } From 0b5c4a39c555d4c6c1d1517ea14f359f0a82f590 Mon Sep 17 00:00:00 2001 From: Graham Steffaniak Date: Mon, 9 Jun 2025 16:45:10 -0500 Subject: [PATCH 2/4] updated editor with safety --- frontend/src/views/files/Editor.vue | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index 31a217273..81d00c96c 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -31,7 +31,6 @@ export default { }, // Use beforeRouteUpdate to react to file changes beforeRouteUpdate(to, from, next) { - // Destroy the old editor instance to ensure a clean state if (this.editor) { this.editor.destroy(); @@ -62,7 +61,7 @@ export default { }, methods: { setupEditor(attempt = 1) { - this.filename = decodeURIComponent(this.$route.path.split("/").pop()) + this.filename = decodeURIComponent(this.$route.path.split("/").pop()); // Safety Check 1: Use the component's 'filename' data property for comparison if (state.req.name !== this.filename) { if (attempt < 5) { @@ -101,27 +100,37 @@ export default { readOnly: state.req.type === "textImmutable", wrap: false, }); - this.filename = decodeURIComponent(this.$route.path.split("/").pop()) + this.filename = decodeURIComponent(this.$route.path.split("/").pop()); }, handleEditorValueRequest() { // Safety Check 2: Final verification before saving if (state.req.name !== this.filename) { // Corrected the error message to be more accurate - const errorMsg = `CRITICAL: Save operation aborted. The application's active file ("${state.req.name}") does not match the file you are trying to save ("${this.filename}").`; + const errorMsg = `Save operation aborted. The application's active file ("${state.req.name}") does not match the file you are trying to save ("${this.filename}").`; notify.showError(errorMsg); return; } - - if (this.editor) { - filesApi.put(state.req.path, state.req.source, this.editor.getValue()); + try { + // Attempt to save the editor content + if (this.editor) { + filesApi.put(state.req.path, state.req.source, this.editor.getValue()); + } else { + notify.showError("Editor instance is not initialized."); + return; + } + notify.showSuccess("File saved successfully."); + } catch (error) { + notify.showError("Failed to save file. Please try again."); } }, keyEvent(event) { const { key, ctrlKey, metaKey } = event; if (getters.currentPromptName() != null) return; - if (!ctrlKey && !metaKey) return; - if (key.toLowerCase() === "s") { + // Check if either Ctrl or Cmd is pressed along with the 'S' key. + if ((ctrlKey || metaKey) && key.toLowerCase() === "s") { + // This is the save shortcut, so prevent the browser's default save action. event.preventDefault(); + // Call your save handler. this.handleEditorValueRequest(); } }, From 085ee99bb5872e5660c897a287612e74f1601197 Mon Sep 17 00:00:00 2001 From: Graham Steffaniak Date: Mon, 9 Jun 2025 16:46:33 -0500 Subject: [PATCH 3/4] streamline --- frontend/src/views/files/Editor.vue | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index 81d00c96c..8f26a1b60 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -8,7 +8,6 @@ import { eventBus } from "@/store/eventBus"; import { state, getters, mutations } from "@/store"; import { filesApi } from "@/api"; -// Assuming 'notify' is a utility you have for showing notifications import { notify } from "@/notify"; import ace, { version as ace_version } from "ace-builds"; import modelist from "ace-builds/src-noconflict/ext-modelist"; @@ -20,7 +19,6 @@ export default { data: function () { return { editor: null, // The editor instance - // Initialize filename from the route it's created with filename: "", }; }, @@ -29,7 +27,6 @@ export default { return getters.isDarkMode(); }, }, - // Use beforeRouteUpdate to react to file changes beforeRouteUpdate(to, from, next) { // Destroy the old editor instance to ensure a clean state if (this.editor) { @@ -56,7 +53,6 @@ export default { } }, mounted: function () { - // This will run only when the component is first added to the page this.setupEditor(); }, methods: { @@ -111,7 +107,6 @@ export default { return; } try { - // Attempt to save the editor content if (this.editor) { filesApi.put(state.req.path, state.req.source, this.editor.getValue()); } else { @@ -126,11 +121,8 @@ export default { keyEvent(event) { const { key, ctrlKey, metaKey } = event; if (getters.currentPromptName() != null) return; - // Check if either Ctrl or Cmd is pressed along with the 'S' key. if ((ctrlKey || metaKey) && key.toLowerCase() === "s") { - // This is the save shortcut, so prevent the browser's default save action. event.preventDefault(); - // Call your save handler. this.handleEditorValueRequest(); } }, From ff4f20d155a2a708ccd3ee6936806d73f637efce Mon Sep 17 00:00:00 2001 From: Graham Steffaniak Date: Mon, 9 Jun 2025 16:56:59 -0500 Subject: [PATCH 4/4] updated with bugfix --- CHANGELOG.md | 13 +++++++++++++ frontend/src/views/files/Editor.vue | 6 +++++- frontend/src/views/files/ListingView.vue | 9 ++++----- frontend/tests-playwright/noauth-setup.ts | 3 ++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9541f41f0..3613d91d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version). +## v0.7.8-beta + + + **New Features**: + - + + **Notes**: + - + + **BugFixes**: + - fix save editor info sometimes saves wrong file. https://github.com/gtsteffaniak/filebrowser/issues/701 + - make ctrl select work on mac or windows. https://github.com/gtsteffaniak/filebrowser/issues/739 + ## v0.7.7-beta This release cleans up some of the native preview (image preview) feature logic. And adds simple docx and epub viewers as well. Going through all of this, I think I know how I can add full-fledge google doc and microsoft office viewer support (no edit). But, for now "onlyOffice" remains the most comprehensive solution with most compatibility and ability to fully edit. One day, I think I will be able to integrate a minimal license-free server into the docker image. But that's something for another time. diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index 8f26a1b60..93740c2a2 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -57,6 +57,7 @@ export default { }, methods: { setupEditor(attempt = 1) { + try { this.filename = decodeURIComponent(this.$route.path.split("/").pop()); // Safety Check 1: Use the component's 'filename' data property for comparison if (state.req.name !== this.filename) { @@ -97,6 +98,9 @@ export default { wrap: false, }); this.filename = decodeURIComponent(this.$route.path.split("/").pop()); + } catch (error) { + notify.showError("Failed to initialize the editor. Please reload."); + } }, handleEditorValueRequest() { // Safety Check 2: Final verification before saving @@ -113,7 +117,7 @@ export default { notify.showError("Editor instance is not initialized."); return; } - notify.showSuccess("File saved successfully."); + notify.showSuccess(`${this.filename} saved successfully.`); } catch (error) { notify.showError("Failed to save file. Please try again."); } diff --git a/frontend/src/views/files/ListingView.vue b/frontend/src/views/files/ListingView.vue index fe387440c..db5516f29 100644 --- a/frontend/src/views/files/ListingView.vue +++ b/frontend/src/views/files/ListingView.vue @@ -557,8 +557,9 @@ export default { } }, clearCtrKey(event) { - const { ctrlKey } = event; - if (!ctrlKey) { + const { ctrlKey, metaKey } = event; + const modifierKeys = ctrlKey || metaKey; + if (!modifierKeys) { this.ctrKeyPressed = false; } }, @@ -594,9 +595,7 @@ export default { let newPath = currentPath.substring(0, currentPath.lastIndexOf("/")); if (modifierKeys) { - if (ctrlKey) { - this.ctrKeyPressed = true; - } + this.ctrKeyPressed = true; return; } diff --git a/frontend/tests-playwright/noauth-setup.ts b/frontend/tests-playwright/noauth-setup.ts index 547cd9a76..43c8183c1 100644 --- a/frontend/tests-playwright/noauth-setup.ts +++ b/frontend/tests-playwright/noauth-setup.ts @@ -6,7 +6,8 @@ async function globalSetup() { const context = await browser.newContext(); const page: Page = await context.newPage(); - await page.goto("http://127.0.0.1/files", { timeout: 500 }); + await page.goto("http://127.0.0.1/files"); + await page.waitForURL("**/files/", { timeout: 1000 }); // Create a share of folder await page.locator('a[aria-label="myfolder"]').waitFor({ state: 'visible' });