commit
84e3a98303
|
@ -170,7 +170,7 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
|||
case strings.HasPrefix(mimetype, "image"):
|
||||
i.Type = "image"
|
||||
return nil
|
||||
case (strings.HasPrefix(mimetype, "text") || (len(buffer) > 0 && !isBinary(buffer))) && i.Size <= 10*1024*1024: // 10 MB
|
||||
case (strings.HasPrefix(mimetype, "text") || !isBinary(buffer)) && i.Size <= 10*1024*1024: // 10 MB
|
||||
i.Type = "text"
|
||||
|
||||
if !modify {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -29,14 +29,6 @@ export default {
|
|||
type: Number,
|
||||
default: () => 200,
|
||||
},
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: () => 4,
|
||||
},
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: () => 0.25,
|
||||
},
|
||||
classList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
|
@ -45,10 +37,6 @@ export default {
|
|||
type: Number,
|
||||
default: () => 0.25,
|
||||
},
|
||||
autofill: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -64,6 +52,8 @@ export default {
|
|||
center: { x: 0, y: 0 },
|
||||
relative: { x: 0, y: 0 },
|
||||
},
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -88,6 +78,10 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
src: function () {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
|
||||
this.scale = 1;
|
||||
this.setZoom();
|
||||
this.setCenter();
|
||||
|
@ -122,6 +116,21 @@ export default {
|
|||
img.classList.add("image-ex-img-ready");
|
||||
|
||||
document.addEventListener("mouseup", this.onMouseUp);
|
||||
|
||||
let realSize = img.naturalWidth;
|
||||
let displaySize = img.offsetWidth;
|
||||
|
||||
// Image is in portrait orientation
|
||||
if (img.naturalHeight > img.naturalWidth) {
|
||||
realSize = img.naturalHeight;
|
||||
displaySize = img.offsetHeight;
|
||||
}
|
||||
|
||||
// Scale needed to display the image on full size
|
||||
const fullScale = realSize / displaySize;
|
||||
|
||||
// Full size plus additional zoom
|
||||
this.maxScale = fullScale + 4;
|
||||
},
|
||||
onMouseUp() {
|
||||
this.inDrag = false;
|
||||
|
@ -251,7 +260,7 @@ export default {
|
|||
}
|
||||
},
|
||||
wheelMove(event) {
|
||||
this.scale += (event.wheelDeltaY / 100) * this.zoomStep;
|
||||
this.scale += -Math.sign(event.deltaY) * this.zoomStep;
|
||||
this.setZoom();
|
||||
},
|
||||
setZoom() {
|
||||
|
|
|
@ -56,6 +56,12 @@ export default {
|
|||
|
||||
let prompt = this.$refs.currentComponent;
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
event.stopImmediatePropagation();
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.show) {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
}
|
||||
|
||||
.share__box__info {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 18em;
|
||||
}
|
||||
|
||||
.share__box__element {
|
||||
|
|
|
@ -110,4 +110,60 @@
|
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic-ext.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-cyrillic.woff2) format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek-ext.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-greek.woff2) format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-vietnamese.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'), url(../assets/fonts/roboto/bold-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
|
||||
@import "~material-design-icons/iconfont/material-icons.css";
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
transition: .1s ease background, .1s ease opacity;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#listing .item div:last-of-type {
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
|
||||
#previewer {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
padding-top: 4em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -142,7 +143,6 @@
|
|||
}
|
||||
|
||||
#previewer .preview {
|
||||
margin-top: 4em;
|
||||
text-align: center;
|
||||
height: calc(100vh - 4em);
|
||||
}
|
||||
|
@ -209,7 +209,7 @@
|
|||
#editor-container {
|
||||
background-color: #fafafa;
|
||||
position: fixed;
|
||||
margin-top: 4em;
|
||||
padding-top: 4em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<header-bar v-if="error || !req.type" showMenu showLogo />
|
||||
<header-bar v-if="error || req.type == null" showMenu showLogo />
|
||||
|
||||
<breadcrumbs base="/files" />
|
||||
|
||||
|
@ -124,15 +124,6 @@ export default {
|
|||
}
|
||||
},
|
||||
keyEvent(event) {
|
||||
if (this.show !== null) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit("closeHovers");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -10,26 +10,22 @@
|
|||
{{ $t("settings.profileSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/shares"
|
||||
<router-link to="/settings/shares" v-if="user.perm.share"
|
||||
><li :class="{ active: $route.path === '/settings/shares' }">
|
||||
{{ $t("settings.shareManagement") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/global"
|
||||
><li
|
||||
:class="{ active: $route.path === '/settings/global' }"
|
||||
v-if="user.perm.admin"
|
||||
>
|
||||
<router-link to="/settings/global" v-if="user.perm.admin"
|
||||
><li :class="{ active: $route.path === '/settings/global' }">
|
||||
{{ $t("settings.globalSettings") }}
|
||||
</li></router-link
|
||||
>
|
||||
<router-link to="/settings/users"
|
||||
<router-link to="/settings/users" v-if="user.perm.admin"
|
||||
><li
|
||||
:class="{
|
||||
active:
|
||||
$route.path === '/settings/users' || $route.name === 'User',
|
||||
}"
|
||||
v-if="user.perm.admin"
|
||||
>
|
||||
{{ $t("settings.userManagement") }}
|
||||
</li></router-link
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<template #actions>
|
||||
<action
|
||||
v-if="user.perm.modify"
|
||||
id="save-button"
|
||||
icon="save"
|
||||
:label="$t('buttons.save')"
|
||||
|
|
|
@ -25,18 +25,21 @@
|
|||
/>
|
||||
<action
|
||||
v-if="headerButtons.copy"
|
||||
id="copy-button"
|
||||
icon="content_copy"
|
||||
:label="$t('buttons.copyFile')"
|
||||
show="copy"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.move"
|
||||
id="move-button"
|
||||
icon="forward"
|
||||
:label="$t('buttons.moveFile')"
|
||||
show="move"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.delete"
|
||||
id="delete-button"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
show="delete"
|
||||
|
@ -55,13 +58,16 @@
|
|||
@action="switchView"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
:counter="selectedCount"
|
||||
/>
|
||||
<action
|
||||
v-if="headerButtons.upload"
|
||||
icon="file_upload"
|
||||
id="upload-button"
|
||||
:label="$t('buttons.upload')"
|
||||
@action="upload"
|
||||
/>
|
||||
|
@ -135,7 +141,7 @@
|
|||
multiple
|
||||
/>
|
||||
</div>
|
||||
<div v-else id="listing" :class="user.viewMode">
|
||||
<div v-else id="listing" ref="listing" :class="user.viewMode">
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div></div>
|
||||
|
@ -253,6 +259,7 @@ import { users, files as api } from "@/api";
|
|||
import { enableExec } from "@/utils/constants";
|
||||
import * as upload from "@/utils/upload";
|
||||
import css from "@/utils/css";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Action from "@/components/header/Action";
|
||||
|
@ -350,15 +357,31 @@ export default {
|
|||
return this.width <= 736;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
req: function () {
|
||||
// Reset the show value
|
||||
this.showLimit = 50;
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
// Check the columns size for the first time.
|
||||
this.resizeEvent();
|
||||
this.colunmsResize();
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
|
||||
// Add the needed event listeners to the window and document.
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
window.addEventListener("resize", this.resizeEvent);
|
||||
window.addEventListener("scroll", this.scrollEvent);
|
||||
window.addEventListener("resize", this.windowsResize);
|
||||
|
||||
if (!this.user.perm.create) return;
|
||||
document.addEventListener("dragover", this.preventDefault);
|
||||
document.addEventListener("dragenter", this.dragEnter);
|
||||
document.addEventListener("dragleave", this.dragLeave);
|
||||
|
@ -367,9 +390,10 @@ export default {
|
|||
beforeDestroy() {
|
||||
// Remove event listeners before destroying this page.
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
window.removeEventListener("resize", this.resizeEvent);
|
||||
window.removeEventListener("scroll", this.scrollEvent);
|
||||
window.removeEventListener("resize", this.windowsResize);
|
||||
|
||||
if (!this.user.perm.create) return;
|
||||
document.removeEventListener("dragover", this.preventDefault);
|
||||
document.removeEventListener("dragenter", this.dragEnter);
|
||||
document.removeEventListener("dragleave", this.dragLeave);
|
||||
|
@ -543,7 +567,7 @@ export default {
|
|||
|
||||
action(overwrite, rename);
|
||||
},
|
||||
resizeEvent() {
|
||||
colunmsResize() {
|
||||
// Update the columns size based on the window width.
|
||||
let columns = Math.floor(
|
||||
document.querySelector("main").offsetWidth / 300
|
||||
|
@ -552,11 +576,27 @@ export default {
|
|||
if (columns === 0) columns = 1;
|
||||
items.style.width = `calc(${100 / columns}% - 1em)`;
|
||||
},
|
||||
scrollEvent() {
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
||||
this.showLimit += 50;
|
||||
scrollEvent: throttle(function () {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// All items are displayed
|
||||
if (this.showLimit >= totalItems) return;
|
||||
|
||||
const currentPos = window.innerHeight + window.scrollY;
|
||||
|
||||
// Trigger at the 75% of the window height
|
||||
const triggerPos = document.body.offsetHeight - window.innerHeight * 0.25;
|
||||
|
||||
if (currentPos > triggerPos) {
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil(
|
||||
(window.innerHeight * 2) / this.itemWeight
|
||||
);
|
||||
|
||||
// Increase the number of displayed items
|
||||
this.showLimit += showQuantity;
|
||||
}
|
||||
},
|
||||
}, 100),
|
||||
dragEnter() {
|
||||
this.dragCounter++;
|
||||
|
||||
|
@ -707,9 +747,19 @@ export default {
|
|||
this.$store.commit("multiple", !this.multiple);
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
windowsResize() {
|
||||
windowsResize: throttle(function () {
|
||||
this.colunmsResize();
|
||||
this.width = window.innerWidth;
|
||||
},
|
||||
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listing == null) return;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill but not fit the window
|
||||
this.fillWindow();
|
||||
}, 100),
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url);
|
||||
|
@ -745,7 +795,12 @@ export default {
|
|||
|
||||
try {
|
||||
await users.update(data, ["viewMode"]);
|
||||
this.$store.commit("updateUser", data);
|
||||
|
||||
// Await ensures correct value for setItemWeight()
|
||||
await this.$store.commit("updateUser", data);
|
||||
|
||||
this.setItemWeight();
|
||||
this.fillWindow();
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
|
@ -757,6 +812,32 @@ export default {
|
|||
document.getElementById("upload-input").click();
|
||||
}
|
||||
},
|
||||
setItemWeight() {
|
||||
let itemQuantity = this.req.numDirs + this.req.numFiles;
|
||||
if (itemQuantity > this.showLimit) itemQuantity = this.showLimit;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.itemWeight = this.$refs.listing.offsetHeight / itemQuantity;
|
||||
},
|
||||
fillWindow(fit = false) {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// More items are displayed than the total
|
||||
if (this.showLimit >= totalItems && !fit) return;
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil(
|
||||
(windowHeight + windowHeight * 2) / this.itemWeight
|
||||
);
|
||||
|
||||
// Less items to display than current
|
||||
if (this.showLimit > showQuantity && !fit) return;
|
||||
|
||||
// Set the number of displayed items
|
||||
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
<template #actions>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.rename"
|
||||
icon="mode_edit"
|
||||
:label="$t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.delete"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
@action="deleteFile"
|
||||
|
@ -30,6 +32,7 @@
|
|||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
|
@ -54,8 +57,22 @@
|
|||
<template v-if="!loading">
|
||||
<div class="preview">
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" controls></audio>
|
||||
<video v-else-if="req.type == 'video'" :src="raw" controls>
|
||||
<audio
|
||||
v-else-if="req.type == 'audio'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
></audio>
|
||||
<video
|
||||
v-else-if="req.type == 'video'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
>
|
||||
<track
|
||||
kind="captions"
|
||||
v-for="(sub, index) in subtitles"
|
||||
|
@ -136,6 +153,7 @@ export default {
|
|||
showNav: true,
|
||||
navTimeout: null,
|
||||
hoverNav: false,
|
||||
autoPlay: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -230,6 +248,14 @@ export default {
|
|||
}
|
||||
},
|
||||
async updatePreview() {
|
||||
if (
|
||||
this.$refs.player &&
|
||||
this.$refs.player.paused &&
|
||||
!this.$refs.player.ended
|
||||
) {
|
||||
this.autoPlay = false;
|
||||
}
|
||||
|
||||
if (this.req.subtitles) {
|
||||
this.subtitles = this.req.subtitles.map(
|
||||
(sub) => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
|
||||
|
|
12
http/raw.go
12
http/raw.go
|
@ -181,14 +181,14 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
|
|||
|
||||
commonDir := fileutils.CommonPrefix(filepath.Separator, filenames...)
|
||||
|
||||
var name string
|
||||
if len(filenames) > 1 {
|
||||
name = "_" + filepath.Base(commonDir)
|
||||
} else {
|
||||
name := filepath.Base(commonDir)
|
||||
if name == "." || name == "" || name == string(filepath.Separator) {
|
||||
name = file.Name
|
||||
}
|
||||
if name == "." || name == "" {
|
||||
name = "archive"
|
||||
// Prefix used to distinguish a filelist generated
|
||||
// archive from the full directory archive
|
||||
if len(filenames) > 1 {
|
||||
name = "_" + name
|
||||
}
|
||||
name += extension
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
|
||||
|
|
|
@ -118,6 +118,11 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
|||
return http.StatusConflict, nil
|
||||
}
|
||||
|
||||
// Permission for overwriting the file
|
||||
if !d.user.Perm.Modify {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = delThumbs(r.Context(), fileCache, file)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
|
@ -125,7 +130,10 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
|||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
|
@ -155,7 +163,10 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
|||
}
|
||||
|
||||
err := d.RunHook(func() error {
|
||||
info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
|
@ -198,6 +209,11 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
|
|||
dst = addVersionSuffix(dst, d.user.Fs)
|
||||
}
|
||||
|
||||
// Permission for overwriting the file
|
||||
if override && !d.user.Perm.Modify {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
switch action {
|
||||
// TODO: use enum
|
||||
|
|
Loading…
Reference in New Issue