feat: user trash dir (#8)

pull/3756/head
Laurynas Gadliauskas 2021-06-04 11:44:04 +03:00 committed by GitHub
parent 8fe3f85d18
commit 1b79b0c166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 9 deletions

View File

@ -27,13 +27,14 @@ var usersCmd = &cobra.Command{
func printUsers(usrs []*users.User) { func printUsers(usrs []*users.User) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") fmt.Fprintln(w, "ID\tUsername\tScope\tTrash Dir\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, u := range usrs { for _, u := range usrs {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
u.ID, u.ID,
u.Username, u.Username,
u.Scope, u.Scope,
u.TrashDir,
u.Locale, u.Locale,
u.ViewMode, u.ViewMode,
u.SingleClick, u.SingleClick,
@ -74,6 +75,7 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.Bool("lockPassword", false, "lock password") flags.Bool("lockPassword", false, "lock password")
flags.StringSlice("commands", nil, "a list of the commands a user can execute") flags.StringSlice("commands", nil, "a list of the commands a user can execute")
flags.String("scope", ".", "scope for users") flags.String("scope", ".", "scope for users")
flags.String("trashDir", "", "trash directory path for users")
flags.String("locale", "en", "locale for users") flags.String("locale", "en", "locale for users")
flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users")
flags.Bool("singleClick", false, "use single clicks only") flags.Bool("singleClick", false, "use single clicks only")
@ -93,6 +95,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
switch flag.Name { switch flag.Name {
case "scope": case "scope":
defaults.Scope = mustGetString(flags, flag.Name) defaults.Scope = mustGetString(flags, flag.Name)
case "trashDir":
defaults.TrashDir = mustGetString(flags, flag.Name)
case "locale": case "locale":
defaults.Locale = mustGetString(flags, flag.Name) defaults.Locale = mustGetString(flags, flag.Name)
case "viewMode": case "viewMode":

View File

@ -42,6 +42,7 @@ options you want to change.`,
defaults := settings.UserDefaults{ defaults := settings.UserDefaults{
Scope: user.Scope, Scope: user.Scope,
TrashDir: user.TrashDir,
Locale: user.Locale, Locale: user.Locale,
ViewMode: user.ViewMode, ViewMode: user.ViewMode,
SingleClick: user.SingleClick, SingleClick: user.SingleClick,
@ -51,6 +52,7 @@ options you want to change.`,
} }
getUserDefaults(flags, &defaults, false) getUserDefaults(flags, &defaults, false)
user.Scope = defaults.Scope user.Scope = defaults.Scope
user.TrashDir = defaults.TrashDir
user.Locale = defaults.Locale user.Locale = defaults.Locale
user.ViewMode = defaults.ViewMode user.ViewMode = defaults.ViewMode
user.SingleClick = defaults.SingleClick user.SingleClick = defaults.SingleClick

View File

@ -49,8 +49,8 @@ async function resourceAction(url, method, content) {
} }
} }
export async function remove(url) { export async function remove(url, skipTrash = true) {
return resourceAction(url, "DELETE"); return resourceAction(`${url}?skip_trash=${skipTrash}`, "DELETE");
} }
export async function put(url, content = "") { export async function put(url, content = "") {

View File

@ -33,6 +33,18 @@
</button> </button>
</div> </div>
<div v-if="trashDir != ''">
<router-link
class="action"
:to="'/files/' + trashDir"
:aria-label="$t('sidebar.trashBin')"
:title="$t('sidebar.trashBin')"
>
<i class="material-icons">delete</i>
<span>{{ $t("sidebar.trashBin") }}</span>
</router-link>
</div>
<div> <div>
<router-link <router-link
class="action" class="action"
@ -109,6 +121,7 @@ import {
version, version,
signup, signup,
disableExternal, disableExternal,
trashDir,
noAuth, noAuth,
authMethod, authMethod,
authLogoutURL, authLogoutURL,
@ -125,6 +138,7 @@ export default {
signup: () => signup, signup: () => signup,
version: () => version, version: () => version,
disableExternal: () => disableExternal, disableExternal: () => disableExternal,
trashDir: () => trashDir,
noAuth: () => noAuth, noAuth: () => noAuth,
authMethod: () => authMethod, authMethod: () => authMethod,
authLogoutURL: () => authLogoutURL, authLogoutURL: () => authLogoutURL,

View File

@ -7,6 +7,10 @@
<p v-else> <p v-else>
{{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }} {{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }}
</p> </p>
<p v-if="trashBinCheckbox">
<input type="checkbox" v-model="skipTrash" />
{{ $t("prompts.skipTrashMessage") }}
</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<button <button
@ -33,12 +37,36 @@
import { mapGetters, mapMutations, mapState } from "vuex"; import { mapGetters, mapMutations, mapState } from "vuex";
import { files as api } from "@/api"; import { files as api } from "@/api";
import buttons from "@/utils/buttons"; import buttons from "@/utils/buttons";
import { trashDir } from "@/utils/constants";
export default { export default {
name: "delete", name: "delete",
data: function () {
return {
skipTrash: true,
};
},
computed: { computed: {
...mapGetters(["isListing", "selectedCount"]), ...mapGetters(["isListing", "selectedCount"]),
...mapState(["req", "selected", "showConfirm"]), ...mapState(["req", "selected", "showConfirm"]),
trashBinCheckbox() {
if (trashDir === "") {
return false;
}
if (this.req.path.startsWith(`/${trashDir}/`)) {
return false;
}
if (
this.selectedCount == 1 &&
this.req.items[this.selected].name == trashDir
) {
return false;
}
return true;
},
}, },
methods: { methods: {
...mapMutations(["closeHovers"]), ...mapMutations(["closeHovers"]),
@ -47,7 +75,7 @@ export default {
try { try {
if (!this.isListing) { if (!this.isListing) {
await api.remove(this.$route.path); await api.remove(this.$route.path, this.skipTrash);
buttons.success("delete"); buttons.success("delete");
this.showConfirm(); this.showConfirm();
@ -63,7 +91,7 @@ export default {
let promises = []; let promises = [];
for (let index of this.selected) { for (let index of this.selected) {
promises.push(api.remove(this.req.items[index].url)); promises.push(api.remove(this.req.items[index].url, this.skipTrash));
} }
await Promise.all(promises); await Promise.all(promises);

View File

@ -138,6 +138,7 @@
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?", "deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?", "deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteMessageShare": "Are you sure you want to delete this share({path})?", "deleteMessageShare": "Are you sure you want to delete this share({path})?",
"skipTrashMessage": "Skip trash bin and delete immediately",
"deleteTitle": "Delete files", "deleteTitle": "Delete files",
"displayName": "Display Name:", "displayName": "Display Name:",
"download": "Download files", "download": "Download files",
@ -262,6 +263,7 @@
"myFiles": "My files", "myFiles": "My files",
"newFile": "New file", "newFile": "New file",
"newFolder": "New folder", "newFolder": "New folder",
"trashBin": "Trash bin",
"preview": "Preview", "preview": "Preview",
"settings": "Settings", "settings": "Settings",
"signup": "Signup", "signup": "Signup",

View File

@ -7,6 +7,7 @@ const recaptchaKey = window.FileBrowser.ReCaptchaKey;
const signup = window.FileBrowser.Signup; const signup = window.FileBrowser.Signup;
const version = window.FileBrowser.Version; const version = window.FileBrowser.Version;
const logoURL = `${staticURL}/img/logo.svg`; const logoURL = `${staticURL}/img/logo.svg`;
const trashDir = window.FileBrowser.TrashDir;
const noAuth = window.FileBrowser.NoAuth; const noAuth = window.FileBrowser.NoAuth;
const authMethod = window.FileBrowser.AuthMethod; const authMethod = window.FileBrowser.AuthMethod;
const authLogoutURL = window.FileBrowser.AuthLogoutURL; const authLogoutURL = window.FileBrowser.AuthLogoutURL;
@ -25,6 +26,7 @@ export {
recaptchaKey, recaptchaKey,
signup, signup,
version, version,
trashDir,
noAuth, noAuth,
authMethod, authMethod,
authLogoutURL, authLogoutURL,

View File

@ -95,9 +95,31 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
return errToStatus(err), err return errToStatus(err), err
} }
err = d.RunHook(func() error { skipTrash := r.URL.Query().Get("skip_trash") == "true"
return d.user.Fs.RemoveAll(r.URL.Path)
}, "delete", r.URL.Path, "", d.user) if d.user.TrashDir == "" || skipTrash {
err = d.RunHook(func() error {
return d.user.Fs.RemoveAll(r.URL.Path)
}, "delete", r.URL.Path, "", d.user)
} else {
src := r.URL.Path
dst := d.user.TrashDir
if !d.Check(src) || !d.Check(dst) {
return http.StatusForbidden, nil
}
src = path.Clean("/" + src)
dst = path.Clean("/" + dst)
err = d.user.Fs.MkdirAll(dst, 0755)
if err != nil {
return errToStatus(err), err
}
dst = path.Join(dst, file.Name)
err = fileutils.MoveFile(d.user.Fs, src, dst)
}
if err != nil { if err != nil {
return errToStatus(err), err return errToStatus(err), err

View File

@ -33,6 +33,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
"Version": version.Version, "Version": version.Version,
"StaticURL": path.Join(d.server.BaseURL, "/static"), "StaticURL": path.Join(d.server.BaseURL, "/static"),
"Signup": d.settings.Signup, "Signup": d.settings.Signup,
"TrashDir": d.settings.Defaults.TrashDir,
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth, "NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
"AuthMethod": d.settings.AuthMethod, "AuthMethod": d.settings.AuthMethod,
"AuthLogoutURL": d.settings.AuthLogoutURL, "AuthLogoutURL": d.settings.AuthLogoutURL,

View File

@ -9,6 +9,7 @@ import (
// for some fields on User. // for some fields on User.
type UserDefaults struct { type UserDefaults struct {
Scope string `json:"scope"` Scope string `json:"scope"`
TrashDir string `json:"trashDir"`
Locale string `json:"locale"` Locale string `json:"locale"`
ViewMode users.ViewMode `json:"viewMode"` ViewMode users.ViewMode `json:"viewMode"`
SingleClick bool `json:"singleClick"` SingleClick bool `json:"singleClick"`
@ -21,6 +22,7 @@ type UserDefaults struct {
// Apply applies the default options to a user. // Apply applies the default options to a user.
func (d *UserDefaults) Apply(u *users.User) { func (d *UserDefaults) Apply(u *users.User) {
u.Scope = d.Scope u.Scope = d.Scope
u.TrashDir = d.TrashDir
u.Locale = d.Locale u.Locale = d.Locale
u.ViewMode = d.ViewMode u.ViewMode = d.ViewMode
u.SingleClick = d.SingleClick u.SingleClick = d.SingleClick

View File

@ -25,6 +25,7 @@ type User struct {
Username string `storm:"unique" json:"username"` Username string `storm:"unique" json:"username"`
Password string `json:"password"` Password string `json:"password"`
Scope string `json:"scope"` Scope string `json:"scope"`
TrashDir string `json:"trashDir"`
Locale string `json:"locale"` Locale string `json:"locale"`
LockPassword bool `json:"lockPassword"` LockPassword bool `json:"lockPassword"`
ViewMode ViewMode `json:"viewMode"` ViewMode ViewMode `json:"viewMode"`