Compare commits

...

8 Commits

Author SHA1 Message Date
transifex-integration[bot]
14ee054359
feat: Translate frontend/src/i18n/en.json in sk 2025-07-27 18:06:44 +02:00
Henrique Dias
7f559ffd07
chore(release): 2.42.0 2025-07-27 13:38:09 +02:00
Henrique Dias
619f6837b0
fix: norsk loading 2025-07-27 13:37:43 +02:00
Henrique Dias
d778c192ae
Revert "chore(release): 2.42.0"
This reverts commit a290c6d7db110efd84c5f6f3b58f773c0c2b1f7a.
2025-07-27 13:31:12 +02:00
Henrique Dias
a290c6d7db
chore(release): 2.42.0 2025-07-27 13:14:20 +02:00
Henrique Dias
c1b0207800 build: bump to go 1.24 2025-07-27 13:14:03 +02:00
Christopher Campo
c7a5c7efee build: bump go version to 1.23.11 2025-07-27 13:09:05 +02:00
Ramires Viana
cbeec6d225
feat: select item on file list after navigating back (#5329) 2025-07-27 13:03:00 +02:00
20 changed files with 199 additions and 105 deletions

View File

@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: 1.23.0 go-version: '1.24'
- run: make lint-backend - run: make lint-backend
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: 1.23.0 go-version: '1.24'
- run: make test-backend - run: make test-backend
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -76,7 +76,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: 1.23.0 go-version: '1.23'
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
package_json_file: "frontend/package.json" package_json_file: "frontend/package.json"

View File

@ -2,6 +2,32 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.42.0](https://github.com/filebrowser/filebrowser/compare/v2.41.0...v2.42.0) (2025-07-27)
### Features
* add Norwegian support ([#5332](https://github.com/filebrowser/filebrowser/issues/5332)) ([25e47c3](https://github.com/filebrowser/filebrowser/commit/25e47c3ce8b35b820b5370a4b8bfdf682bd5ae0b))
* select item on file list after navigating back ([#5329](https://github.com/filebrowser/filebrowser/issues/5329)) ([cbeec6d](https://github.com/filebrowser/filebrowser/commit/cbeec6d225691723c4750d7f84122ebb14d662bf))
* Translate frontend/src/i18n/en.json in no ([5eb3bf4](https://github.com/filebrowser/filebrowser/commit/5eb3bf40586c2ffc32f4834b5dd59f0eb719c1f7))
* Translate frontend/src/i18n/en.json in sk ([07dfdce](https://github.com/filebrowser/filebrowser/commit/07dfdce8e4c371f4ca7480f3cef0bd66ff5c9abb))
### Bug Fixes
* norsk loading ([619f683](https://github.com/filebrowser/filebrowser/commit/619f6837b0d1ec6c654d30f4ecedd6696874721f))
### Reverts
* Revert "chore(release): 2.42.0" ([d778c19](https://github.com/filebrowser/filebrowser/commit/d778c192ae02c5e73781f7632e3b7276c5811e17))
### Build
* bump go version to 1.23.11 ([c7a5c7e](https://github.com/filebrowser/filebrowser/commit/c7a5c7efee2b2bede89ec90bafd1af61c39519ff))
* bump to go 1.24 ([c1b0207](https://github.com/filebrowser/filebrowser/commit/c1b0207800b4bb52c8dd459c1d69ce0f785473b6))
## [2.41.0](https://github.com/filebrowser/filebrowser/compare/v2.40.2...v2.41.0) (2025-07-22) ## [2.41.0](https://github.com/filebrowser/filebrowser/compare/v2.40.2...v2.41.0) (2025-07-22)

View File

@ -62,6 +62,7 @@ import FileList from "./FileList.vue";
import { files as api } from "@/api"; import { files as api } from "@/api";
import buttons from "@/utils/buttons"; import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload"; import * as upload from "@/utils/upload";
import { removePrefix } from "@/api/utils";
export default { export default {
name: "copy", name: "copy",
@ -76,7 +77,7 @@ export default {
computed: { computed: {
...mapState(useFileStore, ["req", "selected"]), ...mapState(useFileStore, ["req", "selected"]),
...mapState(useAuthStore, ["user"]), ...mapState(useAuthStore, ["user"]),
...mapWritableState(useFileStore, ["reload"]), ...mapWritableState(useFileStore, ["reload", "preselect"]),
}, },
methods: { methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]), ...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
@ -100,6 +101,7 @@ export default {
.copy(items, overwrite, rename) .copy(items, overwrite, rename)
.then(() => { .then(() => {
buttons.success("copy"); buttons.success("copy");
this.preselect = removePrefix(items[0].to);
if (this.$route.path === this.dest) { if (this.$route.path === this.dest) {
this.reload = true; this.reload = true;

View File

@ -48,16 +48,15 @@ export default {
"selectedCount", "selectedCount",
"req", "req",
"selected", "selected",
"currentPrompt",
]), ]),
...mapWritableState(useFileStore, ["reload"]), ...mapState(useLayoutStore, ["currentPrompt"]),
...mapWritableState(useFileStore, ["reload", "preselect"]),
}, },
methods: { methods: {
...mapActions(useLayoutStore, ["closeHovers"]), ...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function () { submit: async function () {
buttons.loading("delete"); buttons.loading("delete");
window.sessionStorage.setItem("modified", "true");
try { try {
if (!this.isListing) { if (!this.isListing) {
await api.remove(this.$route.path); await api.remove(this.$route.path);
@ -81,6 +80,12 @@ export default {
await Promise.all(promises); await Promise.all(promises);
buttons.success("delete"); buttons.success("delete");
const nearbyItem =
this.req.items[Math.max(0, Math.min(this.selected) - 1)];
this.preselect = nearbyItem?.path;
this.reload = true; this.reload = true;
} catch (e) { } catch (e) {
buttons.done("delete"); buttons.done("delete");

View File

@ -17,7 +17,7 @@
</button> </button>
<button <button
id="focus-prompt" id="focus-prompt"
@click="submit" @click="currentPrompt.confirm"
class="button button--flat button--red" class="button button--flat button--red"
:aria-label="$t('buttons.discardChanges')" :aria-label="$t('buttons.discardChanges')"
:title="$t('buttons.discardChanges')" :title="$t('buttons.discardChanges')"
@ -30,22 +30,16 @@
</template> </template>
<script> <script>
import { mapActions } from "pinia"; import { mapState, mapActions } from "pinia";
import url from "@/utils/url";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { useFileStore } from "@/stores/file";
export default { export default {
name: "discardEditorChanges", name: "discardEditorChanges",
computed: {
...mapState(useLayoutStore, ["currentPrompt"]),
},
methods: { methods: {
...mapActions(useLayoutStore, ["closeHovers"]), ...mapActions(useLayoutStore, ["closeHovers"]),
...mapActions(useFileStore, ["updateRequest"]),
submit: async function () {
this.updateRequest(null);
const uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri });
},
}, },
}; };
</script> </script>

View File

@ -55,7 +55,7 @@
</template> </template>
<script> <script>
import { mapActions, mapState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
@ -63,6 +63,7 @@ import FileList from "./FileList.vue";
import { files as api } from "@/api"; import { files as api } from "@/api";
import buttons from "@/utils/buttons"; import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload"; import * as upload from "@/utils/upload";
import { removePrefix } from "@/api/utils";
export default { export default {
name: "move", name: "move",
@ -77,6 +78,7 @@ export default {
computed: { computed: {
...mapState(useFileStore, ["req", "selected"]), ...mapState(useFileStore, ["req", "selected"]),
...mapState(useAuthStore, ["user"]), ...mapState(useAuthStore, ["user"]),
...mapWritableState(useFileStore, ["preselect"]),
excludedFolders() { excludedFolders() {
return this.selected return this.selected
.filter((idx) => this.req.items[idx].isDir) .filter((idx) => this.req.items[idx].isDir)
@ -104,6 +106,7 @@ export default {
.move(items, overwrite, rename) .move(items, overwrite, rename)
.then(() => { .then(() => {
buttons.success("move"); buttons.success("move");
this.preselect = removePrefix(items[0].to);
this.$router.push({ path: this.dest }); this.$router.push({ path: this.dest });
}) })
.catch((e) => { .catch((e) => {

View File

@ -46,6 +46,7 @@ import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import url from "@/utils/url"; import url from "@/utils/url";
import { files as api } from "@/api"; import { files as api } from "@/api";
import { removePrefix } from "@/api/utils";
export default { export default {
name: "rename", name: "rename",
@ -65,7 +66,7 @@ export default {
"selectedCount", "selectedCount",
"isListing", "isListing",
]), ]),
...mapWritableState(useFileStore, ["reload"]), ...mapWritableState(useFileStore, ["reload", "preselect"]),
}, },
methods: { methods: {
...mapActions(useLayoutStore, ["closeHovers"]), ...mapActions(useLayoutStore, ["closeHovers"]),
@ -97,7 +98,6 @@ export default {
newLink = newLink =
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name); url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
window.sessionStorage.setItem("modified", "true");
try { try {
await api.move([{ from: oldLink, to: newLink }]); await api.move([{ from: oldLink, to: newLink }]);
if (!this.isListing) { if (!this.isListing) {
@ -105,6 +105,8 @@ export default {
return; return;
} }
this.preselect = removePrefix(newLink);
this.reload = true; this.reload = true;
} catch (e) { } catch (e) {
this.$showError(e); this.$showError(e);

View File

@ -30,6 +30,7 @@ export default {
ja: "日本語", ja: "日本語",
ko: "한국어", ko: "한국어",
"nl-be": "Dutch (Belgium)", "nl-be": "Dutch (Belgium)",
no: "Norsk",
pl: "Polski", pl: "Polski",
"pt-br": "Português", "pt-br": "Português",
pt: "Português (Brasil)", pt: "Português (Brasil)",

View File

@ -96,6 +96,9 @@ main {
height: 3em; height: 3em;
background: var(--background); background: var(--background);
border-bottom: 1px solid var(--divider); border-bottom: 1px solid var(--divider);
position: sticky;
z-index: 1000;
top: 4em;
} }
.breadcrumbs span, .breadcrumbs span,

View File

@ -329,6 +329,7 @@ main .spinner .bounce2 {
#editor-container { #editor-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
background-color: var(--background); background-color: var(--background);
position: fixed; position: fixed;
padding-top: 4em; padding-top: 4em;
@ -351,6 +352,8 @@ main .spinner .bounce2 {
#editor-container .breadcrumbs { #editor-container .breadcrumbs {
height: 2.3em; height: 2.3em;
padding: 0 1em; padding: 0 1em;
position: relative;
top: 0;
} }
/*** RTL - flip and position arrow of path ***/ /*** RTL - flip and position arrow of path ***/

View File

@ -27,7 +27,7 @@ import("dayjs/locale/vi");
import("dayjs/locale/zh-cn"); import("dayjs/locale/zh-cn");
import("dayjs/locale/zh-tw"); import("dayjs/locale/zh-tw");
import("dayjs/locale/cs"); import("dayjs/locale/cs");
import("dayjs/locale/no"); import("dayjs/locale/nb");
// All i18n resources specified in the plugin `include` option can be loaded // All i18n resources specified in the plugin `include` option can be loaded
// at once using the import syntax // at once using the import syntax
@ -102,7 +102,6 @@ export function detectLocale() {
case /^tr\b/.test(locale): case /^tr\b/.test(locale):
locale = "tr"; locale = "tr";
break; break;
// ua wasnt a valid locale for ukraine
case /^uk\b/.test(locale): case /^uk\b/.test(locale):
locale = "uk"; locale = "uk";
break; break;
@ -116,6 +115,10 @@ export function detectLocale() {
case /^nl-be\b/.test(locale): case /^nl-be\b/.test(locale):
locale = "nl-be"; locale = "nl-be";
break; break;
case /^nb\b/.test(locale):
case /^no\b/.test(locale):
locale = "no";
break;
default: default:
locale = "en"; locale = "en";
} }

View File

@ -7,13 +7,13 @@
"copy": "Kopírovať", "copy": "Kopírovať",
"copyFile": "Kopírovať súbor", "copyFile": "Kopírovať súbor",
"copyToClipboard": "Kopírovať do schránky", "copyToClipboard": "Kopírovať do schránky",
"copyDownloadLinkToClipboard": "Copy download link to clipboard", "copyDownloadLinkToClipboard": "Kopírovať odkaz na stiahnutie do schránky",
"create": "Vytvoriť", "create": "Vytvoriť",
"delete": "Odstrániť", "delete": "Odstrániť",
"download": "Stiahnuť", "download": "Stiahnuť",
"file": "Súbor", "file": "Súbor",
"folder": "Priečinok", "folder": "Priečinok",
"fullScreen": "Toggle full screen", "fullScreen": "Prepnúť na celú obrazovku",
"hideDotfiles": "Skryť súbory začínajúce bodkou", "hideDotfiles": "Skryť súbory začínajúce bodkou",
"info": "Info", "info": "Info",
"more": "Viac", "more": "Viac",
@ -24,7 +24,7 @@
"ok": "OK", "ok": "OK",
"permalink": "Získať trvalý odkaz", "permalink": "Získať trvalý odkaz",
"previous": "Predošlé", "previous": "Predošlé",
"preview": "Preview", "preview": "Náhľad",
"publish": "Zverejniť", "publish": "Zverejniť",
"rename": "Premenovať", "rename": "Premenovať",
"replace": "Nahradiť", "replace": "Nahradiť",
@ -42,7 +42,7 @@
"update": "Aktualizovať", "update": "Aktualizovať",
"upload": "Nahrať", "upload": "Nahrať",
"openFile": "Otvoriť súbor", "openFile": "Otvoriť súbor",
"discardChanges": "Discard" "discardChanges": "Zahodiť"
}, },
"download": { "download": {
"downloadFile": "Stiahnuť súbor", "downloadFile": "Stiahnuť súbor",
@ -50,7 +50,7 @@
"downloadSelected": "Stiahnuť vybraté" "downloadSelected": "Stiahnuť vybraté"
}, },
"upload": { "upload": {
"abortUpload": "Are you sure you wish to abort?" "abortUpload": "Naozaj chcete prerušiť?"
}, },
"errors": { "errors": {
"forbidden": "You don't have permissions to access this.", "forbidden": "You don't have permissions to access this.",
@ -110,7 +110,7 @@
"deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?", "deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?",
"deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?", "deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?",
"deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?", "deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?",
"deleteUser": "Are you sure you want to delete this user?", "deleteUser": "Naozaj chcete odstrániť tohto používateľa?",
"deleteTitle": "Odstránenie súborov", "deleteTitle": "Odstránenie súborov",
"displayName": "Zobrazený názov:", "displayName": "Zobrazený názov:",
"download": "Stiahnuť súbory", "download": "Stiahnuť súbory",
@ -137,11 +137,11 @@
"show": "Zobraziť", "show": "Zobraziť",
"size": "Veľkosť", "size": "Veľkosť",
"upload": "Nahrať", "upload": "Nahrať",
"uploadFiles": "Uploading {files} files...", "uploadFiles": "Nahráva sa {files} súborov...",
"uploadMessage": "Zvoľte možnosť nahrávania.", "uploadMessage": "Zvoľte možnosť nahrávania.",
"optionalPassword": "Voliteľné heslo", "optionalPassword": "Voliteľné heslo",
"resolution": "Resolution", "resolution": "Rozlíšenie",
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?" "discardEditorChanges": "Naozaj chcete zahodiť vykonané zmeny?"
}, },
"search": { "search": {
"images": "Obrázky", "images": "Obrázky",
@ -170,14 +170,14 @@
"commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.", "commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.",
"commandsUpdated": "Príkazy upravené!", "commandsUpdated": "Príkazy upravené!",
"createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa", "createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa",
"minimumPasswordLength": "Minimum password length", "minimumPasswordLength": "Minimálna dĺžka hesla",
"tusUploads": "Chunked Uploads", "tusUploads": "Nahrávanie po častiach",
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.", "tusUploadsHelp": "Prehliadač súborov podporuje nahrávanie súborov po častiach, čo umožňuje vytváranie efektívnych, spoľahlivých, obnoviteľných a po častiach nahrávaných súborov aj v prípade nespoľahlivých sietí.",
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.", "tusUploadsChunkSize": "Označuje maximálnu veľkosť požiadavky (pre menšie nahratia sa použijú priame nahratia). Môžete zadať celé číslo označujúce veľkosť v bajtoch alebo reťazec ako 10 MB, 1 GB atď.",
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.", "tusUploadsRetryCount": "Počet opakovaných pokusov, ktoré sa majú vykonať, ak sa nepodarí nahrať časť súboru.",
"userHomeBasePath": "Base path for user home directories", "userHomeBasePath": "Východisková cesta pre domáce adresáre používateľov",
"userScopeGenerationPlaceholder": "The scope will be auto generated", "userScopeGenerationPlaceholder": "Rozsah bude automaticky generovaný",
"createUserHomeDirectory": "Create user home directory", "createUserHomeDirectory": "Vytvoriť domovský adresár používateľa",
"customStylesheet": "Vlastný Stylesheet", "customStylesheet": "Vlastný Stylesheet",
"defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.", "defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.",
"disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)", "disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)",
@ -217,14 +217,14 @@
"rules": "Pravidlá", "rules": "Pravidlá",
"rulesHelp": "Tu môžete definovať pravidlá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n", "rulesHelp": "Tu môžete definovať pravidlá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n",
"scope": "Scope", "scope": "Scope",
"setDateFormat": "Set exact date format", "setDateFormat": "Nastaviť presný formát dátumu",
"settingsUpdated": "Nastavenia upravené!", "settingsUpdated": "Nastavenia upravené!",
"shareDuration": "Trvanie zdieľania", "shareDuration": "Trvanie zdieľania",
"shareManagement": "Správa zdieľania", "shareManagement": "Správa zdieľania",
"shareDeleted": "Zdieľanie odstránené!", "shareDeleted": "Zdieľanie odstránené!",
"singleClick": "Používať jeden klik na otváranie súborov a priečinkov", "singleClick": "Používať jeden klik na otváranie súborov a priečinkov",
"themes": { "themes": {
"default": "System default", "default": "Predvolené nastavenie systému",
"dark": "Tmavá", "dark": "Tmavá",
"light": "Svetlá", "light": "Svetlá",
"title": "Téma" "title": "Téma"

View File

@ -9,6 +9,7 @@ export const useFileStore = defineStore("file", {
selected: number[]; selected: number[];
multiple: boolean; multiple: boolean;
isFiles: boolean; isFiles: boolean;
preselect: string | null;
} => ({ } => ({
req: null, req: null,
oldReq: null, oldReq: null,
@ -16,6 +17,7 @@ export const useFileStore = defineStore("file", {
selected: [], selected: [],
multiple: false, multiple: false,
isFiles: false, isFiles: false,
preselect: null,
}), }),
getters: { getters: {
selectedCount: (state) => state.selected.length, selectedCount: (state) => state.selected.length,

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<header-bar <header-bar
v-if="error || fileStore.req?.type === null" v-if="error || fileStore.req?.type === undefined"
showMenu showMenu
showLogo showLogo
/> />
@ -9,7 +9,7 @@
<breadcrumbs base="/files" /> <breadcrumbs base="/files" />
<errors v-if="error" :errorCode="error.status" /> <errors v-if="error" :errorCode="error.status" />
<component v-else-if="currentView" :is="currentView"></component> <component v-else-if="currentView" :is="currentView"></component>
<div v-else-if="currentView !== null"> <div v-else>
<h2 class="message delayed"> <h2 class="message delayed">
<div class="spinner"> <div class="spinner">
<div class="bounce1"></div> <div class="bounce1"></div>
@ -102,15 +102,7 @@ onUnmounted(() => {
fetchDataController.abort(); fetchDataController.abort();
}); });
watch(route, (to, from) => { watch(route, () => {
if (from.path.endsWith("/")) {
window.sessionStorage.setItem(
"listFrozen",
(!to.path.endsWith("/")).toString()
);
} else if (to.path.endsWith("/")) {
fileStore.updateRequest(null);
}
fetchData(); fetchData();
}); });
watch(reload, (newValue) => { watch(reload, (newValue) => {
@ -122,6 +114,32 @@ watch(uploadError, (newValue) => {
// Define functions // Define functions
const applyPreSelection = () => {
const preselect = fileStore.preselect;
fileStore.preselect = null;
if (!fileStore.req?.isDir || fileStore.oldReq === null) return;
let index = -1;
if (preselect) {
// Find item with the specified path
index = fileStore.req.items.findIndex((item) => item.path === preselect);
} else if (fileStore.oldReq.path.startsWith(fileStore.req.path)) {
// Get immediate child folder of the previous path
const name = fileStore.oldReq.path
.substring(fileStore.req.path.length)
.split("/")
.shift();
index = fileStore.req.items.findIndex(
(val) => val.path == fileStore.req!.path + name
);
}
if (index === -1) return;
fileStore.selected.push(index);
};
const fetchData = async () => { const fetchData = async () => {
// Reset view information. // Reset view information.
fileStore.reload = false; fileStore.reload = false;
@ -130,12 +148,7 @@ const fetchData = async () => {
layoutStore.closeHovers(); layoutStore.closeHovers();
// Set loading to true and reset the error. // Set loading to true and reset the error.
if ( layoutStore.loading = true;
window.sessionStorage.getItem("listFrozen") !== "true" &&
window.sessionStorage.getItem("modified") !== "true"
) {
layoutStore.loading = true;
}
error.value = null; error.value = null;
let url = route.path; let url = route.path;
@ -149,6 +162,9 @@ const fetchData = async () => {
fileStore.updateRequest(res); fileStore.updateRequest(res);
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`; document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
layoutStore.loading = false; layoutStore.loading = false;
// Selects the post-reload target item or the previously visited child folder
applyPreSelection();
} catch (err) { } catch (err) {
if (err instanceof StatusError && err.is_canceled) { if (err instanceof StatusError && err.is_canceled) {
return; return;

View File

@ -32,17 +32,25 @@
/> />
</header-bar> </header-bar>
<Breadcrumbs base="/files" noLink />
<!-- preview container --> <!-- preview container -->
<div <div class="loading delayed" v-if="layoutStore.loading">
v-show="isPreview && isMarkdownFile" <div class="spinner">
id="preview-container" <div class="bounce1"></div>
class="md_preview" <div class="bounce2"></div>
v-html="previewContent" <div class="bounce3"></div>
></div> </div>
</div>
<template v-else>
<Breadcrumbs base="/files" noLink />
<form v-show="!isPreview || !isMarkdownFile" id="editor"></form> <div
v-show="isPreview && isMarkdownFile"
id="preview-container"
class="md_preview"
v-html="previewContent"
></div>
<form v-show="!isPreview || !isMarkdownFile" id="editor"></form>
</template>
</div> </div>
</template> </template>
@ -146,12 +154,19 @@ onBeforeUnmount(() => {
}); });
onBeforeRouteUpdate((to, from, next) => { onBeforeRouteUpdate((to, from, next) => {
if (!editor.value?.session.getUndoManager().isClean()) { if (editor.value?.session.getUndoManager().isClean()) {
layoutStore.showHover("discardEditorChanges");
next(false);
} else {
next(); next();
return;
} }
layoutStore.showHover({
prompt: "discardEditorChanges",
confirm: (event: Event) => {
event.preventDefault();
next();
},
});
}); });
const keyEvent = (event: KeyboardEvent) => { const keyEvent = (event: KeyboardEvent) => {
@ -216,13 +231,6 @@ const decreaseFontSize = () => {
}; };
const close = () => { const close = () => {
if (!editor.value?.session.getUndoManager().isClean()) {
layoutStore.showHover("discardEditorChanges");
return;
}
fileStore.updateRequest(null);
const uri = url.removeLastDir(route.path) + "/"; const uri = url.removeLastDir(route.path) + "/";
router.push({ path: uri }); router.push({ path: uri });
}; };

View File

@ -303,6 +303,7 @@ import {
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { removePrefix } from "@/api/utils";
const showLimit = ref<number>(50); const showLimit = ref<number>(50);
const columnWidth = ref<number>(280); const columnWidth = ref<number>(280);
@ -420,25 +421,19 @@ const isMobile = computed(() => {
watch(req, () => { watch(req, () => {
// Reset the show value // Reset the show value
if ( showLimit.value = 50;
window.sessionStorage.getItem("listFrozen") !== "true" &&
window.sessionStorage.getItem("modified") !== "true"
) {
showLimit.value = 50;
nextTick(() => { nextTick(() => {
// Ensures that the listing is displayed // Ensures that the listing is displayed
// How much every listing item affects the window height // How much every listing item affects the window height
setItemWeight(); setItemWeight();
// Scroll to the item opened previously
if (!revealPreviousItem()) {
// Fill and fit the window with listing items // Fill and fit the window with listing items
fillWindow(true); fillWindow(true);
}); }
} });
if (req.value?.isDir) {
window.sessionStorage.setItem("listFrozen", "false");
window.sessionStorage.setItem("modified", "false");
}
}); });
onMounted(() => { onMounted(() => {
@ -448,8 +443,11 @@ onMounted(() => {
// How much every listing item affects the window height // How much every listing item affects the window height
setItemWeight(); setItemWeight();
// Fill and fit the window with listing items // Scroll to the item opened previously
fillWindow(true); if (!revealPreviousItem()) {
// Fill and fit the window with listing items
fillWindow(true);
}
// Add the needed event listeners to the window and document. // Add the needed event listeners to the window and document.
window.addEventListener("keydown", keyEvent); window.addEventListener("keydown", keyEvent);
@ -589,10 +587,13 @@ const paste = (event: Event) => {
return; return;
} }
const preselect = removePrefix(route.path) + items[0].name;
let action = (overwrite: boolean, rename: boolean) => { let action = (overwrite: boolean, rename: boolean) => {
api api
.copy(items, overwrite, rename) .copy(items, overwrite, rename)
.then(() => { .then(() => {
fileStore.preselect = preselect;
fileStore.reload = true; fileStore.reload = true;
}) })
.catch($showError); .catch($showError);
@ -604,6 +605,7 @@ const paste = (event: Event) => {
.move(items, overwrite, rename) .move(items, overwrite, rename)
.then(() => { .then(() => {
clipboardStore.resetClipboard(); clipboardStore.resetClipboard();
fileStore.preselect = preselect;
fileStore.reload = true; fileStore.reload = true;
}) })
.catch($showError); .catch($showError);
@ -731,6 +733,8 @@ const drop = async (event: DragEvent) => {
const conflict = upload.checkConflict(files, items); const conflict = upload.checkConflict(files, items);
const preselect = removePrefix(path) + (files[0].fullPath || files[0].name);
if (conflict) { if (conflict) {
layoutStore.showHover({ layoutStore.showHover({
prompt: "replace", prompt: "replace",
@ -738,11 +742,13 @@ const drop = async (event: DragEvent) => {
event.preventDefault(); event.preventDefault();
layoutStore.closeHovers(); layoutStore.closeHovers();
upload.handleFiles(files, path, false); upload.handleFiles(files, path, false);
fileStore.preselect = preselect;
}, },
confirm: (event: Event) => { confirm: (event: Event) => {
event.preventDefault(); event.preventDefault();
layoutStore.closeHovers(); layoutStore.closeHovers();
upload.handleFiles(files, path, true); upload.handleFiles(files, path, true);
fileStore.preselect = preselect;
}, },
}); });
@ -750,6 +756,7 @@ const drop = async (event: DragEvent) => {
} }
upload.handleFiles(files, path); upload.handleFiles(files, path);
fileStore.preselect = preselect;
}; };
const uploadInput = (event: Event) => { const uploadInput = (event: Event) => {
@ -953,4 +960,21 @@ const fillWindow = (fit = false) => {
// Set the number of displayed items // Set the number of displayed items
showLimit.value = showQuantity > totalItems ? totalItems : showQuantity; showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
}; };
const revealPreviousItem = () => {
if (!fileStore.req || !fileStore.oldReq) return;
const index = fileStore.selected[0];
if (index === undefined) return;
showLimit.value =
index + Math.ceil((window.innerHeight * 2) / itemWeight.value);
nextTick(() => {
const items = document.querySelectorAll("#listing .item");
items[index].scrollIntoView({ block: "center" });
});
return true;
};
</script> </script>

View File

@ -301,10 +301,8 @@ watch(route, () => {
// Specify hooks // Specify hooks
onMounted(async () => { onMounted(async () => {
window.addEventListener("keydown", key); window.addEventListener("keydown", key);
if (fileStore.oldReq) { listing.value = fileStore.oldReq?.items ?? null;
listing.value = fileStore.oldReq.items; updatePreview();
updatePreview();
}
}); });
onBeforeUnmount(() => window.removeEventListener("keydown", key)); onBeforeUnmount(() => window.removeEventListener("keydown", key));
@ -317,11 +315,16 @@ const deleteFile = () => {
if (listing.value === null) { if (listing.value === null) {
return; return;
} }
listing.value = listing.value.filter((item) => item.name !== name.value);
const index = listing.value.findIndex((item) => item.name == name.value);
listing.value.splice(index, 1);
if (hasNext.value) { if (hasNext.value) {
next(); next();
} else if (!hasPrevious.value && !hasNext.value) { } else if (!hasPrevious.value && !hasNext.value) {
const nearbyItem = listing.value[Math.max(0, index - 1)];
fileStore.preselect = nearbyItem?.path;
close(); close();
} else { } else {
prev(); prev();
@ -427,8 +430,6 @@ const toggleNavigation = throttle(function () {
}, 500); }, 500);
const close = () => { const close = () => {
fileStore.updateRequest(null);
const uri = url.removeLastDir(route.path) + "/"; const uri = url.removeLastDir(route.path) + "/";
router.push({ path: uri }); router.push({ path: uri });
}; };

View File

@ -5,6 +5,7 @@
"exclude": [ "exclude": [
"src/components/Shell.vue", "src/components/Shell.vue",
"src/components/prompts/Copy.vue", "src/components/prompts/Copy.vue",
"src/components/prompts/Move.vue",
"src/components/prompts/Delete.vue", "src/components/prompts/Delete.vue",
"src/components/prompts/FileList.vue", "src/components/prompts/FileList.vue",
"src/components/prompts/Rename.vue", "src/components/prompts/Rename.vue",

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/filebrowser/filebrowser/v2 module github.com/filebrowser/filebrowser/v2
go 1.23.0 go 1.24
require ( require (
github.com/asdine/storm/v3 v3.2.1 github.com/asdine/storm/v3 v3.2.1

View File

@ -1,6 +1,6 @@
module github.com/filebrowser/filebrowser/v2/tools module github.com/filebrowser/filebrowser/v2/tools
go 1.23.0 go 1.24
require ( require (
github.com/golangci/golangci-lint/v2 v2.1.6 github.com/golangci/golangci-lint/v2 v2.1.6