feat(link shares): allows switching the initial view by passing a query parameter (#2335)
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2335 Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
commit
a6e9b36bd6
4 changed files with 90 additions and 48 deletions
|
@ -79,30 +79,59 @@
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('list.share.attributes.link') }}</th>
|
<th></th>
|
||||||
<th>{{ $t('list.share.attributes.name') }}</th>
|
<th>{{ $t('list.share.links.view') }}</th>
|
||||||
<th>{{ $t('list.share.attributes.sharedBy') }}</th>
|
|
||||||
<th>{{ $t('list.share.attributes.right') }}</th>
|
|
||||||
<th>{{ $t('list.share.attributes.delete') }}</th>
|
<th>{{ $t('list.share.attributes.delete') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr :key="s.id" v-for="s in linkShares">
|
<tr :key="s.id" v-for="s in linkShares">
|
||||||
<td>
|
<td>
|
||||||
|
<p class="mb-2 is-italic" v-if="s.name !== ''">
|
||||||
|
{{ s.name }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mb-2">
|
||||||
|
<i18n-t keypath="list.share.links.sharedBy">
|
||||||
|
<strong>{{ s.sharedBy.getDisplayName() }}</strong>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mb-2">
|
||||||
|
<template v-if="s.right === RIGHTS.ADMIN">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="lock"/>
|
||||||
|
</span>
|
||||||
|
{{ $t('list.share.right.admin') }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="s.right === RIGHTS.READ_WRITE">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="pen"/>
|
||||||
|
</span>
|
||||||
|
{{ $t('list.share.right.readWrite') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="users"/>
|
||||||
|
</span>
|
||||||
|
{{ $t('list.share.right.read') }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="field has-addons no-input-mobile">
|
<div class="field has-addons no-input-mobile">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
:value="getShareLink(s.hash)"
|
:value="getShareLink(s.hash, selectedView[s.id])"
|
||||||
class="input"
|
class="input"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<x-button
|
<x-button
|
||||||
@click="copy(getShareLink(s.hash))"
|
@click="copy(getShareLink(s.hash, selectedView[s.id]))"
|
||||||
:shadow="false"
|
:shadow="false"
|
||||||
v-tooltip="$t('misc.copy')"
|
v-tooltip="$t('misc.copy')"
|
||||||
>
|
>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<icon icon="paste"/>
|
<icon icon="paste"/>
|
||||||
|
@ -112,33 +141,16 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="s.name !== ''">
|
<div class="select">
|
||||||
{{ s.name }}
|
<select v-model="selectedView[s.id]">
|
||||||
</template>
|
<option
|
||||||
<i v-else>{{ $t('list.share.links.noName') }}</i>
|
v-for="(title, key) in availableViews"
|
||||||
</td>
|
:value="key"
|
||||||
<td>
|
:key="key">
|
||||||
{{ s.sharedBy.getDisplayName() }}
|
{{ title }}
|
||||||
</td>
|
</option>
|
||||||
<td class="type">
|
</select>
|
||||||
<template v-if="s.right === RIGHTS.ADMIN">
|
</div>
|
||||||
<span class="icon is-small">
|
|
||||||
<icon icon="lock"/>
|
|
||||||
</span>
|
|
||||||
{{ $t('list.share.right.admin') }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="s.right === RIGHTS.READ_WRITE">
|
|
||||||
<span class="icon is-small">
|
|
||||||
<icon icon="pen"/>
|
|
||||||
</span>
|
|
||||||
{{ $t('list.share.right.readWrite') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="icon is-small">
|
|
||||||
<icon icon="users"/>
|
|
||||||
</span>
|
|
||||||
{{ $t('list.share.right.read') }}
|
|
||||||
</template>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<x-button
|
<x-button
|
||||||
|
@ -190,6 +202,8 @@ import LinkShareService from '@/services/linkShare'
|
||||||
|
|
||||||
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
import type {ListView} from '@/types/ListView'
|
||||||
|
import {LIST_VIEWS} from '@/types/ListView'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
listId: {
|
listId: {
|
||||||
|
@ -209,6 +223,17 @@ const showDeleteModal = ref(false)
|
||||||
const linkIdToDelete = ref(0)
|
const linkIdToDelete = ref(0)
|
||||||
const showNewForm = ref(false)
|
const showNewForm = ref(false)
|
||||||
|
|
||||||
|
type SelectedViewMapper = Record<IList['id'], ListView>
|
||||||
|
|
||||||
|
const selectedView = ref<SelectedViewMapper>({})
|
||||||
|
|
||||||
|
const availableViews = computed<Record<ListView, string>>(() => ({
|
||||||
|
list: t('list.list.title'),
|
||||||
|
gantt: t('list.gantt.title'),
|
||||||
|
table: t('list.table.title'),
|
||||||
|
kanban: t('list.kanban.title'),
|
||||||
|
}))
|
||||||
|
|
||||||
const copy = useCopyToClipboard()
|
const copy = useCopyToClipboard()
|
||||||
watch(
|
watch(
|
||||||
() => props.listId,
|
() => props.listId,
|
||||||
|
@ -225,7 +250,11 @@ async function load(listId: IList['id']) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
linkShares.value = await linkShareService.getAll({listId})
|
const links = await linkShareService.getAll({listId})
|
||||||
|
links.forEach((l: ILinkShare) => {
|
||||||
|
selectedView.value[l.id] = 'list'
|
||||||
|
})
|
||||||
|
linkShares.value = links
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add(listId: IList['id']) {
|
async function add(listId: IList['id']) {
|
||||||
|
@ -257,15 +286,15 @@ async function remove(listId: IList['id']) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getShareLink(hash: string) {
|
function getShareLink(hash: string, view: ListView = LIST_VIEWS.LIST) {
|
||||||
return frontendUrl.value + 'share/' + hash + '/auth'
|
return frontendUrl.value + 'share/' + hash + '/auth?view=' + view
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// FIXME: I think this is not needed
|
// FIXME: I think this is not needed
|
||||||
.sharables-list:not(.card-content) {
|
.sharables-list:not(.card-content) {
|
||||||
overflow-y: auto
|
overflow-y: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
@include modal-transition();
|
@include modal-transition();
|
||||||
|
|
|
@ -242,7 +242,9 @@
|
||||||
"remove": "Remove a link share",
|
"remove": "Remove a link share",
|
||||||
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
|
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
|
||||||
"createSuccess": "The link share was successfully created.",
|
"createSuccess": "The link share was successfully created.",
|
||||||
"deleteSuccess": "The link share was successfully deleted"
|
"deleteSuccess": "The link share was successfully deleted",
|
||||||
|
"view": "View",
|
||||||
|
"sharedBy": "Shared by {0}"
|
||||||
},
|
},
|
||||||
"userTeam": {
|
"userTeam": {
|
||||||
"typeUser": "user | users",
|
"typeUser": "user | users",
|
||||||
|
@ -264,9 +266,6 @@
|
||||||
},
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
"name": "Name",
|
|
||||||
"sharedBy": "Shared by",
|
|
||||||
"right": "Right",
|
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
8
src/types/ListView.ts
Normal file
8
src/types/ListView.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export const LIST_VIEWS = {
|
||||||
|
LIST: 'list',
|
||||||
|
GANTT: 'gantt',
|
||||||
|
TABLE: 'table',
|
||||||
|
KANBAN: 'kanban',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ListView = typeof LIST_VIEWS[keyof typeof LIST_VIEWS]
|
|
@ -41,6 +41,7 @@ import {useTitle} from '@vueuse/core'
|
||||||
|
|
||||||
import Message from '@/components/misc/message.vue'
|
import Message from '@/components/misc/message.vue'
|
||||||
import {LOGO_VISIBLE} from '@/store/mutation-types'
|
import {LOGO_VISIBLE} from '@/store/mutation-types'
|
||||||
|
import {LIST_VIEWS, type ListView} from '@/types/ListView'
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
useTitle(t('sharing.authenticating'))
|
useTitle(t('sharing.authenticating'))
|
||||||
|
@ -79,7 +80,12 @@ function useAuth() {
|
||||||
? route.query.logoVisible === 'true'
|
? route.query.logoVisible === 'true'
|
||||||
: true
|
: true
|
||||||
store.commit(LOGO_VISIBLE, logoVisible)
|
store.commit(LOGO_VISIBLE, logoVisible)
|
||||||
router.push({name: 'list.list', params: {listId}})
|
|
||||||
|
const view = route.query.view && Object.values(LIST_VIEWS).includes(route.query.view as ListView)
|
||||||
|
? route.query.view
|
||||||
|
: 'list'
|
||||||
|
|
||||||
|
router.push({name: `list.${view}`, params: {listId}})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.response?.data?.code === 13001) {
|
if (e.response?.data?.code === 13001) {
|
||||||
authenticateWithPassword.value = true
|
authenticateWithPassword.value = true
|
||||||
|
|
Loading…
Reference in a new issue