Move list edit/namespace to separate pages and in a menu (#397)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/397 Co-authored-by: konrad <konrad@kola-entertainments.de> Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
@ -64,25 +64,7 @@
|
||||
{{ n.title }} ({{ n.lists.filter(l => !l.isArchived).length }})
|
||||
</span>
|
||||
</label>
|
||||
<div class="actions">
|
||||
<router-link
|
||||
:key="n.id + 'list.create'"
|
||||
:to="{ name: 'list.create', params: { id: n.id} }"
|
||||
v-if="n.id > 0"
|
||||
v-tooltip="'Add a new list in the ' + n.title + ' namespace'">
|
||||
<span class="icon">
|
||||
<icon icon="plus"/>
|
||||
</span>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{name: 'namespace.edit', params: {id: n.id} }"
|
||||
v-if="n.id > 0"
|
||||
v-tooltip="'Settings'">
|
||||
<span class="icon">
|
||||
<icon icon="cog"/>
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
|
||||
</div>
|
||||
<input
|
||||
:id="n.id + 'checker'"
|
||||
@ -118,6 +100,7 @@
|
||||
<icon :icon="['far', 'star']" v-else/>
|
||||
</span>
|
||||
</router-link>
|
||||
<list-settings-dropdown :list="l"/>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
@ -134,9 +117,15 @@
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown'
|
||||
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
||||
|
||||
export default {
|
||||
name: 'navigation',
|
||||
components: {
|
||||
ListSettingsDropdown,
|
||||
NamespaceSettingsDropdown,
|
||||
},
|
||||
computed: mapState({
|
||||
namespaces(state) {
|
||||
return state.namespaces.namespaces.filter(n => !n.isArchived)
|
||||
|
@ -31,58 +31,49 @@
|
||||
class="title">
|
||||
{{ currentList.title === '' ? 'Loading...' : currentList.title }}
|
||||
</h1>
|
||||
<router-link
|
||||
:to="{ name: 'list.edit', params: { id: currentList.id } }"
|
||||
class="icon"
|
||||
v-if="canWriteCurrentList">
|
||||
<icon icon="cog" size="2x"/>
|
||||
</router-link>
|
||||
|
||||
<list-settings-dropdown v-if="canWriteCurrentList" :list="currentList"/>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<update/>
|
||||
<div class="user">
|
||||
<img :src="userAvatar" alt="" class="avatar"/>
|
||||
<div class="dropdown is-right is-active">
|
||||
<div class="dropdown-trigger">
|
||||
<x-button
|
||||
@click.stop="userMenuActive = !userMenuActive"
|
||||
type="secondary"
|
||||
<dropdown class="is-right">
|
||||
<template v-slot:trigger>
|
||||
<x-button
|
||||
type="secondary"
|
||||
:shadow="false">
|
||||
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
|
||||
<span class="icon is-small">
|
||||
<icon icon="chevron-down"/>
|
||||
</span>
|
||||
</x-button>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div class="dropdown-menu" v-if="userMenuActive">
|
||||
<div class="dropdown-content">
|
||||
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
|
||||
Settings
|
||||
</router-link>
|
||||
<a
|
||||
:href="imprintUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
v-if="imprintUrl">
|
||||
Imprint
|
||||
</a>
|
||||
<a
|
||||
:href="privacyPolicyUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
v-if="privacyPolicyUrl">
|
||||
Privacy policy
|
||||
</a>
|
||||
<a @click="$store.commit('keyboardShortcutsActive', true)" class="dropdown-item">Keyboard
|
||||
Shortcuts</a>
|
||||
<a @click="logout()" class="dropdown-item">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
|
||||
Settings
|
||||
</router-link>
|
||||
<a
|
||||
:href="imprintUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
v-if="imprintUrl">
|
||||
Imprint
|
||||
</a>
|
||||
<a
|
||||
:href="privacyPolicyUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
v-if="privacyPolicyUrl">
|
||||
Privacy policy
|
||||
</a>
|
||||
<a @click="$store.commit('keyboardShortcutsActive', true)" class="dropdown-item">Keyboard
|
||||
Shortcuts</a>
|
||||
<a @click="logout()" class="dropdown-item">
|
||||
Logout
|
||||
</a>
|
||||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -93,21 +84,16 @@ import {mapState} from 'vuex'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
import Rights from '@/models/rights.json'
|
||||
import Update from '@/components/home/update'
|
||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown'
|
||||
import Dropdown from '@/components/misc/dropdown'
|
||||
|
||||
export default {
|
||||
name: 'topNavigation',
|
||||
data() {
|
||||
return {
|
||||
userMenuActive: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Dropdown,
|
||||
ListSettingsDropdown,
|
||||
Update,
|
||||
},
|
||||
created() {
|
||||
// This will hide the menu once clicked outside of it
|
||||
this.$nextTick(() => document.addEventListener('click', () => this.userMenuActive = false))
|
||||
},
|
||||
computed: mapState({
|
||||
userInfo: state => state.auth.info,
|
||||
userAvatar: state => state.auth.avatarUrl,
|
||||
|
106
src/components/list/list-settings-dropdown.vue
Normal file
106
src/components/list/list-settings-dropdown.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<dropdown>
|
||||
<template v-if="isSavedFilter">
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
Edit
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.delete`, params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
>
|
||||
Delete
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else-if="list.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
Un-Archive
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
Edit
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.background`, params: { listId: list.id } }"
|
||||
v-if="backgroundsEnabled"
|
||||
icon="image"
|
||||
>
|
||||
Set background
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.share`, params: { listId: list.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
Share
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.duplicate`, params: { listId: list.id } }"
|
||||
icon="paste"
|
||||
>
|
||||
Duplicate
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
Archive
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.settings.delete`, params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
Delete
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
import Dropdown from '@/components/misc/dropdown'
|
||||
import DropdownItem from '@/components/misc/dropdown-item'
|
||||
|
||||
export default {
|
||||
name: 'list-settings-dropdown',
|
||||
components: {
|
||||
DropdownItem,
|
||||
Dropdown,
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
backgroundsEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders.length > 0
|
||||
},
|
||||
listRoutePrefix() {
|
||||
let name = 'list'
|
||||
|
||||
if (this.$route.name.startsWith('list.')) {
|
||||
name = this.$route.name
|
||||
}
|
||||
|
||||
if (this.isSavedFilter) {
|
||||
name = name.replace('list.', 'filter.')
|
||||
}
|
||||
|
||||
return name
|
||||
},
|
||||
isSavedFilter() {
|
||||
return getSavedFilterIdFromListId(this.list.id) > 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,172 +0,0 @@
|
||||
<template>
|
||||
<card
|
||||
:class="{ 'is-loading': backgroundService.loading}"
|
||||
class="list-background-setting loader-container"
|
||||
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
||||
title="Set list background"
|
||||
>
|
||||
<div class="mb-4" v-if="uploadBackgroundEnabled">
|
||||
<input
|
||||
@change="uploadBackground"
|
||||
accept="image/*"
|
||||
class="is-hidden"
|
||||
ref="backgroundUploadInput"
|
||||
type="file"
|
||||
/>
|
||||
<x-button
|
||||
:loading="backgroundUploadService.loading"
|
||||
@click="$refs.backgroundUploadInput.click()"
|
||||
type="primary"
|
||||
>
|
||||
Choose a background from your pc
|
||||
</x-button>
|
||||
</div>
|
||||
<template v-if="unsplashBackgroundEnabled">
|
||||
<input
|
||||
:class="{'is-loading': backgroundService.loading}"
|
||||
@keyup="() => newBackgroundSearch()"
|
||||
class="input is-expanded"
|
||||
placeholder="Search for a background..."
|
||||
type="text"
|
||||
v-model="backgroundSearchTerm"
|
||||
/>
|
||||
<p class="unsplash-link"><a href="https://unsplash.com" target="_blank">Powered by Unsplash</a></p>
|
||||
<div class="image-search-result">
|
||||
<a
|
||||
:key="im.id"
|
||||
:style="{'background-image': `url(${backgroundThumbs[im.id]})`}"
|
||||
@click="() => setBackground(im.id)"
|
||||
class="image"
|
||||
v-for="im in backgroundSearchResult">
|
||||
<a :href="`https://unsplash.com/@${im.info.author}`" target="_blank" class="info">
|
||||
{{ im.info.authorName }}
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
<x-button
|
||||
:disabled="backgroundService.loading"
|
||||
@click="() => searchBackgrounds(currentPage + 1)"
|
||||
class="is-load-more-button mt-4"
|
||||
:shadow="false"
|
||||
type="secondary"
|
||||
v-if="backgroundSearchResult.length > 0"
|
||||
>
|
||||
<template v-if="backgroundService.loading">
|
||||
Loading...
|
||||
</template>
|
||||
<template v-else>
|
||||
Load more photos
|
||||
</template>
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BackgroundUnsplashService from '../../../services/backgroundUnsplash'
|
||||
import BackgroundUploadService from '../../../services/backgroundUpload'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
|
||||
export default {
|
||||
name: 'background-settings',
|
||||
data() {
|
||||
return {
|
||||
backgroundSearchTerm: '',
|
||||
backgroundSearchResult: [],
|
||||
backgroundService: null,
|
||||
backgroundThumbs: {},
|
||||
currentPage: 1,
|
||||
backgroundSearchTimeout: null,
|
||||
|
||||
backgroundUploadService: null,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
listId: {
|
||||
default: 0,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
unsplashBackgroundEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders.includes('unsplash')
|
||||
},
|
||||
uploadBackgroundEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders.includes('upload')
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.backgroundService = new BackgroundUnsplashService()
|
||||
this.backgroundUploadService = new BackgroundUploadService()
|
||||
// Show the default collection of backgrounds
|
||||
this.newBackgroundSearch()
|
||||
},
|
||||
methods: {
|
||||
newBackgroundSearch() {
|
||||
if (!this.unsplashBackgroundEnabled) {
|
||||
return
|
||||
}
|
||||
// This is an extra method to reset a few things when searching to not break loading more photos.
|
||||
this.$set(this, 'backgroundSearchResult', [])
|
||||
this.$set(this, 'backgroundThumbs', {})
|
||||
this.searchBackgrounds()
|
||||
},
|
||||
searchBackgrounds(page = 1) {
|
||||
|
||||
if (this.backgroundSearchTimeout !== null) {
|
||||
clearTimeout(this.backgroundSearchTimeout)
|
||||
}
|
||||
|
||||
// We're using the timeout to not search on every keypress but with a 300ms delay.
|
||||
// If another key is pressed within these 300ms, the last search request is dropped and a new one is scheduled.
|
||||
this.backgroundSearchTimeout = setTimeout(() => {
|
||||
this.currentPage = page
|
||||
this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
|
||||
.then(r => {
|
||||
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
|
||||
r.forEach(b => {
|
||||
this.backgroundService.thumb(b)
|
||||
.then(t => {
|
||||
this.$set(this.backgroundThumbs, b.id, t)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
setBackground(backgroundId) {
|
||||
// Don't set a background if we're in the process of setting one
|
||||
if (this.backgroundService.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundService.update({id: backgroundId, listId: this.listId})
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
this.success({message: 'The background has been set successfully!'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
uploadBackground() {
|
||||
if (this.$refs.backgroundUploadInput.files.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundUploadService.create(this.listId, this.$refs.backgroundUploadInput.files[0])
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
this.success({message: 'The background has been set successfully!'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -10,7 +10,7 @@
|
||||
</span>
|
||||
</a>
|
||||
</header>
|
||||
<div class="card-content" :class="{'p-0': !padding}">
|
||||
<div class="card-content loader-container" :class="{'p-0': !padding, 'is-loading': loading}">
|
||||
<div :class="{'content': hasContent}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@ -46,6 +46,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
85
src/components/misc/create-edit.vue
Normal file
85
src/components/misc/create-edit.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<modal @close="$router.back()" :overflow="true" :wide="wide">
|
||||
<card
|
||||
:title="title"
|
||||
:shadow="false"
|
||||
:padding="false"
|
||||
class="has-text-left has-overflow"
|
||||
:has-close="true"
|
||||
close-icon="times"
|
||||
@close="$router.back()"
|
||||
:loading="loading"
|
||||
>
|
||||
<div class="p-4">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
||||
<x-button
|
||||
:shadow="false"
|
||||
type="tertary"
|
||||
@click.prevent.stop="$emit('tertary')"
|
||||
v-if="tertary !== ''"
|
||||
>
|
||||
{{ tertary }}
|
||||
</x-button>
|
||||
<x-button
|
||||
type="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
Cancel
|
||||
</x-button>
|
||||
<x-button
|
||||
type="primary"
|
||||
@click.prevent.stop="primary"
|
||||
:icon="primaryIcon"
|
||||
:disabled="primaryDisabled"
|
||||
v-if="primaryLabel !== ''"
|
||||
>
|
||||
{{ primaryLabel }}
|
||||
</x-button>
|
||||
</footer>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'create-edit',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
primaryLabel: {
|
||||
type: String,
|
||||
default: 'Create',
|
||||
},
|
||||
primaryIcon: {
|
||||
type: String,
|
||||
default: 'plus',
|
||||
},
|
||||
primaryDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
tertary: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
wide: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
primary() {
|
||||
this.$emit('create')
|
||||
this.$emit('primary')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,55 +0,0 @@
|
||||
<template>
|
||||
<modal @close="$router.back()" :overflow="true">
|
||||
<card
|
||||
:title="title"
|
||||
:shadow="false"
|
||||
:padding="false"
|
||||
class="has-text-left has-overflow"
|
||||
:has-close="true"
|
||||
close-icon="times"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<div class="p-4">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
||||
<x-button
|
||||
:shadow="false"
|
||||
type="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
Cancel
|
||||
</x-button>
|
||||
<x-button
|
||||
:shadow="false"
|
||||
type="primary"
|
||||
@click.prevent.stop="$emit('create')"
|
||||
icon="plus"
|
||||
:disabled="createDisabled"
|
||||
>
|
||||
{{ createLabel }}
|
||||
</x-button>
|
||||
</footer>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'create',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
createLabel: {
|
||||
type: String,
|
||||
default: 'Create',
|
||||
},
|
||||
createDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
28
src/components/misc/dropdown-item.vue
Normal file
28
src/components/misc/dropdown-item.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<router-link
|
||||
:to="to"
|
||||
class="dropdown-item">
|
||||
<span class="icon" v-if="icon !== ''">
|
||||
<icon :icon="icon"/>
|
||||
</span>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'dropdown-item',
|
||||
props: {
|
||||
to: {
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
50
src/components/misc/dropdown.vue
Normal file
50
src/components/misc/dropdown.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="dropdown is-right is-active" ref="dropdown">
|
||||
<div class="dropdown-trigger" @click="open = !open">
|
||||
<slot name="trigger">
|
||||
<icon :icon="triggerIcon" class="icon"/>
|
||||
</slot>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div class="dropdown-menu" v-if="open">
|
||||
<div class="dropdown-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
|
||||
export default {
|
||||
name: 'dropdown',
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.hide)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.hide)
|
||||
},
|
||||
props: {
|
||||
triggerIcon: {
|
||||
type: String,
|
||||
default: 'ellipsis-h',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hide(e) {
|
||||
if (this.open) {
|
||||
closeWhenClickedOutside(e, this.$refs.dropdown, () => {
|
||||
this.open = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
11
src/components/misc/nothing.vue
Normal file
11
src/components/misc/nothing.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<p class="has-text-centered has-text-grey is-italic p-4 mb-4">
|
||||
<slot></slot>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'nothing'
|
||||
}
|
||||
</script>
|
@ -2,7 +2,7 @@
|
||||
<transition name="modal">
|
||||
<div class="modal-mask">
|
||||
<div class="modal-container" @click.self.prevent.stop="$emit('close')">
|
||||
<div class="modal-content" :class="{'has-overflow': overflow}">
|
||||
<div class="modal-content" :class="{'has-overflow': overflow, 'is-wide': wide}">
|
||||
<slot>
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
@ -49,6 +49,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
wide: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
63
src/components/namespace/namespace-settings-dropdown.vue
Normal file
63
src/components/namespace/namespace-settings-dropdown.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<dropdown>
|
||||
<template v-if="namespace.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
Un-Archive
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
Edit
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.share', params: { id: namespace.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
Share
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.create', params: { id: namespace.id } }"
|
||||
icon="plus"
|
||||
>
|
||||
New list
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
Archive
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
Delete
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '@/components/misc/dropdown'
|
||||
import DropdownItem from '@/components/misc/dropdown-item'
|
||||
|
||||
export default {
|
||||
name: 'namespace-settings-dropdown',
|
||||
components: {
|
||||
DropdownItem,
|
||||
Dropdown,
|
||||
},
|
||||
props: {
|
||||
namespace: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<card title="Share links" class="is-fullwidth" :padding="false">
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Share Links</p>
|
||||
<div class="sharables-list">
|
||||
<div class="p-4">
|
||||
<p>Share with a link:</p>
|
||||
@ -21,7 +22,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class="table is-striped is-hoverable is-fullwidth link-share-list"
|
||||
class="table has-actions is-striped is-hoverable is-fullwidth link-share-list"
|
||||
v-if="linkShares.length > 0"
|
||||
>
|
||||
<thead>
|
||||
@ -112,7 +113,7 @@
|
||||
</p>
|
||||
</modal>
|
||||
</transition>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<card class="is-fullwidth has-overflow" :title="`Shared with these ${shareType}s`" :padding="false">
|
||||
<div class="p-4" v-if="userIsAdmin">
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Shared with these {{ shareType }}s</p>
|
||||
<div v-if="userIsAdmin">
|
||||
<div class="field has-addons">
|
||||
<p
|
||||
class="control is-expanded"
|
||||
v-bind:class="{ 'is-loading': searchService.loading }"
|
||||
:class="{ 'is-loading': searchService.loading }"
|
||||
>
|
||||
<multiselect
|
||||
:loading="searchService.loading"
|
||||
@ -20,7 +21,8 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table is-striped is-hoverable is-fullwidth">
|
||||
|
||||
<table class="table has-actions is-striped is-hoverable is-fullwidth mb-4" v-if="sharables.length > 0">
|
||||
<tbody>
|
||||
<tr :key="s.id" v-for="s in sharables">
|
||||
<template v-if="shareType === 'user'">
|
||||
@ -105,6 +107,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<nothing v-else>
|
||||
Not shared with any {{ shareType }} yet.
|
||||
</nothing>
|
||||
|
||||
<transition name="modal">
|
||||
<modal
|
||||
@close="showDeleteModal = false"
|
||||
@ -121,7 +127,7 @@
|
||||
</p>
|
||||
</modal>
|
||||
</transition>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -143,6 +149,7 @@ import TeamModel from '../../models/team'
|
||||
|
||||
import rights from '../../models/rights'
|
||||
import Multiselect from '@/components/input/multiselect'
|
||||
import Nothing from '@/components/misc/nothing'
|
||||
|
||||
export default {
|
||||
name: 'userTeamShare',
|
||||
@ -182,6 +189,7 @@ export default {
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Nothing,
|
||||
Multiselect,
|
||||
},
|
||||
computed: mapState({
|
||||
|
@ -12,6 +12,8 @@ export default {
|
||||
pages: [],
|
||||
currentPage: 0,
|
||||
|
||||
loadedList: null,
|
||||
|
||||
showTaskSearch: false,
|
||||
searchTerm: '',
|
||||
|
||||
@ -53,6 +55,17 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
const list = {listId: parseInt(this.$route.params.listId)}
|
||||
|
||||
const currentList = {
|
||||
id: list.listId,
|
||||
params: params,
|
||||
search: search,
|
||||
}
|
||||
if (JSON.stringify(currentList) === JSON.stringify(this.loadedList)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$set(this, 'tasks', [])
|
||||
|
||||
if (params === null) {
|
||||
@ -62,7 +75,8 @@ export default {
|
||||
if (search !== '') {
|
||||
params.s = search
|
||||
}
|
||||
this.taskCollectionService.getAll({listId: this.$route.params.listId}, params, page)
|
||||
|
||||
this.taskCollectionService.getAll(list, params, page)
|
||||
.then(r => {
|
||||
this.$set(this, 'tasks', r)
|
||||
this.$set(this, 'pages', [])
|
||||
@ -95,6 +109,8 @@ export default {
|
||||
isEllipsis: false,
|
||||
})
|
||||
}
|
||||
|
||||
this.loadedList = currentList
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
|
Reference in New Issue
Block a user