I added a new button to the file listing view, a new API function to call the backend, and the corresponding logic to handle the user interaction.

This commit is contained in:
google-labs-jules[bot] 2025-08-24 09:20:21 +00:00
parent 280fa562a6
commit bdfe930ab0
5 changed files with 110 additions and 0 deletions

View File

@ -213,6 +213,14 @@ export function getSubtitlesURL(file: ResourceItem) {
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
}
export async function downloadFromURL(url: string, path: string) {
const res = await fetchURL(`/api/downloads`, {
method: "POST",
body: JSON.stringify({ url, path }),
});
return res.json();
}
export async function usage(url: string, signal: AbortSignal) {
url = removePrefix(url);

View File

@ -11,6 +11,7 @@
"create": "Create",
"delete": "Delete",
"download": "Download",
"downloadFromUrl": "Download from URL",
"file": "File",
"folder": "Folder",
"fullScreen": "Toggle full screen",

View File

@ -72,6 +72,13 @@
:label="t('buttons.upload')"
@action="uploadFunc"
/>
<action
v-if="headerButtons.upload"
icon="file_download"
id="download-url-button"
:label="t('buttons.downloadFromUrl')"
@action="downloadUrl"
/>
<action icon="info" :label="t('buttons.info')" show="info" />
<action
icon="check_circle"
@ -894,6 +901,26 @@ const download = () => {
});
};
const downloadUrl = () => {
layoutStore.showHover({
prompt: "url",
confirm: (event: Event, url: string) => {
event.preventDefault();
layoutStore.closeHovers();
if (url === "") {
return;
}
api.downloadFromURL(url, route.path)
.then(() => {
fileStore.reload = true;
})
.catch($showError);
},
});
};
const switchView = async () => {
layoutStore.closeHovers();

73
http/download.go Normal file
View File

@ -0,0 +1,73 @@
package http
import (
"encoding/json"
"net/http"
"net/url"
"path"
"github.com/filebrowser/filebrowser/v2/files"
)
type downloadBody struct {
URL string `json:"url"`
Path string `json:"path"`
}
var urlDownloadHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Create {
return http.StatusForbidden, nil
}
var body downloadBody
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
return http.StatusBadRequest, err
}
if body.URL == "" || body.Path == "" {
return http.StatusBadRequest, nil
}
_, err = url.ParseRequestURI(body.URL)
if err != nil {
return http.StatusBadRequest, err
}
fileName := path.Base(body.URL)
filePath := path.Join(body.Path, fileName)
if !d.Check(filePath) {
return http.StatusForbidden, nil
}
resp, err := http.Get(body.URL)
if err != nil {
return http.StatusInternalServerError, err
}
defer resp.Body.Close()
err = d.RunHook(func() error {
_, writeErr := writeFile(d.user.Fs, filePath, resp.Body, d.settings.FileMode, d.settings.DirMode)
return writeErr
}, "upload", filePath, "", d.user)
if err != nil {
_ = d.user.Fs.RemoveAll(filePath)
return http.StatusInternalServerError, err
}
file, err := files.NewFileInfo(&files.FileOptions{
Fs: d.user.Fs,
Path: filePath,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
})
if err != nil {
return http.StatusInternalServerError, err
}
return renderJSON(w, r, file)
})

View File

@ -70,6 +70,7 @@ func NewHandler(
api.PathPrefix("/tus").Handler(monkey(tusHeadHandler(), "/api/tus")).Methods("HEAD", "GET")
api.PathPrefix("/tus").Handler(monkey(tusPatchHandler(), "/api/tus")).Methods("PATCH")
api.PathPrefix("/tus").Handler(monkey(tusDeleteHandler(), "/api/tus")).Methods("DELETE")
api.PathPrefix("/downloads").Handler(monkey(urlDownloadHandler, "/api/downloads")).Methods("POST")
api.PathPrefix("/usage").Handler(monkey(diskUsage, "/api/usage")).Methods("GET")