fix: cursor position on Enter for '..' now lands on source folder (same as Backspace); feat: permanent delete via F11/d; fix: footer F-key order (F1-F11)
This commit is contained in:
parent
7a55fb289e
commit
813c40a41e
3 changed files with 253 additions and 114 deletions
|
|
@ -262,6 +262,65 @@ func DeletePath(path string) error {
|
||||||
return os.RemoveAll(path)
|
return os.RemoveAll(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MoveToTrash moves a file or directory to the FreeDesktop Trash directory
|
||||||
|
// (~/.local/share/Trash). Follows the FreeDesktop Trash specification:
|
||||||
|
// - The original item is moved to Trash/files/<basename>
|
||||||
|
// - A .trashinfo file is written to Trash/info/<basename>.trashinfo
|
||||||
|
// - If <basename> already exists in Trash/files, a numeric suffix is appended.
|
||||||
|
func MoveToTrash(path string) error {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot determine home directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trashDir := filepath.Join(home, ".local", "share", "Trash")
|
||||||
|
filesDir := filepath.Join(trashDir, "files")
|
||||||
|
infoDir := filepath.Join(trashDir, "info")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filesDir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("cannot create trash files directory: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(infoDir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("cannot create trash info directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName := filepath.Base(path)
|
||||||
|
|
||||||
|
// Generate a unique name in the trash directory
|
||||||
|
destName := baseName
|
||||||
|
for counter := 1; ; counter++ {
|
||||||
|
destPath := filepath.Join(filesDir, destName)
|
||||||
|
if _, err := os.Stat(destPath); os.IsNotExist(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("cannot stat trash path: %w", err)
|
||||||
|
}
|
||||||
|
destName = fmt.Sprintf("%s.%d", baseName, counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
destPath := filepath.Join(filesDir, destName)
|
||||||
|
if err := os.Rename(path, destPath); err != nil {
|
||||||
|
// Cross-filesystem move: fall back to copy+delete
|
||||||
|
return fmt.Errorf("cannot move to trash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write .trashinfo file
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
absPath = path
|
||||||
|
}
|
||||||
|
now := time.Now().Format("2006-01-02T15:04:05")
|
||||||
|
infoContent := fmt.Sprintf("[Trash Info]\nPath=%s\nDeletionDate=%s\n", absPath, now)
|
||||||
|
infoPath := filepath.Join(infoDir, destName+".trashinfo")
|
||||||
|
if err := os.WriteFile(infoPath, []byte(infoContent), 0o600); err != nil {
|
||||||
|
// Best-effort: if info file fails, try to move the file back
|
||||||
|
_ = os.Rename(destPath, path)
|
||||||
|
return fmt.Errorf("cannot write trash info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func MakeDir(parent string, name string) (string, error) {
|
func MakeDir(parent string, name string) (string, error) {
|
||||||
target := filepath.Join(parent, name)
|
target := filepath.Join(parent, name)
|
||||||
if err := os.MkdirAll(target, 0o755); err != nil {
|
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ type KeyMap struct {
|
||||||
Move key.Binding
|
Move key.Binding
|
||||||
Mkdir key.Binding
|
Mkdir key.Binding
|
||||||
Delete key.Binding
|
Delete key.Binding
|
||||||
|
PermanentDelete key.Binding
|
||||||
Confirm key.Binding
|
Confirm key.Binding
|
||||||
Background key.Binding
|
Background key.Binding
|
||||||
ProgressCancel key.Binding
|
ProgressCancel key.Binding
|
||||||
|
|
@ -69,7 +70,8 @@ func DefaultKeyMap() KeyMap {
|
||||||
Copy: key.NewBinding(key.WithKeys("f5", "c"), key.WithHelp("F5/c", "copy")),
|
Copy: key.NewBinding(key.WithKeys("f5", "c"), key.WithHelp("F5/c", "copy")),
|
||||||
Move: key.NewBinding(key.WithKeys("f6", "m"), key.WithHelp("F6/m", "move")),
|
Move: key.NewBinding(key.WithKeys("f6", "m"), key.WithHelp("F6/m", "move")),
|
||||||
Mkdir: key.NewBinding(key.WithKeys("f7", "n"), key.WithHelp("F7/n", "mkdir")),
|
Mkdir: key.NewBinding(key.WithKeys("f7", "n"), key.WithHelp("F7/n", "mkdir")),
|
||||||
Delete: key.NewBinding(key.WithKeys("f8", "delete", "x"), key.WithHelp("F8/x", "delete")),
|
Delete: key.NewBinding(key.WithKeys("f8", "delete", "x"), key.WithHelp("F8/x", "trash")),
|
||||||
|
PermanentDelete: key.NewBinding(key.WithKeys("f11", "d"), key.WithHelp("F11/d", "permanent delete")),
|
||||||
Confirm: key.NewBinding(key.WithKeys("enter", "y"), key.WithHelp("Enter/y", "confirm")),
|
Confirm: key.NewBinding(key.WithKeys("enter", "y"), key.WithHelp("Enter/y", "confirm")),
|
||||||
Background: key.NewBinding(key.WithKeys("b"), key.WithHelp("b", "background")),
|
Background: key.NewBinding(key.WithKeys("b"), key.WithHelp("b", "background")),
|
||||||
ProgressCancel: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "cancel transfer")),
|
ProgressCancel: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "cancel transfer")),
|
||||||
|
|
@ -81,13 +83,13 @@ func DefaultKeyMap() KeyMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k KeyMap) ShortHelp() []key.Binding {
|
func (k KeyMap) ShortHelp() []key.Binding {
|
||||||
return []key.Binding{k.Help, k.Rename, k.View, k.Archive, k.Copy, k.Move, k.Mkdir, k.Delete, k.Info, k.Quit}
|
return []key.Binding{k.Help, k.Rename, k.View, k.Archive, k.Copy, k.Move, k.Mkdir, k.Delete, k.Info, k.Quit, k.PermanentDelete}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k KeyMap) FullHelp() [][]key.Binding {
|
func (k KeyMap) FullHelp() [][]key.Binding {
|
||||||
return [][]key.Binding{
|
return [][]key.Binding{
|
||||||
{k.Help, k.Up, k.Down, k.SelectUp, k.SelectDown, k.Open, k.Back},
|
{k.Help, k.Up, k.Down, k.SelectUp, k.SelectDown, k.Open, k.Back},
|
||||||
{k.Rename, k.View, k.Caret, k.Edit, k.Archive, k.Copy, k.Move, k.Delete},
|
{k.Rename, k.View, k.Caret, k.Edit, k.Archive, k.Copy, k.Move, k.Delete},
|
||||||
{k.SelectText, k.DirSize, k.Refresh, k.ToggleHidden, k.CycleSort, k.CycleTheme, k.Quit},
|
{k.PermanentDelete, k.SelectText, k.DirSize, k.Refresh, k.ToggleHidden, k.CycleSort, k.CycleTheme, k.Quit},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ const (
|
||||||
opCopy fileOpKind = iota
|
opCopy fileOpKind = iota
|
||||||
opMove
|
opMove
|
||||||
opDelete
|
opDelete
|
||||||
|
opPermanentDelete
|
||||||
opMkdir
|
opMkdir
|
||||||
opRename
|
opRename
|
||||||
opEdit
|
opEdit
|
||||||
|
|
@ -104,6 +105,7 @@ type copyProgressMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type deletePlanMsg struct {
|
type deletePlanMsg struct {
|
||||||
|
kind fileOpKind
|
||||||
sourcePaths []string
|
sourcePaths []string
|
||||||
stats vfs.TransferStats
|
stats vfs.TransferStats
|
||||||
err error
|
err error
|
||||||
|
|
@ -336,7 +338,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
case opMove:
|
case opMove:
|
||||||
m.status = fmt.Sprintf("Moved to %s", msg.targetPath)
|
m.status = fmt.Sprintf("Moved to %s", msg.targetPath)
|
||||||
case opDelete:
|
case opDelete:
|
||||||
m.status = "Deleted"
|
m.status = "Moved to trash"
|
||||||
|
m.activePane().ClearMarks()
|
||||||
|
case opPermanentDelete:
|
||||||
|
m.status = "Permanently deleted"
|
||||||
m.activePane().ClearMarks()
|
m.activePane().ClearMarks()
|
||||||
case opMkdir:
|
case opMkdir:
|
||||||
m.status = fmt.Sprintf("Created %s", msg.targetPath)
|
m.status = fmt.Sprintf("Created %s", msg.targetPath)
|
||||||
|
|
@ -396,7 +401,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
title := "Delete selected entr" + pluralSuffix(len(msg.sourcePaths), "y", "ies") + "?"
|
title := "Move selected entr" + pluralSuffix(len(msg.sourcePaths), "y", "ies") + " to trash?"
|
||||||
|
if msg.kind == opPermanentDelete {
|
||||||
|
title = "Permanently delete selected entr" + pluralSuffix(len(msg.sourcePaths), "y", "ies") + "?"
|
||||||
|
}
|
||||||
bodyLines := []string{
|
bodyLines := []string{
|
||||||
fmt.Sprintf("Items: %d", len(msg.sourcePaths)),
|
fmt.Sprintf("Items: %d", len(msg.sourcePaths)),
|
||||||
fmt.Sprintf("Files: %d", msg.stats.FilesTotal),
|
fmt.Sprintf("Files: %d", msg.stats.FilesTotal),
|
||||||
|
|
@ -407,7 +415,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
strings.Join(bodyLines, "\n"),
|
strings.Join(bodyLines, "\n"),
|
||||||
"confirm-actions",
|
"confirm-actions",
|
||||||
pendingOperation{
|
pendingOperation{
|
||||||
kind: opDelete,
|
kind: msg.kind,
|
||||||
sourcePaths: append([]string(nil), msg.sourcePaths...),
|
sourcePaths: append([]string(nil), msg.sourcePaths...),
|
||||||
stats: msg.stats,
|
stats: msg.stats,
|
||||||
},
|
},
|
||||||
|
|
@ -893,6 +901,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
case key.Matches(msg, m.keys.Delete):
|
case key.Matches(msg, m.keys.Delete):
|
||||||
return m.handleDelete()
|
return m.handleDelete()
|
||||||
|
case key.Matches(msg, m.keys.PermanentDelete):
|
||||||
|
return m.handlePermanentDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
case tea.MouseMsg:
|
case tea.MouseMsg:
|
||||||
|
|
@ -1376,19 +1386,10 @@ func (m *Model) enterSelected() error {
|
||||||
m.status = "File is shown in the middle pane. Use F3 for pager or F4 for editor."
|
m.status = "File is shown in the middle pane. Use F3 for pager or F4 for editor."
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// When inside an archive mount and selecting "..", use archive-aware
|
|
||||||
// navigation (goParent) instead of blindly setting pane.Path to the
|
|
||||||
// parent directory (which would be /tmp for a temp-mounted archive).
|
|
||||||
if selected.IsParent {
|
|
||||||
if _, archiveMounted := pane.CurrentArchive(); archiveMounted {
|
|
||||||
return m.goParent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save current directory to history before navigating.
|
// Save current directory to history before navigating.
|
||||||
pane.PushHistory(pane.Path)
|
pane.PushHistory(pane.Path)
|
||||||
currentName := selected.Name
|
|
||||||
pane.Path = selected.Path
|
pane.Path = selected.Path
|
||||||
if err := m.reloadPane(pane.ID, currentName); err != nil {
|
if err := m.reloadPane(pane.ID, selected.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.status = fmt.Sprintf("Entered %s", pane.Path)
|
m.status = fmt.Sprintf("Entered %s", pane.Path)
|
||||||
|
|
@ -1409,6 +1410,19 @@ func (m *Model) handleOpenSelected() (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigating up via ".." — use goParent which preserves the cursor
|
||||||
|
// position on the directory/archive we came from (by finding its name
|
||||||
|
// in the parent listing via FindSelected). This applies both inside
|
||||||
|
// archive mounts (where pane.Path must stay within the temp mount)
|
||||||
|
// and regular directories (for consistent cursor placement).
|
||||||
|
if selected.IsParent {
|
||||||
|
if err := m.goParent(); err != nil {
|
||||||
|
m.status = err.Error()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, m.loadPreviewCmd()
|
||||||
|
}
|
||||||
|
|
||||||
if selected.IsDir {
|
if selected.IsDir {
|
||||||
if err := m.enterSelected(); err != nil {
|
if err := m.enterSelected(); err != nil {
|
||||||
m.status = err.Error()
|
m.status = err.Error()
|
||||||
|
|
@ -1647,13 +1661,35 @@ func (m *Model) handleDelete() (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
if !m.cfg.Behavior.ConfirmDelete {
|
if !m.cfg.Behavior.ConfirmDelete {
|
||||||
m.busy = true
|
m.busy = true
|
||||||
m.status = fmt.Sprintf("Deleting %d entr%s", len(sources), pluralSuffix(len(sources), "y", "ies"))
|
m.status = fmt.Sprintf("Moving %d entr%s to trash", len(sources), pluralSuffix(len(sources), "y", "ies"))
|
||||||
return m, deletePathsCmd(sources)
|
return m, trashPathsCmd(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.busy = true
|
||||||
|
m.status = "Calculating trash size"
|
||||||
|
return m, trashPlanCmd(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) handlePermanentDelete() (tea.Model, tea.Cmd) {
|
||||||
|
if m.activePane().InArchive() {
|
||||||
|
m.status = "Archive mode is read-only; permanent delete is disabled"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sources := m.operationSources()
|
||||||
|
if len(sources) == 0 {
|
||||||
|
m.status = "Nothing to delete"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
if !m.cfg.Behavior.ConfirmDelete {
|
||||||
|
m.busy = true
|
||||||
|
m.status = fmt.Sprintf("Permanently deleting %d entr%s", len(sources), pluralSuffix(len(sources), "y", "ies"))
|
||||||
|
return m, deletePathsPermanentCmd(sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.busy = true
|
m.busy = true
|
||||||
m.status = "Calculating delete size"
|
m.status = "Calculating delete size"
|
||||||
return m, deletePlanCmd(sources)
|
return m, deletePlanPermanentCmd(sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) handleView() (tea.Model, tea.Cmd) {
|
func (m *Model) handleView() (tea.Model, tea.Cmd) {
|
||||||
|
|
@ -2181,6 +2217,8 @@ func (m *Model) openHelpModal() {
|
||||||
" t cycle theme",
|
" t cycle theme",
|
||||||
"",
|
"",
|
||||||
"Dialogs and Transfers",
|
"Dialogs and Transfers",
|
||||||
|
" F8 / x move selected entry to trash",
|
||||||
|
" F11 / d permanently delete selected entry",
|
||||||
" r rename selected entry",
|
" r rename selected entry",
|
||||||
" Enter / y confirm action",
|
" Enter / y confirm action",
|
||||||
" Esc / n cancel action",
|
" Esc / n cancel action",
|
||||||
|
|
@ -3526,7 +3564,9 @@ func (p pendingOperation) cmd() tea.Cmd {
|
||||||
case opMove:
|
case opMove:
|
||||||
return nil
|
return nil
|
||||||
case opDelete:
|
case opDelete:
|
||||||
return deletePathsCmd(p.sourcePaths)
|
return trashPathsCmd(p.sourcePaths)
|
||||||
|
case opPermanentDelete:
|
||||||
|
return deletePathsPermanentCmd(p.sourcePaths)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -3607,27 +3647,6 @@ func (m *Model) cleanupArchiveMounts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePlanCmd(sourcePaths []string) tea.Cmd {
|
|
||||||
return func() tea.Msg {
|
|
||||||
stats := vfs.TransferStats{}
|
|
||||||
var err error
|
|
||||||
for _, sourcePath := range sourcePaths {
|
|
||||||
part, statErr := vfs.CopyStats(sourcePath)
|
|
||||||
if statErr != nil {
|
|
||||||
err = statErr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
stats.FilesTotal += part.FilesTotal
|
|
||||||
stats.BytesTotal += part.BytesTotal
|
|
||||||
}
|
|
||||||
return deletePlanMsg{
|
|
||||||
sourcePaths: append([]string(nil), sourcePaths...),
|
|
||||||
stats: stats,
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func archivePlanCmd(sourcePaths []string, targetDir string) tea.Cmd {
|
func archivePlanCmd(sourcePaths []string, targetDir string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
stats := vfs.TransferStats{}
|
stats := vfs.TransferStats{}
|
||||||
|
|
@ -3822,10 +3841,10 @@ func moveCmd(sourcePath, targetDir string, overwrite bool) tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePathsCmd(paths []string) tea.Cmd {
|
func trashPathsCmd(paths []string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if err := vfs.DeletePath(path); err != nil {
|
if err := vfs.MoveToTrash(path); err != nil {
|
||||||
return opMsg{kind: opDelete, sourcePath: path, err: err}
|
return opMsg{kind: opDelete, sourcePath: path, err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3833,6 +3852,61 @@ func deletePathsCmd(paths []string) tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deletePathsPermanentCmd(paths []string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
for _, path := range paths {
|
||||||
|
if err := vfs.DeletePath(path); err != nil {
|
||||||
|
return opMsg{kind: opPermanentDelete, sourcePath: path, err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opMsg{kind: opPermanentDelete}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trashPlanCmd(sourcePaths []string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
stats := vfs.TransferStats{}
|
||||||
|
var err error
|
||||||
|
for _, sourcePath := range sourcePaths {
|
||||||
|
part, statErr := vfs.CopyStats(sourcePath)
|
||||||
|
if statErr != nil {
|
||||||
|
err = statErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
stats.FilesTotal += part.FilesTotal
|
||||||
|
stats.BytesTotal += part.BytesTotal
|
||||||
|
}
|
||||||
|
return deletePlanMsg{
|
||||||
|
kind: opDelete,
|
||||||
|
sourcePaths: append([]string(nil), sourcePaths...),
|
||||||
|
stats: stats,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deletePlanPermanentCmd(sourcePaths []string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
stats := vfs.TransferStats{}
|
||||||
|
var err error
|
||||||
|
for _, sourcePath := range sourcePaths {
|
||||||
|
part, statErr := vfs.CopyStats(sourcePath)
|
||||||
|
if statErr != nil {
|
||||||
|
err = statErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
stats.FilesTotal += part.FilesTotal
|
||||||
|
stats.BytesTotal += part.BytesTotal
|
||||||
|
}
|
||||||
|
return deletePlanMsg{
|
||||||
|
kind: opPermanentDelete,
|
||||||
|
sourcePaths: append([]string(nil), sourcePaths...),
|
||||||
|
stats: stats,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mkdirCmd(parent, name string) tea.Cmd {
|
func mkdirCmd(parent, name string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
targetPath, err := vfs.MakeDir(parent, name)
|
targetPath, err := vfs.MakeDir(parent, name)
|
||||||
|
|
@ -3932,7 +4006,9 @@ func operationDoneLabel(kind fileOpKind) string {
|
||||||
case opCopy:
|
case opCopy:
|
||||||
return "Copied"
|
return "Copied"
|
||||||
case opDelete:
|
case opDelete:
|
||||||
return "Deleted"
|
return "Moved to trash"
|
||||||
|
case opPermanentDelete:
|
||||||
|
return "Permanently deleted"
|
||||||
case opArchive:
|
case opArchive:
|
||||||
return "Archived"
|
return "Archived"
|
||||||
default:
|
default:
|
||||||
|
|
@ -3947,7 +4023,9 @@ func operationVerb(kind fileOpKind) string {
|
||||||
case opMove:
|
case opMove:
|
||||||
return "move"
|
return "move"
|
||||||
case opDelete:
|
case opDelete:
|
||||||
return "delete"
|
return "trash"
|
||||||
|
case opPermanentDelete:
|
||||||
|
return "permanent delete"
|
||||||
case opArchive:
|
case opArchive:
|
||||||
return "archive"
|
return "archive"
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue