mirror of https://github.com/nextcloud/bookmarks
Implement moving items between folders
This commit is contained in:
parent
a13fc4bd5f
commit
5d6c5c3748
|
@ -32,6 +32,9 @@
|
|||
<ActionButton icon="icon-rename" @click="onRename">{{
|
||||
t('bookmarks', 'Rename')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-category-files" @click="onMove">{{
|
||||
t('bookmarks', 'Move')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-delete" @click="onDelete">{{
|
||||
t('bookmarks', 'Delete')
|
||||
}}</ActionButton>
|
||||
|
@ -52,7 +55,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { Actions, ActionButton } from 'nextcloud-vue';
|
||||
import { actions } from '../store';
|
||||
import { actions, mutations } from '../store';
|
||||
import TagLine from './TagLine';
|
||||
|
||||
export default {
|
||||
|
@ -95,11 +98,19 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onDelete() {
|
||||
this.$store.dispatch(actions.DELETE_BOOKMARK, this.bookmark.id);
|
||||
this.$store.dispatch(actions.DELETE_BOOKMARK, {
|
||||
id: this.bookmark.id,
|
||||
folder: this.$store.state.fetchState.query.folder
|
||||
});
|
||||
},
|
||||
onDetails() {
|
||||
this.$store.dispatch(actions.OPEN_BOOKMARK, this.bookmark.id);
|
||||
},
|
||||
onMove() {
|
||||
this.$store.commit(mutations.RESET_SELECTION);
|
||||
this.$store.commit(mutations.ADD_SELECTION_BOOKMARK, this.bookmark);
|
||||
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, true);
|
||||
},
|
||||
onRename() {
|
||||
this.renaming = true;
|
||||
},
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
{{ folder.title }}
|
||||
</h3>
|
||||
<Actions class="Bookmarks__BookmarksList__Folder__Actions">
|
||||
<ActionButton icon="icon-info" @click="onDetails">{{
|
||||
t('bookmarks', 'Details')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-rename" @click="onRename">{{
|
||||
t('bookmarks', 'Rename')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-category-folder" @click="onMove">{{
|
||||
t('bookmarks', 'Move')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-delete" @click="onDelete">{{
|
||||
t('bookmarks', 'Delete')
|
||||
}}</ActionButton>
|
||||
|
@ -58,7 +58,11 @@ export default {
|
|||
onDelete() {
|
||||
this.$store.dispatch(actions.DELETE_FOLDER, this.folder.id);
|
||||
},
|
||||
onDetails() {},
|
||||
onMove() {
|
||||
this.$store.commit(mutations.RESET_SELECTION);
|
||||
this.$store.commit(mutations.ADD_SELECTION_FOLDER, this.folder);
|
||||
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, true);
|
||||
},
|
||||
onSelect() {
|
||||
this.$router.push({ name: 'folder', params: { folder: this.folder.id } });
|
||||
},
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<Modal v-if="showModal" @close="onClose" :title="title">
|
||||
<div class="Bookmarks__ModalMove">
|
||||
<TreeFolder
|
||||
:folder="{
|
||||
title: t('bookmarks', 'Root folder'),
|
||||
id: '-1',
|
||||
children: allFolders
|
||||
}"
|
||||
@select="onSelect"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
import { Modal } from 'nextcloud-vue';
|
||||
import { actions, mutations } from '../store';
|
||||
import TreeFolder from './TreeFolder';
|
||||
|
||||
export default {
|
||||
name: 'ModalMove',
|
||||
components: {
|
||||
Modal,
|
||||
TreeFolder
|
||||
},
|
||||
created() {},
|
||||
computed: {
|
||||
showModal() {
|
||||
return this.$store.state.displayMoveDialog;
|
||||
},
|
||||
selection() {
|
||||
return this.$store.state.selection;
|
||||
},
|
||||
allFolders() {
|
||||
return this.$store.state.folders;
|
||||
},
|
||||
title() {
|
||||
if (this.selection.folders.length) {
|
||||
if (this.selection.bookmarks.length) {
|
||||
return n(
|
||||
'bookmarks',
|
||||
'Moving %n folder and some bookmarks',
|
||||
'Moving %n folders and some bookmarks',
|
||||
this.selection.folders.length
|
||||
);
|
||||
} else {
|
||||
return n(
|
||||
'bookmarks',
|
||||
'Moving %n folder',
|
||||
'Moving %n folders',
|
||||
this.selection.folders.length
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return n(
|
||||
'bookmarks',
|
||||
'Moving %n bookmark',
|
||||
'Moving %n bookmarks',
|
||||
this.selection.bookmarks.length
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onSelect(folderId) {
|
||||
await this.$store.dispatch(actions.MOVE_SELECTION, folderId);
|
||||
this.$store.commit(mutations.RESET_SELECTION);
|
||||
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, false);
|
||||
this.$store.dispatch(actions.RELOAD_VIEW);
|
||||
},
|
||||
onClose() {
|
||||
this.$store.commit(mutations.DISPLAY_MOVE_DIALOG, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.Bookmarks__ModalMove {
|
||||
min-width: 300px;
|
||||
height: 300px;
|
||||
overflow-y: scroll;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div class="Bookmarks__TreeFolder">
|
||||
<div class="Bookmarks__TreeFolder__Title">
|
||||
<h4 @click="showChildren = !showChildren">
|
||||
<figure class="icon-folder"></figure>
|
||||
{{ folder.title }}
|
||||
</h4>
|
||||
<button @click="$emit('select', folder.id)">
|
||||
{{ t('bookmarks', 'Select') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="Bookmarks__TreeFolder__Children" v-if="showChildren">
|
||||
<TreeFolder
|
||||
v-for="folder in folder.children"
|
||||
:folder="folder"
|
||||
@select="$emit('select', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { Modal } from 'nextcloud-vue';
|
||||
import { actions, mutations } from '../store';
|
||||
import TreeFolder from './TreeFolder';
|
||||
|
||||
export default {
|
||||
name: 'TreeFolder',
|
||||
components: {
|
||||
Modal,
|
||||
TreeFolder
|
||||
},
|
||||
props: {
|
||||
folder: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { showChildren: false };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.Bookmarks__TreeFolder__Title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.Bookmarks__TreeFolder__Title > h4 {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
.Bookmarks__TreeFolder__Title > h4 > figure {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.Bookmarks__TreeFolder__Title > button {
|
||||
flex: 0;
|
||||
}
|
||||
.Bookmarks__TreeFolder__Children {
|
||||
padding-left: 20px;
|
||||
}
|
||||
</style>
|
|
@ -6,6 +6,7 @@
|
|||
<BookmarksList :loading="loading.bookmarks" :bookmarks="bookmarks" />
|
||||
</AppContent>
|
||||
<SidebarBookmark />
|
||||
<ModalMove />
|
||||
</Content>
|
||||
</template>
|
||||
|
||||
|
@ -15,6 +16,7 @@ import Navigation from './Navigation';
|
|||
import BookmarksList from './BookmarksList';
|
||||
import Breadcrumbs from './Breadcrumbs';
|
||||
import SidebarBookmark from './SidebarBookmark';
|
||||
import ModalMove from './ModalMove';
|
||||
import { actions } from '../store';
|
||||
|
||||
export default {
|
||||
|
@ -25,7 +27,8 @@ export default {
|
|||
AppContent,
|
||||
Breadcrumbs,
|
||||
BookmarksList,
|
||||
SidebarBookmark
|
||||
SidebarBookmark,
|
||||
ModalMove
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
|
|
150
src/store.js
150
src/store.js
|
@ -10,6 +10,12 @@ const BATCH_SIZE = 42;
|
|||
export const mutations = {
|
||||
DISPLAY_NEW_BOOKMARK: 'DISPLAY_NEW_BOOKMARK',
|
||||
DISPLAY_NEW_FOLDER: 'DISPLAY_NEW_FOLDER',
|
||||
DISPLAY_MOVE_DIALOG: 'DISPLAY_MOVE_DIALOG',
|
||||
RESET_SELECTION: 'RESET_SELECTION',
|
||||
REMOVE_SELECTION_BOOKMARK: 'REMOVE_SELECTION_BOOKMARK',
|
||||
ADD_SELECTION_BOOKMARK: 'ADD_SELECTION_BOOKMARK',
|
||||
REMOVE_SELECTION_FOLDER: 'REMOVE_SELECTION_FOLDER',
|
||||
ADD_SELECTION_FOLDER: 'ADD_SELECTION_FOLDER',
|
||||
ADD_BOOKMARK: 'ADD_BOOKMARK',
|
||||
REMOVE_BOOKMARK: 'REMOVE_BOOKMARK',
|
||||
REMOVE_ALL_BOOKMARK: 'REMOVE_ALL_BOOKMARK',
|
||||
|
@ -33,6 +39,7 @@ export const actions = {
|
|||
DELETE_BOOKMARK: 'DELETE_BOOKMARK',
|
||||
OPEN_BOOKMARK: 'OPEN_BOOKMARK',
|
||||
SAVE_BOOKMARK: 'SAVE_BOOKMARK',
|
||||
MOVE_BOOKMARK: 'MOVE_BOOKMARK',
|
||||
IMPORT_BOOKMARKS: 'IMPORT_BOOKMARKS',
|
||||
DELETE_BOOKMARKS: 'IMPORT_BOOKMARKS',
|
||||
|
||||
|
@ -45,6 +52,10 @@ export const actions = {
|
|||
SAVE_FOLDER: 'SAVE_FOLDER',
|
||||
DELETE_FOLDER: 'DELETE_FOLDER',
|
||||
|
||||
MOVE_SELECTION: 'MOVE_SELECTION',
|
||||
|
||||
RELOAD_VIEW: 'RELOAD_VIEW',
|
||||
|
||||
NO_FILTER: 'NO_FILTER',
|
||||
FILTER_BY_RECENT: 'FILTER_BY_RECENT',
|
||||
FILTER_BY_UNTAGGED: 'FILTER_BY_UNTAGGED',
|
||||
|
@ -84,8 +95,13 @@ export default new Vuex.Store({
|
|||
tags: [],
|
||||
folders: [],
|
||||
foldersById: {},
|
||||
selection: {
|
||||
folders: [],
|
||||
bookmarks: []
|
||||
},
|
||||
displayNewBookmark: false,
|
||||
displayNewFolder: false,
|
||||
displayMoveDialog: false,
|
||||
sidebar: null,
|
||||
viewMode: 'list'
|
||||
},
|
||||
|
@ -121,10 +137,36 @@ export default new Vuex.Store({
|
|||
[mutations.DISPLAY_NEW_BOOKMARK](state, display) {
|
||||
state.displayNewBookmark = display;
|
||||
},
|
||||
|
||||
[mutations.DISPLAY_NEW_FOLDER](state, display) {
|
||||
state.displayNewFolder = display;
|
||||
},
|
||||
[mutations.DISPLAY_MOVE_DIALOG](state, display) {
|
||||
state.displayMoveDialog = display;
|
||||
},
|
||||
|
||||
[mutations.RESET_SELECTION](state) {
|
||||
state.selection = { folders: [], bookmarks: [] };
|
||||
},
|
||||
[mutations.ADD_SELECTION_BOOKMARK](state, item) {
|
||||
state.selection.bookmarks.push(item);
|
||||
},
|
||||
[mutations.REMOVE_SELECTION_BOOKMARK](state, item) {
|
||||
Vue.set(
|
||||
state.selection,
|
||||
'bookmarks',
|
||||
state.selection.bookmarks.filter(s => !(s.id === item.id))
|
||||
);
|
||||
},
|
||||
[mutations.ADD_SELECTION_FOLDER](state, item) {
|
||||
state.selection.folders.push(item);
|
||||
},
|
||||
[mutations.REMOVE_SELECTION_FOLDER](state, item) {
|
||||
Vue.set(
|
||||
state.selection,
|
||||
'folders',
|
||||
state.selection.folders.filter(s => !(s.id === item.id))
|
||||
);
|
||||
},
|
||||
|
||||
[mutations.ADD_BOOKMARK](state, bookmark) {
|
||||
const existingBookmark = state.bookmarksById[bookmark.id];
|
||||
|
@ -232,29 +274,75 @@ export default new Vuex.Store({
|
|||
commit(mutations.FETCH_END, 'saveBookmark');
|
||||
});
|
||||
},
|
||||
async [actions.MOVE_BOOKMARK](
|
||||
{ commit, dispatch, state },
|
||||
{ bookmark, oldFolder, newFolder }
|
||||
) {
|
||||
commit(mutations.FETCH_START, 'moveBookmark');
|
||||
try {
|
||||
let response = await axios.post(
|
||||
url(`/folder/${newFolder}/bookmarks/${bookmark}`)
|
||||
);
|
||||
if (response.data.status !== 'success') {
|
||||
throw new Error(response.data);
|
||||
}
|
||||
let response2 = await axios.delete(
|
||||
url(`/folder/${oldFolder}/bookmarks/${bookmark}`)
|
||||
);
|
||||
if (response2.data.status !== 'success') {
|
||||
throw new Error(response2.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
commit(
|
||||
mutations.SET_ERROR,
|
||||
AppGlobal.methods.t('bookmarks', 'Failed to move bookmark')
|
||||
);
|
||||
throw err;
|
||||
} finally {
|
||||
commit(mutations.FETCH_END, 'moveBookmark');
|
||||
}
|
||||
},
|
||||
[actions.OPEN_BOOKMARK]({ commit }, id) {
|
||||
commit(mutations.SET_SIDEBAR, { type: 'bookmark', id });
|
||||
},
|
||||
[actions.DELETE_BOOKMARK]({ commit, dispatch, state }, id) {
|
||||
return axios
|
||||
.delete(url(`/bookmark/${id}`))
|
||||
.then(response => {
|
||||
const {
|
||||
data: { status }
|
||||
} = response;
|
||||
if (status !== 'success') {
|
||||
async [actions.DELETE_BOOKMARK](
|
||||
{ commit, dispatch, state },
|
||||
{ id, folder }
|
||||
) {
|
||||
if (folder) {
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
url(`/folder/${folder}/bookmarks/${id}`)
|
||||
);
|
||||
if (response.data.status !== 'success') {
|
||||
throw new Error(response.data);
|
||||
}
|
||||
commit(mutations.REMOVE_BOOKMARK, id);
|
||||
})
|
||||
.catch(err => {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
commit(
|
||||
mutations.SET_ERROR,
|
||||
AppGlobal.methods.t('bookmarks', 'Failed to delete bookmark')
|
||||
);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await axios.delete(url(`/bookmark/${id}`));
|
||||
if (response.data.status !== 'success') {
|
||||
throw new Error(response.data);
|
||||
}
|
||||
commit(mutations.REMOVE_BOOKMARK, id);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
commit(
|
||||
mutations.SET_ERROR,
|
||||
AppGlobal.methods.t('bookmarks', 'Failed to delete bookmark')
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[actions.IMPORT_BOOKMARKS]({ commit, dispatch, state }, file) {
|
||||
var data = new FormData();
|
||||
|
@ -453,7 +541,7 @@ export default new Vuex.Store({
|
|||
},
|
||||
[actions.SAVE_FOLDER]({ commit, dispatch, state }, id) {
|
||||
const folder = this.getters.getFolder(id)[0];
|
||||
commit(mutations.FETCH_END, 'saveFolder');
|
||||
commit(mutations.FETCH_START, 'saveFolder');
|
||||
return axios
|
||||
.put(url(`/folder/${id}`), {
|
||||
parent_folder: folder.parent_folder,
|
||||
|
@ -480,6 +568,42 @@ export default new Vuex.Store({
|
|||
});
|
||||
},
|
||||
|
||||
async [actions.MOVE_SELECTION]({ commit, dispatch, state }, folderId) {
|
||||
commit(mutations.FETCH_START, 'moveSelection');
|
||||
try {
|
||||
for (const folder of state.selection.folders) {
|
||||
if (folderId === folder.id) {
|
||||
throw new Error('Cannot move folder into itself');
|
||||
}
|
||||
folder.parent_folder = folderId;
|
||||
await dispatch(actions.SAVE_FOLDER, folder.id);
|
||||
}
|
||||
|
||||
for (const bookmark of state.selection.bookmarks) {
|
||||
await dispatch(actions.MOVE_BOOKMARK, {
|
||||
oldFolder: bookmark.folders[bookmark.folders.length - 1], // FIXME This is veeeery ugly and will cause issues. Inevitably.
|
||||
newFolder: folderId,
|
||||
bookmark: bookmark.id
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
commit(
|
||||
mutations.SET_ERROR,
|
||||
AppGlobal.methods.t('bookmarks', 'Failed to move parts of selection')
|
||||
);
|
||||
throw err;
|
||||
} finally {
|
||||
commit(mutations.FETCH_END, 'moveSelection');
|
||||
}
|
||||
},
|
||||
|
||||
[actions.RELOAD_VIEW]({ state, dispatch, commit }) {
|
||||
commit(mutations.SET_QUERY, state.fetchState.query);
|
||||
dispatch(actions.LOAD_FOLDERS);
|
||||
dispatch(actions.LOAD_TAGS);
|
||||
},
|
||||
|
||||
[actions.NO_FILTER]({ dispatch, commit }) {
|
||||
commit(mutations.SET_QUERY, {});
|
||||
return dispatch(actions.FETCH_PAGE);
|
||||
|
|
Loading…
Reference in New Issue