Compare commits

...
5 Commits
18 changed files with 105 additions and 56 deletions

No files matched your search

+14
View File
@@ -2,6 +2,20 @@
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.14-beta
**Notes**:
- Updated translations https://github.com/gtsteffaniak/filebrowser/issues/957
- enabled more doc types for onlyoffice https://github.com/gtsteffaniak/filebrowser/discussions/945
**BugFixes**:
- noauth user issue https://github.com/gtsteffaniak/filebrowser/issues/955
- error 403 on source name with special characters https://github.com/gtsteffaniak/filebrowser/issues/952
- delete pictures in previewer issue https://github.com/gtsteffaniak/filebrowser/issues/456
- trailing slash source name issue https://github.com/gtsteffaniak/filebrowser/issues/920
- image lazy loading issue causing all items to get previews at one time, not just whats in view.
## v0.7.13-beta
**New Features**:
+1
View File
@@ -5,6 +5,7 @@ server:
- levels: "info|error|debug"
sources:
- path: "../frontend/tests/playwright-files"
name: "playwright + files"
config:
defaultEnabled: true
- path: "."
+3 -1
View File
@@ -86,7 +86,9 @@ func quickSetup(store *Storage) {
utils.CheckErr("store.Settings.Save", err)
err = store.Settings.SaveServer(&settings.Config.Server)
utils.CheckErr("store.Settings.SaveServer", err)
if settings.Config.Auth.Methods.PasswordAuth.Enabled {
passwordAuth := settings.Config.Auth.Methods.PasswordAuth.Enabled
noAuth := settings.Config.Auth.Methods.NoAuth
if passwordAuth || noAuth {
user := &users.User{}
settings.ApplyUserDefaults(user)
user.Username = settings.Config.Auth.AdminUsername
+9
View File
@@ -46,6 +46,10 @@ func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContex
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
}
source, err = url.QueryUnescape(source)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid source encoding: %v", err)
}
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source)
if err != nil {
return http.StatusForbidden, err
@@ -169,6 +173,11 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
logger.Debugf("invalid source encoding: %v", err)
return http.StatusBadRequest, fmt.Errorf("invalid source encoding: %v", err)
}
path, err = url.QueryUnescape(path)
if err != nil {
logger.Debugf("invalid path encoding: %v", err)
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
}
if !d.user.Permissions.Modify {
logger.Debugf("user is not allowed to create or modify")
return http.StatusForbidden, fmt.Errorf("user is not allowed to create or modify")
+15 -2
View File
@@ -104,14 +104,27 @@ var documentTypes = []string{
}
var onlyOfficeSupported = []string{
// Word Processing Documents
".doc", ".docm", ".docx", ".dot", ".dotm", ".dotx", ".epub",
".fb2", ".fodt", ".htm", ".html", ".mht", ".mhtml", ".odt",
".ott", ".rtf", ".stw", ".sxw", ".txt", ".wps", ".wpt", ".xml",
".hwp", ".hwpx", ".md", ".pages", // Added missing Word extensions
// Spreadsheet Documents
".csv", ".et", ".ett", ".fods", ".ods", ".ots", ".sxc", ".xls",
".xlsb", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx", ".dps",
".dpt", ".fodp", ".odp", ".otp", ".pot", ".potm", ".potx",
".xlsb", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx",
".numbers", // Added missing Spreadsheet extension
// Presentation Documents
".dps", ".dpt", ".fodp", ".odp", ".otp", ".pot", ".potm", ".potx",
".pps", ".ppsm", ".ppsx", ".ppt", ".pptm", ".pptx", ".sxi",
".key", ".odg", // Added missing Presentation extensions
// Other Office-Related Formats
".djvu", ".docxf", ".oform", ".oxps", ".pdf", ".xps",
// Diagram Documents (New category from List 2)
".vsdm", ".vsdx", ".vssm", ".vssx", ".vstm", ".vstx",
}
// Text-based file extensions
+2 -2
View File
@@ -96,8 +96,8 @@ export function post(
) {
try {
const apiPath = getApiPath("api/resources", {
path: encodeURIComponent(path),
source: encodeURIComponent(source),
path: doubleEncode(path),
source: doubleEncode(source),
override: overwrite,
});
+2 -2
View File
@@ -21,7 +21,7 @@
<action
v-if="!showCreate && !isSearchActive && userPerms.modify"
icon="add"
label="New"
:label="$t('buttons.new')"
@action="startShowCreate"
/>
@@ -92,7 +92,7 @@
<action
v-if="!showCreate && selectedCount > 0 && userPerms.modify"
icon="file_upload"
:label="$t('buttons.replace')"
:label="$t('buttons.upload')"
@action="showUpload"
/>
<action
+2 -1
View File
@@ -395,7 +395,8 @@ export default {
if (path.startsWith("/")) {
path = path.slice(1); // remove leading slash
}
const encodedPath = encodeURIComponent(this.getContext + path).replaceAll("%2F", "/");
const context = url.removeTrailingSlash(this.getContext)
const encodedPath = encodeURIComponent(context + "/" + path).replaceAll("%2F", "/");
let fullpath = encodedPath;
if (serverHasMultipleSources) {
fullpath = baseURL+"files/" + this.selectedSource + encodedPath;
+8 -10
View File
@@ -31,7 +31,12 @@
@mouseup="cancelContext($event)"
>
<div @click="toggleClick" :class="{ 'gallery-div': galleryView }">
<Icon :mimetype="type" :active="isSelected" :thumbnailUrl="thumbnailUrl" :filename="name" />
<Icon
:mimetype="type"
:active="isSelected"
:thumbnailUrl="isThumbnailInView ? thumbnailUrl : ''"
:filename="name"
/>
</div>
<div class="text" :class="{ activecontent: isMaximized && isSelected }">
@@ -158,23 +163,16 @@ export default {
isThumbsEnabled() {
return enableThumbs;
},
isInView() {
return enableThumbs;
},
},
mounted() {
// Prevent default navigation for left-clicks
const observer = new IntersectionObserver(this.handleIntersect, {
root: null,
rootMargin: "0px",
threshold: 0.5, // Adjust threshold as needed
threshold: 0.1, // Adjust threshold as needed
});
// Get the thumbnail element and start observing
const thumbnailElement = this.$refs.thumbnail; // Add ref="thumbnail" to the <img> tag
if (thumbnailElement) {
observer.observe(thumbnailElement);
}
observer.observe(this.$el);
},
methods: {
downloadFile(event) {
@@ -77,6 +77,7 @@ export default {
notify.showSuccess("Deleted item successfully");
mutations.closeHovers();
mutations.setDeletedItem(true);
mutations.setReload(true);
return;
}
if (!this.isListing) {
+20 -14
View File
@@ -1,14 +1,15 @@
<template>
<div class="card floating">
<div class="card floating" >
<div class="card-title">
<h2>{{ $t("prompts.upload") }}</h2>
</div>
<div
class="upload-prompt"
:class="{ dropping: isDragging }"
@dragenter.prevent="onDragEnter"
@dragover.prevent="onDragOver"
@dragleave.prevent="onDragLeave"
:class="{ 'drag-over': isDragging }"
@drop.prevent="onDrop"
>
<div class="upload-prompt-container">
<i v-if="files.length === 0" class="material-icons">cloud_upload</i>
@@ -30,7 +31,6 @@
<div
class="card-content"
@drop.prevent="onDrop"
:class="{ 'drag-over': isDragging }"
>
<div v-if="showConflictPrompt" class="conflict-overlay">
<div class="card">
@@ -211,7 +211,6 @@ export default {
},
},
setup(props) {
console.log("Upload.vue: setup started. Props:", props);
const fileInput = ref(null);
const folderInput = ref(null);
const files = computed(() => uploadManager.queue);
@@ -298,6 +297,14 @@ export default {
}
};
const processItems = async (items) => {
if (Array.isArray(items)) {
processFileList(items);
} else if (items) {
await processDroppedItems(Array.from(items));
}
};
onMounted(async () => {
document.addEventListener("visibilitychange", handleVisibilityChange);
uploadManager.setOnConflict(handleConflict);
@@ -340,11 +347,13 @@ export default {
isDragging.value = false;
if (event.dataTransfer.items) {
const items = Array.from(event.dataTransfer.items);
console.log("Upload.vue: Processing dropped items:", items);
await processDroppedItems(items);
} else {
const droppedFiles = event.dataTransfer.files;
console.log("Upload.vue: Processing dropped files (fallback):", droppedFiles);
console.log(
"Upload.vue: Processing dropped files (fallback):",
droppedFiles
);
processFileList(droppedFiles);
}
};
@@ -461,9 +470,10 @@ export default {
margin: 0 1em;
}
.upload-prompt.drag-over {
background-color: #f0f0f0;
border-color: #333;
.dropping {
transform: scale(0.97);
border-radius: 1em;
box-shadow: var(--primaryColor) 0 0 1em;
}
.upload-prompt-container {
@@ -489,6 +499,7 @@ export default {
.upload-item {
display: flex;
align-items: center;
}
.file-icon {
@@ -522,11 +533,6 @@ export default {
font-size: 1.2em;
}
.drag-over {
border-color: #2196f3;
background: #f4f4f4;
}
.card-actions {
display: flex;
justify-content: space-between;
+7 -7
View File
@@ -34,7 +34,7 @@
"switchView": "Ansicht wechseln",
"toggleSidebar": "Seitenleiste anzeigen",
"update": "Aktualisieren",
"upload": "Upload",
"upload": "Hochladen",
"openFile": "Datei öffnen",
"continue": "Fortfahren",
"copyDownloadLinkToClipboard": "Download-Link in die Zwischenablage kopieren",
@@ -46,7 +46,7 @@
"retry": "Wiederholung",
"pauseAll": "Pause alle",
"resumeAll": "Alle zusammenfassen",
"clearCompleted": "Löschen abgeschlossen"
"clearCompleted": "Abgeschlossene löschen"
},
"download": {
"downloadFile": "Datei herunterladen",
@@ -164,9 +164,9 @@
"scheduleMessage": "Wählen Sie ein Datum und eine Zeit für die Veröffentlichung dieses Beitrags.",
"show": "Anzeigen",
"size": "Größe",
"upload": "Upload",
"uploadFiles": "Upload von {files} Dateien...",
"uploadMessage": "Wählen Sie eine Upload-Methode",
"upload": "Hochladen",
"uploadFiles": "Hochladen von {files} Dateien...",
"uploadMessage": "Wählen Sie eine Hochladen-Methode",
"optionalPassword": "Optionales Kennwort",
"deleteUserMessage": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?",
"filesSelected": "ausgewählte Dateien",
@@ -210,9 +210,9 @@
"commandRunnerHelp": "Hier könne Sie Befehle eintragen, welche bei den benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen Befehl eingeben. Die Umgebungsvariablen {0} und {1} sind verfügbar, wobei {0} relativ zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen lesen Sie bitte die {2}.",
"commandsUpdated": "Befehle aktualisiert!",
"createUserDir": "Automatisches Erstellen des Home-Verzeichnisses beim Anlegen neuer Benutzer",
"tusUploads": "Gestückelter Upload",
"tusUploads": "Gestückeltes Hochladen",
"tusUploadsHelp": "File Browser unterstützt das Hochladen von gestückelten Dateien und ermöglicht so einen effizienten, zuverlässigen und fortsetzbaren Datei-Upload auch in unzuverlässigen Netzwerken.",
"tusUploadsChunkSize": "Gibt die maximale Größe pro Anfrage an (direkte Uploads werden für kleinere Dateien verwendet). Bitte geben Sie eine Byte-Angabe oder eine Zeichenfolge wie 10 MB, 1 GB usw. an",
"tusUploadsChunkSize": "Gibt die maximale Größe pro Anfrage an (direkte Hochladen werden für kleinere Dateien verwendet). Bitte geben Sie eine Byte-Angabe oder eine Zeichenfolge wie 10 MB, 1 GB usw. an",
"tusUploadsRetryCount": "Anzahl der Wiederholungsversuche, falls das Hochladen eines Stückes fehlschlägt.",
"customStylesheet": "Individuelles Stylesheet",
"defaultUserDescription": "Das sind die Standardeinstellung für Benutzer",
+4 -2
View File
@@ -113,6 +113,9 @@ export default {
}
},
async fetchData() {
if (state.deletedItem) {
return
}
if (!getters.isLoggedIn()) {
return;
}
@@ -124,8 +127,7 @@ export default {
routePath == "/";
// lets redirect if multiple sources and user went to /files/
if (state.serverHasMultipleSources && rootRoute) {
const urlEncodedSource = encodeURIComponent(state.sources.current)
const targetPath = `${routePath}/${urlEncodedSource}`;
const targetPath = `${routePath}/${state.sources.current}`;
// Prevent infinite loop by checking if we're already at the target path
if (this.$route.path !== targetPath) {
router.push(targetPath);
-1
View File
@@ -57,7 +57,6 @@ export default {
},
computed: {
showQuickSave() {
console.log("showQuickSave", getters.currentView());
if (getters.currentView() != "editor" || !state.user.permissions.modify) {
return false;
}
+9 -6
View File
@@ -1,6 +1,6 @@
<template>
<div id="previewer" @mousemove="toggleNavigation" @touchstart="toggleNavigation">
<div class="preview">
<div class="preview" v-if="!isDeleted">
<ExtendedImage v-if="previewType == 'image' || pdfConvertable" :src="raw">
</ExtendedImage>
<audio v-else-if="previewType == 'audio'" ref="player" :src="raw" controls :autoplay="autoPlay"
@@ -82,6 +82,7 @@ export default {
nextRaw: "",
currentPrompt: null, // Replaces Vuex getter `currentPrompt`
subtitlesList: [],
isDeleted: false,
};
},
computed: {
@@ -161,6 +162,10 @@ export default {
if (!state.deletedItem) {
return;
}
this.isDeleted = true;
this.listing = null; // Invalidate the listing to force a refresh
this.nextRaw = "";
this.previousRaw = "";
if (this.hasNext) {
this.next();
} else if (!this.hasPrevious && !this.hasNext) {
@@ -174,6 +179,7 @@ export default {
if (!getters.isLoggedIn()) {
return;
}
this.isDeleted = false;
this.updatePreview();
this.toggleNavigation();
mutations.resetSelected();
@@ -282,18 +288,15 @@ export default {
}
});
}
const directoryPath = url.removeLastDir(state.req.path);
if (!this.listing) {
const path = url.removeLastDir(state.req.path);
const res = await filesApi.fetchFiles(state.req.source, path);
const res = await filesApi.fetchFiles(state.req.source, directoryPath);
this.listing = res.items;
}
this.name = state.req.name;
this.previousLink = "";
this.nextLink = "";
const path = state.req.path;
let directoryPath = path.substring(0, path.lastIndexOf("/"));
for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) {
continue;
@@ -77,7 +77,7 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
await expect(page.locator('li[aria-selected="true"]')).toHaveCount(1);
await page.locator('button[aria-label="Copy"]').click();
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
await page.goto("/files/playwright-files/myfolder/");
await page.goto("/files/playwright%20%2B%20files/myfolder/");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - myfolder");
// verify exists and copy again
await page.locator('a[aria-label="copyme.txt"]').waitFor({ state: 'visible' });
@@ -103,7 +103,7 @@ test("2x copy from listing to new folder", async({ page, checkForErrors, context
await page.locator('li[aria-label="newfolder"]').click();
await page.locator('button[aria-label="Copy"]').click();
await expect(page.locator('#popup-notification-content')).toHaveText("Resources copied successfully");
await page.goto("/files/playwright-files/myfolder/newfolder/");
await page.goto("/files/playwright%20%2B%20files/myfolder/newfolder/");
await expect(page).toHaveTitle(/.* - newfolder/);
checkForErrors();
})
@@ -126,7 +126,7 @@ test("delete file", async({ page, checkForErrors, context }) => {
})
test("delete nested file prompt", async({ page, checkForErrors, context }) => {
await page.goto("/files/playwright-files/folder%23hash/");
await page.goto("/files/playwright%20%2B%20files/folder%23hash/");
await expect(page).toHaveTitle("Graham's Filebrowser - Files - folder#hash");
await page.locator('a[aria-label="file#.sh"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="file#.sh"]').click({ button: "right" });
@@ -15,7 +15,7 @@ test("navigate with hash in file name", async({ page, checkForErrors, context })
})
test("breadcrumbs navigation checks", async({ page, checkForErrors, context }) => {
await page.goto("/files/playwright-files/myfolder");
await page.goto("/files/playwright%20+%20files/myfolder");
await page.waitForSelector('#breadcrumbs');
let spanChildrenCount = await page.locator('#breadcrumbs > ul > li.item').count();
spanChildrenCount = await page.locator('#breadcrumbs > ul > li.item').count();
@@ -23,14 +23,14 @@ test("breadcrumbs navigation checks", async({ page, checkForErrors, context }) =
let breadCrumbLink = page.locator('a[aria-label="breadcrumb-link-myfolder"]')
await expect(breadCrumbLink).toHaveText("myfolder");
await page.goto("/files/playwright-files/myfolder/testdata");
await page.goto("/files/playwright%20+%20files/myfolder/testdata");
await page.waitForSelector('#breadcrumbs');
spanChildrenCount = await page.locator('#breadcrumbs > ul > li.item').count();
expect(spanChildrenCount).toBe(2);
breadCrumbLink = page.locator('a[aria-label="breadcrumb-link-testdata"]')
await expect(breadCrumbLink).toHaveText("testdata");
await page.goto("/files/playwright-files/files");
await page.goto("/files/playwright%20+%20files/files");
await page.waitForSelector('#breadcrumbs');
spanChildrenCount = await page.locator('#breadcrumbs > ul > li.item').count();
expect(spanChildrenCount).toBe(1);
+2 -2
View File
@@ -16,7 +16,7 @@ async function globalSetup() {
expect(cookies.find((c) => c.name === "auth")?.value).toBeDefined();
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
await page.waitForURL("**/files/playwright-files", { timeout: 500 });
await page.waitForURL("**/files/playwright%20+%20files", { timeout: 500 });
// Create a share of folder
await page.locator('a[aria-label="myfolder"]').waitFor({ state: 'visible' });
@@ -36,7 +36,7 @@ async function globalSetup() {
localStorage.setItem('shareHash', hash);
}, shareHash);
await page.goto("http://127.0.0.1/files/playwright-files", { timeout: 500 });
await page.goto("http://127.0.0.1/files/playwright%20%2B%20files", { timeout: 500 });
// Create a share of file
await page.locator('a[aria-label="1file1.txt"]').waitFor({ state: 'visible' });
await page.locator('a[aria-label="1file1.txt"]').click({ button: "right" });